ubic.gemma.web.services.rest.AnnotationsWebService.java Source code

Java tutorial

Introduction

Here is the source code for ubic.gemma.web.services.rest.AnnotationsWebService.java

Source

/*
 * The gemma-web project
 *
 * Copyright (c) 2015 University of British Columbia
 *
 * 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 ubic.gemma.web.services.rest;

import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import ubic.gemma.core.expression.experiment.service.ExpressionExperimentSearchService;
import ubic.gemma.core.ontology.OntologyService;
import ubic.gemma.core.search.SearchResult;
import ubic.gemma.core.search.SearchService;
import ubic.gemma.model.common.search.SearchSettings;
import ubic.gemma.model.common.search.SearchSettingsImpl;
import ubic.gemma.model.expression.experiment.ExpressionExperiment;
import ubic.gemma.model.expression.experiment.ExpressionExperimentValueObject;
import ubic.gemma.model.genome.Taxon;
import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject;
import ubic.gemma.persistence.service.common.description.CharacteristicService;
import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService;
import ubic.gemma.persistence.service.genome.taxon.TaxonService;
import ubic.gemma.web.services.rest.util.Responder;
import ubic.gemma.web.services.rest.util.ResponseDataObject;
import ubic.gemma.web.services.rest.util.WebService;
import ubic.gemma.web.services.rest.util.WebServiceWithFiltering;
import ubic.gemma.web.services.rest.util.args.*;

import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import java.util.*;

/**
 * RESTful interface for annotations.
 *
 * @author tesarst
 */
