All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.molgenis.pathways.WikiPathwaysController Maven / Gradle / Ivy

There is a newer version: 5.2.2
Show newest version
package org.molgenis.pathways;

import com.google.common.collect.Multimap;
import org.molgenis.data.DataService;
import org.molgenis.data.Entity;
import org.molgenis.data.Repository;
import org.molgenis.data.meta.model.EntityType;
import org.molgenis.pathways.model.Impact;
import org.molgenis.pathways.model.Pathway;
import org.molgenis.pathways.service.WikiPathwaysService;
import org.molgenis.ui.MolgenisPluginController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.validation.Valid;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringReader;
import java.rmi.RemoteException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import static java.util.function.BinaryOperator.maxBy;
import static java.util.stream.Collectors.*;
import static java.util.stream.IntStream.range;
import static java.util.stream.StreamSupport.stream;
import static org.molgenis.pathways.WikiPathwaysController.URI;
import static org.molgenis.util.stream.MultimapCollectors.toArrayListMultimap;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;

@Controller
@RequestMapping(URI)
public class WikiPathwaysController extends MolgenisPluginController
{
	private static final Logger LOG = LoggerFactory.getLogger(WikiPathwaysController.class);

	private static final String ID = "pathways";
	public static final String URI = MolgenisPluginController.PLUGIN_URI_PREFIX + ID;
	private static final Pattern EFFECT_PATTERN = Pattern
			.compile("([A-Z]*\\|)(\\|*[0-9]+\\||\\|+)+([0-9A-Z]+)(\\|*)(.*)");

	private static final String HOMO_SAPIENS = "Homo sapiens";
	public static final String EFFECT_ATTRIBUTE_NAME = "EFF";
	private final WikiPathwaysService wikiPathwaysService;
	@Autowired
	private DataService dataService;
	private static final Pattern GENE_SYMBOL_PATTERN = Pattern.compile("^[0-9A-Za-z\\-]*");
	private static final DocumentBuilderFactory DB_FACTORY = DocumentBuilderFactory.newInstance();

	@Autowired
	public WikiPathwaysController(WikiPathwaysService wikiPathwaysService)
	{
		super(URI);
		this.wikiPathwaysService = wikiPathwaysService;
	}

	/**
	 * Shows the start screen.
	 *
	 * @param model the {@link Model} to fill
	 * @return the view name
	 */
	@RequestMapping(method = GET)
	public String init(Model model)
	{
		model.addAttribute("entitiesMeta", getVCFEntities());
		return "view-pathways";
	}

	/**
	 * Retrieves the list of VCF entities. They are recognized by the fact that they have an effect attribute.
	 *
	 * @return {@link List} of {@link EntityType} for the VCF entities
	 */
	private List getVCFEntities()
	{
		return stream(dataService.getEntityTypeIds().spliterator(), false).map(dataService::getEntityType)
				.filter(this::hasEffectAttribute).collect(toList());
	}

	/**
	 * Determines if an entity has an effect attribute.
	 *
	 * @param emd {@link EntityType} of the entity
	 * @return boolean indicating if the entity has an effect column
	 */
	private boolean hasEffectAttribute(EntityType emd)
	{
		return emd.getAttribute(EFFECT_ATTRIBUTE_NAME) != null;
	}

	/**
	 * Retrieves all pathways.
	 *
	 * @return {@link Collection} of all {@link Pathway}s.
	 * @throws ExecutionException if load from cache fails
	 */
	@RequestMapping(value = "/allPathways", method = POST)
	@ResponseBody
	public Collection getAllPathways() throws ExecutionException
	{
		return wikiPathwaysService.getAllPathways(HOMO_SAPIENS);
	}

	/**
	 * Searches pathways.
	 *
	 * @param searchTerm string to search for
	 * @return {@link Collection} of all {@link Pathway}s found for searchTerm
	 * @throws RemoteException
	 * @throws ExecutionException
	 */
	@RequestMapping(value = "/filteredPathways", method = POST)
	@ResponseBody
	public Collection getFilteredPathways(@RequestBody String searchTerm)
			throws RemoteException, ExecutionException
	{
		if (StringUtils.isEmpty(searchTerm))
		{
			return getAllPathways();
		}
		return wikiPathwaysService.getFilteredPathways(searchTerm, HOMO_SAPIENS);
	}

