import jalview.bin.ApplicationSingletonProvider;
import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
-import jalview.bin.Cache;
-import jalview.fts.api.FTSData;
-import jalview.fts.api.FTSDataColumnI;
-import jalview.fts.api.FTSRestClientI;
-import jalview.fts.core.FTSRestClient;
-import jalview.fts.core.FTSRestRequest;
-import jalview.fts.core.FTSRestResponse;
-import jalview.util.MessageManager;
-import jalview.util.Platform;
+import java.lang.invoke.MethodHandles;
+import java.net.MalformedURLException;
+import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.DefaultClientConfig;
+import jalview.bin.Cache;
+import jalview.bin.Console;
+import jalview.fts.api.FTSData;
+import jalview.fts.api.FTSDataColumnI;
+import jalview.fts.api.FTSRestClientI;
+import jalview.fts.core.FTSRestClient;
+import jalview.fts.core.FTSRestRequest;
+import jalview.fts.core.FTSRestResponse;
+import jalview.util.ChannelProperties;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+
+/*
+ * 2022-07-20 bsoares
+ * See https://issues.jalview.org/browse/JAL-4036
+ * The new Uniprot API is not dissimilar to the old one, but has some important changes.
+ * Some group names have changed slightly, some old groups have gone and there are quite a few new groups.
+ *
+ * Most changes are mappings of old column ids to new field ids. There are a handful of old
+ * columns not mapped to new fields, and new fields without an old column.
+ * [aside: not all possible columns were listed in the resources/fts/uniprot_data_columns.txt file.
+ * These were presumably additions after the file was created]
+ * For existing/mapped fields, the same preferences found in the resource file have been migrated to
+ * the new file with the new field name, id and group.
+ *
+ * The new mapped groups and files are stored and read from resources/fts/uniprot_data_columns-2022.txt.
+ *
+ * There is now no "sort" query string parameter.
+ *
+ * See https://www.uniprot.org/help/api_queries
+ *
+ * SIGNIFICANT CHANGE: Pagination is no longer performed using a record offset, but with a "cursor"
+ * query string parameter that is not really a cursor. The value is an opaque string that is passed (or
+ * rather a whole URL is passed) in the "Link" header of the HTTP response of the previous page.
+ * Where such a link is passed it is put into the cursors ArrayList.
+ * There are @Overridden methods in UniprotFTSPanel.
+ */
public class UniProtFTSRestClient extends FTSRestClient
-implements ApplicationSingletonI
+ implements ApplicationSingletonI,FTSRestClientI
{
-
-public static FTSRestClientI getInstance()
+public static UniProtFTSRestClient getInstance()
{
-return (FTSRestClientI) ApplicationSingletonProvider
+return (UniProtFTSRestClient) ApplicationSingletonProvider
.getInstance(UniProtFTSRestClient.class);
}
+ private static final String DEFAULT_UNIPROT_DOMAIN = "https://rest.uniprot.org";
- private static final String DEFAULT_UNIPROT_DOMAIN = "https://www.uniprot.org";
+ private static final String USER_AGENT = ChannelProperties
+ .getProperty("app_name", "Jalview") + " "
+ + Cache.getDefault("VERSION", "Unknown") + " "
+ + MethodHandles.lookup().lookupClass() + " help@jalview.org";
static
{
Platform.addJ2SDirectDatabaseCall(DEFAULT_UNIPROT_DOMAIN);
}
+
public final String uniprotSearchEndpoint;
private UniProtFTSRestClient()
{
super();
- uniprotSearchEndpoint = Cache.getDefault("UNIPROT_DOMAIN",
- DEFAULT_UNIPROT_DOMAIN) + "/uniprot/";
+ this.clearCursors();
+ uniprotSearchEndpoint = Cache.getDefault("UNIPROT_2022_DOMAIN",
+ DEFAULT_UNIPROT_DOMAIN) + "/uniprotkb/search";
}
@SuppressWarnings("unchecked")
public FTSRestResponse executeRequest(FTSRestRequest uniprotRestRequest)
throws Exception
{
+ return executeRequest(uniprotRestRequest, null);
+ }
+
+ public FTSRestResponse executeRequest(FTSRestRequest uniprotRestRequest,
+ String cursor) throws Exception
+ {
try
{
String wantedFields = getDataColumnsFieldsAsCommaDelimitedString(
}
else
{
- query = uniprotRestRequest.getFieldToSearchBy()
- .equalsIgnoreCase("Search All")
- ? uniprotRestRequest.getSearchTerm()
- + " or mnemonic:"
- + uniprotRestRequest.getSearchTerm()
+ query = uniprotRestRequest.getFieldToSearchBy().equalsIgnoreCase(
+ "Search All") ? uniprotRestRequest.getSearchTerm()
+ // + " or mnemonic:"
+ // + uniprotRestRequest.getSearchTerm()
: uniprotRestRequest.getFieldToSearchBy() + ":"
+ uniprotRestRequest.getSearchTerm();
}
WebResource webResource = null;
webResource = client.resource(uniprotSearchEndpoint)
- .queryParam("format", "tab")
- .queryParam("columns", wantedFields)
- .queryParam("limit", String.valueOf(responseSize))
- .queryParam("offset", String.valueOf(offSet))
- .queryParam("sort", "score").queryParam("query", query);
+ .queryParam("format", "tsv")
+ .queryParam("fields", wantedFields)
+ .queryParam("size", String.valueOf(responseSize))
+ /* 2022 new api has no "sort"
+ * .queryParam("sort", "score")
+ */
+ .queryParam("query", query);
+ if (offSet != 0 && cursor != null && cursor.length() > 0)
+ // 2022 new api does not do pagination with an offset, it requires a
+ // "cursor" parameter with a key (given for the next page).
+ // (see https://www.uniprot.org/help/pagination)
+ {
+ webResource = webResource.queryParam("cursor", cursor);
+ }
+ Console.debug(
+ "Uniprot FTS Request: " + webResource.getURI().toString());
// Execute the REST request
- ClientResponse clientResponse = webResource
- .accept(MediaType.TEXT_PLAIN).get(clientResponseClass);
+ WebResource.Builder wrBuilder = webResource
+ .accept(MediaType.TEXT_PLAIN);
+ if (!Platform.isJS())
+ /**
+ * Java only
+ *
+ * @j2sIgnore
+ */
+ {
+ wrBuilder.header("User-Agent", USER_AGENT);
+ }
+ ClientResponse clientResponse = wrBuilder.get(clientResponseClass);
+
+ if (!Platform.isJS())
+ /**
+ * Java only
+ *
+ * @j2sIgnore
+ */
+ {
+ if (clientResponse.getHeaders().containsKey("Link"))
+ {
+ // extract the URL from the 'Link: <URL>; ref="stuff"' header
+ String linkHeader = clientResponse.getHeaders().get("Link")
+ .get(0);
+ if (linkHeader.indexOf("<") > -1)
+ {
+ String temp = linkHeader.substring(linkHeader.indexOf("<") + 1);
+ if (temp.indexOf(">") > -1)
+ {
+ String nextUrl = temp.substring(0, temp.indexOf(">"));
+ // then get the cursor value from the query string parameters
+ String nextCursor = getQueryParam("cursor", nextUrl);
+ setCursor(cursorPage + 1, nextCursor);
+ }
+ }
+ }
+ }
String uniProtTabDelimittedResponseString = clientResponse
.getEntity(String.class);
// Make redundant objects eligible for garbage collection to conserve
throw new Exception(errorMessage);
}
- int xTotalResults = Platform.isJS() ? 1
- : Integer.valueOf(clientResponse.getHeaders()
- .get("X-Total-Results").get(0));
+ // new Uniprot API is not including a "X-Total-Results" header when there
+ // are 0 results
+ List<String> resultsHeaders = clientResponse.getHeaders()
+ .get("X-Total-Results");
+ int xTotalResults = 0;
+ if (Platform.isJS())
+ {
+ xTotalResults = 1;
+ }
+ else if (resultsHeaders != null && resultsHeaders.size() >= 1)
+ {
+ xTotalResults = Integer.valueOf(resultsHeaders.get(0));
+ }
clientResponse = null;
client = null;
return parseUniprotResponse(uniProtTabDelimittedResponseString,
uniprotRestRequest, xTotalResults);
} catch (Exception e)
{
+ Console.warn("Problem with the query: " + e.getMessage());
+ Console.debug("Exception stacktrace:", e);
String exceptionMsg = e.getMessage();
if (exceptionMsg.contains("SocketException"))
{
};
}
+
@Override
public String getColumnDataConfigFileName()
{
- return "/fts/uniprot_data_columns.txt";
+ return "/fts/uniprot_data_columns-2022.txt";
+ }
+
+ /* 2022-07-20 bsoares
+ * used for the new API "cursor" pagination. See https://www.uniprot.org/help/pagination
+ */
+ private ArrayList<String> cursors;
+
+ private int cursorPage = 0;
+
+ protected int getCursorPage()
+ {
+ return cursorPage;
+ }
+
+ protected void setCursorPage(int i)
+ {
+ cursorPage = i;
+ }
+
+ protected void setPrevCursorPage()
+ {
+ if (cursorPage > 0)
+ cursorPage--;
+ }
+
+ protected void setNextCursorPage()
+ {
+ cursorPage++;
+ }
+
+ protected void clearCursors()
+ {
+ cursors = new ArrayList(10);
}
+ protected String getCursor(int i)
+ {
+ return cursors.get(i);
+ }
+
+ protected String getNextCursor()
+ {
+ if (cursors.size() < cursorPage + 2)
+ return null;
+ return cursors.get(cursorPage + 1);
+ }
+
+ protected String getPrevCursor()
+ {
+ if (cursorPage == 0)
+ return null;
+ return cursors.get(cursorPage - 1);
+ }
+
+ protected void setCursor(int i, String c)
+ {
+ cursors.ensureCapacity(i + 1);
+ while (cursors.size() <= i)
+ {
+ cursors.add(null);
+ }
+ cursors.set(i, c);
+ Console.debug(
+ "Set UniprotFRSRestClient cursors[" + i + "] to '" + c + "'");
+ // cursors.add(c);
+ }
+
+ public static String getQueryParam(String param, String u)
+ {
+ if (param == null || u == null)
+ return null;
+ try
+ {
+ URL url = new URL(u);
+ String[] kevs = url.getQuery().split("&");
+ for (int j = 0; j < kevs.length; j++)
+ {
+ String[] kev = kevs[j].split("=", 2);
+ if (param.equals(kev[0]))
+ {
+ return kev[1];
+ }
+ }
+ } catch (MalformedURLException e)
+ {
+ Console.warn("Could not obtain next page 'cursor' value from 'u");
+ }
+ return null;
+ }
}