package com.revolsys.jdbc.io;
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.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import com.revolsys.collection.ResultPager;
import com.revolsys.collection.iterator.AbstractIterator;
import com.revolsys.collection.map.Maps;
import com.revolsys.datatype.DataType;
import com.revolsys.datatype.DataTypes;
import com.revolsys.identifier.Identifier;
import com.revolsys.io.PathName;
import com.revolsys.io.PathUtil;
import com.revolsys.jdbc.JdbcConnection;
import com.revolsys.jdbc.JdbcUtils;
import com.revolsys.jdbc.field.JdbcFieldAdder;
import com.revolsys.jdbc.field.JdbcFieldDefinition;
import com.revolsys.jdbc.field.JdbcFieldFactory;
import com.revolsys.jdbc.field.JdbcFieldFactoryAdder;
import com.revolsys.logging.Logs;
import com.revolsys.record.ArrayRecord;
import com.revolsys.record.Record;
import com.revolsys.record.RecordFactory;
import com.revolsys.record.RecordState;
import com.revolsys.record.code.AbstractCodeTable;
import com.revolsys.record.io.RecordStoreExtension;
import com.revolsys.record.io.RecordStoreQueryReader;
import com.revolsys.record.io.RecordWriter;
import com.revolsys.record.property.GlobalIdProperty;
import com.revolsys.record.query.Query;
import com.revolsys.record.schema.AbstractRecordStore;
import com.revolsys.record.schema.FieldDefinition;
import com.revolsys.record.schema.RecordDefinition;
import com.revolsys.record.schema.RecordDefinitionImpl;
import com.revolsys.record.schema.RecordStore;
import com.revolsys.record.schema.RecordStoreSchema;
import com.revolsys.record.schema.RecordStoreSchemaElement;
import com.revolsys.transaction.Propagation;
import com.revolsys.transaction.Transaction;
import com.revolsys.util.Booleans;
import com.revolsys.util.Property;
public abstract class AbstractJdbcRecordStore extends AbstractRecordStore
implements JdbcRecordStore, RecordStoreExtension {
public static final List<String> DEFAULT_PERMISSIONS = Arrays.asList("SELECT");
public static final AbstractIterator<Record> newJdbcIterator(final RecordStore recordStore,
final Query query, final Map<String, Object> properties) {
return new JdbcQueryIterator((AbstractJdbcRecordStore)recordStore, query, properties);
}
private final Set<String> allSchemaNames = new TreeSet<>();
private int batchSize;
private JdbcDatabaseFactory databaseFactory;
private DataSource dataSource;
private final Object exceptionWriterKey = new Object();
private Set<String> excludeTablePaths = new HashSet<>();
private List<String> excludeTablePatterns = new ArrayList<>();
private final Map<String, JdbcFieldAdder> fieldDefinitionAdders = new HashMap<>();
private boolean flushBetweenTypes;
private String hints;
private String primaryKeySql;
private String primaryKeyTableCondition;
private final Map<PathName, String> qualifiedTableNameMap = new HashMap<>();
private final Map<PathName, String> schemaNameMap = new HashMap<>();
private String schemaPermissionsSql;
private String schemaTablePermissionsSql;
private final Map<String, String> sequenceTypeSqlMap = new HashMap<>();
private String sqlPrefix;
private String sqlSuffix;
private final Map<PathName, String> tableNameMap = new HashMap<>();
private String tablePermissionsSql;
private DataSourceTransactionManager transactionManager;
private final Object writerKey = new Object();
private boolean usesSchema = true;
public AbstractJdbcRecordStore() {
this(ArrayRecord.FACTORY);
}
public AbstractJdbcRecordStore(final DataSource dataSource) {
this();
setDataSource(dataSource);
}
public AbstractJdbcRecordStore(final JdbcDatabaseFactory databaseFactory,
final Map<String, ? extends Object> connectionProperties) {
this(databaseFactory, ArrayRecord.FACTORY);
setConnectionProperties(connectionProperties);
final DataSource dataSource = databaseFactory.newDataSource(connectionProperties);
setDataSource(dataSource);
try (
JdbcConnection jdbcConnection = getJdbcConnection()) {
}
}
public AbstractJdbcRecordStore(final JdbcDatabaseFactory databaseFactory,
final RecordFactory<? extends Record> recordFactory) {
this(recordFactory);
this.databaseFactory = databaseFactory;
}
public AbstractJdbcRecordStore(final RecordFactory<? extends Record> recordFactory) {
super(recordFactory);
setIteratorFactory(new RecordStoreIteratorFactory(AbstractJdbcRecordStore::newJdbcIterator));
addRecordStoreExtension(this);
}
protected void addAllSchemaNames(final String schemaName) {
this.allSchemaNames.add(schemaName.toUpperCase());
}
public void addExcludeTablePaths(final String tableName) {
addExcludeTablePaths(tableName);
}
protected JdbcFieldDefinition addField(final RecordDefinitionImpl recordDefinition,
final String dbColumnName, final String name, final String dataType, final int sqlType,
final int length, final int scale, final boolean required, final String description) {
JdbcFieldAdder fieldAdder = this.fieldDefinitionAdders.get(dataType);
if (fieldAdder == null) {
fieldAdder = new JdbcFieldAdder(DataTypes.OBJECT);
}
return (JdbcFieldDefinition)fieldAdder.addField(this, recordDefinition, dbColumnName, name,
dataType, sqlType, length, scale, required, description);
}
protected void addField(final ResultSetMetaData resultSetMetaData,
final RecordDefinitionImpl recordDefinition, final String name, final int i,
final String description) throws SQLException {
final String dataType = resultSetMetaData.getColumnTypeName(i);
final int sqlType = resultSetMetaData.getColumnType(i);
final int length = resultSetMetaData.getPrecision(i);
final int scale = resultSetMetaData.getScale(i);
final boolean required = false;
addField(recordDefinition, name, name.toUpperCase(), dataType, sqlType, length, scale, required,
description);
}
protected void addFieldAdder(final String sqlTypeName, final DataType dataType) {
final JdbcFieldAdder adder = new JdbcFieldAdder(dataType);
this.fieldDefinitionAdders.put(sqlTypeName, adder);
}
public void addFieldAdder(final String sqlTypeName, final JdbcFieldAdder adder) {
this.fieldDefinitionAdders.put(sqlTypeName, adder);
}
protected void addFieldAdder(final String sqlTypeName, final JdbcFieldFactory fieldFactory) {
final JdbcFieldFactoryAdder fieldAdder = new JdbcFieldFactoryAdder(fieldFactory);
addFieldAdder(sqlTypeName, fieldAdder);
}
/**
* Add a new field definition for record definitions that don't have a primary key.
*
* @param recordDefinition
*/
protected void addRowIdFieldDefinition(final RecordDefinitionImpl recordDefinition) {
final JdbcFieldDefinition idFieldDefinition = newRowIdFieldDefinition();
if (idFieldDefinition != null) {
recordDefinition.addField(idFieldDefinition);
final String idFieldName = idFieldDefinition.getName();
recordDefinition.setIdFieldName(idFieldName);
}
}
@Override
@PreDestroy
public synchronized void close() {
try {
super.close();
if (this.databaseFactory != null && this.dataSource != null) {
JdbcDatabaseFactory.closeDataSource(this.dataSource);
}
} finally {
this.allSchemaNames.clear();
this.fieldDefinitionAdders.clear();
this.transactionManager = null;
this.databaseFactory = null;
this.dataSource = null;
this.excludeTablePatterns.clear();
this.hints = null;
this.schemaNameMap.clear();
this.sequenceTypeSqlMap.clear();
this.sqlPrefix = null;
this.sqlSuffix = null;
this.tableNameMap.clear();
}
}
@Override
public boolean deleteRecord(final Record record) {
final RecordState state = RecordState.DELETED;
write(record, state);
return true;
}
@Override
public int deleteRecords(final Iterable<? extends Record> records) {
return writeAll(records, RecordState.DELETED);
}
@Override
public int deleteRecords(final Query query) {
final String typeName = query.getTypeName();
RecordDefinition recordDefinition = query.getRecordDefinition();
if (recordDefinition == null) {
if (typeName != null) {
recordDefinition = getRecordDefinition(typeName);
query.setRecordDefinition(recordDefinition);
}
}
final String sql = JdbcUtils.getDeleteSql(query);
try (
Transaction transaction = newTransaction(com.revolsys.transaction.Propagation.REQUIRED)) {
// It's important to have this in an inner try. Otherwise the exceptions
// won't get caught on closing the writer and the transaction won't get
// rolled back.
try (
JdbcConnection connection = getJdbcConnection(isAutoCommit());
final PreparedStatement statement = connection.prepareStatement(sql)) {
JdbcUtils.setPreparedStatementParameters(statement, query);
return statement.executeUpdate();
} catch (final SQLException e) {
transaction.setRollbackOnly();
throw new RuntimeException("Unable to delete : " + sql, e);
} catch (final RuntimeException e) {
transaction.setRollbackOnly();
throw e;
} catch (final Error e) {
transaction.setRollbackOnly();
throw e;
}
}
}
public Set<String> getAllSchemaNames() {
return this.allSchemaNames;
}
public int getBatchSize() {
return this.batchSize;
}
public List<String> getColumnNames(final String typePath) {
final RecordDefinition recordDefinition = getRecordDefinition(typePath);
return recordDefinition.getFieldNames();
}
@Override
public String getDatabaseQualifiedTableName(final PathName typePath) {
return this.qualifiedTableNameMap.get(typePath);
}
@Override
public String getDatabaseSchemaName(final PathName schemaPath) {
return this.schemaNameMap.get(schemaPath);
}
public String getDatabaseSchemaName(final RecordStoreSchema schema) {
if (schema == null) {
return null;
} else {
final PathName schemaPath = schema.getPathName();
return getDatabaseSchemaName(schemaPath);
}
}
protected Set<String> getDatabaseSchemaNames() {
final Set<String> schemaNames = new TreeSet<>();
try {
try (
final Connection connection = getJdbcConnection();
final PreparedStatement statement = connection.prepareStatement(this.schemaPermissionsSql);
final ResultSet resultSet = statement.executeQuery();) {
while (resultSet.next()) {
final String schemaName = resultSet.getString("SCHEMA_NAME");
addAllSchemaNames(schemaName);
if (!isSchemaExcluded(schemaName)) {
schemaNames.add(schemaName);
}
}
}
} catch (final Throwable e) {
Logs.error(this, "Unable to get schema and table permissions", e);
}
return schemaNames;
}
@Override
public String getDatabaseTableName(final PathName typePath) {
return this.tableNameMap.get(typePath);
}
protected DataSource getDataSource() {
return this.dataSource;
}
public Set<String> getExcludeTablePaths() {
return this.excludeTablePaths;
}
public JdbcFieldDefinition getField(final String schemaName, final String tableName,
final String columnName) {
final String typePath = PathUtil.toPath(schemaName, tableName);
final RecordDefinition recordDefinition = getRecordDefinition(typePath);
if (recordDefinition == null) {
return null;
} else {
final FieldDefinition attribute = recordDefinition.getField(columnName);
return (JdbcFieldDefinition)attribute;
}
}
@Override
public String getGeneratePrimaryKeySql(final RecordDefinition recordDefinition) {
throw new UnsupportedOperationException(
"Cannot create SQL to generate Primary Key for " + recordDefinition);
}
public String getHints() {
return this.hints;
}
public String getIdFieldName(final String typePath) {
final RecordDefinition recordDefinition = getRecordDefinition(typePath);
if (recordDefinition == null) {
return null;
} else {
return recordDefinition.getIdFieldName();
}
}
@Override
public JdbcConnection getJdbcConnection() {
return new JdbcConnection(this.dataSource);
}
@Override
public JdbcConnection getJdbcConnection(final boolean autoCommit) {
return new JdbcConnection(this.dataSource, autoCommit);
}
@Override
public int getRecordCount(Query query) {
if (query == null) {
return 0;
} else {
query = query.clone();
query.setSql(null);
query.setFieldNames("count(*)");
query.setOrderBy(Collections.<String, Boolean> emptyMap());
final String sql = JdbcUtils.getSelectSql(query);
try (
JdbcConnection connection = getJdbcConnection()) {
try (
final PreparedStatement statement = connection.prepareStatement(sql)) {
JdbcUtils.setPreparedStatementParameters(statement, query);
try (
final ResultSet resultSet = statement.executeQuery()) {
if (resultSet.next()) {
final int rowCount = resultSet.getInt(1);
return rowCount;
} else {
return 0;
}
}
} catch (final SQLException e) {
throw connection.getException("getRecordCount", sql, e);
} catch (final IllegalArgumentException e) {
Logs.error(this, "Cannot get row count: " + query, e);
return 0;
}
}
}
}
@Override
public RecordDefinition getRecordDefinition(String typePath,
final ResultSetMetaData resultSetMetaData) {
if (Property.isEmpty(typePath)) {
typePath = "Record";
}
try {
final PathName pathName = PathName.newPathName(typePath);
final PathName schemaName = pathName.getParent();
final RecordStoreSchema schema = getSchema(schemaName);
final RecordDefinitionImpl recordDefinition = newRecordDefinition(schema, pathName);
final String idFieldName = getIdFieldName(typePath);
for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
final String name = resultSetMetaData.getColumnName(i).toUpperCase();
if (name.equals(idFieldName)) {
recordDefinition.setIdFieldIndex(i - 1);
}
addField(resultSetMetaData, recordDefinition, name, i, null);
}
addRecordDefinitionProperties(recordDefinition);
return recordDefinition;
} catch (final SQLException e) {
throw new IllegalArgumentException("Unable to load metadata for " + typePath);
}
}
public String getSchemaTablePermissionsSql() {
return this.schemaTablePermissionsSql;
}
protected String getSequenceInsertSql(final RecordDefinition recordDefinition) {
final String typePath = recordDefinition.getPath();
final String tableName = JdbcUtils.getQualifiedTableName(typePath);
String sql = this.sequenceTypeSqlMap.get(typePath);
if (sql == null) {
final StringBuilder sqlBuffer = new StringBuilder();
sqlBuffer.append("insert ");
sqlBuffer.append(" into ");
sqlBuffer.append(tableName);
sqlBuffer.append(" (");
sqlBuffer.append('"').append(recordDefinition.getIdFieldName()).append('"');
sqlBuffer.append(",");
for (int i = 0; i < recordDefinition.getFieldCount(); i++) {
if (i != recordDefinition.getIdFieldIndex()) {
final String fieldName = recordDefinition.getFieldName(i);
sqlBuffer.append('"').append(fieldName).append('"');
if (i < recordDefinition.getFieldCount() - 1) {
sqlBuffer.append(", ");
}
}
}
sqlBuffer.append(") VALUES (");
sqlBuffer.append(getGeneratePrimaryKeySql(recordDefinition));
sqlBuffer.append(",");
for (int i = 0; i < recordDefinition.getFieldCount(); i++) {
if (i != recordDefinition.getIdFieldIndex()) {
sqlBuffer.append("?");
if (i < recordDefinition.getFieldCount() - 1) {
sqlBuffer.append(", ");
}
}
}
sqlBuffer.append(")");
sql = sqlBuffer.toString();
this.sequenceTypeSqlMap.put(typePath, sql);
}
return sql;
}
public String getSqlPrefix() {
return this.sqlPrefix;
}
public String getSqlSuffix() {
return this.sqlSuffix;
}
@Override
public PlatformTransactionManager getTransactionManager() {
return this.transactionManager;
}
@Override
@PostConstruct
public void initialize() {
super.initialize();
if (this.dataSource != null) {
this.transactionManager = new DataSourceTransactionManager(this.dataSource);
}
}
@Override
public void initialize(final RecordStore recordStore,
final Map<String, Object> connectionProperties) {
}
@Override
public void insertRecord(final Record record) {
write(record, RecordState.NEW);
}
@Override
public void insertRecords(final Iterable<? extends Record> records) {
writeAll(records, RecordState.NEW);
}
public boolean isAutoCommit() {
boolean autoCommit = false;
if (Booleans.getBoolean(getProperties().get("autoCommit"))) {
autoCommit = true;
}
return autoCommit;
}
@Override
public boolean isEditable(final PathName typePath) {
final RecordDefinition recordDefinition = getRecordDefinition(typePath);
return recordDefinition.getIdFieldIndex() != -1;
}
@Override
public boolean isEnabled(final RecordStore recordStore) {
return true;
}
protected boolean isExcluded(final String dbSchemaName, final String tableName) {
final String path = ("/" + dbSchemaName + "/" + tableName).toUpperCase().replaceAll("/+", "/");
if (this.excludeTablePaths.contains(path)) {
return true;
} else {
for (final String pattern : this.excludeTablePatterns) {
if (path.matches(pattern) || tableName.matches(pattern)) {
return true;
}
}
}
return false;
}
public boolean isFlushBetweenTypes() {
return this.flushBetweenTypes;
}
public abstract boolean isSchemaExcluded(String schemaName);
protected synchronized Map<String, List<String>> loadIdFieldNames(final String dbSchemaName) {
final String schemaName = "/" + dbSchemaName.toUpperCase();
final Map<String, List<String>> idFieldNames = new HashMap<>();
if (Property.hasValue(this.primaryKeySql)) {
try {
try (
final Connection connection = getJdbcConnection();
final PreparedStatement statement = connection.prepareStatement(this.primaryKeySql);) {
if (this.primaryKeySql.indexOf('?') != -1) {
statement.setString(1, dbSchemaName);
}
try (
final ResultSet rs = statement.executeQuery()) {
while (rs.next()) {
final String tableName = rs.getString("TABLE_NAME").toUpperCase();
final String idFieldName = rs.getString("COLUMN_NAME");
if (Property.hasValue(dbSchemaName)) {
Maps.addToList(idFieldNames, schemaName + "/" + tableName, idFieldName);
} else {
Maps.addToList(idFieldNames, "/" + tableName, idFieldName);
}
}
}
}
} catch (final Throwable e) {
throw new IllegalArgumentException("Unable to primary keys for schema " + dbSchemaName, e);
}
}
return idFieldNames;
}
protected synchronized List<String> loadIdFieldNames(final String dbSchemaName,
final String dbTableName) {
final List<String> idFieldNames = new ArrayList<>();
try {
try (
final Connection connection = getJdbcConnection();
final PreparedStatement statement = connection
.prepareStatement(this.primaryKeySql + this.primaryKeyTableCondition);) {
statement.setString(1, dbSchemaName);
statement.setString(2, dbTableName);
try (
final ResultSet rs = statement.executeQuery()) {
while (rs.next()) {
final String idFieldName = rs.getString("COLUMN_NAME");
idFieldNames.add(idFieldName);
}
}
}
} catch (final Throwable e) {
throw new IllegalArgumentException(
"Unable to primary keys for table " + dbSchemaName + "." + dbTableName, e);
}
return idFieldNames;
}
protected void loadSchemaTablePermissions(final String schemaName,
final Map<String, List<String>> tablePermissionsMap,
final Map<String, String> tableDescriptionMap) {
if (Property.hasValue(this.schemaTablePermissionsSql)) {
try (
final Connection connection = getJdbcConnection();
final PreparedStatement statement = connection
.prepareStatement(this.schemaTablePermissionsSql)) {
if (this.schemaTablePermissionsSql.indexOf('?') != -1) {
statement.setString(1, schemaName);
}
try (
final ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
final String dbTableName = resultSet.getString("TABLE_NAME");
if (!isExcluded(schemaName, dbTableName)) {
final String privilege = resultSet.getString("PRIVILEGE");
if ("ALL".equals(privilege)) {
Maps.addToList(tablePermissionsMap, dbTableName, "SELECT");
Maps.addToList(tablePermissionsMap, dbTableName, "INSERT");
Maps.addToList(tablePermissionsMap, dbTableName, "UPDATE");
Maps.addToList(tablePermissionsMap, dbTableName, "DELETE");
} else {
Maps.addToList(tablePermissionsMap, dbTableName, privilege);
}
final String description = resultSet.getString("REMARKS");
tableDescriptionMap.put(dbTableName, description);
}
}
}
} catch (final Throwable e) {
Logs.error(this, "Unable to get schema and table permissions", e);
}
}
}
protected void loadSchemaTablePermissions(final String schemaName, final String tableName,
final List<String> tablePermissions, final Map<String, String> tableDescriptionMap) {
try (
final Connection connection = getJdbcConnection();
final PreparedStatement statement = connection.prepareStatement(this.tablePermissionsSql)) {
statement.setString(1, schemaName);
statement.setString(2, tableName);
try (
final ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
final String dbTableName = resultSet.getString("TABLE_NAME");
if (!isExcluded(schemaName, dbTableName)) {
final String privilege = resultSet.getString("PRIVILEGE");
if ("ALL".equals(privilege)) {
tablePermissions.add("SELECT");
tablePermissions.add("INSERT");
tablePermissions.add("UPDATE");
tablePermissions.add("DELETE");
} else {
tablePermissions.add(privilege);
}
final String description = resultSet.getString("REMARKS");
tableDescriptionMap.put(dbTableName, description);
}
}
}
} catch (final Throwable e) {
Logs.error(this, "Unable to get table permissions for " + schemaName + "." + tableName, e);
}
}
@Override
public Identifier newPrimaryIdentifier(final PathName typePath) {
final RecordDefinition recordDefinition = getRecordDefinition(typePath);
final GlobalIdProperty globalIdProperty = GlobalIdProperty.getProperty(recordDefinition);
if (globalIdProperty == null) {
return getNextPrimaryKey(recordDefinition);
} else {
return Identifier.newIdentifier(UUID.randomUUID().toString());
}
}
protected RecordDefinitionImpl newRecordDefinition(final RecordStoreSchema schema,
final PathName pathName) {
return new RecordDefinitionImpl(schema, pathName);
}
protected RecordStoreQueryReader newRecordReader(final Query query) {
final RecordStoreQueryReader reader = newRecordReader();
reader.addQuery(query);
return reader;
}
@Override
public RecordWriter newRecordWriter() {
return newRecordWriter(false);
}
protected RecordWriter newRecordWriter(final boolean throwExceptions) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Object writerKey;
if (throwExceptions) {
writerKey = this.exceptionWriterKey;
} else {
writerKey = this.writerKey;
}
JdbcWriterResourceHolder resourceHolder = (JdbcWriterResourceHolder)TransactionSynchronizationManager
.getResource(writerKey);
if (resourceHolder == null) {
resourceHolder = new JdbcWriterResourceHolder();
TransactionSynchronizationManager.bindResource(writerKey, resourceHolder);
}
final JdbcWriterWrapper writerWrapper = resourceHolder.getWriterWrapper(this, throwExceptions,
this.batchSize);
if (!resourceHolder.isSynchronizedWithTransaction()) {
final JdbcWriterSynchronization synchronization = new JdbcWriterSynchronization(this,
resourceHolder, writerKey);
TransactionSynchronizationManager.registerSynchronization(synchronization);
resourceHolder.setSynchronizedWithTransaction(true);
}
return writerWrapper;
} else {
return newRecordWriter(this.batchSize);
}
}
protected JdbcWriterImpl newRecordWriter(final int batchSize) {
final JdbcWriterImpl writer = new JdbcWriterImpl(this);
writer.setSqlPrefix(this.sqlPrefix);
writer.setSqlSuffix(this.sqlSuffix);
writer.setBatchSize(batchSize);
writer.setHints(this.hints);
writer.setLabel(getLabel());
writer.setFlushBetweenTypes(this.flushBetweenTypes);
writer.setQuoteColumnNames(false);
return writer;
}
/**
* Create the field definition for the row identifier column for tables that don't have a primary key.
* @return
*/
protected JdbcFieldDefinition newRowIdFieldDefinition() {
return null;
}
@Override
public ResultPager<Record> page(final Query query) {
return new JdbcQueryResultPager(this, getProperties(), query);
}
@Override
public void postProcess(final RecordStoreSchema schema) {
}
@Override
public void preProcess(final RecordStoreSchema schema) {
for (final JdbcFieldAdder fieldDefinitionAdder : this.fieldDefinitionAdders.values()) {
fieldDefinitionAdder.initialize(schema);
}
}
@Override
protected synchronized RecordDefinition refreshRecordDefinition(final RecordStoreSchema schema,
final PathName typePath) {
final List<String> pathElements = typePath.getElements();
if (pathElements.size() == 2) {
final String schemaName = pathElements.get(0).toUpperCase();
final String tableName = pathElements.get(1).toUpperCase();
final Map<String, String> tableDescriptionMap = new HashMap<>();
final List<String> tablePermissions = new ArrayList<>();
loadSchemaTablePermissions(schemaName, tableName, tablePermissions, tableDescriptionMap);
if (tableDescriptionMap.isEmpty()) {
return null;
} else {
final PathName schemaPath = schema.getPathName();
final String dbSchemaName = getDatabaseSchemaName(schemaPath);
final Entry<String, String> descriptionEntry = tableDescriptionMap.entrySet()
.iterator()
.next();
final String dbTableName = descriptionEntry.getKey();
final String tableDescription = descriptionEntry.getValue();
try {
try (
final Connection connection = getJdbcConnection()) {
final DatabaseMetaData databaseMetaData = connection.getMetaData();
final List<String> idFieldNames = loadIdFieldNames(dbSchemaName, dbTableName);
final RecordDefinitionImpl recordDefinition = newRecordDefinition(schema, typePath);
recordDefinition.setDescription(tableDescription);
recordDefinition.setProperty("permissions", tablePermissions);
if (idFieldNames.isEmpty()) {
addRowIdFieldDefinition(recordDefinition);
}
try (
final ResultSet columnsRs = databaseMetaData.getColumns(null, dbSchemaName, tableName,
"%")) {
while (columnsRs.next()) {
final String dbColumnName = columnsRs.getString("COLUMN_NAME");
final String name = dbColumnName.toUpperCase();
final int sqlType = columnsRs.getInt("DATA_TYPE");
final String dataType = columnsRs.getString("TYPE_NAME");
final int length = columnsRs.getInt("COLUMN_SIZE");
int scale = columnsRs.getInt("DECIMAL_DIGITS");
if (columnsRs.wasNull()) {
scale = -1;
}
final boolean required = !columnsRs.getString("IS_NULLABLE").equals("YES");
final String fieldDescription = columnsRs.getString("REMARKS");
addField(recordDefinition, dbColumnName, name, dataType, sqlType, length, scale,
required, fieldDescription);
}
}
recordDefinition.setIdFieldNames(idFieldNames);
return recordDefinition;
}
} catch (final Throwable e) {
throw new IllegalArgumentException("Unable to load metadata for schema " + schemaName, e);
}
}
} else {
return null;
}
}
@Override
protected Map<PathName, ? extends RecordStoreSchemaElement> refreshSchemaElements(
final RecordStoreSchema schema) {
final RecordStoreSchema rootSchema = getRootSchema();
final PathName schemaPath = schema.getPathName();
if (schema == rootSchema) {
if (this.usesSchema) {
final Map<PathName, RecordStoreSchemaElement> schemas = new TreeMap<>();
final Set<String> databaseSchemaNames = getDatabaseSchemaNames();
for (final String dbSchemaName : databaseSchemaNames) {
final PathName childSchemaPath = schemaPath.newChild(dbSchemaName.toUpperCase());
this.schemaNameMap.put(childSchemaPath, dbSchemaName);
RecordStoreSchema childSchema = schema.getSchema(childSchemaPath);
if (childSchema == null) {
childSchema = new RecordStoreSchema(rootSchema, childSchemaPath);
} else {
if (childSchema.isInitialized()) {
childSchema.refresh();
}
}
schemas.put(childSchemaPath, childSchema);
}
return schemas;
} else {
return refreshSchemaElementsDo(schema, schemaPath);
}
} else {
return refreshSchemaElementsDo(schema, schemaPath);
}
}
protected Map<PathName, ? extends RecordStoreSchemaElement> refreshSchemaElementsDo(
final RecordStoreSchema schema, final PathName schemaPath) {
final String schemaName = schema.getPath();
final String dbSchemaName = getDatabaseSchemaName(schemaPath);
final Map<String, String> tableDescriptionMap = new HashMap<>();
final Map<String, List<String>> tablePermissionsMap = new TreeMap<>();
loadSchemaTablePermissions(dbSchemaName, tablePermissionsMap, tableDescriptionMap);
final Map<PathName, RecordStoreSchemaElement> elementsByPath = new TreeMap<>();
final Map<PathName, RecordDefinition> recordDefinitionMap = new TreeMap<>();
try {
try (
final Connection connection = getJdbcConnection()) {
final DatabaseMetaData databaseMetaData = connection.getMetaData();
final Map<String, List<String>> idFieldNameMap = loadIdFieldNames(dbSchemaName);
final Set<String> tableNames = tablePermissionsMap.keySet();
for (final String dbTableName : tableNames) {
final String tableName = dbTableName.toUpperCase();
final PathName typePath = schemaPath.newChild(tableName);
setDbSchemaAndTableName(typePath, dbSchemaName, dbTableName);
final RecordDefinitionImpl recordDefinition = newRecordDefinition(schema, typePath);
final List<String> idFieldNames = idFieldNameMap.get(typePath);
if (Property.isEmpty(idFieldNames)) {
addRowIdFieldDefinition(recordDefinition);
}
final String description = tableDescriptionMap.get(dbTableName);
recordDefinition.setDescription(description);
final List<String> permissions = Maps.get(tablePermissionsMap, dbTableName,
DEFAULT_PERMISSIONS);
recordDefinition.setProperty("permissions", permissions);
recordDefinitionMap.put(typePath, recordDefinition);
elementsByPath.put(typePath, recordDefinition);
}
try (
final ResultSet columnsRs = databaseMetaData.getColumns(null, dbSchemaName, "%", "%")) {
while (columnsRs.next()) {
final String tableName = columnsRs.getString("TABLE_NAME").toUpperCase();
final PathName typePath = schemaPath.newChild(tableName);
final RecordDefinitionImpl recordDefinition = (RecordDefinitionImpl)recordDefinitionMap
.get(typePath);
if (recordDefinition != null) {
final String dbColumnName = columnsRs.getString("COLUMN_NAME");
final String name = dbColumnName.toUpperCase();
final int sqlType = columnsRs.getInt("DATA_TYPE");
final String dataType = columnsRs.getString("TYPE_NAME");
final int length = columnsRs.getInt("COLUMN_SIZE");
int scale = columnsRs.getInt("DECIMAL_DIGITS");
if (columnsRs.wasNull()) {
scale = -1;
}
final boolean required = !columnsRs.getString("IS_NULLABLE").equals("YES");
final String description = columnsRs.getString("REMARKS");
addField(recordDefinition, dbColumnName, name, dataType, sqlType, length, scale,
required, description);
}
}
for (final RecordDefinition recordDefinition : recordDefinitionMap.values()) {
final String typePath = recordDefinition.getPath();
final List<String> idFieldNames = idFieldNameMap.get(typePath);
if (!Property.isEmpty(idFieldNames)) {
((RecordDefinitionImpl)recordDefinition).setIdFieldNames(idFieldNames);
}
}
}
}
} catch (final Throwable e) {
throw new IllegalArgumentException("Unable to load metadata for schema " + schemaName, e);
}
return elementsByPath;
}
public void setBatchSize(final int batchSize) {
this.batchSize = batchSize;
}
public void setCodeTables(final List<AbstractCodeTable> codeTables) {
for (final AbstractCodeTable codeTable : codeTables) {
addCodeTable(codeTable);
}
}
public void setDataSource(final DataSource dataSource) {
this.dataSource = dataSource;
}
protected void setDbSchemaAndTableName(final PathName typePath, final String dbSchemaName,
final String dbTableName) {
this.tableNameMap.put(typePath, dbTableName);
if (Property.hasValue(dbSchemaName)) {
this.qualifiedTableNameMap.put(typePath, dbSchemaName + "." + dbTableName);
} else {
this.qualifiedTableNameMap.put(typePath, dbTableName);
}
}
public void setExcludeTablePaths(final Collection<String> excludeTablePaths) {
this.excludeTablePaths = new HashSet<>(excludeTablePaths);
}
public void setExcludeTablePaths(final String... excludeTablePaths) {
setExcludeTablePaths(Arrays.asList(excludeTablePaths));
}
public void setExcludeTablePatterns(final String... excludeTablePatterns) {
this.excludeTablePatterns = new ArrayList<>(Arrays.asList(excludeTablePatterns));
}
public void setFlushBetweenTypes(final boolean flushBetweenTypes) {
this.flushBetweenTypes = flushBetweenTypes;
}
public void setHints(final String hints) {
this.hints = hints;
}
public void setPrimaryKeySql(final String primaryKeySql) {
this.primaryKeySql = primaryKeySql;
}
public void setPrimaryKeyTableCondition(final String primaryKeyTableCondition) {
this.primaryKeyTableCondition = primaryKeyTableCondition;
}
protected void setSchemaPermissionsSql(final String scehmaPermissionsSql) {
this.schemaPermissionsSql = scehmaPermissionsSql;
}
public void setSchemaTablePermissionsSql(final String tablePermissionsSql) {
this.schemaTablePermissionsSql = tablePermissionsSql;
}
public void setSqlPrefix(final String sqlPrefix) {
this.sqlPrefix = sqlPrefix;
}
public void setSqlSuffix(final String sqlSuffix) {
this.sqlSuffix = sqlSuffix;
}
public void setTablePermissionsSql(final String tablePermissionsSql) {
this.tablePermissionsSql = tablePermissionsSql;
}
protected void setUsesSchema(final boolean usesSchema) {
this.usesSchema = usesSchema;
}
@Override
public void updateRecord(final Record record) {
write(record, null);
}
@Override
public void updateRecords(final Iterable<? extends Record> records) {
writeAll(records, null);
}
protected void write(final Record record, final RecordState state) {
try (
Transaction transaction = newTransaction(com.revolsys.transaction.Propagation.REQUIRED)) {
// It's important to have this in an inner try. Otherwise the exceptions
// won't get caught on closing the writer and the transaction won't get
// rolled back.
try (
RecordWriter writer = newRecordWriter(true)) {
write(writer, record, state);
} catch (final RuntimeException e) {
transaction.setRollbackOnly();
throw e;
} catch (final Error e) {
transaction.setRollbackOnly();
throw e;
}
}
}
protected Record write(final RecordWriter writer, Record record, final RecordState state) {
if (state == RecordState.NEW) {
if (record.getState() != state) {
record = newRecord(record);
}
} else if (state != null) {
record.setState(state);
}
writer.write(record);
return record;
}
protected int writeAll(final Iterable<? extends Record> records, final RecordState state) {
int count = 0;
try (
Transaction transaction = newTransaction(Propagation.REQUIRED)) {
// It's important to have this in an inner try. Otherwise the exceptions
// won't get caught on closing the writer and the transaction won't get
// rolled back.
try (
final RecordWriter writer = newRecordWriter(true)) {
for (final Record record : records) {
write(writer, record, state);
count++;
}
} catch (final RuntimeException e) {
transaction.setRollbackOnly();
throw e;
} catch (final Error e) {
transaction.setRollbackOnly();
throw e;
}
}
return count;
}
}