/*
* Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
* Copyright (C) $$Year-Rel$$ The Jalview Authors
*
* This file is part of Jalview.
*
* Jalview is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* Jalview 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 General Public License
* along with Jalview. If not, see .
* The Jalview Authors are detailed in the 'AUTHORS' file.
*/
package jalview.gui.structurechooser;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.swing.JTable;
import jalview.bin.Console;
import jalview.datamodel.DBRefEntry;
import jalview.datamodel.DBRefSource;
import jalview.datamodel.PDBEntry;
import jalview.datamodel.SequenceI;
import jalview.fts.api.FTSData;
import jalview.fts.api.FTSDataColumnI;
import jalview.fts.api.FTSRestClientI;
import jalview.fts.core.FTSDataColumnPreferences;
import jalview.fts.core.FTSDataColumnPreferences.PreferenceSource;
import jalview.fts.core.FTSRestRequest;
import jalview.fts.core.FTSRestResponse;
import jalview.fts.service.threedbeacons.TDB_FTSData;
import jalview.fts.service.threedbeacons.TDBeaconsFTSRestClient;
import jalview.jbgui.FilterOption;
/**
* logic for querying the 3DBeacons API for structures of sequences
*
* @author jprocter
*/
public class ThreeDBStructureChooserQuerySource
extends StructureChooserQuerySource
{
private Set tdBeaconsFilters = null, defaultFilters = null;
public static final String FILTER_TDBEACONS_COVERAGE = "3d_beacons_coverage";
public static final String FILTER_FIRST_BEST_COVERAGE = "3d_beacons_first_best_coverage";
private static final String FILTER_SOURCE_PREFIX = "only_";
protected FTSRestRequest lastTdbRequest;
protected FTSRestClientI tdbRestClient;
private FTSRestRequest lastPdbRequest;
public ThreeDBStructureChooserQuerySource()
{
defaultFilters = new LinkedHashSet();
defaultFilters.add(FILTER_TDBEACONS_COVERAGE);
defaultFilters.add(FILTER_FIRST_BEST_COVERAGE);
tdbRestClient = TDBeaconsFTSRestClient.getInstance();
docFieldPrefs = new FTSDataColumnPreferences(
PreferenceSource.STRUCTURE_CHOOSER,
TDBeaconsFTSRestClient.getInstance());
}
/**
* Builds a query string for a given sequences using its DBRef entries 3d
* Beacons is only useful for uniprot IDs
*
* @param seq
* the sequences to build a query for
* @return the built query string
*/
@Override
public String buildQuery(SequenceI seq)
{
List refs = seq.getDBRefs();
int ib = checkUniprotRefs(refs);
if (ib > -1)
{
return getDBRefId(refs.get(ib));
}
return null;
}
/**
* Searches DBRefEntry for uniprot refs
*
* @param seq
* @return -2 if no uniprot refs, -1 if no canonical ref., otherwise index of
* Uniprot canonical DBRefEntry
*/
public static int checkUniprotRefs(List refs)
{
boolean hasUniprot = false;
if (refs != null && refs.size() != 0)
{
for (int ib = 0, nb = refs.size(); ib < nb; ib++)
{
DBRefEntry dbRef = refs.get(ib);
if (dbRef.getSource().equalsIgnoreCase(DBRefSource.UNIPROT))
{
hasUniprot = true;
if (dbRef.isCanonical())
{
return ib;
}
}
}
}
return hasUniprot ? -1 : -2;
}
/**
* Ensures sequence ref names are not less than 3 characters and does not
* contain a database name
*
* @param seqName
* @return
*/
static boolean isValidSeqName(String seqName)
{
String ignoreList = "pdb,uniprot,swiss-prot";
if (seqName.length() < 3)
{
return false;
}
if (seqName.contains(":"))
{
return false;
}
seqName = seqName.toLowerCase(Locale.ROOT);
for (String ignoredEntry : ignoreList.split(","))
{
if (seqName.contains(ignoredEntry))
{
return false;
}
}
return true;
}
static String getDBRefId(DBRefEntry dbRef)
{
String ref = dbRef.getAccessionId().replaceAll("GO:", "");
return ref;
}
/**
* FTSRestClient specific query builder to recover associated structure data
* records for a sequence
*
* @param seq
* - seq to generate a query for
* @param wantedFields
* - fields to retrieve
* @param selectedFilterOpt
* - criterion for ranking results (e.g. resolution)
* @param b
* - sort ascending or descending
* @return
* @throws Exception
*/
@Override
public FTSRestResponse fetchStructuresMetaData(SequenceI seq,
Collection wantedFields,
FilterOption selectedFilterOpt, boolean b) throws Exception
{
FTSRestResponse resultList;
if (selectedFilterOpt != null
&& tdBeaconsFilter(selectedFilterOpt.getValue()))
{
FTSRestRequest tdbRequest = getTDBeaconsRequest(seq, wantedFields);
resultList = tdbRestClient.executeRequest(tdbRequest);
lastTdbRequest = tdbRequest;
if (resultList != null)
{ // Query the PDB and add additional metadata
List pdbResponse = fetchStructuresMetaDataFor(
getPDBQuerySource(), resultList);
resultList = joinResponses(resultList, pdbResponse);
}
return resultList;
}
// use the PDBFTS directly
resultList = getPDBQuerySource().fetchStructuresMetaData(seq,
wantedFields, selectedFilterOpt, b);
lastTdbRequest = getPDBQuerySource().lastPdbRequest;
lastPdbRequest = lastTdbRequest; // both queries the same - indicates we
// rank using PDBe
return resultList;
}
PDBStructureChooserQuerySource pdbQuerySource = null;
private PDBStructureChooserQuerySource getPDBQuerySource()
{
if (pdbQuerySource == null)
{
pdbQuerySource = new PDBStructureChooserQuerySource();
}
return pdbQuerySource;
}
private FTSRestRequest getTDBeaconsRequest(SequenceI seq,
Collection wantedFields)
{
FTSRestRequest pdbRequest = new FTSRestRequest();
pdbRequest.setAllowEmptySeq(false);
pdbRequest.setResponseSize(500);
pdbRequest.setWantedFields(wantedFields);
String query = buildQuery(seq);
if (query == null)
{
return null;
}
pdbRequest.setSearchTerm(query + ".json");
pdbRequest.setAssociatedSequence(seq);
return pdbRequest;
}
@Override
public List getAvailableFilterOptions(String VIEWS_FILTER)
{
List filters = getPDBQuerySource()
.getAvailableFilterOptions(VIEWS_FILTER);
tdBeaconsFilters = new LinkedHashSet();
tdBeaconsFilters.addAll(defaultFilters);
filters.add(0, new FilterOption("Best 3D-Beacons Coverage",
FILTER_FIRST_BEST_COVERAGE, VIEWS_FILTER, false, this));
filters.add(1, new FilterOption("Multiple 3D-Beacons Coverage",
FILTER_TDBEACONS_COVERAGE, VIEWS_FILTER, true, this));
return filters;
}
@Override
public void updateAvailableFilterOptions(String VIEWS_FILTER,
List xtantOptions, Collection tdbEntries)
{
if (tdbEntries != null && lastTdbRequest != null)
{
boolean hasPDBe = false;
for (FTSData _row : tdbEntries)
{
// tdb returns custom object
TDB_FTSData row = (TDB_FTSData) _row;
String provider = row.getProvider();
FilterOption providerOpt = new FilterOption(
"3DB Provider - " + provider,
FILTER_SOURCE_PREFIX + provider, VIEWS_FILTER, false, this);
if (!xtantOptions.contains(providerOpt))
{
xtantOptions.add(1, providerOpt);
tdBeaconsFilters.add(FILTER_SOURCE_PREFIX + provider);
if ("PDBe".equalsIgnoreCase(provider))
{
hasPDBe = true;
}
}
}
if (!hasPDBe)
{
// remove the PDBe options from the available filters
int op = 0;
while (op < xtantOptions.size())
{
FilterOption filter = xtantOptions.get(op);
if (filter
.getQuerySource() instanceof PDBStructureChooserQuerySource)
{
xtantOptions.remove(op);
}
else
{
op++;
}
}
}
}
}
private boolean tdBeaconsFilter(String fieldToFilterBy)
{
return tdBeaconsFilters != null
&& tdBeaconsFilters.contains(fieldToFilterBy);
}
private String remove_prefix(String fieldToFilterBy)
{
if (tdBeaconsFilters != null
&& tdBeaconsFilters.contains(fieldToFilterBy)
&& !defaultFilters.contains(fieldToFilterBy))
{
return fieldToFilterBy.substring(FILTER_SOURCE_PREFIX.length());
}
else
{
return null;
}
}
@Override
public boolean needsRefetch(FilterOption selectedFilterOpt)
{
return selectedFilterOpt == null
|| !tdBeaconsFilter(selectedFilterOpt.getValue())
&& lastPdbRequest != lastTdbRequest;
}
/**
* FTSRestClient specific query builder to pick top ranked entry from a
* fetchStructuresMetaData query
*
* @param seq
* - seq to generate a query for
* @param wantedFields
* - fields to retrieve
* @param selectedFilterOpt
* - criterion for ranking results (e.g. resolution)
* @param b
* - sort ascending or descending
* @return
* @throws Exception
*/
@Override
public FTSRestResponse selectFirstRankedQuery(SequenceI seq,
Collection collectedResults,
Collection wantedFields, String fieldToFilterBy,
boolean b) throws Exception
{
if (fieldToFilterBy != null && tdBeaconsFilter(fieldToFilterBy))
{
TDBResultAnalyser analyser = new TDBResultAnalyser(seq,
collectedResults, lastTdbRequest, fieldToFilterBy,
remove_prefix(fieldToFilterBy));
FTSRestResponse resultList = new FTSRestResponse();
List filteredResponse = analyser.getFilteredResponse();
List selectedStructures = analyser
.selectStructures(filteredResponse);
resultList.setNumberOfItemsFound(selectedStructures.size());
resultList.setSearchSummary(selectedStructures);
return resultList;
}
// Fall back to PDBe rankings
return getPDBQuerySource().selectFirstRankedQuery(seq, collectedResults,
wantedFields, fieldToFilterBy, b);
}
@Override
public PDBEntry[] collectSelectedRows(JTable restable, int[] selectedRows,
List selectedSeqsToView)
{
int refSeqColIndex = restable.getColumn("Ref Sequence").getModelIndex();
PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
int count = 0;
int idColumnIndex = restable.getColumn("Model id").getModelIndex();
int urlColumnIndex = restable.getColumn("Url").getModelIndex();
int typeColumnIndex = restable.getColumn("Provider").getModelIndex();
int humanUrl = restable.getColumn("Page URL").getModelIndex();
int modelformat = restable.getColumn("Model Format").getModelIndex();
final int up_start_idx = restable.getColumn("Uniprot Start")
.getModelIndex();
final int up_end_idx = restable.getColumn("Uniprot End")
.getModelIndex();
int i = 0;
// bleugh!
Integer[] sellist = new Integer[selectedRows.length];
for (Integer row : selectedRows)
{
sellist[i++] = row;
}
// Sort rows by coverage
Arrays.sort(sellist, new Comparator()
{
@Override
public int compare(Integer o1, Integer o2)
{
int o1_xt = ((Integer) restable.getValueAt(o1, up_end_idx))
- (Integer) restable.getValueAt(o1, up_start_idx);
int o2_xt = ((Integer) restable.getValueAt(o2, up_end_idx))
- (Integer) restable.getValueAt(o2, up_start_idx);
return o2_xt - o1_xt;
}
});
for (int row : sellist)
{
// unique id - could be a horrible hash
String pdbIdStr = restable.getValueAt(row, idColumnIndex).toString();
String urlStr = restable.getValueAt(row, urlColumnIndex).toString();
String typeColumn = restable.getValueAt(row, typeColumnIndex)
.toString();
String modelPage = humanUrl < 1 ? null
: (String) restable.getValueAt(row, humanUrl);
String strucFormat = restable.getValueAt(row, modelformat).toString();
SequenceI selectedSeq = (SequenceI) restable.getValueAt(row,
refSeqColIndex);
selectedSeqsToView.add(selectedSeq);
PDBEntry pdbEntry = selectedSeq.getPDBEntry(pdbIdStr);
if (pdbEntry == null)
{
pdbEntry = getFindEntry(pdbIdStr, selectedSeq.getAllPDBEntries());
}
if (pdbEntry == null)
{
pdbEntry = new PDBEntry();
pdbEntry.setId(pdbIdStr);
pdbEntry.setAuthoritative(true);
try
{
pdbEntry.setType(PDBEntry.Type.valueOf(strucFormat));
} catch (Exception q)
{
Console.warn("Unknown filetype for 3D Beacons Model from: "
+ strucFormat + " - " + pdbIdStr + " - " + modelPage);
}
if (!"PDBe".equalsIgnoreCase(typeColumn))
{
pdbEntry.setRetrievalUrl(urlStr);
}
pdbEntry.setProvider(typeColumn);
if (modelPage != null)
{
pdbEntry.setProviderPage(modelPage);
}
selectedSeq.getDatasetSequence().addPDBId(pdbEntry);
}
pdbEntriesToView[count++] = pdbEntry;
}
return pdbEntriesToView;
}
@Override
protected FTSRestRequest getLastFTSRequest()
{
return lastTdbRequest;
}
/**
* generate a query for PDBFTS to retrieve structure metadata
*
* @param ftsRestRequest
* @param upResponse
* @return
*/
public List buildPDBFTSQueryFor(FTSRestResponse upResponse)
{
List ftsQueries = new ArrayList();
Set pdbIds = new HashSet();
int idx_modelId = getLastFTSRequest().getFieldIndex("Model id");
int idx_provider = getLastFTSRequest().getFieldIndex("Provider");
for (FTSData row : upResponse.getSearchSummary())
{
String id = (String) row.getSummaryData()[idx_modelId];
String provider = (String) row.getSummaryData()[idx_provider];
if ("PDBe".equalsIgnoreCase(provider))
{
pdbIds.add(id);
}
}
StringBuilder sb = new StringBuilder();
for (String pdbId : pdbIds)
{
if (sb.length() > 2500)
{
ftsQueries.add(sb.toString());
sb.setLength(0);
}
if (sb.length() > 0)
{
sb.append(" OR ");
}
sb.append(pdbId);
}
if (sb.length() > 0)
{
ftsQueries.add(sb.toString());
}
return ftsQueries;
}
/**
* query PDBe for structure metadata
*
* @param pdbquery
* @param upResponse
* @return FTSRestResponse via PDBStructureChooserQuerySource
*/
public List fetchStructuresMetaDataFor(
PDBStructureChooserQuerySource pdbquery,
FTSRestResponse upResponse) throws Exception
{
List pdb_Queries = buildPDBFTSQueryFor(upResponse);
if (pdb_Queries.size() == 0)
{
return null;
}
List results = new ArrayList();
for (String pdb_Query : pdb_Queries)
{
FTSRestResponse resultList;
FTSRestRequest pdbRequest = new FTSRestRequest();
pdbRequest.setAllowEmptySeq(false);
pdbRequest.setResponseSize(500);
pdbRequest.setFieldToSearchBy("(");
// pdbRequest.setFieldToSortBy("pdb_id");
pdbRequest.setWantedFields(
pdbquery.getDocFieldPrefs().getStructureSummaryFields());
pdbRequest.setSearchTerm(pdb_Query + ")");
// handle exceptions like server errors here - means the threedbeacons
// discovery isn't broken by issues to do with the PDBe SOLR api
try
{
resultList = pdbquery.executePDBFTSRestRequest(pdbRequest);
if (resultList.getNumberOfItemsFound() == 0)
{
Console.info("Unexpectedly returned no results for pdbe query: "
+ pdb_Query);
}
results.add(resultList);
lastPdbRequest = pdbRequest;
} catch (Exception ex)
{
Console.error("PDBFTSQuery failed", ex);
}
}
return results;
}
public FTSRestResponse joinResponses(FTSRestResponse upResponse,
List pdbResponses)
{
boolean hasPdbResp = lastPdbRequest != null;
int idx_provider = getLastFTSRequest().getFieldIndex("Provider");
// join on
int idx_modelId = getLastFTSRequest().getFieldIndex("Model id");
int pdbIdx = hasPdbResp ? lastPdbRequest.getFieldIndex("PDB Id") : -1;
int pdbTitle_idx = hasPdbResp ? lastPdbRequest.getFieldIndex("Title")
: -1;
int tdbTitle_idx = getLastFTSRequest().getFieldIndex("Title");
for (final FTSData row : upResponse.getSearchSummary())
{
String id = (String) row.getSummaryData()[idx_modelId];
String provider = (String) row.getSummaryData()[idx_provider];
if ("PDBe".equalsIgnoreCase(provider))
{
if (!hasPdbResp)
{
jalview.bin.Console.outPrintln(
"Warning: seems like we couldn't get to the PDBe search interface.");
}
else
{
for (final FTSRestResponse pdbResponse : pdbResponses)
{
for (final FTSData pdbrow : pdbResponse.getSearchSummary())
{
String pdbid = (String) pdbrow.getSummaryData()[pdbIdx];
if (id.equalsIgnoreCase(pdbid))
{
row.getSummaryData()[tdbTitle_idx] = pdbrow
.getSummaryData()[pdbTitle_idx];
}
}
}
}
}
else
{
row.getSummaryData()[tdbTitle_idx] = "Model from TDB";
}
}
return upResponse;
}
public TDB_FTSData getFTSDataFor(JTable restable, int selectedRow,
Collection discoveredStructuresSet)
{
int idColumnIndex = restable.getColumn("Model id").getModelIndex();
String modelId = (String) restable.getValueAt(selectedRow,
idColumnIndex);
for (FTSData row : discoveredStructuresSet)
{
if (row instanceof TDB_FTSData
&& ((TDB_FTSData) row).getModelId().equals(modelId))
{
return ((TDB_FTSData) row);
}
}
return null;
}
}