	/**
	 * Retrieves uncolored pathway image.
	 *
	 * @param pathwayId the id of the pathway
	 * @return single-line svg string of the pathway image
	 * @throws ExecutionException if load from cache fails
	 */
	@RequestMapping(value = "/pathwayViewer/{pathwayId}", method = GET)
	@ResponseBody
	public String getPathway(@PathVariable String pathwayId) throws ExecutionException
	{
		return wikiPathwaysService.getUncoloredPathwayImage(pathwayId);
	}

	/**
	 * Retrieves gene symbols plus impact from the EFF attributes of all {@link Entity}s in a VCF repository that have
	 * an EFF attribute containing a gene symbol.
	 *
	 * @param selectedVcf name of the VCF {@link Repository}
	 * @return Map mapping Gene name to highest {@link Impact} for that gene
	 */
	private Map getGenesForVcf(String selectedVcf)
	{
		return stream(dataService.getRepository(selectedVcf).spliterator(), false)
				.map(entity -> entity.getString(EFFECT_ATTRIBUTE_NAME))
				.filter(eff -> !StringUtils.isEmpty(getGeneFromEffect(eff))).collect(
						groupingBy(WikiPathwaysController::getGeneFromEffect,
								reducing(Impact.NONE, WikiPathwaysController::getImpactFromEffect,
										maxBy((i1, i2) -> i1.compareTo(i2)))));
	}

	/**
	 * Parses the impact from an effect attribute. Recognizes the strings HIGH, MODERATE, and LOW.
	 *
	 * @param eff String value of the effect attribute
	 * @return the highest {@link Impact} found in the effect attribute, or {@link Impact#NONE} if none found
	 */
	private static Impact getImpactFromEffect(String eff)
	{
		return eff.contains("HIGH") ? Impact.HIGH : eff.contains("MODERATE") ? Impact.MODERATE : eff
				.contains("LOW") ? Impact.LOW : Impact.NONE;
	}

	/**
	 * Parses the Gene symbol from an effect attribute.
	 *
	 * @param eff String value of the effect attribute
	 * @return the gene symbol or null if none found
	 */
	private static String getGeneFromEffect(String eff)
	{
		if (!StringUtils.isEmpty(eff))
		{
			Matcher effectMatcher = EFFECT_PATTERN.matcher(eff);
			if (effectMatcher.find())
			{
				return effectMatcher.group(3);
			}
		}
		return null;
	}

	/**
	 * Retrieves all pathways for the genes in a vcf.
	 *
	 * @param selectedVcf the name of the vcf {@link Repository}
	 * @return {@link Collection} of {@link Pathway}s found for genes in the VCF
	 * @throws ExecutionException if the loading from cache fails
	 */
	@RequestMapping(value = "/pathwaysByGenes", method = POST)
	@ResponseBody
	public Collection getListOfPathwayNamesByGenes(@Valid @RequestBody String selectedVcf)
			throws ExecutionException
	{
		return getGenesForVcf(selectedVcf).keySet().stream().map(this::getPathwaysForGene).flatMap(Collection::stream)
				.collect(toCollection(LinkedHashSet::new));
	}

	/**
	 * Retrieves all pathways for a gene
	 *
	 * @param gene the HGNC name of the gene
	 * @return Collection of {@link Pathway}s
	 */
	private Collection getPathwaysForGene(String gene)
	{
		try
		{
			return wikiPathwaysService.getPathwaysForGene(gene, HOMO_SAPIENS);
		}
		catch (ExecutionException e)
		{
			throw new RuntimeException(e);
		}
	}