@Component
@Path("/annotations")
public class AnnotationsWebService extends
        WebServiceWithFiltering<ExpressionExperiment, ExpressionExperimentValueObject, ExpressionExperimentService> {
    private static final String URL_PREFIX = "http://";

    private OntologyService ontologyService;
    private SearchService searchService;
    private CharacteristicService characteristicService;
    private ExpressionExperimentService expressionExperimentService;
    private TaxonService taxonService;

    /**
     * Required by spring
     */
    public AnnotationsWebService() {
    }

    /**
     * Constructor for service autowiring
     */
    @Autowired
    public AnnotationsWebService(OntologyService ontologyService, SearchService searchService,
            CharacteristicService characteristicService, ExpressionExperimentService expressionExperimentService,
            TaxonService taxonService) {
        super(expressionExperimentService);
        this.ontologyService = ontologyService;
        this.searchService = searchService;
        this.characteristicService = characteristicService;
        this.expressionExperimentService = expressionExperimentService;
        this.taxonService = taxonService;
    }

    /**
     * Placeholder for root call
     */
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public ResponseDataObject all( // Params:
            @Context final HttpServletResponse sr // The servlet response, needed for response code setting.
    ) {
        return Responder.code404(WebService.ERROR_MSG_UNMAPPED_PATH, sr);
    }

    /**
     * Placeholder for search call without a query parameter
     */
    @GET
    @Path("/search/")
    @Produces(MediaType.APPLICATION_JSON)
    public ResponseDataObject emptySearch( // Params:
            @Context final HttpServletResponse sr // The servlet response, needed for response code setting.
    ) {
        return Responder.code400("Search query empty.", sr);
    }

    /**
     * Does a search for annotation tags based on the given string.
     *
     * @param query the search query. Either plain text, or an ontology term URI
     * @return response data object with a collection of found terms, each wrapped in a CharacteristicValueObject.
     * @see OntologyService#findTermsInexact(String, Taxon) for better description of the search process.
     * @see CharacteristicValueObject for the output object structure.
     */
    @GET
    @Path("/search/{query}")
    @Produces(MediaType.APPLICATION_JSON)
    public ResponseDataObject search( // Params:
            @PathParam("query") ArrayStringArg query, // Required
            @Context final HttpServletResponse sr // The servlet response, needed for response code setting.
    ) {
        return Responder.autoCode(this.getTerms(query), sr);
    }

    /**
     * Does a search for datasets containing characteristics matching the given string.
     * If filter, offset, limit or sort parameters are provided, acts same as
     * {@link WebServiceWithFiltering#some(ArrayEntityArg, FilterArg, IntArg, IntArg, SortArg, HttpServletResponse) }.
     *
     * @param query the search query. Either plain text, or an ontology term URI
     * @return response data object with a collection of dataset that match the search query.
     * @see ExpressionExperimentSearchService#searchExpressionExperiments(String) for better description of the search process.
     */
    @GET
    @Path("/search/{query}/datasets")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public ResponseDataObject datasets( // Params:
            @PathParam("query") ArrayStringArg query, // Required
            @QueryParam("filter") @DefaultValue("") DatasetFilterArg filter, // Optional, default null
            @QueryParam("offset") @DefaultValue("0") IntArg offset, // Optional, default 0
            @QueryParam("limit") @DefaultValue("0") IntArg limit, // Optional, default 0
            @QueryParam("sort") @DefaultValue("+id") SortArg sort, // Optional, default +id
            @Context final HttpServletResponse sr // The servlet response, needed for response code setting.
    ) {
        Collection<Long> foundIds = this.searchEEs(query.getValue());

        if (foundIds.isEmpty()) {
            return Responder.autoCode(foundIds, sr);
        }

        // If there are filters other than the search query, intersect the results.
        if (filter.getObjectFilters() != null || offset.getValue() != 0 || limit.getValue() != 0
                || !sort.getField().equals("id") || !sort.isAsc()) {
            // Converting list to string that will be parsed out again - not ideal, but is currently the best way to do
            // this without cluttering the code.
            return super.some(ArrayDatasetArg.valueOf(StringUtils.join(foundIds, ',')), filter, offset, limit, sort,
                    sr);
        }

        // Otherwise there is no need to go the pre-filter path since we already know exactly what IDs we want.
        return Responder.autoCode(expressionExperimentService.loadValueObjects(foundIds, false), sr);
    }

    /**
     * Same as this#datasets(ArrayStringArg, DatasetFilterArg, IntArg, IntArg, SortArg, HttpServletResponse) but
     * also filters by taxon.
     * see this#datasets(ArrayStringArg, DatasetFilterArg, IntArg, IntArg, SortArg, HttpServletResponse).
     */
    @GET
    @Path("/{taxonArg: [a-zA-Z0-9%20\\.]+}/search/{query}/datasets")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public ResponseDataObject taxonDatasets( // Params:
            @PathParam("taxonArg") TaxonArg<Object> taxonArg, // Required
            @PathParam("query") ArrayStringArg query, // Required
            @QueryParam("filter") @DefaultValue("") DatasetFilterArg filter, // Optional, default null
            @QueryParam("offset") @DefaultValue("0") IntArg offset, // Optional, default 0
            @QueryParam("limit") @DefaultValue("0") IntArg limit, // Optional, default 0
            @QueryParam("sort") @DefaultValue("+id") SortArg sort, // Optional, default +id
            @Context final HttpServletResponse sr // The servlet response, needed for response code setting.
    ) {
        Collection<Long> foundIds = this.searchEEs(query.getValue());

        if (foundIds.isEmpty()) {
            return Responder.autoCode(foundIds, sr);
        }

        // We always have to do filtering, because we always have at least the taxon argument (otherwise this#datasets method is used)
        return Responder.autoCode(taxonArg.getTaxonDatasets(expressionExperimentService, taxonService,
                ArrayDatasetArg.valueOf(StringUtils.join(foundIds, ',')).combineFilters(filter.getObjectFilters(),
                        expressionExperimentService),
                offset.getValue(), limit.getValue(), sort.getField(), sort.isAsc()), sr);
    }

    /**
     * Performs a dataset search for each given value, then intersects the results to create a final set of dataset IDs.
     *
     * @param values the values that the datasets should match.
     * @return set of IDs that satisfy all given search values.
     */
    private Collection<Long> searchEEs(List<String> values) {
        Set<Long> ids = new HashSet<>();
        boolean firstRun = true;
        for (String value : values) {
            Set<Long> valueIds = new HashSet<>();

            SearchSettings settings = new SearchSettingsImpl();
            settings.setQuery(value);
            settings.setSearchGenes(false);
            settings.setSearchPlatforms(false);
            settings.setSearchExperimentSets(false);
            settings.setSearchPhenotypes(false);
            settings.setSearchProbes(false);
            settings.setSearchGeneSets(false);
            settings.setSearchBioSequences(false);
            settings.setSearchBibrefs(false);

            Map<Class<?>, List<SearchResult>> results = searchService.search(settings, false, false);
            List<SearchResult> eeResults = results.get(ExpressionExperiment.class);

            if (eeResults == null) {
                return new HashSet<>(); // No terms found for the current term means the intersection will be empty.
            }

            // Working only with IDs
            for (SearchResult result : eeResults) {
                valueIds.add(result.getId());
            }

            // Intersecting with previous results
            if (firstRun) {
                // In the first run we keep the whole list od IDs
                ids = valueIds;
            } else {
                // Intersecting with the IDs found in the current run
                ids.retainAll(valueIds);
            }
            firstRun = false;
        }
        return ids;
    }

    /**
     * Finds characteristics by either a plain text or URI.
     *
     * @param arg the array arg containing all the strings to search for.
     * @return a collection of characteristics matching the input query.
     */
    private Collection<AnnotationSearchResultValueObject> getTerms(ArrayStringArg arg) {
        Collection<AnnotationSearchResultValueObject> vos = new LinkedList<>();
        for (String query : arg.getValue()) {
            query = query.trim();
            if (query.startsWith(AnnotationsWebService.URL_PREFIX)) {
                this.addAsSearchResults(vos, characteristicService.loadValueObjects(
                        characteristicService.findByUri(StringEscapeUtils.escapeJava(StringUtils.strip(query)))));
            } else {
                this.addAsSearchResults(vos, ontologyService.findExperimentsCharacteristicTags(query, true));
            }
        }
        return vos;
    }

    private void addAsSearchResults(Collection<AnnotationSearchResultValueObject> to,
            Collection<CharacteristicValueObject> vos) {
        for (CharacteristicValueObject vo : vos) {
            to.add(new AnnotationSearchResultValueObject(vo.getValue(), vo.getValueUri(), vo.getCategory(),
                    vo.getCategoryUri()));
        }
    }

    private class AnnotationSearchResultValueObject {
        public String value;
        public String valueUri;
        public String category;
        public String categoryUri;

        AnnotationSearchResultValueObject(String value, String valueUri, String category, String categoryUri) {
            this.value = value;
            this.valueUri = valueUri;
            this.category = category;
            this.categoryUri = categoryUri;
        }
    }
}