/*
* 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 java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedSet;
import javax.validation.constraints.NotNull;
import org.obiba.magma.AbstractVariableValueSource;
import org.obiba.magma.Value;
import org.obiba.magma.ValueSet;
import org.obiba.magma.ValueType;
import org.obiba.magma.Variable;
import org.obiba.magma.VariableEntity;
import org.obiba.magma.VariableValueSource;
import org.obiba.magma.VectorSource;
import com.google.common.collect.Maps;
import liquibase.structure.core.Column;
class JdbcVariableValueSource extends AbstractVariableValueSource implements VariableValueSource, VectorSource {
//
// Instance Variables
//
private final JdbcValueTable valueTable;
private final Variable variable;
private final String columnName;
//
// Constructors
//
JdbcVariableValueSource(JdbcValueTable valueTable, Column column, int idx) {
this.valueTable = valueTable;
columnName = column.getName();
variable = Variable.Builder
.newVariable(valueTable.getVariableName(columnName), SqlTypes.valueTypeFor(column.getType().getDataTypeId()),
valueTable.getEntityType()) //
.index(idx) //
.repeatable(isMultilines()) //
.occurrenceGroup(isMultilines() ? valueTable.getSqlName() : null).build();
}
JdbcVariableValueSource(JdbcValueTable valueTable, Variable variable) {
this.valueTable = valueTable;
this.variable = variable;
columnName = valueTable.getVariableSqlName(variable.getName());
}
//
// VariableValueSource Methods
//
@NotNull
@Override
public Variable getVariable() {
return variable;
}
@NotNull
@Override
public Value getValue(ValueSet valueSet) {
JdbcValueSet jdbcValueSet = (JdbcValueSet) valueSet;
return jdbcValueSet.getValue(variable);
}
@NotNull
@Override
public ValueType getValueType() {
return variable.getValueType();
}
@Override
public boolean supportVectorSource() {
return true;
}
@NotNull
@Override
public VectorSource asVectorSource() {
return this;
}
@Override
public Iterable<Value> getValues(final SortedSet<VariableEntity> entities) {
return () -> {
try {
return new ValueIterator(valueTable.getDatasource().getJdbcTemplate().getDataSource().getConnection(),
entities);
} catch(SQLException e) {
throw new RuntimeException(e);
}
};
}
private boolean isMultilines() {
return valueTable.isMultilines();
}
private class ValueIterator implements Iterator<Value> {
private final Connection connection;
private final PreparedStatement statement;
private final ResultSet rs;
private final Iterator<VariableEntity> entities;
private boolean hasNextResults;
private boolean closed = false;
private final Map<String, Value> valueMap = Maps.newHashMap();
@edu.umd.cs.findbugs.annotations.SuppressWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING")
private ValueIterator(Connection connection, Iterable<VariableEntity> entities) throws SQLException {
this.connection = connection;
JdbcDatasource datasource = valueTable.getDatasource();
String escapedIdentifierColumn = valueTable.getEntityIdentifierColumnSql();
statement = connection.prepareStatement(
String.format("SELECT %s, %s FROM %s %s ORDER BY %s", escapedIdentifierColumn,
datasource.escapeColumnName(columnName), datasource.escapeTableName(valueTable.getSqlName()),
getWhereClause(), escapedIdentifierColumn));
rs = statement.executeQuery();
hasNextResults = rs.next();
this.entities = entities.iterator();
closeCursorIfNecessary();
}
private String getWhereClause() {
if (!valueTable.getSettings().hasEntityIdentifiersWhere()) return "";
else return String.format("WHERE %s", valueTable.getSettings().getEntityIdentifiersWhere());
}
@Override
public boolean hasNext() {
return entities.hasNext();
}
@Override
public Value next() {
VariableEntity entity = entities.next();
String nextId = entity.getIdentifier();
if(valueMap.containsKey(nextId)) return getValueFromMap(entity);
try {
// Scroll until we find the required entity or reach the end of the results
boolean found = false;
while(hasNextResults && !found) {
String id = valueTable.extractEntityIdentifier(rs);
valueMap.put(id, getValueFromResult());
hasNextResults = rs.next();
found = nextId.equals(id);
}
closeCursorIfNecessary();
if(valueMap.containsKey(nextId)) return getValueFromMap(entity);
return getVariable().isRepeatable() ? getValueType().nullSequence() : getValueType().nullValue();
} catch(SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private Value getValueFromResult() throws SQLException {
Object resObj = rs.getObject(columnName);
if(resObj == null) {
return variable.isRepeatable() ? getValueType().nullSequence() : getValueType().nullValue();
}
if(variable.isRepeatable()) {
return getValueType().sequenceOf(resObj.toString());
}
return getValueType().valueOf(resObj);
}
private void closeCursorIfNecessary() {
if(!closed) {
// Close the cursor if we don't have any more results or no more entities to return
if(!hasNextResults || !hasNext()) {
closed = true;
closeQuietly(rs, statement, connection);
}
}
}
/**
* No duplicate of entities, so remove value from map once get.
*
* @param entity
* @return
*/
private Value getValueFromMap(VariableEntity entity) {
Value value = valueMap.get(entity.getIdentifier());
valueMap.remove(entity.getIdentifier());
return value;
}
@SuppressWarnings({ "OverlyStrongTypeCast", "ChainOfInstanceofChecks" })
private void closeQuietly(Object... objs) {
if(objs != null) {
for(Object o : objs) {
try {
if(o instanceof ResultSet) {
((ResultSet) o).close();
}
if(o instanceof Statement) {
((Statement) o).close();
}
if(o instanceof Connection) {
((Connection) o).close();
}
} catch(SQLException e) {
// ignored
}
}
}
}
}
}