// BridgeDb,
// An abstraction layer for identifier mapping services, both local and online.
// Copyright 2006-2009 BridgeDb developers
//
// 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.webservice.bridgerest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.bridgedb.AttributeMapper;
import org.bridgedb.BridgeDb;
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.webservice.IDMapperWebservice;
/**
* IDMapper implementation for BridgeRest, the REST interface of BridgeDb itself.
*/
public class BridgeRest extends IDMapperWebservice implements AttributeMapper
{
static {
BridgeDb.register ("idmapper-bridgerest", new Driver());
}
private final static class Driver implements org.bridgedb.Driver {
/** private constructor to prevent outside instantiation. */
private Driver() { }
/** {@inheritDoc} */
public IDMapper connect(String location) throws IDMapperException {
// replace all spaces by "%20" to access organisms such as "Arabidopsis theliana"
return new BridgeRest(location.replaceAll(" ", "%20"));
}
}
private final class RestCapabilities implements IDMapperCapabilities {
private Map<String, String> properties = new HashMap<String, String>();
private boolean freeSearchSupported;
/**
* Capabilities for the BridgeRest IDMapper.
* @throws IDMapperException when the webservice was not available.
*/
public RestCapabilities() throws IDMapperException {
try {
loadProperties();
loadFreeSearchSupported();
} catch(IOException e) {
throw new IDMapperException(e);
}
}
/**
* Helper method, reads a list of supported datasources from the webservice.
* @param cmd can be sourceDataSources or targetDataSources
* @return Set of DataSources
* @throws IDMapperException when service is unavailable.
*/
private Set<DataSource> loadDataSources(String cmd) throws IDMapperException
{
try
{
Set<DataSource> results = new HashSet<DataSource>();
BufferedReader in = new UrlBuilder (cmd).openReader();
String line = null;
while((line = in.readLine()) != null) {
results.add(DataSource.getByFullName(line));
}
in.close();
return results;
}
catch (IOException ex)
{
throw new IDMapperException (ex);
}
}
/**
* Helper method, checks if free search is supported by the webservice.
* @throws IOException when service is unavailable.
*/
private void loadFreeSearchSupported() throws IOException {
BufferedReader in = new UrlBuilder ("isFreeSearchSupported").openReader();
String line = null;
while((line = in.readLine()) != null) {
freeSearchSupported = Boolean.parseBoolean(line);
}
in.close();
}
/**
* Helper method, reads properties from the webservice.
* @throws IOException when service is unavailable.
*/
private void loadProperties() throws IOException {
BufferedReader in = new UrlBuilder ("properties").openReader();
String line = null;
while((line = in.readLine()) != null) {
String[] cols = line.split("\t");
properties.put(cols[0], cols[1]);
}
in.close();
}
/** {@inheritDoc} */
public Set<String> getKeys() {
return properties.keySet();
}
/** {@inheritDoc} */
public String getProperty(String key) {
return properties.get(key);
}
/** {@inheritDoc} */
public Set<DataSource> getSupportedSrcDataSources()
throws IDMapperException
{
if (supportedSrcDataSources==null) {
supportedSrcDataSources = loadDataSources("sourceDataSources");
}
return supportedSrcDataSources;
}
/** {@inheritDoc} */
public Set<DataSource> getSupportedTgtDataSources()
throws IDMapperException
{
if (supportedTgtDataSources==null) {
supportedTgtDataSources = loadDataSources("targetDataSources");
}
return supportedTgtDataSources;
}
/** {@inheritDoc} */
public boolean isFreeSearchSupported() {
return freeSearchSupported;
}
/** {@inheritDoc} */
public boolean isMappingSupported(DataSource src, DataSource tgt)
throws IDMapperException {
try {
boolean supported = false;
BufferedReader in = new UrlBuilder("isMappingSupported")
.ordered(src.getSystemCode(), tgt.getSystemCode()).openReader();
String line = null;
while((line = in.readLine()) != null) {
supported = Boolean.parseBoolean(line);
}
in.close();
return supported;
} catch(IOException e) {
throw new IDMapperException(e);
}
}
}
private final String baseUrl;
private final IDMapperCapabilities capabilities;
private Set<DataSource> supportedSrcDataSources = null;
private Set<DataSource> supportedTgtDataSources = null;
private Set<String> attributeSet = null;
/**
* Helper class for constructing URL of a BridgeRest webservice command.
*/
private final class UrlBuilder
{
private StringBuilder builder = new StringBuilder(baseUrl);
/**
* Start building a new Url for the BridgeRest webservice.
* @param cmd the command, or the first parameter after the base Url. For example "properties".
* This param will be added after the baseUrl, separated by a "/"
*/
private UrlBuilder(String cmd)
{
builder.append ("/");
builder.append (cmd);
}
/**
* Ordered, unnamed arguments.
* @param args Optional arguments, these will be URLencoded and separated by "/".
* Null arguments are skipped.
* @return this, for chaining purposes
* @throws IOException in case there is an encoding problem (really unlikely)
*/
UrlBuilder ordered (String... args) throws IOException
{
for (String arg : args)
{
if (arg != null)
{
builder.append ("/");
builder.append (URLEncoder.encode (arg, "UTF-8"));
}
}
return this;
}
private boolean hasQMark = false;
/**
* Named parameters, these will be appended after ? and formatted as key=val pairs,
* separated by &
* @param key name of parameter
* @param val value of parameter
* @throws IOException in case there is an encoding problem (really unlikely)
* @return this, for chaining purposes
*/
UrlBuilder named (String key, String val) throws IOException
{
if (!hasQMark)
{
builder.append ("?");
hasQMark = true;
}
else
{
builder.append ("&");
}
builder.append (URLEncoder.encode (key, "UTF-8"));
builder.append ("=");
builder.append (URLEncoder.encode (val, "UTF-8"));
return this;
}
/**
* Open an InputStream to a given URL. For certain requests, when there are 0 results, the
* BridgeWebservice helpfully redirects to an error page instead of simply returning
* an empty list. Here we detect that situation and throw IOException.
* @return inputstream to given url.
* @throws IOException when there is a timeout, or when the http response code is not 200 - OK
*/
private BufferedReader openReader() throws IOException
{
URL url = new URL (builder.toString());
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setInstanceFollowRedirects(false);
int response = con.getResponseCode();
if (response < HttpURLConnection.HTTP_OK || response >= HttpURLConnection.HTTP_MULT_CHOICE )
throw new IOException("HTTP response: " + con.getResponseCode() + " - " + con.getResponseMessage());
return new BufferedReader(new InputStreamReader(con.getInputStream()));
}
}
/**
* @param baseUrl base Url, e.g. http://webservice.bridgedb.org/Human or
* http://localhost:8182
* @throws IDMapperException when service is unavailable
*/
BridgeRest (String baseUrl) throws IDMapperException
{
this.baseUrl = baseUrl;
//Get the capabilities
capabilities = new RestCapabilities();
}
private boolean isConnected = true;
/** {@inheritDoc} */
public void close() throws IDMapperException { isConnected = false; }
/** {@inheritDoc} */
public Set<Xref> freeSearch(String text, int limit)
throws IDMapperException
{
try
{
BufferedReader r = new UrlBuilder ("search").ordered(text).openReader();
Set<Xref> result = new HashSet<Xref>();
{
Xref dest;
while ((dest = parseLine(r)) != null) result.add (dest);
}
return result;
}
catch (IOException ex) {
throw new IDMapperException (ex);
}
}
/** {@inheritDoc} */
public IDMapperCapabilities getCapabilities() {
return capabilities;
}
/** {@inheritDoc} */
public boolean isConnected() {
return isConnected;
}
/** {@inheritDoc} */
public Map<Xref, Set<Xref>> mapID(Collection<Xref> srcXrefs,
DataSource... tgtDataSources) throws IDMapperException
{
return InternalUtils.mapMultiFromSingle(this, srcXrefs, tgtDataSources);
}
/** {@inheritDoc} */
public Set<Xref> mapID(Xref src,
DataSource... tgtDataSources) throws IDMapperException
{
Set<DataSource> dsFilter = new HashSet<DataSource>();
if(tgtDataSources != null) dsFilter.addAll(Arrays.asList(tgtDataSources));
try
{
UrlBuilder builder = new UrlBuilder ("xrefs")
.ordered(src.getDataSource().getSystemCode(), src.getId());
if (tgtDataSources.length == 1)
builder = builder.named("dataSource", tgtDataSources[0].getSystemCode());
BufferedReader r = builder.openReader();
Set<Xref> result = new HashSet<Xref>();
{
Xref dest;
while ((dest = parseLine(r)) != null)
{
if (dsFilter.size() == 0 || dsFilter.contains(dest.getDataSource()))
{
result.add (dest);
}
}
}
return result;
}
catch (IOException ex)
{
throw new IDMapperException(ex);
}
}
/**
* parse a single line of input.
* @param reader reader to read line from
* @return Xref or null if end of stream was reached
* @throws IOException if there was an error while reading (not EOF!) */
private Xref parseLine(BufferedReader reader) throws IOException
{
StringBuilder builder = new StringBuilder();
String id = null;
int c;
while (true)
{
// read char by char and use switch statement
// for efficiency
c = reader.read();
switch (c)
{
case -1:
return null; // end of the stream
case '\n':
DataSource ds = DataSource.getByFullName(builder.toString());
return new Xref(id, ds);
case '\t':
id = builder.toString();
builder = new StringBuilder();
break;
default:
builder.append((char)c);
}
}
}
/** {@inheritDoc} */
public boolean xrefExists(Xref xref) throws IDMapperException {
try {
boolean exists = false;
BufferedReader in = new UrlBuilder ("xrefExists")
.ordered(xref.getDataSource().getSystemCode(), xref.getId())
.openReader();
String line = in.readLine();
exists = Boolean.parseBoolean(line);
in.close();
return exists;
} catch(IOException e) {
throw new IDMapperException(e);
}
}
/**
*
* @return true
*/
public boolean isFreeAttributeSearchSupported()
{
return true;
}
/** {@inheritDoc} */
public Map<Xref, String> freeAttributeSearch(String query, String attrType,
int limit) throws IDMapperException {
try {
Map<Xref, String> result = new HashMap<Xref, String>();
BufferedReader in = new UrlBuilder("attributeSearch")
.ordered (query).named("limit", "" + limit)
.named ("attrName", attrType)
.openReader();
String line;
while ((line = in.readLine()) != null) {
String[] cols = line.split("\t", -1);
Xref x = new Xref (cols[0], DataSource.getByFullName(cols[1]));
String value = cols[2];
result.put(x, value);
}
in.close();
return result;
} catch (IOException ex) {
throw new IDMapperException (ex);
}
}
/** {@inheritDoc} */
public Set<String> getAttributes(Xref ref, String attrType)
throws IDMapperException {
try {
Set<String> results = new HashSet<String>();
BufferedReader in = new UrlBuilder ("attributes")
.ordered(ref.getDataSource().getSystemCode(), ref.getId())
.named ("attrName", attrType)
.openReader();
String line;
while ((line = in.readLine()) != null) {
results.add(line);
}
in.close();
return results;
} catch (IOException ex) {
throw new IDMapperException (ex);
}
}
/** {@inheritDoc} */
public Set<String> getAttributeSet() throws IDMapperException {
if (attributeSet==null) {
try {
Set<String> results = new HashSet<String>();
BufferedReader in = new UrlBuilder ("attributeSet")
.openReader();
String line;
while ((line = in.readLine()) != null) {
results.add(line);
}
in.close();
attributeSet = results;
} catch (IOException ex) {
throw new IDMapperException (ex);
}
}
return attributeSet;
}
/** {@inheritDoc} */
public Map<String, Set<String>> getAttributes(Xref ref)
throws IDMapperException {
try {
Map<String, Set<String>> results = new HashMap<String, Set<String>>();
BufferedReader in = new UrlBuilder ("attributes")
.ordered(ref.getDataSource().getSystemCode(), ref.getId())
.openReader();
String line;
while ((line = in.readLine()) != null) {
String[] cols = line.split("\t", -1);
Set<String> rs = results.get(cols[0]);
if(rs == null) results.put(cols[0], rs = new HashSet<String>());
rs.add(cols[1]);
}
in.close();
return results;
} catch (IOException ex) {
throw new IDMapperException (ex);
}
}
@Override
public Map<Xref, Set<String>> freeAttributeSearchEx(String query, String attrType, int limit)
throws IDMapperException
{
try {
Map<Xref, Set<String>> result = new HashMap<Xref, Set<String>>();
BufferedReader in = new UrlBuilder("attributeSearch")
.ordered (query).named("limit", "" + limit)
.named ("attrName", attrType)
.openReader();
String line;
while ((line = in.readLine()) != null) {
String[] cols = line.split("\t", -1);
Xref x = new Xref (cols[0], DataSource.getByFullName(cols[1]));
String value = cols[2];
InternalUtils.multiMapPut(result, x, value);
}
in.close();
return result;
} catch (IOException ex) {
throw new IDMapperException (ex);
}
}
}