	/**
	 * Retrieves a colored pathway.
	 *
	 * @param selectedVcf name of the VCF {@link Repository}
	 * @param pathwayId   ID of the pathway
	 * @return svg for the pathway, with the genes in the VCF colored according to their {@link Impact}
	 * @throws ParserConfigurationException if the creation of the {@link DocumentBuilder} fails
	 * @throws IOException                  If any IO errors occur when parsing the GPML
	 * @throws SAXException                 If any parse errors occur when parsing the GPML
	 * @throws ExecutionException           if the loading of the colored pathway from cache fails
	 */
	@RequestMapping(value = "/getColoredPathway/{selectedVcf}/{pathwayId}", method = GET)
	@ResponseBody
	public String getColoredPathway(@PathVariable String selectedVcf, @PathVariable String pathwayId)
			throws ParserConfigurationException, SAXException, IOException, ExecutionException
	{
		return getColoredPathway(selectedVcf, pathwayId, analyzeGPML(wikiPathwaysService.getPathwayGPML(pathwayId)));
	}

	/**
	 * Analyses pathway GPML. Determines for each gene in which graphIds it is displayed.
	 *
	 * @param gpml String containing the pathway GPML
	 * @return {@link Multimap} mapping gene symbol to graphIDs
	 * @throws IllegalArgumentException if the gpml is invalid
	 */
	Multimap analyzeGPML(String gpml) throws ParserConfigurationException, IOException, SAXException
	{
		return streamDataNodes(gpml).filter(node -> !node.getAttribute("GraphId").isEmpty()).collect(
				toArrayListMultimap(node -> getGeneSymbol(node.getAttribute("TextLabel")),
						node -> node.getAttribute("GraphId")));
	}

	/**
	 * Finds the DataNode elements in a gpml string.
	 *
	 * @param gpml String containing the gpml document
	 * @return {@link Stream} of DataNode {@link Element}s
	 * @throws IllegalArgumentException if the gpml is invalid
	 */
	private Stream streamDataNodes(String gpml)
	{
		Document document;
		try
		{
			document = DB_FACTORY.newDocumentBuilder().parse(new InputSource(new StringReader(gpml)));
			NodeList dataNodes = document.getElementsByTagName("DataNode");
			return range(0, dataNodes.getLength()).mapToObj(dataNodes::item).map(Element.class::cast);
		}
		catch (SAXException | IOException | ParserConfigurationException e)
		{
			LOG.error("Invalid GPML " + gpml);
			throw new IllegalArgumentException("Invalid GPML");
		}
	}

	/**
	 * Finds the gene symbol in a text label.
	 *
	 * @param textLabel the text label to look in
	 * @return Gene symbol if found, otherwise ""
	 */
	String getGeneSymbol(String textLabel)
	{
		String geneSymbol = "";
		if (textLabel.contains("""))
		{
			LOG.warn("Textlabel(" + textLabel
					+ ") contains quotes, which is inconsistent with the gene names. Removing the quotes.");
			textLabel = textLabel.replace(""", "");
		}
		Matcher geneSymbolMatcher = GENE_SYMBOL_PATTERN.matcher(textLabel);
		if (geneSymbolMatcher.find())
		{
			geneSymbol = geneSymbolMatcher.group(0);
		}
		return geneSymbol;
	}

	/**
	 * Retrieves a colored pathway. Sometimes WikiPathways returns no graphIds for a pathway, then the pathway is
	 * returned uncolored.
	 *
	 * @param selectedVcf     the name of the vcf entity
	 * @param pathwayId       the id of the pathway in WikiPathways
	 * @param graphIdsPerGene {@link Multimap} mapping gene symbol to graphId
	 * @return String svg from for the pathway
	 * @throws ExecutionException if the loading from cache fails
	 */
	private String getColoredPathway(String selectedVcf, String pathwayId, Multimap graphIdsPerGene)
			throws ExecutionException
	{
		Map impactPerGraphId = new HashMap();
		getGenesForVcf(selectedVcf).forEach(
				(gene, impact) -> graphIdsPerGene.get(gene).forEach(graphId -> impactPerGraphId.put(graphId, impact)));

		if (!impactPerGraphId.isEmpty())
		{
			return wikiPathwaysService.getColoredPathwayImage(pathwayId, impactPerGraphId);
		}
		else
		{
			return wikiPathwaysService.getUncoloredPathwayImage(pathwayId);
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy