diff --git a/reactome/src/main/java/lcsb/mapviewer/reactome/utils/ReactomeConnector.java b/reactome/src/main/java/lcsb/mapviewer/reactome/utils/ReactomeConnector.java index dcc5cdb14ec81803fbffab2d59318a2717d3aae1..f41b6ae6261905346b8845af1fe16a21fa5c64fc 100644 --- a/reactome/src/main/java/lcsb/mapviewer/reactome/utils/ReactomeConnector.java +++ b/reactome/src/main/java/lcsb/mapviewer/reactome/utils/ReactomeConnector.java @@ -1,778 +1,779 @@ -package lcsb.mapviewer.reactome.utils; - -import java.io.DataOutputStream; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.io.IOUtils; -import org.apache.log4j.Logger; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import lcsb.mapviewer.annotation.cache.CachableInterface; -import lcsb.mapviewer.annotation.cache.GeneralCacheInterface; -import lcsb.mapviewer.annotation.cache.SourceNotAvailable; -import lcsb.mapviewer.annotation.services.ExternalServiceStatus; -import lcsb.mapviewer.annotation.services.ExternalServiceStatusType; -import lcsb.mapviewer.common.HttpConnectionMethodType; -import lcsb.mapviewer.common.MimeType; -import lcsb.mapviewer.common.exception.InvalidArgumentException; -import lcsb.mapviewer.common.exception.InvalidXmlSchemaException; -import lcsb.mapviewer.model.map.MiriamData; -import lcsb.mapviewer.model.map.MiriamType; -import lcsb.mapviewer.reactome.model.ReactomeCandidateSet; -import lcsb.mapviewer.reactome.model.ReactomeCatalystActivity; -import lcsb.mapviewer.reactome.model.ReactomeComplex; -import lcsb.mapviewer.reactome.model.ReactomeDatabaseObject; -import lcsb.mapviewer.reactome.model.ReactomeEntitySet; -import lcsb.mapviewer.reactome.model.ReactomeEntityWithAccessionedSequence; -import lcsb.mapviewer.reactome.model.ReactomePhysicalEntity; -import lcsb.mapviewer.reactome.model.ReactomeReactionlikeEvent; -import lcsb.mapviewer.reactome.model.ReactomeReferenceMolecule; -import lcsb.mapviewer.reactome.model.ReactomeReferenceSequence; -import lcsb.mapviewer.reactome.model.ReactomeSimpleEntity; -import lcsb.mapviewer.reactome.model.ReactomeStableIdentifier; -import lcsb.mapviewer.reactome.model.ReactomeStatus; -import lcsb.mapviewer.reactome.xml.ReactomeNodeParser; -import lcsb.mapviewer.reactome.xml.ReactomeParserFactory; - -/** - * Data access object for reactome data. - * - * @author Piotr Gawron - * - */ -@Transactional -@Service -public class ReactomeConnector extends CachableInterface implements DataSourceUpdater { - - /** - * Identifier of the object in reactome database used for testing connection. - */ - private static final int CHECK_SERVICE_STATUS_EXAMPLE = 109581; - - /** - * Default timeout of http connection (in miliseconds). - */ - private static final int CONNECTION_TIMEOUT = 10000; - - /** - * Prefix used in cache entries for objects queried by the identifier. - */ - private static final String IDENTIFIER_QUERY_PREFIX = "ID: "; - - @Override - public Object refreshCacheQuery(Object query) throws SourceNotAvailable { - ReactomeConnector connector = new ReactomeConnector(); - - String name; - Object result = null; - if (query instanceof String) { - name = (String) query; - if (name.startsWith(IDENTIFIER_QUERY_PREFIX)) { - String strId = name.substring(IDENTIFIER_QUERY_PREFIX.length()); - Integer id = Integer.valueOf(strId); - try { - return connector.getFullNodeForDbId(id); - } catch (InvalidXmlSchemaException e) { - throw new SourceNotAvailable(e); - } - } else if (name.contains("\n")) { - String[] tmp = name.split("\n"); - if (tmp.length != 2) { - throw new InvalidArgumentException("Don't know what to do with string \"" + query + "\""); - } else { - String params = tmp[0]; - String url = tmp[1]; - try { - result = connector.getNodesFromServer(params, url); - } catch (Exception e) { - throw new SourceNotAvailable(e); - } - } - } else { - throw new InvalidArgumentException("Don't know what to do with string \"" + query + "\""); - } - } else { - throw new InvalidArgumentException("Don't know what to do with class: " + query.getClass()); - } - return result; - } - - /** - * Default constructor. - */ - public ReactomeConnector() { - super(ReactomeConnector.class); - } - - /** - * Determines if only one object instance should be created for single - * reactome database identifier. - */ - private boolean oneInstancePerId = true; - - /** - * Map of all reactome objects retrieved by this connector. - */ - private Map<Integer, ReactomeDatabaseObject> objectMap = new HashMap<Integer, ReactomeDatabaseObject>(); - - /** - * Default class logger. - */ - private static Logger logger = Logger.getLogger(ReactomeConnector.class); - /** - * Url used for accessing Reactome RestFULL API. - */ - private static final String REACTOME_URL = "http://reactome.org/ReactomeRESTfulAPI/RESTfulWS/"; - - @Override - public List<ReactomePhysicalEntity> getEntitiesForName(String name) throws IOException { - return getEntitiesForName(name, true); - } - - @Override - public List<ReactomePhysicalEntity> getEntitiesForName(String name, boolean full) throws IOException { - List<ReactomePhysicalEntity> result = new ArrayList<ReactomePhysicalEntity>(); - - String query = "name=" + name; - List<ReactomeDatabaseObject> objects = getSimpleObjectListByQuery(query, ReactomePhysicalEntity.class); - - for (ReactomeDatabaseObject object : objects) { - if (full) { - ReactomePhysicalEntity fullEntity = (ReactomePhysicalEntity) getFullObjectForDbId(object.getDbId()); - result.add(fullEntity); - } else { - result.add((ReactomePhysicalEntity) object); - } - } - return result; - } - - @Override - public List<ReactomePhysicalEntity> getEntitiesForChebiId(Set<MiriamData> ids, boolean deepSearch) throws IOException { - Set<ReactomePhysicalEntity> set = new HashSet<ReactomePhysicalEntity>(); - List<ReactomePhysicalEntity> result = new ArrayList<ReactomePhysicalEntity>(); - if (ids == null) { - return result; - } - for (MiriamData md : ids) { - if (md == null) { - return result; - } - String id = md.getResource().replace("CHEBI:", ""); - - String query = "identifier=" + id; - List<ReactomeDatabaseObject> objects = getSimpleObjectListByQuery(query, ReactomeReferenceMolecule.class); - - for (ReactomeDatabaseObject object : objects) { - ReactomeReferenceMolecule reference = (ReactomeReferenceMolecule) getFullObjectForDbId(object.getDbId()); - List<ReactomeSimpleEntity> list = getSimpleEntitiesByReferenceMolecule(reference); - set.addAll(list); - } - if (deepSearch) { - int lastSize = 0; - do { - lastSize = set.size(); - Set<ReactomePhysicalEntity> complexes = getComplexesContainingOneOfElements(set); - set.addAll(complexes); - Set<ReactomePhysicalEntity> sets = getSetsContainingOneOfElements(set); - set.addAll(sets); - } while (lastSize != set.size()); - } - result.addAll(set); - } - return result; - } - - @Override - public List<ReactomePhysicalEntity> getEntitiesForUniprotId(Collection<MiriamData> uniprot) throws IOException { - Set<ReactomePhysicalEntity> set = new HashSet<ReactomePhysicalEntity>(); - - for (MiriamData md : uniprot) { - if (!MiriamType.UNIPROT.equals(md.getDataType())) { - throw new InvalidArgumentException("Only uniprot miriam are acceptable"); - } - String id = md.getResource(); - int lastSize = set.size(); - do { - lastSize = set.size(); - - String query = "identifier=" + id; - List<ReactomeDatabaseObject> objects = getSimpleObjectListByQuery(query, ReactomeReferenceSequence.class); - - for (ReactomeDatabaseObject object : objects) { - ReactomeReferenceSequence reference = (ReactomeReferenceSequence) getFullObjectForDbId(object.getDbId()); - List<ReactomePhysicalEntity> list = getPhysicalEnitiesForReferenceSequence(reference); - set.addAll(list); - } - query = "secondaryIdentifier=" + id; - objects = getSimpleObjectListByQuery(query, ReactomeReferenceSequence.class); - - for (ReactomeDatabaseObject object : objects) { - ReactomeReferenceSequence reference = (ReactomeReferenceSequence) getFullObjectForDbId(object.getDbId()); - List<ReactomePhysicalEntity> list = getPhysicalEnitiesForReferenceSequence(reference); - set.addAll(list); - } - - Set<ReactomePhysicalEntity> complexes = getComplexesContainingOneOfElements(set); - set.addAll(complexes); - Set<ReactomePhysicalEntity> sets = getSetsContainingOneOfElements(set); - set.addAll(sets); - } while (lastSize != set.size()); - } - List<ReactomePhysicalEntity> result = new ArrayList<ReactomePhysicalEntity>(); - result.addAll(set); - return result; - } - - /** - * Returns list of ReactomeComplex that contain one of the elements given in - * the parameter. - * - * @param objects - * list of objects for which we want to retrieve complexes - * @return list of ReactomeComplex that contain one of the elements given in - * the parameter - * @throws IOException - * thrown when there is a problem with connection to reactome - * database - */ - private Set<ReactomePhysicalEntity> getComplexesContainingOneOfElements(Collection<ReactomePhysicalEntity> objects) throws IOException { - Set<ReactomePhysicalEntity> result = new HashSet<ReactomePhysicalEntity>(); - for (ReactomeDatabaseObject obj : objects) { - String query = "hasComponent=" + obj.getDbId(); - List<ReactomeDatabaseObject> complexes = getSimpleObjectListByQuery(query, ReactomeComplex.class); - for (ReactomeDatabaseObject complex : complexes) { - result.add((ReactomePhysicalEntity) getFullObjectForDbId(complex.getDbId())); - } - } - return result; - } - - /** - * Returns list of ReactomeEntitySet that contain one of the elements given in - * the parameter. - * - * @param objects - * list of objects for which we want to retrieve sets - * @return list of ReactomeEntitySet that contain one of the elements given in - * the parameter - * @throws IOException - * thrown when there is a problem with connection to reactome - * database - */ - private Set<ReactomePhysicalEntity> getSetsContainingOneOfElements(Collection<ReactomePhysicalEntity> objects) throws IOException { - - Set<ReactomePhysicalEntity> result = new HashSet<ReactomePhysicalEntity>(); - for (ReactomeDatabaseObject obj : objects) { - String query = "hasMember=" + obj.getDbId(); - List<ReactomeDatabaseObject> complexes = getSimpleObjectListByQuery(query, ReactomeEntitySet.class); - for (ReactomeDatabaseObject complex : complexes) { - result.add((ReactomeEntitySet) getFullObjectForDbId(complex.getDbId())); - } - query = "hasCandidate=" + obj.getDbId(); - complexes = getSimpleObjectListByQuery(query, ReactomeCandidateSet.class); - for (ReactomeDatabaseObject complex : complexes) { - result.add((ReactomeCandidateSet) getFullObjectForDbId(complex.getDbId())); - } - } - return result; - } - - /** - * Returns list of ReactomeEntityWithAccessionedSequence that refers to - * parameter reference. - * - * @param reference - * object to which result should refer to - * @return list of ReactomeEntityWithAccessionedSequence that contain one of - * the elements given in the parameter - * @throws IOException - * thrown when there is a problem with connection to reactome - * database - */ - private List<ReactomePhysicalEntity> getPhysicalEnitiesForReferenceSequence(ReactomeReferenceSequence reference) throws IOException { - List<ReactomePhysicalEntity> result = new ArrayList<ReactomePhysicalEntity>(); - - String query = "referenceEntity=" + reference.getDbId(); - List<ReactomeDatabaseObject> objects = getSimpleObjectListByQuery(query, ReactomeEntityWithAccessionedSequence.class); - - for (ReactomeDatabaseObject object : objects) { - result.add((ReactomePhysicalEntity) getFullObjectForDbId(object.getDbId())); - } - return result; - } - - /** - * Returns list of ReactomeSimpleEntity that refers to parameter reference. - * - * @param reference - * object to which result should refer to - * @return list of ReactomeSimpleEntity that contain one of the elements given - * in the parameter - * @throws IOException - * thrown when there is a problem with connection to reactome - * database - */ - private List<ReactomeSimpleEntity> getSimpleEntitiesByReferenceMolecule(ReactomeReferenceMolecule reference) throws IOException { - List<ReactomeSimpleEntity> result = new ArrayList<ReactomeSimpleEntity>(); - - String query = "referenceEntity=" + reference.getDbId(); - List<ReactomeDatabaseObject> objects = getSimpleObjectListByQuery(query, ReactomeSimpleEntity.class); - - for (ReactomeDatabaseObject object : objects) { - result.add((ReactomeSimpleEntity) getFullObjectForDbId(object.getDbId())); - } - return result; - } - - /** - * Ges list of reactions that interact with object given in the parameter. - * - * @param id - * identifier of the object that should be included in the result - * reactions - * @return list of reactions that interact with object given in the parameter - * @throws IOException - * thrown when there is a problem with connection to reactome - * database - */ - public List<ReactomeReactionlikeEvent> getReactionsForEntityId(Integer id) throws IOException { - List<ReactomeReactionlikeEvent> result = new ArrayList<ReactomeReactionlikeEvent>(); - - String query = "input=" + id; - List<ReactomeDatabaseObject> objects = getSimpleObjectListByQuery(query, ReactomeReactionlikeEvent.class); - - for (ReactomeDatabaseObject object : objects) { - ReactomeDatabaseObject entity = getFullObjectForDbId(object.getDbId()); - if (entity instanceof ReactomeReactionlikeEvent) { - result.add((ReactomeReactionlikeEvent) entity); - } - } - - query = "output=" + id; - objects = getSimpleObjectListByQuery(query, ReactomeReactionlikeEvent.class); - - for (ReactomeDatabaseObject object : objects) { - ReactomeDatabaseObject entity = getFullObjectForDbId(object.getDbId()); - if (entity instanceof ReactomeReactionlikeEvent) { - result.add((ReactomeReactionlikeEvent) entity); - } - } - - // and now find reaction with apropriate catalyst - query = "physicalEntity=" + id; - List<ReactomeDatabaseObject> catalysts = getSimpleObjectListByQuery(query, ReactomeCatalystActivity.class); - for (ReactomeDatabaseObject catalyst : catalysts) { - query = "catalystActivity=" + catalyst.getDbId(); - objects = getSimpleObjectListByQuery(query, ReactomeReactionlikeEvent.class); - for (ReactomeDatabaseObject object : objects) { - ReactomeDatabaseObject entity = getFullObjectForDbId(object.getDbId()); - if (entity instanceof ReactomeReactionlikeEvent) { - result.add((ReactomeReactionlikeEvent) entity); - } - } - } - - return result; - } - - @Override - public Class<?> getTypeForDbId(Integer id) throws ClassNotFoundException, InvalidXmlSchemaException { - Node node = getFullNodeForDbId(id); - Class<?> result = getTypeForNode(node); - return result; - } - - /** - * Returns class type of the data represented in the xml node. - * - * @param node - * xml node - * @return class type of the data represented in the xml node - * @throws ClassNotFoundException - * thrown when there is a problem with finding apropriate class - */ - protected Class<?> getTypeForNode(Node node) throws ClassNotFoundException { - Class<?> result = null; - for (int i = 0; i < node.getChildNodes().getLength(); i++) { - Node child = node.getChildNodes().item(i); - if (child.getNodeName().equals("schemaClass")) { - // our classes aren't exactly the same as reactome names, change what is - // different - String shortName = child.getTextContent().replaceAll("GO_", "Go"); - String className = ReactomeDatabaseObject.class.getPackage().getName() + ".Reactome" + shortName; - result = Class.forName(className); - } - } - return result; - } - - @Override - public ReactomeDatabaseObject getFullObjectForDbId(Integer id) { - try { - ReactomeDatabaseObject result = null; - if (oneInstancePerId) { - result = objectMap.get(id); - if (result != null) { - return result; - } - } - Class<?> classType; - Node node = getFullNodeForDbId(id); - classType = getTypeForNode(node); - ReactomeNodeParser<?> parser = ReactomeParserFactory.getParserForClass(classType); - result = parser.parseObject(node); - if (oneInstancePerId) { - objectMap.put(id, result); - } - return result; - } catch (ClassNotFoundException e) { - throw new InvalidArgumentException("Unknown class type: " + e.getMessage()); - } catch (InvalidXmlSchemaException e) { - logger.error(e, e); - return null; - } - } - - @Override - @SuppressWarnings("unchecked") - public void updateOnlyIdFields(ReactomeDatabaseObject object) { - DataSourceUpdater dsu = this; - for (Method method : object.getClass().getMethods()) { - - if (Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0 - && (method.getName().startsWith("get") || method.getName().startsWith("is"))) { - - if (ReactomeDatabaseObject.class.isAssignableFrom(method.getReturnType())) { - try { - ReactomeDatabaseObject value = (ReactomeDatabaseObject) method.invoke(object); - if (value != null) { - if (value.getStatus().equals(ReactomeStatus.ONLY_ID)) { - ReactomeDatabaseObject newValue = dsu.getFullObjectForDbId(value.getDbId()); - String setterName = method.getName(); - if (setterName.startsWith("get")) { - setterName = setterName.replaceFirst("get", "set"); - } else { - setterName = setterName.replaceFirst("is", "set"); - } - try { - Method setterMethod = object.getClass().getMethod(setterName, method.getReturnType()); - setterMethod.invoke(object, newValue); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (IllegalArgumentException e) { - logger.error( - "Cannot assign " + newValue.getClass() + " (id: " + value.getDbId() + ") to field: " + setterName.replace("set", "") + " (" - + method.getReturnType() + ")"); - } catch (SecurityException e) { - - e.printStackTrace(); - // ignore it - however this would be strange - } - } - } - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - // ignore it - } - - } else if (List.class.isAssignableFrom(method.getReturnType())) { - try { - @SuppressWarnings("rawtypes") - List list = (List) method.invoke(object); - for (int i = 0; i < list.size(); i++) { - Object obj = list.get(i); - if (obj instanceof ReactomeDatabaseObject) { - if (((ReactomeDatabaseObject) obj).getStatus().equals(ReactomeStatus.ONLY_ID)) { - ReactomeDatabaseObject newValue = dsu.getFullObjectForDbId(((ReactomeDatabaseObject) obj).getDbId()); - list.set(i, newValue); - } - } - } - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - // ignore it - } - } - } - } - } - - @Override - public ReactomeDatabaseObject getFullObjectForStableIdentifier(String stableIdentifier) throws IOException { - if (stableIdentifier == null) { - return null; - } - String[] tmp = stableIdentifier.split("\\."); - String identifier = tmp[0]; - String version = "0"; - if (tmp.length > 1) { - version = tmp[1]; - } - return getFullObjectForStableIdentifier(identifier, version); - } - - @Override - public ReactomeDatabaseObject getFullObjectForStableIdentifier(String identifier, String version) throws IOException { - String stableIdentifierQuery = "identifier=" + identifier; - - List<ReactomeDatabaseObject> stableIdentifiers = getSimpleObjectListByQuery(stableIdentifierQuery, ReactomeStableIdentifier.class); - - ReactomeStableIdentifier newestIdentifier = null; - ReactomeStableIdentifier matchedIdentifier = null; - - for (ReactomeDatabaseObject object : stableIdentifiers) { - ReactomeStableIdentifier entity = (ReactomeStableIdentifier) getFullObjectForDbId(object.getDbId()); - if (newestIdentifier == null) { - newestIdentifier = entity; - } else { - try { - Integer id1 = Integer.parseInt(newestIdentifier.getIdentifierVersion()); - Integer id2 = Integer.parseInt(entity.getIdentifierVersion()); - if (id2 > id1) { - newestIdentifier = entity; - } - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - } - if (entity.getDisplayName().equals(identifier + "." + version) || entity.getIdentifierVersion().equalsIgnoreCase(version)) { - matchedIdentifier = entity; - } - } - if (matchedIdentifier == null) { - if (newestIdentifier != null) { - logger.warn( - "Invalid REACTOME stable identifier: \"" + identifier + "." + version + "\". Using another similar: " + newestIdentifier.getIdentifier() + "." - + newestIdentifier.getIdentifierVersion()); - } - matchedIdentifier = newestIdentifier; - } - if (matchedIdentifier != null) { - String query = "stableIdentifier=" + matchedIdentifier.getDbId(); - List<ReactomeDatabaseObject> objects = getSimpleObjectListByQuery(query, ReactomeDatabaseObject.class); - - for (ReactomeDatabaseObject object : objects) { - return getFullObjectForDbId(object.getDbId()); - } - - } - return null; - } - - // ------------------------------------------------ - - // Methods that directly connect to reactome database (via restfull API) - - // ------------------------------------------------ - - /** - * This method get content of the page of the url (and post content). - * - * @param url - * url address to connect - * @param postParam - * params of the post method - * @param type - * MIME type of the result - * @param method - * request method (POST/GET) - * @return return the string with the content of the webpage - * @throws IOException - * thrown when there are some problem with network connection - */ - protected String getResponse(String url, String postParam, MimeType type, HttpConnectionMethodType method) throws IOException { - URL obj = new URL(url); - HttpURLConnection con = (HttpURLConnection) obj.openConnection(); - - // add reuqest header - con.setRequestMethod(method.name()); - con.setRequestProperty("Accept", type.getTextRepresentation()); - con.setRequestProperty("Content-Type", type.getTextRepresentation()); - con.setConnectTimeout(CONNECTION_TIMEOUT); - con.setReadTimeout(CONNECTION_TIMEOUT); - - String urlParameters = postParam; - - logger.debug("Sending '" + method + "' request to URL : " + url); - if (method.name().equals("POST")) { - logger.debug("Parameters : " + urlParameters); - } - - // Send post request - con.setDoOutput(true); - if (method.name().equalsIgnoreCase("POST")) { - DataOutputStream wr = new DataOutputStream(con.getOutputStream()); - wr.writeBytes(urlParameters); - wr.flush(); - wr.close(); - } - - int responseCode = con.getResponseCode(); - logger.debug("Response Code : " + responseCode); - - String result = IOUtils.toString(con.getInputStream()); - logger.debug(result); - return result; - } - - @Override - public List<ReactomeDatabaseObject> getSimpleObjectListByQuery(String query, Class<? extends ReactomeDatabaseObject> clazz) throws IOException { - if (query.contains("\n")) { - throw new InvalidArgumentException("Query cannot contain new line character"); - } - ReactomeNodeParser<?> parser = ReactomeParserFactory.getParserForClass(clazz); - List<ReactomeDatabaseObject> result = new ArrayList<ReactomeDatabaseObject>(); - - String url = REACTOME_URL + "listByQuery/" + clazz.getSimpleName().replaceFirst("Reactome", "").replaceAll("Go", "GO_"); - - String key = query + "\n" + url; - - Node node = getCacheNode(key); - NodeList nodes = null; - if (node != null) { - nodes = node.getChildNodes(); - } else { - try { - nodes = getNodesFromServer(query, url); - } catch (InvalidXmlSchemaException e) { - throw new IOException(e); - } - setCacheNode(key, nodes.item(0)); - nodes = nodes.item(0).getChildNodes(); - } - for (int i = 0; i < nodes.getLength(); i++) { - if (nodes.item(i).getNodeType() != Node.ELEMENT_NODE) { - continue; - } - ReactomeDatabaseObject object = parser.parseSimplifiedObject(nodes.item(i)); - result.add(object); - } - return result; - } - - /** - * Returns list of nodes from the server identified by the query and POST - * params. - * - * @param query - * string query to the database sent by POST method - * @param url - * direct url to the API - * @return list of nodes from the server identified by the query and POST - * params. - * @throws IOException - * thrown when there are some problem with network connection - * @throws InvalidXmlSchemaException - * thrown when there is a problem with xml - */ - private NodeList getNodesFromServer(String query, String url) throws IOException, InvalidXmlSchemaException { - String response = getResponse(url, query, MimeType.XML, HttpConnectionMethodType.POST); - logger.debug(response); - Node node = getXmlDocumentFromString(response); - NodeList nodes = node.getChildNodes(); - if (nodes.getLength() < 1) { - throw new InvalidArgumentException("Problem with xml document"); - } - return nodes; - } - - /** - * This method returns xml NODE for object identified by id (identifier in - * reactome database). However if object is in local cache then local copy is - * returned. If object doesn't exist in local cache then after retrieving it - * from reactome is put to the cache. - * - * @param id - * identifier in reactome database - * @return xml node for the id given reactome id - * @throws InvalidXmlSchemaException - * thrown when there is a problem with xml - */ - @Override - public Node getFullNodeForDbId(Integer id) throws InvalidXmlSchemaException { - Node result = getCacheNode(IDENTIFIER_QUERY_PREFIX + id); - if (result != null) { - return result; - } - - String response; - String url = REACTOME_URL + "queryById/DatabaseObject/" + id; - try { - response = getResponse(url, "", MimeType.XML, HttpConnectionMethodType.GET); - Document doc = getXmlDocumentFromString(response); - NodeList nodes = doc.getChildNodes(); - result = nodes.item(0); - setCacheNode(IDENTIFIER_QUERY_PREFIX + id, result); - return result; - } catch (IOException e) { - logger.error(e.getMessage(), e); - return null; - } - } - - @Override - public Map<Integer, ReactomeDatabaseObject> getFullObjectsForDbIds(List<Integer> ids) throws ClassNotFoundException, IOException { - Map<Integer, ReactomeDatabaseObject> result = new HashMap<Integer, ReactomeDatabaseObject>(); - for (Integer integer : ids) { - result.put(integer, getFullObjectForDbId(integer)); - } - - return result; - - } - - @Override - public List<ReactomePhysicalEntity> getEntitiesForSetOfIds(Set<MiriamData> identifiers) throws IOException { - return getEntitiesForUniprotId(identifiers); - } - - @Override - public ExternalServiceStatus getServiceStatus() { - ExternalServiceStatus status = new ExternalServiceStatus( - "Reactome RESTful API", "http://reactome.org/ReactomeRESTfulAPI/ReactomeRESTFulAPI.html"); - - GeneralCacheInterface cacheCopy = getCache(); - this.setCache(null); - - try { - ReactomeDatabaseObject obj = getFullObjectForDbId(CHECK_SERVICE_STATUS_EXAMPLE); - status.setStatus(ExternalServiceStatusType.OK); - if (obj == null) { - status.setStatus(ExternalServiceStatusType.DOWN); - } else if (obj.getClass().equals(ReactomeDatabaseObject.class)) { - status.setStatus(ExternalServiceStatusType.CHANGED); - } - } catch (Exception e) { - logger.error(status.getName() + " is down", e); - status.setStatus(ExternalServiceStatusType.DOWN); - } - this.setCache(cacheCopy); - return status; - } - -} +package lcsb.mapviewer.reactome.utils; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import lcsb.mapviewer.common.XmlParser; +import org.apache.commons.io.IOUtils; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import lcsb.mapviewer.annotation.cache.CachableInterface; +import lcsb.mapviewer.annotation.cache.GeneralCacheInterface; +import lcsb.mapviewer.annotation.cache.SourceNotAvailable; +import lcsb.mapviewer.annotation.services.ExternalServiceStatus; +import lcsb.mapviewer.annotation.services.ExternalServiceStatusType; +import lcsb.mapviewer.common.HttpConnectionMethodType; +import lcsb.mapviewer.common.MimeType; +import lcsb.mapviewer.common.exception.InvalidArgumentException; +import lcsb.mapviewer.common.exception.InvalidXmlSchemaException; +import lcsb.mapviewer.model.map.MiriamData; +import lcsb.mapviewer.model.map.MiriamType; +import lcsb.mapviewer.reactome.model.ReactomeCandidateSet; +import lcsb.mapviewer.reactome.model.ReactomeCatalystActivity; +import lcsb.mapviewer.reactome.model.ReactomeComplex; +import lcsb.mapviewer.reactome.model.ReactomeDatabaseObject; +import lcsb.mapviewer.reactome.model.ReactomeEntitySet; +import lcsb.mapviewer.reactome.model.ReactomeEntityWithAccessionedSequence; +import lcsb.mapviewer.reactome.model.ReactomePhysicalEntity; +import lcsb.mapviewer.reactome.model.ReactomeReactionlikeEvent; +import lcsb.mapviewer.reactome.model.ReactomeReferenceMolecule; +import lcsb.mapviewer.reactome.model.ReactomeReferenceSequence; +import lcsb.mapviewer.reactome.model.ReactomeSimpleEntity; +import lcsb.mapviewer.reactome.model.ReactomeStableIdentifier; +import lcsb.mapviewer.reactome.model.ReactomeStatus; +import lcsb.mapviewer.reactome.xml.ReactomeNodeParser; +import lcsb.mapviewer.reactome.xml.ReactomeParserFactory; + +/** + * Data access object for reactome data. + * + * @author Piotr Gawron + * + */ +@Transactional +@Service +public class ReactomeConnector extends CachableInterface implements DataSourceUpdater { + + /** + * Identifier of the object in reactome database used for testing connection. + */ + private static final int CHECK_SERVICE_STATUS_EXAMPLE = 109581; + + /** + * Default timeout of http connection (in miliseconds). + */ + private static final int CONNECTION_TIMEOUT = 10000; + + /** + * Prefix used in cache entries for objects queried by the identifier. + */ + private static final String IDENTIFIER_QUERY_PREFIX = "ID: "; + + @Override + public Object refreshCacheQuery(Object query) throws SourceNotAvailable { + ReactomeConnector connector = new ReactomeConnector(); + + String name; + Object result = null; + if (query instanceof String) { + name = (String) query; + if (name.startsWith(IDENTIFIER_QUERY_PREFIX)) { + String strId = name.substring(IDENTIFIER_QUERY_PREFIX.length()); + Integer id = Integer.valueOf(strId); + try { + return connector.getFullNodeForDbId(id); + } catch (InvalidXmlSchemaException e) { + throw new SourceNotAvailable(e); + } + } else if (name.contains("\n")) { + String[] tmp = name.split("\n"); + if (tmp.length != 2) { + throw new InvalidArgumentException("Don't know what to do with string \"" + query + "\""); + } else { + String params = tmp[0]; + String url = tmp[1]; + try { + result = connector.getNodesFromServer(params, url); + } catch (Exception e) { + throw new SourceNotAvailable(e); + } + } + } else { + throw new InvalidArgumentException("Don't know what to do with string \"" + query + "\""); + } + } else { + throw new InvalidArgumentException("Don't know what to do with class: " + query.getClass()); + } + return result; + } + + /** + * Default constructor. + */ + public ReactomeConnector() { + super(ReactomeConnector.class); + } + + /** + * Determines if only one object instance should be created for single + * reactome database identifier. + */ + private boolean oneInstancePerId = true; + + /** + * Map of all reactome objects retrieved by this connector. + */ + private Map<Integer, ReactomeDatabaseObject> objectMap = new HashMap<Integer, ReactomeDatabaseObject>(); + + /** + * Default class logger. + */ + private static Logger logger = Logger.getLogger(ReactomeConnector.class); + /** + * Url used for accessing Reactome RestFULL API. + */ + private static final String REACTOME_URL = "http://reactome.org/ReactomeRESTfulAPI/RESTfulWS/"; + + @Override + public List<ReactomePhysicalEntity> getEntitiesForName(String name) throws IOException { + return getEntitiesForName(name, true); + } + + @Override + public List<ReactomePhysicalEntity> getEntitiesForName(String name, boolean full) throws IOException { + List<ReactomePhysicalEntity> result = new ArrayList<ReactomePhysicalEntity>(); + + String query = "name=" + name; + List<ReactomeDatabaseObject> objects = getSimpleObjectListByQuery(query, ReactomePhysicalEntity.class); + + for (ReactomeDatabaseObject object : objects) { + if (full) { + ReactomePhysicalEntity fullEntity = (ReactomePhysicalEntity) getFullObjectForDbId(object.getDbId()); + result.add(fullEntity); + } else { + result.add((ReactomePhysicalEntity) object); + } + } + return result; + } + + @Override + public List<ReactomePhysicalEntity> getEntitiesForChebiId(Set<MiriamData> ids, boolean deepSearch) throws IOException { + Set<ReactomePhysicalEntity> set = new HashSet<ReactomePhysicalEntity>(); + List<ReactomePhysicalEntity> result = new ArrayList<ReactomePhysicalEntity>(); + if (ids == null) { + return result; + } + for (MiriamData md : ids) { + if (md == null) { + return result; + } + String id = md.getResource().replace("CHEBI:", ""); + + String query = "identifier=" + id; + List<ReactomeDatabaseObject> objects = getSimpleObjectListByQuery(query, ReactomeReferenceMolecule.class); + + for (ReactomeDatabaseObject object : objects) { + ReactomeReferenceMolecule reference = (ReactomeReferenceMolecule) getFullObjectForDbId(object.getDbId()); + List<ReactomeSimpleEntity> list = getSimpleEntitiesByReferenceMolecule(reference); + set.addAll(list); + } + if (deepSearch) { + int lastSize = 0; + do { + lastSize = set.size(); + Set<ReactomePhysicalEntity> complexes = getComplexesContainingOneOfElements(set); + set.addAll(complexes); + Set<ReactomePhysicalEntity> sets = getSetsContainingOneOfElements(set); + set.addAll(sets); + } while (lastSize != set.size()); + } + result.addAll(set); + } + return result; + } + + @Override + public List<ReactomePhysicalEntity> getEntitiesForUniprotId(Collection<MiriamData> uniprot) throws IOException { + Set<ReactomePhysicalEntity> set = new HashSet<ReactomePhysicalEntity>(); + + for (MiriamData md : uniprot) { + if (!MiriamType.UNIPROT.equals(md.getDataType())) { + throw new InvalidArgumentException("Only uniprot miriam are acceptable"); + } + String id = md.getResource(); + int lastSize = set.size(); + do { + lastSize = set.size(); + + String query = "identifier=" + id; + List<ReactomeDatabaseObject> objects = getSimpleObjectListByQuery(query, ReactomeReferenceSequence.class); + + for (ReactomeDatabaseObject object : objects) { + ReactomeReferenceSequence reference = (ReactomeReferenceSequence) getFullObjectForDbId(object.getDbId()); + List<ReactomePhysicalEntity> list = getPhysicalEnitiesForReferenceSequence(reference); + set.addAll(list); + } + query = "secondaryIdentifier=" + id; + objects = getSimpleObjectListByQuery(query, ReactomeReferenceSequence.class); + + for (ReactomeDatabaseObject object : objects) { + ReactomeReferenceSequence reference = (ReactomeReferenceSequence) getFullObjectForDbId(object.getDbId()); + List<ReactomePhysicalEntity> list = getPhysicalEnitiesForReferenceSequence(reference); + set.addAll(list); + } + + Set<ReactomePhysicalEntity> complexes = getComplexesContainingOneOfElements(set); + set.addAll(complexes); + Set<ReactomePhysicalEntity> sets = getSetsContainingOneOfElements(set); + set.addAll(sets); + } while (lastSize != set.size()); + } + List<ReactomePhysicalEntity> result = new ArrayList<ReactomePhysicalEntity>(); + result.addAll(set); + return result; + } + + /** + * Returns list of ReactomeComplex that contain one of the elements given in + * the parameter. + * + * @param objects + * list of objects for which we want to retrieve complexes + * @return list of ReactomeComplex that contain one of the elements given in + * the parameter + * @throws IOException + * thrown when there is a problem with connection to reactome + * database + */ + private Set<ReactomePhysicalEntity> getComplexesContainingOneOfElements(Collection<ReactomePhysicalEntity> objects) throws IOException { + Set<ReactomePhysicalEntity> result = new HashSet<ReactomePhysicalEntity>(); + for (ReactomeDatabaseObject obj : objects) { + String query = "hasComponent=" + obj.getDbId(); + List<ReactomeDatabaseObject> complexes = getSimpleObjectListByQuery(query, ReactomeComplex.class); + for (ReactomeDatabaseObject complex : complexes) { + result.add((ReactomePhysicalEntity) getFullObjectForDbId(complex.getDbId())); + } + } + return result; + } + + /** + * Returns list of ReactomeEntitySet that contain one of the elements given in + * the parameter. + * + * @param objects + * list of objects for which we want to retrieve sets + * @return list of ReactomeEntitySet that contain one of the elements given in + * the parameter + * @throws IOException + * thrown when there is a problem with connection to reactome + * database + */ + private Set<ReactomePhysicalEntity> getSetsContainingOneOfElements(Collection<ReactomePhysicalEntity> objects) throws IOException { + + Set<ReactomePhysicalEntity> result = new HashSet<ReactomePhysicalEntity>(); + for (ReactomeDatabaseObject obj : objects) { + String query = "hasMember=" + obj.getDbId(); + List<ReactomeDatabaseObject> complexes = getSimpleObjectListByQuery(query, ReactomeEntitySet.class); + for (ReactomeDatabaseObject complex : complexes) { + result.add((ReactomeEntitySet) getFullObjectForDbId(complex.getDbId())); + } + query = "hasCandidate=" + obj.getDbId(); + complexes = getSimpleObjectListByQuery(query, ReactomeCandidateSet.class); + for (ReactomeDatabaseObject complex : complexes) { + result.add((ReactomeCandidateSet) getFullObjectForDbId(complex.getDbId())); + } + } + return result; + } + + /** + * Returns list of ReactomeEntityWithAccessionedSequence that refers to + * parameter reference. + * + * @param reference + * object to which result should refer to + * @return list of ReactomeEntityWithAccessionedSequence that contain one of + * the elements given in the parameter + * @throws IOException + * thrown when there is a problem with connection to reactome + * database + */ + private List<ReactomePhysicalEntity> getPhysicalEnitiesForReferenceSequence(ReactomeReferenceSequence reference) throws IOException { + List<ReactomePhysicalEntity> result = new ArrayList<ReactomePhysicalEntity>(); + + String query = "referenceEntity=" + reference.getDbId(); + List<ReactomeDatabaseObject> objects = getSimpleObjectListByQuery(query, ReactomeEntityWithAccessionedSequence.class); + + for (ReactomeDatabaseObject object : objects) { + result.add((ReactomePhysicalEntity) getFullObjectForDbId(object.getDbId())); + } + return result; + } + + /** + * Returns list of ReactomeSimpleEntity that refers to parameter reference. + * + * @param reference + * object to which result should refer to + * @return list of ReactomeSimpleEntity that contain one of the elements given + * in the parameter + * @throws IOException + * thrown when there is a problem with connection to reactome + * database + */ + private List<ReactomeSimpleEntity> getSimpleEntitiesByReferenceMolecule(ReactomeReferenceMolecule reference) throws IOException { + List<ReactomeSimpleEntity> result = new ArrayList<ReactomeSimpleEntity>(); + + String query = "referenceEntity=" + reference.getDbId(); + List<ReactomeDatabaseObject> objects = getSimpleObjectListByQuery(query, ReactomeSimpleEntity.class); + + for (ReactomeDatabaseObject object : objects) { + result.add((ReactomeSimpleEntity) getFullObjectForDbId(object.getDbId())); + } + return result; + } + + /** + * Ges list of reactions that interact with object given in the parameter. + * + * @param id + * identifier of the object that should be included in the result + * reactions + * @return list of reactions that interact with object given in the parameter + * @throws IOException + * thrown when there is a problem with connection to reactome + * database + */ + public List<ReactomeReactionlikeEvent> getReactionsForEntityId(Integer id) throws IOException { + List<ReactomeReactionlikeEvent> result = new ArrayList<ReactomeReactionlikeEvent>(); + + String query = "input=" + id; + List<ReactomeDatabaseObject> objects = getSimpleObjectListByQuery(query, ReactomeReactionlikeEvent.class); + + for (ReactomeDatabaseObject object : objects) { + ReactomeDatabaseObject entity = getFullObjectForDbId(object.getDbId()); + if (entity instanceof ReactomeReactionlikeEvent) { + result.add((ReactomeReactionlikeEvent) entity); + } + } + + query = "output=" + id; + objects = getSimpleObjectListByQuery(query, ReactomeReactionlikeEvent.class); + + for (ReactomeDatabaseObject object : objects) { + ReactomeDatabaseObject entity = getFullObjectForDbId(object.getDbId()); + if (entity instanceof ReactomeReactionlikeEvent) { + result.add((ReactomeReactionlikeEvent) entity); + } + } + + // and now find reaction with apropriate catalyst + query = "physicalEntity=" + id; + List<ReactomeDatabaseObject> catalysts = getSimpleObjectListByQuery(query, ReactomeCatalystActivity.class); + for (ReactomeDatabaseObject catalyst : catalysts) { + query = "catalystActivity=" + catalyst.getDbId(); + objects = getSimpleObjectListByQuery(query, ReactomeReactionlikeEvent.class); + for (ReactomeDatabaseObject object : objects) { + ReactomeDatabaseObject entity = getFullObjectForDbId(object.getDbId()); + if (entity instanceof ReactomeReactionlikeEvent) { + result.add((ReactomeReactionlikeEvent) entity); + } + } + } + + return result; + } + + @Override + public Class<?> getTypeForDbId(Integer id) throws ClassNotFoundException, InvalidXmlSchemaException { + Node node = getFullNodeForDbId(id); + Class<?> result = getTypeForNode(node); + return result; + } + + /** + * Returns class type of the data represented in the xml node. + * + * @param node + * xml node + * @return class type of the data represented in the xml node + * @throws ClassNotFoundException + * thrown when there is a problem with finding apropriate class + */ + protected Class<?> getTypeForNode(Node node) throws ClassNotFoundException { + Class<?> result = null; + for (int i = 0; i < node.getChildNodes().getLength(); i++) { + Node child = node.getChildNodes().item(i); + if (child.getNodeName().equals("schemaClass")) { + // our classes aren't exactly the same as reactome names, change what is + // different + String shortName = child.getTextContent().replaceAll("GO_", "Go"); + String className = ReactomeDatabaseObject.class.getPackage().getName() + ".Reactome" + shortName; + result = Class.forName(className); + } + } + return result; + } + + @Override + public ReactomeDatabaseObject getFullObjectForDbId(Integer id) { + try { + ReactomeDatabaseObject result = null; + if (oneInstancePerId) { + result = objectMap.get(id); + if (result != null) { + return result; + } + } + Class<?> classType; + Node node = getFullNodeForDbId(id); + classType = getTypeForNode(node); + ReactomeNodeParser<?> parser = ReactomeParserFactory.getParserForClass(classType); + result = parser.parseObject(node); + if (oneInstancePerId) { + objectMap.put(id, result); + } + return result; + } catch (ClassNotFoundException e) { + throw new InvalidArgumentException("Unknown class type: " + e.getMessage()); + } catch (InvalidXmlSchemaException e) { + logger.error(e, e); + return null; + } + } + + @Override + @SuppressWarnings("unchecked") + public void updateOnlyIdFields(ReactomeDatabaseObject object) { + DataSourceUpdater dsu = this; + for (Method method : object.getClass().getMethods()) { + + if (Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0 + && (method.getName().startsWith("get") || method.getName().startsWith("is"))) { + + if (ReactomeDatabaseObject.class.isAssignableFrom(method.getReturnType())) { + try { + ReactomeDatabaseObject value = (ReactomeDatabaseObject) method.invoke(object); + if (value != null) { + if (value.getStatus().equals(ReactomeStatus.ONLY_ID)) { + ReactomeDatabaseObject newValue = dsu.getFullObjectForDbId(value.getDbId()); + String setterName = method.getName(); + if (setterName.startsWith("get")) { + setterName = setterName.replaceFirst("get", "set"); + } else { + setterName = setterName.replaceFirst("is", "set"); + } + try { + Method setterMethod = object.getClass().getMethod(setterName, method.getReturnType()); + setterMethod.invoke(object, newValue); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalArgumentException e) { + logger.error( + "Cannot assign " + newValue.getClass() + " (id: " + value.getDbId() + ") to field: " + setterName.replace("set", "") + " (" + + method.getReturnType() + ")"); + } catch (SecurityException e) { + + e.printStackTrace(); + // ignore it - however this would be strange + } + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + // ignore it + } + + } else if (List.class.isAssignableFrom(method.getReturnType())) { + try { + @SuppressWarnings("rawtypes") + List list = (List) method.invoke(object); + for (int i = 0; i < list.size(); i++) { + Object obj = list.get(i); + if (obj instanceof ReactomeDatabaseObject) { + if (((ReactomeDatabaseObject) obj).getStatus().equals(ReactomeStatus.ONLY_ID)) { + ReactomeDatabaseObject newValue = dsu.getFullObjectForDbId(((ReactomeDatabaseObject) obj).getDbId()); + list.set(i, newValue); + } + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + // ignore it + } + } + } + } + } + + @Override + public ReactomeDatabaseObject getFullObjectForStableIdentifier(String stableIdentifier) throws IOException { + if (stableIdentifier == null) { + return null; + } + String[] tmp = stableIdentifier.split("\\."); + String identifier = tmp[0]; + String version = "0"; + if (tmp.length > 1) { + version = tmp[1]; + } + return getFullObjectForStableIdentifier(identifier, version); + } + + @Override + public ReactomeDatabaseObject getFullObjectForStableIdentifier(String identifier, String version) throws IOException { + String stableIdentifierQuery = "identifier=" + identifier; + + List<ReactomeDatabaseObject> stableIdentifiers = getSimpleObjectListByQuery(stableIdentifierQuery, ReactomeStableIdentifier.class); + + ReactomeStableIdentifier newestIdentifier = null; + ReactomeStableIdentifier matchedIdentifier = null; + + for (ReactomeDatabaseObject object : stableIdentifiers) { + ReactomeStableIdentifier entity = (ReactomeStableIdentifier) getFullObjectForDbId(object.getDbId()); + if (newestIdentifier == null) { + newestIdentifier = entity; + } else { + try { + Integer id1 = Integer.parseInt(newestIdentifier.getIdentifierVersion()); + Integer id2 = Integer.parseInt(entity.getIdentifierVersion()); + if (id2 > id1) { + newestIdentifier = entity; + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + if (entity.getDisplayName().equals(identifier + "." + version) || entity.getIdentifierVersion().equalsIgnoreCase(version)) { + matchedIdentifier = entity; + } + } + if (matchedIdentifier == null) { + if (newestIdentifier != null) { + logger.warn( + "Invalid REACTOME stable identifier: \"" + identifier + "." + version + "\". Using another similar: " + newestIdentifier.getIdentifier() + "." + + newestIdentifier.getIdentifierVersion()); + } + matchedIdentifier = newestIdentifier; + } + if (matchedIdentifier != null) { + String query = "stableIdentifier=" + matchedIdentifier.getDbId(); + List<ReactomeDatabaseObject> objects = getSimpleObjectListByQuery(query, ReactomeDatabaseObject.class); + + for (ReactomeDatabaseObject object : objects) { + return getFullObjectForDbId(object.getDbId()); + } + + } + return null; + } + + // ------------------------------------------------ + + // Methods that directly connect to reactome database (via restfull API) + + // ------------------------------------------------ + + /** + * This method get content of the page of the url (and post content). + * + * @param url + * url address to connect + * @param postParam + * params of the post method + * @param type + * MIME type of the result + * @param method + * request method (POST/GET) + * @return return the string with the content of the webpage + * @throws IOException + * thrown when there are some problem with network connection + */ + protected String getResponse(String url, String postParam, MimeType type, HttpConnectionMethodType method) throws IOException { + URL obj = new URL(url); + HttpURLConnection con = (HttpURLConnection) obj.openConnection(); + + // add reuqest header + con.setRequestMethod(method.name()); + con.setRequestProperty("Accept", type.getTextRepresentation()); + con.setRequestProperty("Content-Type", type.getTextRepresentation()); + con.setConnectTimeout(CONNECTION_TIMEOUT); + con.setReadTimeout(CONNECTION_TIMEOUT); + + String urlParameters = postParam; + + logger.debug("Sending '" + method + "' request to URL : " + url); + if (method.name().equals("POST")) { + logger.debug("Parameters : " + urlParameters); + } + + // Send post request + con.setDoOutput(true); + if (method.name().equalsIgnoreCase("POST")) { + DataOutputStream wr = new DataOutputStream(con.getOutputStream()); + wr.writeBytes(urlParameters); + wr.flush(); + wr.close(); + } + + int responseCode = con.getResponseCode(); + logger.debug("Response Code : " + responseCode); + + String result = IOUtils.toString(con.getInputStream()); + logger.debug(result); + return result; + } + + @Override + public List<ReactomeDatabaseObject> getSimpleObjectListByQuery(String query, Class<? extends ReactomeDatabaseObject> clazz) throws IOException { + if (query.contains("\n")) { + throw new InvalidArgumentException("Query cannot contain new line character"); + } + ReactomeNodeParser<?> parser = ReactomeParserFactory.getParserForClass(clazz); + List<ReactomeDatabaseObject> result = new ArrayList<ReactomeDatabaseObject>(); + + String url = REACTOME_URL + "listByQuery/" + clazz.getSimpleName().replaceFirst("Reactome", "").replaceAll("Go", "GO_"); + + String key = query + "\n" + url; + + Node node = getCacheNode(key); + NodeList nodes = null; + if (node != null) { + nodes = node.getChildNodes(); + } else { + try { + nodes = getNodesFromServer(query, url); + } catch (InvalidXmlSchemaException e) { + throw new IOException(e); + } + setCacheNode(key, nodes.item(0)); + nodes = nodes.item(0).getChildNodes(); + } + for (int i = 0; i < nodes.getLength(); i++) { + if (nodes.item(i).getNodeType() != Node.ELEMENT_NODE) { + continue; + } + ReactomeDatabaseObject object = parser.parseSimplifiedObject(nodes.item(i)); + result.add(object); + } + return result; + } + + /** + * Returns list of nodes from the server identified by the query and POST + * params. + * + * @param query + * string query to the database sent by POST method + * @param url + * direct url to the API + * @return list of nodes from the server identified by the query and POST + * params. + * @throws IOException + * thrown when there are some problem with network connection + * @throws InvalidXmlSchemaException + * thrown when there is a problem with xml + */ + private NodeList getNodesFromServer(String query, String url) throws IOException, InvalidXmlSchemaException { + String response = getResponse(url, query, MimeType.XML, HttpConnectionMethodType.POST); + logger.debug(response); + Node node = XmlParser.getXmlDocumentFromString(response); + NodeList nodes = node.getChildNodes(); + if (nodes.getLength() < 1) { + throw new InvalidArgumentException("Problem with xml document"); + } + return nodes; + } + + /** + * This method returns xml NODE for object identified by id (identifier in + * reactome database). However if object is in local cache then local copy is + * returned. If object doesn't exist in local cache then after retrieving it + * from reactome is put to the cache. + * + * @param id + * identifier in reactome database + * @return xml node for the id given reactome id + * @throws InvalidXmlSchemaException + * thrown when there is a problem with xml + */ + @Override + public Node getFullNodeForDbId(Integer id) throws InvalidXmlSchemaException { + Node result = getCacheNode(IDENTIFIER_QUERY_PREFIX + id); + if (result != null) { + return result; + } + + String response; + String url = REACTOME_URL + "queryById/DatabaseObject/" + id; + try { + response = getResponse(url, "", MimeType.XML, HttpConnectionMethodType.GET); + Document doc = XmlParser.getXmlDocumentFromString(response); + NodeList nodes = doc.getChildNodes(); + result = nodes.item(0); + setCacheNode(IDENTIFIER_QUERY_PREFIX + id, result); + return result; + } catch (IOException e) { + logger.error(e.getMessage(), e); + return null; + } + } + + @Override + public Map<Integer, ReactomeDatabaseObject> getFullObjectsForDbIds(List<Integer> ids) throws ClassNotFoundException, IOException { + Map<Integer, ReactomeDatabaseObject> result = new HashMap<Integer, ReactomeDatabaseObject>(); + for (Integer integer : ids) { + result.put(integer, getFullObjectForDbId(integer)); + } + + return result; + + } + + @Override + public List<ReactomePhysicalEntity> getEntitiesForSetOfIds(Set<MiriamData> identifiers) throws IOException { + return getEntitiesForUniprotId(identifiers); + } + + @Override + public ExternalServiceStatus getServiceStatus() { + ExternalServiceStatus status = new ExternalServiceStatus( + "Reactome RESTful API", "http://reactome.org/ReactomeRESTfulAPI/ReactomeRESTFulAPI.html"); + + GeneralCacheInterface cacheCopy = getCache(); + this.setCache(null); + + try { + ReactomeDatabaseObject obj = getFullObjectForDbId(CHECK_SERVICE_STATUS_EXAMPLE); + status.setStatus(ExternalServiceStatusType.OK); + if (obj == null) { + status.setStatus(ExternalServiceStatusType.DOWN); + } else if (obj.getClass().equals(ReactomeDatabaseObject.class)) { + status.setStatus(ExternalServiceStatusType.CHANGED); + } + } catch (Exception e) { + logger.error(status.getName() + " is down", e); + status.setStatus(ExternalServiceStatusType.DOWN); + } + this.setCache(cacheCopy); + return status; + } + +}