/* * Copyright (c) 2017 OBiBa. All rights reserved. * * This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.obiba.magma.datasource.jdbc; import com.google.common.base.Strings; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import liquibase.change.Change; import liquibase.change.core.DropTableChange; import liquibase.snapshot.DatabaseSnapshot; import liquibase.structure.core.*; import org.obiba.magma.*; import org.obiba.magma.datasource.jdbc.JdbcDatasource.ChangeDatabaseCallback; import org.obiba.magma.datasource.jdbc.support.CreateIndexChangeBuilder; import org.obiba.magma.datasource.jdbc.support.CreateTableChangeBuilder; import org.obiba.magma.datasource.jdbc.support.TableUtils; import org.obiba.magma.support.AbstractValueTable; import org.obiba.magma.support.Initialisables; import org.obiba.magma.type.DateTimeType; import org.springframework.jdbc.core.RowMapper; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import javax.annotation.Nullable; import javax.validation.constraints.NotNull; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.obiba.magma.datasource.jdbc.JdbcValueTableWriter.*; import static org.obiba.magma.datasource.jdbc.support.TableUtils.newTable; import static org.obiba.magma.datasource.jdbc.support.TableUtils.newView; class JdbcValueTable extends AbstractValueTable { private final JdbcValueTableSettings settings; private Relation tableOrView; private BiMap<String, String> variableMap; private final String ESC_CATEGORY_ATTRIBUTES_TABLE, ESC_DATASOURCE_COLUMN, ESC_VALUE_TABLE_COLUMN, ESC_CATEGORIES_TABLE, ESC_VARIABLES_TABLE, ESC_VARIABLE_ATTRIBUTES_TABLE, ESC_VALUE_TABLES_TABLE, ESC_NAME_COLUMN, ESC_VARIABLE_COLUMN, ESC_CATEGORY_COLUMN, ESC_MISSING_COLUMN, ESC_SQL_NAME_COLUMN; JdbcValueTable(Datasource datasource, JdbcValueTableSettings settings) { super(datasource, settings.getMagmaTableName()); this.settings = settings; // first, check if it is an existing View View view = getDatasource().getDatabaseSnapshot().get(newView(getSqlName())); if (view == null) { // if not a view, make sure the SQL table exists if (getDatasource().getDatabaseSnapshot().get(newTable(getSqlName())) == null) { createSqlTable(getSqlName()); getDatasource().databaseChanged(); } tableOrView = getDatasource().getDatabaseSnapshot().get(newTable(getSqlName())); } else { tableOrView = view; } setVariableEntityProvider(new JdbcVariableEntityProvider(this)); ESC_CATEGORY_ATTRIBUTES_TABLE = getDatasource().escapeTableName(CATEGORY_ATTRIBUTES_TABLE); ESC_CATEGORIES_TABLE = getDatasource().escapeTableName(CATEGORIES_TABLE); ESC_VARIABLES_TABLE = getDatasource().escapeTableName(VARIABLES_TABLE); ESC_VARIABLE_ATTRIBUTES_TABLE = getDatasource().escapeTableName(VARIABLE_ATTRIBUTES_TABLE); ESC_VALUE_TABLES_TABLE = getDatasource().escapeTableName(VALUE_TABLES_TABLE); ESC_DATASOURCE_COLUMN= getDatasource().escapeColumnName(DATASOURCE_COLUMN); ESC_VALUE_TABLE_COLUMN = getDatasource().escapeColumnName(VALUE_TABLE_COLUMN); ESC_NAME_COLUMN = getDatasource().escapeColumnName(NAME_COLUMN); ESC_VARIABLE_COLUMN = getDatasource().escapeColumnName(VARIABLE_COLUMN); ESC_CATEGORY_COLUMN = getDatasource().escapeColumnName(CATEGORY_COLUMN); ESC_MISSING_COLUMN = getDatasource().escapeColumnName(MISSING_COLUMN); ESC_SQL_NAME_COLUMN = getDatasource().escapeColumnName(SQL_NAME_COLUMN); } JdbcValueTable(Datasource datasource, String tableName, Table table, String entityType) { this(datasource, JdbcValueTableSettings.newSettings(table.getName()).tableName(tableName).entityType(entityType) // .entityIdentifierColumn(getEntityIdentifierColumn(table)).build()); } JdbcValueTable(Datasource datasource, String tableName, View view, String entityType, String entityIdentifierColumn) { this(datasource, JdbcValueTableSettings.newSettings(view.getName()).tableName(tableName).entityType(entityType) // .entityIdentifierColumn(entityIdentifierColumn).build()); } // // AbstractValueTable Methods // @Override public void initialise() { super.initialise(); Initialisables.initialise(getVariableEntityProvider()); refreshVariablesMap(); initialiseVariableValueSources(); } @Override public String getEntityType() { return settings.getEntityType() == null ? getDatasource().getSettings().getDefaultEntityType() : settings.getEntityType(); } @NotNull @Override public JdbcDatasource getDatasource() { return (JdbcDatasource) super.getDatasource(); } @Override public ValueSet getValueSet(VariableEntity entity) throws NoSuchValueSetException { return new JdbcValueSet(this, entity); } @Override protected ValueSetBatch getValueSetsBatch(List<VariableEntity> entities) { return new JdbcValueSetBatch(this, entities); } @Override public Timestamps getValueSetTimestamps(VariableEntity entity) throws NoSuchValueSetException { return new ValueSetTimestamps(entity, getCreatedTimestampColumnName(), getUpdatedTimestampColumnName()); } @NotNull @Override public Timestamps getTimestamps() { return new JdbcValueTableTimestamps(this); } // // Methods // boolean isSQLView() { return tableOrView instanceof View; } public void drop() { if(getDatasource().getDatabaseSnapshot().get(newTable(getSqlName())) != null) { DropTableChange dtt = new DropTableChange(); dtt.setTableName(getSqlName()); getDatasource().doWithDatabase(new ChangeDatabaseCallback(dtt)); getDatasource().databaseChanged(); } if(getDatasource().getSettings().isUseMetadataTables()) { getDatasource().getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { dropCategoriesMetaData(); dropVariablesMetaData(); dropTableMetaData(); } }); } } private void dropCategoriesMetaData() { Object[] params = getDatasource().getSettings().isMultipleDatasources() ? new Object[] { getDatasource().getName(), getName() } : new Object[] { getName() }; String sql = getDatasource().getSettings().isMultipleDatasources() ? String.format("DELETE FROM %s WHERE %s = ? AND %s = ?", ESC_CATEGORY_ATTRIBUTES_TABLE, ESC_DATASOURCE_COLUMN, ESC_VALUE_TABLE_COLUMN) : String.format("DELETE FROM %s WHERE %s = ?", ESC_CATEGORY_ATTRIBUTES_TABLE, ESC_VALUE_TABLE_COLUMN); getDatasource().getJdbcTemplate().update(sql, params); sql = getDatasource().getSettings().isMultipleDatasources() ? String .format("DELETE FROM %s WHERE %s = ? AND %s = ?", ESC_CATEGORIES_TABLE, ESC_DATASOURCE_COLUMN, ESC_VALUE_TABLE_COLUMN) : String.format("DELETE FROM %s WHERE %s = ?", ESC_CATEGORIES_TABLE, ESC_VALUE_TABLE_COLUMN); getDatasource().getJdbcTemplate().update(sql, params); } private void dropVariablesMetaData() { Object[] params = getDatasource().getSettings().isMultipleDatasources() ? new Object[] { getDatasource().getName(), getName() } : new Object[] { getName() }; String sql = getDatasource().getSettings().isMultipleDatasources() ? String.format("DELETE FROM %s WHERE %s = ? AND %s = ?", ESC_VARIABLE_ATTRIBUTES_TABLE, ESC_DATASOURCE_COLUMN, ESC_VALUE_TABLE_COLUMN) : String.format("DELETE FROM %s WHERE %s = ?", ESC_VARIABLE_ATTRIBUTES_TABLE, ESC_VALUE_TABLE_COLUMN); getDatasource().getJdbcTemplate().update(sql, params); sql = getDatasource().getSettings().isMultipleDatasources() ? String .format("DELETE FROM %s WHERE %s = ? AND %s = ?", ESC_VARIABLES_TABLE, ESC_DATASOURCE_COLUMN, ESC_VALUE_TABLE_COLUMN) : String.format("DELETE FROM %s WHERE %s = ?", ESC_VARIABLES_TABLE, ESC_VALUE_TABLE_COLUMN); getDatasource().getJdbcTemplate().update(sql, params); } private void dropTableMetaData() { Object[] params = getDatasource().getSettings().isMultipleDatasources() ? new Object[] { getDatasource().getName(), getName() } : new Object[] { getName() }; String sql = getDatasource().getSettings().isMultipleDatasources() ? String.format("DELETE FROM %s WHERE %s = ? AND %s = ?", ESC_VALUE_TABLES_TABLE, ESC_DATASOURCE_COLUMN, ESC_NAME_COLUMN) : String.format("DELETE FROM %s WHERE %s = ?", ESC_VALUE_TABLES_TABLE, ESC_NAME_COLUMN); getDatasource().getJdbcTemplate().update(sql, params); } public JdbcValueTableSettings getSettings() { return settings; } String getSqlName() { return settings.getSqlTableName(); } void tableChanged() { refreshTable(); initialise(); } boolean hasCreatedTimestampColumn() { return getSettings().hasCreatedTimestampColumnName() || getDatasource().getSettings().hasCreatedTimestampColumnName(); } String getCreatedTimestampColumnName() { return getSettings().hasCreatedTimestampColumnName() ? getSettings().getCreatedTimestampColumnName() : getDatasource().getSettings().getDefaultCreatedTimestampColumnName(); } boolean hasUpdatedTimestampColumn() { return getSettings().hasUpdatedTimestampColumnName() || getDatasource().getSettings().hasUpdatedTimestampColumnName(); } String getUpdatedTimestampColumnName() { return getSettings().hasUpdatedTimestampColumnName() ? getSettings().getUpdatedTimestampColumnName() : getDatasource().getSettings().getDefaultUpdatedTimestampColumnName(); } void writeVariableValueSource(Variable source) { addVariableValueSource(new JdbcVariableValueSource(this, source)); } boolean isMultilines() { // either detected or configured (at table or datasource level) return ((JdbcVariableEntityProvider) getVariableEntityProvider()).isMultilines() || isSettingsMultilines(); } private boolean isSettingsMultilines() { return getSettings().isMultilines() || getDatasource().getSettings().isMultilines(); } static String getEntityIdentifierColumn(Table table) { List<String> entityIdentifierColumns = new ArrayList<>(); PrimaryKey pk = table.getPrimaryKey(); entityIdentifierColumns.addAll(table.getColumns().stream() // .filter(column -> pk != null && pk.getColumns().contains(column)) // .map(Column::getName) // .collect(Collectors.toList())); return entityIdentifierColumns.isEmpty() ? "" : entityIdentifierColumns.get(0); } static boolean hasEntityIdentifierColumn(Relation tableOrView, String defaultEntityIdColumnName) { return tableOrView.getColumn(defaultEntityIdColumnName) != null; } private void initialiseVariableValueSources() { clearSources(); if(getDatasource().getSettings().isUseMetadataTables()) { initialiseVariableValueSourcesFromMetaData(); } else { initialiseVariableValueSourcesFromColumns(); } } private void initialiseVariableValueSourcesFromMetaData() { if(!metadataTablesExist()) { throw new MagmaRuntimeException("metadata tables not found"); } String sql = getDatasource().getSettings().isMultipleDatasources() ? String .format("SELECT * FROM %s WHERE %s = ? AND %s = ?", ESC_VARIABLES_TABLE, ESC_DATASOURCE_COLUMN, ESC_VALUE_TABLE_COLUMN) : String.format("SELECT * FROM %s WHERE %s = ?", ESC_VARIABLES_TABLE, ESC_VALUE_TABLE_COLUMN); Object[] params = getDatasource().getSettings().isMultipleDatasources() ? new Object[] { getDatasource().getName(), getName() } : new Object[] { getName() }; List<Variable> results = getDatasource().getJdbcTemplate().query(sql, params, new VariableRowMapper()); for(Variable variable : results) { addVariableValueSource(new JdbcVariableValueSource(this, variable)); } } private void initialiseVariableValueSourcesFromColumns() { List<String> reserved = Lists.newArrayList(getSettings().getEntityIdentifierColumn()); if(getCreatedTimestampColumnName() != null) reserved.add(getCreatedTimestampColumnName()); if(getCreatedTimestampColumnName() != null) reserved.add(getUpdatedTimestampColumnName()); Pattern exclusion = Pattern.compile(getSettings().hasExcludedColumns() ? getSettings().getExcludedColumns() : "^$"); Pattern inclusion = Pattern.compile(getSettings().hasIncludedColumns() ? getSettings().getIncludedColumns() : ".*"); int idx = 1; for (Column column : tableOrView.getColumns().stream() // .filter(column -> isColumnIncluded(column, reserved, exclusion, inclusion)).collect(Collectors.toList())) { addVariableValueSource(new JdbcVariableValueSource(this, column, idx++)); } } /** * Check if the column name is neither reserved, nor excluded and is included. * * @param column * @param reserved * @param exclusion * @param inclusion * @return */ private boolean isColumnIncluded(Column column, List<String> reserved, Pattern exclusion, Pattern inclusion) { String name = column.getName(); return !reserved.contains(name) && !reserved.contains(name.toLowerCase()) && !exclusion.matcher(name).find() && inclusion.matcher(name).find(); } private class VariableRowMapper implements RowMapper<Variable> { @Override public Variable mapRow(ResultSet rs, int rowNum) throws SQLException { return buildVariableFromResultSet(rs); } private Variable buildVariableFromResultSet(ResultSet rs) throws SQLException { String variableName = rs.getString("name"); ValueType valueType = ValueType.Factory.forName(rs.getString("value_type")); String refEntityType = rs.getString("ref_entity_type"); String mimeType = rs.getString("mime_type"); String units = rs.getString("units"); boolean isRepeatable = rs.getBoolean("is_repeatable"); String occurrenceGroup = rs.getString("occurrence_group"); int index = rs.getInt("index"); Variable.Builder builder = Variable.Builder.newVariable(variableName, valueType, getEntityType()) .referencedEntityType(refEntityType).mimeType(mimeType).unit(units).index(index); if(isRepeatable) { builder.repeatable(); builder.occurrenceGroup(occurrenceGroup); } addVariableAttributes(variableName, builder); addVariableCategories(variableName, builder); return builder.build(); } private void addVariableAttributes(String variableName, Variable.Builder builder) { String sql = getDatasource().getSettings().isMultipleDatasources() ? String .format("SELECT * FROM %s WHERE %s = ? AND %s = ? AND %s = ? ", ESC_VARIABLE_ATTRIBUTES_TABLE, ESC_DATASOURCE_COLUMN, ESC_VALUE_TABLE_COLUMN, ESC_VARIABLE_COLUMN) : String.format("SELECT * FROM %s WHERE %s = ? AND %s = ? ", ESC_VARIABLE_ATTRIBUTES_TABLE, ESC_VALUE_TABLE_COLUMN, ESC_VARIABLE_COLUMN); Object[] params = getDatasource().getSettings().isMultipleDatasources() ? new Object[] { getDatasource().getName(), getName(), variableName } : new Object[] { getName(), variableName }; builder.addAttributes(getDatasource().getJdbcTemplate().query(sql, params, new AttributeRowMapper())); } private void addVariableCategories(final String variableName, Variable.Builder builder) { String sql = getDatasource().getSettings().isMultipleDatasources() ? String.format("SELECT %s, %s FROM %s WHERE %s = ? AND %s= ? AND %s = ?", ESC_NAME_COLUMN, ESC_MISSING_COLUMN, ESC_CATEGORIES_TABLE, ESC_DATASOURCE_COLUMN, ESC_VALUE_TABLE_COLUMN, ESC_VARIABLE_COLUMN) : String.format("SELECT %s, %s FROM %s WHERE %s= ? AND %s = ?", ESC_NAME_COLUMN, ESC_MISSING_COLUMN, ESC_CATEGORIES_TABLE, ESC_VALUE_TABLE_COLUMN, ESC_VARIABLE_COLUMN); Object[] params = getDatasource().getSettings().isMultipleDatasources() ? new Object[] { getDatasource().getName(), getName(), variableName } : new Object[] { getName(), variableName }; builder.addCategories(getDatasource().getJdbcTemplate().query(sql, params, new RowMapper<Category>() { @Override public Category mapRow(ResultSet rs, int rowNum) throws SQLException { String categoryName = rs.getString(NAME_COLUMN); Category.Builder catBuilder = Category.Builder.newCategory(categoryName) .missing(rs.getBoolean(MISSING_COLUMN)); addVariableCategoryAtributes(variableName, categoryName, catBuilder); return catBuilder.build(); } })); } private void addVariableCategoryAtributes(String variableName, String categoryName, Category.Builder builder) { String sql = getDatasource().getSettings().isMultipleDatasources() ? String.format("SELECT * FROM %s WHERE %s = ? AND %s = ? AND %s = ? AND %s = ?", ESC_CATEGORY_ATTRIBUTES_TABLE, ESC_DATASOURCE_COLUMN, ESC_VALUE_TABLE_COLUMN, ESC_VARIABLE_COLUMN, ESC_CATEGORY_COLUMN) : String.format("SELECT * FROM %s WHERE %s = ? AND %s = ? AND %s = ?", ESC_CATEGORY_ATTRIBUTES_TABLE, ESC_VALUE_TABLE_COLUMN, ESC_VARIABLE_COLUMN, ESC_CATEGORY_COLUMN); Object[] params = getDatasource().getSettings().isMultipleDatasources() ? new Object[] { getDatasource().getName(), getName(), variableName, categoryName } : new Object[] { getName(), variableName, categoryName }; builder.addAttributes(getDatasource().getJdbcTemplate().query(sql, params, new AttributeRowMapper())); } } private static class AttributeRowMapper implements RowMapper<Attribute> { @Override public Attribute mapRow(ResultSet rs, int rowNum) throws SQLException { String attributeName = rs.getString(NAME_COLUMN); String attributeNamespace = mayNotHaveColumn(rs, NAMESPACE_COLUMN); String attributeValue = rs.getString(VALUE_COLUMN); String attributeLocale = rs.getString(LOCALE_COLUMN); Attribute.Builder attr = Attribute.Builder.newAttribute(attributeName).withNamespace(attributeNamespace); if(attributeLocale != null && attributeLocale.length() > 0) { attr.withValue(new Locale(attributeLocale), attributeValue); } else { attr.withValue(attributeValue); } return attr.build(); } @Nullable private String mayNotHaveColumn(ResultSet rs, String column) { try { return rs.getString(column); } catch(SQLException e) { return null; } } } private boolean metadataTablesExist() { DatabaseSnapshot snapshot = getDatasource().getDatabaseSnapshot(); return snapshot.get(newTable(VARIABLES_TABLE)) != null && snapshot.get(newTable(VARIABLE_ATTRIBUTES_TABLE)) != null && snapshot.get(newTable(CATEGORIES_TABLE)) != null; } private void createSqlTable(String sqlTableName) { CreateTableChangeBuilder ctc = CreateTableChangeBuilder.newBuilder().tableName(sqlTableName); ctc.withColumn(getSettings().getEntityIdentifierColumn(), "VARCHAR(255)").primaryKey(!isSettingsMultilines()); createTimestampColumns(ctc); List<Change> changes = Lists.<Change>newArrayList(ctc.build()); if(hasCreatedTimestampColumn()) { changes.add(CreateIndexChangeBuilder.newBuilder().name(String.format("idx_%s_created", sqlTableName)).table(sqlTableName) .withColumn(getCreatedTimestampColumnName()).build()); } if(hasUpdatedTimestampColumn()) { changes.add(CreateIndexChangeBuilder.newBuilder().name(String.format("idx_%s_updated", sqlTableName)).table(sqlTableName) .withColumn(getUpdatedTimestampColumnName()).build()); } getDatasource().doWithDatabase(new ChangeDatabaseCallback(changes)); } private void createTimestampColumns(CreateTableChangeBuilder changeWithColumns) { if(hasCreatedTimestampColumn()) { changeWithColumns.withColumn(getCreatedTimestampColumnName(), "TIMESTAMP", JdbcDatasource.EPOCH).notNull(); } if(hasUpdatedTimestampColumn()) { changeWithColumns.withColumn(getUpdatedTimestampColumnName(), "TIMESTAMP", JdbcDatasource.EPOCH).notNull(); } } String getEntityIdentifierColumnSql() { return getDatasource().escapeColumnName(getSettings().getEntityIdentifierColumn()); } String extractEntityIdentifier(ResultSet rs) throws SQLException { return rs.getObject(1).toString(); } String getVariableSqlName(String variableName) { if(getVariablesMap().containsKey(variableName)) return getVariablesMap().get(variableName); return TableUtils.normalize(variableName); } String getVariableName(String variableName) { BiMap<String, String> tmp = getVariablesMap().inverse(); if(tmp.containsKey(variableName.toLowerCase())) return tmp.get(variableName.toLowerCase()); if(tmp.containsKey(variableName)) return tmp.get(variableName); return variableName; } public void refreshTable() { getDatasource().databaseChanged(); // no need to refresh a view if (tableOrView instanceof Table) { tableOrView = getDatasource().getDatabaseSnapshot().get(newTable(settings.getSqlTableName())); } } public synchronized void refreshVariablesMap() { variableMap = null; getVariablesMap(); } private BiMap<String, String> getVariablesMap() { if(variableMap != null) return variableMap; variableMap = HashBiMap.create(); if(getDatasource().getSettings().isUseMetadataTables()) { String sql = getDatasource().getSettings().isMultipleDatasources() ? String .format("SELECT %s, %s FROM %s WHERE %s = ? AND %s = ?", ESC_NAME_COLUMN, ESC_SQL_NAME_COLUMN, ESC_VARIABLES_TABLE, ESC_DATASOURCE_COLUMN, ESC_VALUE_TABLE_COLUMN) : String.format("SELECT %s, %s FROM %s WHERE %s = ?", ESC_NAME_COLUMN, ESC_SQL_NAME_COLUMN, ESC_VARIABLES_TABLE, ESC_VALUE_TABLE_COLUMN); Object[] params = getDatasource().getSettings().isMultipleDatasources() ? new Object[] { getDatasource().getName(), getName() } : new Object[] { getName() }; List<Map.Entry<String, String>> res = getDatasource().getJdbcTemplate() .query(sql, params, (rs, rowNum) -> Maps.immutableEntry(rs.getString(NAME_COLUMN), rs.getString(SQL_NAME_COLUMN))); for(Map.Entry<String, String> e : res) variableMap.put(e.getKey(), e.getValue()); } return variableMap; } // // Inner Classes // private class ValueSetTimestamps implements Timestamps { private final VariableEntity entity; private final String createdTimestampColumnName; private final String updatedTimestampColumnName; private ValueSetTimestamps(VariableEntity entity, String createdTimestampColumnName, String updatedTimestampColumnName) { this.entity = entity; this.createdTimestampColumnName = createdTimestampColumnName; this.updatedTimestampColumnName = updatedTimestampColumnName; } @NotNull @Override public Value getLastUpdate() { if (Strings.isNullOrEmpty(updatedTimestampColumnName)) return DateTimeType.get().nullValue(); String sql = appendIdentifierColumns( String.format("SELECT %s FROM %s", getDatasource().escapeColumnName(updatedTimestampColumnName), getDatasource().escapeTableName(getSqlName()))); return DateTimeType.get().valueOf(executeQuery(sql)); } @NotNull @Override public Value getCreated() { if (Strings.isNullOrEmpty(createdTimestampColumnName)) return DateTimeType.get().nullValue(); String sql = appendIdentifierColumns( String.format("SELECT %s FROM %s", getDatasource().escapeColumnName(createdTimestampColumnName), getDatasource().escapeTableName(getSqlName()))); return DateTimeType.get().valueOf(executeQuery(sql)); } private String appendIdentifierColumns(String sql) { StringBuilder sb = new StringBuilder(sql); sb.append(" WHERE "); sb.append(getDatasource().escapeColumnName(getSettings().getEntityIdentifierColumn())).append(" = ?"); return sb.toString(); } private Date executeQuery(String sql) { String[] entityIdentifierColumnValues = entity.getIdentifier().split("-"); try { return getDatasource().getJdbcTemplate().queryForObject(sql, entityIdentifierColumnValues, Date.class); } catch (Exception e) { return null; } } } }