
org.bridgedb.sql.SQLIdMapper Maven / Gradle / Ivy
// BridgeDb,
// An abstraction layer for identifier mapping services, both local and online.
//
// Copyright 2006-2009 BridgeDb developers
// Copyright 2012-2013 Christian Y. A. Brenninkmeijer
// Copyright 2012-2013 OpenPhacts
//
// Licensed 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.bridgedb.sql;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import org.bridgedb.DataSource;
import org.bridgedb.IDMapper;
import org.bridgedb.IDMapperCapabilities;
import org.bridgedb.IDMapperException;
import org.bridgedb.Xref;
import org.bridgedb.impl.InternalUtils;
import org.bridgedb.pairs.CodeMapper;
import org.bridgedb.pairs.IdSysCodePair;
import org.bridgedb.utils.BridgeDBException;
/**
* Builds on the SQLListener to implement the Standard BridgeDB functions of IDMapper and IDMapperCapabilities.
*
* This Allows the OPS version to function as any other BridgeDB implementation
*
* @author Christian
*/
public class SQLIdMapper extends SQLListener implements IDMapper, IDMapperCapabilities {
/**
* FreeSearch has proven to be very slow over large database so for large database we say it is unsupported.
*/
private static final int FREESEARCH_CUTOFF = 100000;
//Internal parameters
protected static final int DEFAULT_LIMIT = 1000;
/**
* This identifies version of SQL such as MySQL that use "LIMIT" to restrict the number of tuples returned.
*/
private final boolean useLimit;
/**
* This identifies version of SQL such as Virtuoso that use "TOP" to restrict the number of tuples returned.
*/
private final boolean useTop;
/**
* Map between IdSysCodePair and Xref or DataSources
*/
protected final CodeMapper codeMapper;
private static final Logger logger = Logger.getLogger(SQLIdMapper.class);
public SQLIdMapper(boolean dropTables, CodeMapper codeMapper) throws BridgeDBException{
super(dropTables);
useLimit = SqlFactory.supportsLimit();
useTop = SqlFactory.supportsTop();
this.codeMapper = codeMapper;
}
//*** IDMapper Methods
@Override
public Map> mapID(Collection srcXrefs, DataSource... tgtDataSources) throws IDMapperException {
return InternalUtils.mapMultiFromSingle(this, srcXrefs, tgtDataSources);
}
@Override
public Set mapID(Xref xref, DataSource... tgtDataSources) throws BridgeDBException {
IdSysCodePair ref = toIdSysCodePair(xref);
if (ref == null) {
logger.debug("mapId called with a badXref " + xref);
return new HashSet();
}
String[] tgtSysCodes = toCodes(tgtDataSources);
Set pairs = mapID(ref, tgtSysCodes);
return toXrefs(pairs);
}
private Set mapID(IdSysCodePair ref, String... tgtSysCodes) throws BridgeDBException {
StringBuilder query = new StringBuilder();
query.append("SELECT ");
query.append(TARGET_ID_COLUMN_NAME);
query.append(", ");
query.append(TARGET_DATASOURCE_COLUMN_NAME);
query.append(" FROM ");
query.append(MAPPING_TABLE_NAME);
query.append(", ");
query.append(MAPPING_SET_TABLE_NAME);
appendMappingJoinMapping(query);
appendSourceIdSysCodePair(query, ref);
if (tgtSysCodes != null &&tgtSysCodes.length > 0){
query.append(" AND ( ");
query.append(TARGET_DATASOURCE_COLUMN_NAME);
query.append(" = '");
query.append(tgtSysCodes[0]);
query.append("' ");
for (int i = 1; i < tgtSysCodes.length; i++){
query.append(" OR ");
query.append(TARGET_DATASOURCE_COLUMN_NAME);
query.append(" = '");
query.append(tgtSysCodes[i]);
query.append("'");
}
query.append(")");
}
Statement statement = this.createStatement();
ResultSet rs = null;
try {
rs = statement.executeQuery(query.toString());
} catch (SQLException ex) {
close(statement, rs);
throw new BridgeDBException("Unable to run query. " + query, ex);
}
Set results = resultSetToIdSysCodePairSet(rs);
if (tgtSysCodes.length == 0){
results.add(ref);
} else {
for (String tgtSysCode: tgtSysCodes){
if (ref.getSysCode().equals(tgtSysCode)){
results.add(ref);
}
}
}
if (results.size() <= 1){
String targets = "";
for (String tgtSysCode: tgtSysCodes){
targets+= tgtSysCode + ", ";
}
if (targets.isEmpty()){
targets = "all DataSources";
}
if (logger.isDebugEnabled()){
if (results.isEmpty()){
logger.debug("Unable to map " + ref + " to any results for " + targets);
} else {
logger.debug("Only able to map " + ref + " to itself for " + targets);
}
}
} else {
if (logger.isDebugEnabled()){
logger.debug("Mapped " + ref + " to " + results.size() + " results");
}
}
close(statement, rs);
return results;
}
/**
* Get all cross-references for the given entity, restricting the
* result to contain only references from the given set of data sources.
* @param xref the entity to get cross-references for.
* @param tgtDataSource target ID types/data source. Can not be null
* @return A Set containing the cross references, or an empty
* Set when no cross references could be found. This method does not return null.
* @throws BridgeDBException if the mapping service is (temporarily) unavailable
*/
public Set mapID(Xref xref, DataSource tgtDataSource) throws BridgeDBException {
IdSysCodePair ref = toIdSysCodePair(xref);
if (ref == null) {
if (logger.isDebugEnabled()){
logger.debug("mapId called with a badXref " + xref);
}
return new HashSet();
}
if (tgtDataSource == null){
throw new BridgeDBException("Target DataSource can not be null");
}
String tgtSysCode = toCode(tgtDataSource);
Set pairs = mapID(ref, tgtSysCode);
return toXrefs(pairs);
}
private Set mapID(IdSysCodePair ref, String tgtSysCode) throws BridgeDBException {
StringBuilder query = new StringBuilder();
query.append("SELECT ");
query.append(TARGET_ID_COLUMN_NAME);
query.append(", ");
query.append(TARGET_DATASOURCE_COLUMN_NAME);
query.append(" FROM ");
query.append(MAPPING_TABLE_NAME);
query.append(", ");
query.append(MAPPING_SET_TABLE_NAME);
appendMappingJoinMapping(query);
appendSourceIdSysCodePair(query, ref);
query.append(" AND ");
query.append(TARGET_DATASOURCE_COLUMN_NAME);
query.append(" = '");
query.append(tgtSysCode);
query.append("' ");
Statement statement = this.createStatement();
ResultSet rs = null;
try {
rs = statement.executeQuery(query.toString());
} catch (SQLException ex) {
close(statement, rs);
throw new BridgeDBException("Unable to run query. " + query, ex);
}
Set pairs = resultSetToIdSysCodePairSet(rs);
if (ref.getSysCode().equals(tgtSysCode)){
pairs.add(ref);
}
close(statement, rs);
return pairs;
}
@Override
public boolean xrefExists(Xref xref) throws BridgeDBException {
IdSysCodePair ref = toIdSysCodePair(xref);
if (ref == null) {
return false;
}
return IdSysCodePairExists(ref);
}
protected boolean IdSysCodePairExists(IdSysCodePair ref) throws BridgeDBException {
StringBuilder query = new StringBuilder();
query.append("SELECT ");
appendTopConditions(query, 0, 1);
query.append(TARGET_ID_COLUMN_NAME);
query.append(" FROM ");
query.append(MAPPING_TABLE_NAME);
query.append(", ");
query.append(MAPPING_SET_TABLE_NAME);
query.append(" WHERE ");
query.append(MAPPING_SET_ID_COLUMN_NAME);
query.append(" = ");
query.append(MAPPING_SET_DOT_ID_COLUMN_NAME);
appendSourceIdSysCodePair(query, ref);
appendLimitConditions(query,0, 1);
Statement statement = this.createStatement();
ResultSet rs = null;
try {
rs = statement.executeQuery(query.toString());
boolean result = rs.next();
if (logger.isDebugEnabled()){
logger.debug(ref + " exists = " + result);
}
close(statement, rs);
return result;
} catch (SQLException ex) {
close(statement, rs);
throw new BridgeDBException("Unable to run query. " + query, ex);
}
}
@Override
public Set freeSearch(String text, int limit) throws BridgeDBException {
StringBuilder query = new StringBuilder();
query.append("SELECT ");
appendTopConditions(query, 0, limit);
query.append(SOURCE_ID_COLUMN_NAME);
query.append(" as ");
query.append(TARGET_ID_COLUMN_NAME);
query.append(", ");
query.append(SOURCE_DATASOURCE_COLUMN_NAME);
query.append(" as ");
query.append(TARGET_DATASOURCE_COLUMN_NAME);
query.append(" FROM ");
query.append(MAPPING_TABLE_NAME);
query.append(", ");
query.append(MAPPING_SET_TABLE_NAME);
query.append(" WHERE ");
query.append(MAPPING_SET_ID_COLUMN_NAME);
query.append(" = ");
query.append(MAPPING_SET_DOT_ID_COLUMN_NAME);
query.append(" AND ");
query.append(SOURCE_ID_COLUMN_NAME);
query.append(" = '");
query.append(text);
query.append("' ");
appendLimitConditions(query,0, limit);
Statement statement = this.createStatement();
ResultSet rs = null;
try {
rs = statement.executeQuery(query.toString());
} catch (SQLException ex) {
close(statement, rs);
throw new BridgeDBException("Unable to run query. " + query, ex);
}
Set pairs = resultSetToIdSysCodePairSet(rs);
if (logger.isDebugEnabled()){
logger.debug("Freesearch for " + text + " gave " + pairs.size() + " results");
}
close(statement, rs);
return toXrefs(pairs);
}
@Override
public IDMapperCapabilities getCapabilities() {
return this;
}
//BridgeDB expects that once close is called isConnected will return false
private boolean isConnected = true;
@Override
/** {@inheritDoc} */
public void close() {
isConnected = false;
closeConnection();
}
@Override
/** {@inheritDoc} */
public boolean isConnected() {
if (isConnected){
try {
sqlAccess.getConnection();
return true;
} catch (BridgeDBException ex) {
return false;
}
}
if (logger.isDebugEnabled()){
logger.debug("isConnected() returned " + isConnected);
}
return isConnected;
}
// ***IDMapperCapabilities
@Override
public boolean isFreeSearchSupported() {
if (logger.isDebugEnabled()){
logger.debug("isFreeSearchSupported() returned true");
}
return true;
}
@Override
public Set getSupportedSrcDataSources() throws BridgeDBException {
StringBuilder query = new StringBuilder();
query.append("SELECT ");
query.append(SOURCE_DATASOURCE_COLUMN_NAME);
query.append(" as ");
query.append(SYSCODE_COLUMN_NAME);
query.append(" FROM ");
query.append(MAPPING_SET_TABLE_NAME);
Statement statement = this.createStatement();
ResultSet rs = null;
try {
rs = statement.executeQuery(query.toString());
} catch (SQLException ex) {
close(statement, rs);
throw new BridgeDBException("Unable to run query. " + query, ex);
}
Set results = resultSetToDataSourceSet(rs);
if (logger.isDebugEnabled()){
logger.debug("getSupportedSrcDataSources() returned " + results.size() + " results");
}
close(statement, rs);
return results;
}
@Override
public Set getSupportedTgtDataSources() throws BridgeDBException {
StringBuilder query = new StringBuilder();
query.append("SELECT ");
query.append(TARGET_DATASOURCE_COLUMN_NAME);
query.append(" as ");
query.append(SYSCODE_COLUMN_NAME);
query.append(" FROM ");
query.append(MAPPING_SET_TABLE_NAME);
Statement statement = this.createStatement();
ResultSet rs = null;
try {
rs = statement.executeQuery(query.toString());
} catch (SQLException ex) {
close(statement, rs);
throw new BridgeDBException("Unable to run query. " + query, ex);
}
Set results = resultSetToDataSourceSet(rs);
if (logger.isDebugEnabled()){
logger.debug("getSupportedTgtDataSources() returned " + results.size() + " results");
}
close(statement, rs);
return results;
}
@Override
public boolean isMappingSupported(DataSource src, DataSource tgt) throws BridgeDBException {
StringBuilder query = new StringBuilder();
query.append("SELECT ");
query.append(ID_COLUMN_NAME);
query.append(" FROM ");
query.append(MAPPING_SET_TABLE_NAME);
query.append(" WHERE ");
query.append(SOURCE_DATASOURCE_COLUMN_NAME);
query.append(" = '");
query.append(getDataSourceKey(src));
query.append("' ");
query.append(" AND ");
query.append(TARGET_DATASOURCE_COLUMN_NAME);
query.append(" = '");
query.append(getDataSourceKey(tgt));
query.append("' ");
Statement statement = this.createStatement();
ResultSet rs = null;
try {
rs = statement.executeQuery(query.toString());
boolean result = rs.next();
if (logger.isDebugEnabled()){
logger.debug("isMappingSupported " + src + " to " + tgt + " is " + result);
}
close(statement, rs);
return result;
} catch (SQLException ex) {
close(statement, rs);
throw new BridgeDBException("Unable to run query. " + query, ex);
}
}
@Override
public String getProperty(String key) {
String query = "SELECT DISTINCT " + PROPERTY_COLUMN_NAME
+ " FROM " + PROPERTIES_TABLE_NAME
+ " WHERE " + KEY_COLUMN_NAME + " = '" + key + "'";
Statement statement = null;
ResultSet rs = null;
try {
statement = this.createStatement();
rs = statement.executeQuery(query);
if (rs.next()){
String result = rs.getString("property");
if (logger.isDebugEnabled()){
logger.debug("property " + key + " is " + result);
}
close(statement, rs);
return result;
} else {
if (logger.isDebugEnabled()){
logger.warn("No property " + key + " found! ");
}
close(statement, rs);
return null;
}
} catch (Exception ex) {
close(statement, rs);
return null;
}
}
@Override
public Set getKeys() {
HashSet results = new HashSet();
String query = "SELECT " + KEY_COLUMN_NAME
+ " FROM " + PROPERTIES_TABLE_NAME
+ " WHERE " + IS_PUBLIC_COLUMN_NAME + " = 1"; //one works where isPublic is a boolean
Statement statement = null;
ResultSet rs = null;
try {
statement = this.createStatement();
rs = statement.executeQuery(query);
while (rs.next()){
results.add(rs.getString(KEY_COLUMN_NAME));
}
if (logger.isDebugEnabled()){
logger.warn("getKeys() returned " + results.size() + " keys! ");
}
close(statement, rs);
return results;
} catch (Exception ex) {
close(statement, rs);
logger.error("Error getting keys ", ex);
return null;
}
}
//**** Support methods
/**
* Check if the Xref is invalid in some way.
*
* For example if it is null or if either the Id or DataSource part is null.
* @param ref - the xref to check
* @return if the xref whether or not the xref is valid
*/
protected final boolean badXref(Xref ref) {
if (ref == null) return true;
if (ref.getId() == null || ref.getId().isEmpty()) return true;
if (ref.getDataSource() == null ) return true;
return false;
}
/**
* Add a condition to the query that only mappings with a specific source IdSysCodePair should be used.
* @param query Query to add to.
* @param ref IdSysCodePair that forms the base of the condition.
*/
protected final void appendSourceIdSysCodePair(StringBuilder query, IdSysCodePair ref){
query.append(" AND ");
query.append(SOURCE_ID_COLUMN_NAME);
query.append(" = '");
query.append(ref.getId());
query.append("' ");
query.append(" AND ");
query.append(SOURCE_DATASOURCE_COLUMN_NAME);
query.append(" = '");
query.append(ref.getSysCode());
query.append("' ");
}
/**
* Add a condition to the query that only mappings with a specific source Xref should be used.
* @param query Query to add to.
* @param id - identifier of the Xref
* @param sysCode - system code of the source database of the Xref
*/
protected final void appendSourceXref(StringBuilder query, String id, String sysCode){
query.append(" AND ");
query.append(SOURCE_ID_COLUMN_NAME);
query.append(" = '");
query.append(id);
query.append("' ");
query.append(" AND ");
query.append(SOURCE_DATASOURCE_COLUMN_NAME);
query.append(" = '");
query.append(sysCode);
query.append("' ");
}
/**
* Converts a ResultSet to a Set of individual Xrefs.
* @throws BridgeDBException
*/
final Set resultSetToIdSysCodePairSet(ResultSet rs) throws BridgeDBException {
HashSet results = new HashSet();
try {
while (rs.next()){
String id = rs.getString(TARGET_ID_COLUMN_NAME);
String sysCode = rs.getString(TARGET_DATASOURCE_COLUMN_NAME);
IdSysCodePair pair = new IdSysCodePair(id, sysCode);
results.add(pair);
}
return results;
} catch (SQLException ex) {
throw new BridgeDBException("Unable to parse results.", ex);
}
}
/**
* Converts a ResultSet to a Set of BridgeDB DataSources by obtaining the SysCode from the ResultsSet a
* and looking the DataSource up in the DataSource Registry.
*
* All DataSources have been preloaded by loadDataSources() called during the super constructor.
* @param rs
* @return
* @throws BridgeDBException
*/
private Set resultSetToDataSourceSet(ResultSet rs) throws BridgeDBException {
HashSet results = new HashSet();
try {
while (rs.next()){
String sysCode = rs.getString(SYSCODE_COLUMN_NAME);
results.add(DataSource.getExistingBySystemCode(sysCode));
}
return results;
} catch (SQLException ex) {
throw new BridgeDBException("Unable to parse results.", ex);
}
}
/**
* Adds the limit condition if this is the support method for limitng the number of results.
*
* System such as MYSQL use a Limit clause at the end of the query. Where applicable this is added here.
* @param query Query to add Limit to
* @param position The offset of the fragment of results to return
* @param limit The size of the fragment of results to return
*/
protected void appendLimitConditions(StringBuilder query, Integer position, Integer limit){
if (useLimit){
if (position == null) {
position = 0;
}
if (limit == null){
limit = DEFAULT_LIMIT;
}
query.append(" LIMIT " + position + ", " + limit);
}
}
/**
* Adds the top condition if this is the support method for limitng the number of results.
*
* System such as Virtuosos use a Top clause directly after the select. Where applicable this is added here.
* @param query Query to add TOP to
* @param position The offset of the fragment of results to return
* @param limit The size of the fragment of results to return
*/
protected void appendTopConditions(StringBuilder query, Integer position, Integer limit){
if (useTop){
if (position == null) {
position = 0;
}
if (limit == null){
limit = DEFAULT_LIMIT;
}
query.append("TOP " + position + ", " + limit + " ");
}
}
protected final void appendMappingJoinMapping(StringBuilder query){
query.append(" WHERE ");
query.append(MAPPING_SET_ID_COLUMN_NAME);
query.append(" = ");
query.append(MAPPING_SET_DOT_ID_COLUMN_NAME);
}
protected final String[] toCodes(DataSource[] tgtDataSources) {
if (tgtDataSources == null || tgtDataSources.length == 0){
return new String[0];
}
String[] results = new String[tgtDataSources.length];
for (int i = 0; i < tgtDataSources.length; i++){
results[i] = tgtDataSources[i].getSystemCode();
}
return results;
}
protected final IdSysCodePair toIdSysCodePair(Xref xref) throws BridgeDBException {
if (xref == null || xref.getId() == null || xref.getDataSource() == null){
return null;
}
return codeMapper.toIdSysCodePair(xref);
}
protected final Set toXrefs(Set pairs) throws BridgeDBException {
HashSet results = new HashSet();
for (IdSysCodePair pair:pairs){
results.add(codeMapper.toXref(pair));
}
return results;
}
protected final String toCode(DataSource tgtDataSource) {
if (tgtDataSource == null){
return null;
}
return tgtDataSource.getSystemCode();
}
}