Skip to content

Commit

Permalink
Add ability to fetch variant data from track via web services (#1867)
Browse files Browse the repository at this point in the history
* move checkPermission to TrackService

* Add new enums to FeatureStringEnum

* Implement ability to fetch variant data from a track via web services

* Bump htsjdk to 2.14.3

* fixed for the BAM canvas (#1869)

* fixed for the BAM canvas

* fixed biotypes to match codebase

* Use 'seqId' instead of 'sequence' in JSON

* Enable caching

* add API documentation

* Update REST API docs

* just updated the labels brielfy

* fixed mismatches
in sequence header

* fixed a long-running bug in caching
  • Loading branch information
deepakunni3 authored and nathandunn committed Mar 9, 2018
1 parent b75d84a commit 04d9a6d
Show file tree
Hide file tree
Showing 8 changed files with 1,635 additions and 1,019 deletions.
2 changes: 1 addition & 1 deletion grails-app/conf/BuildConfig.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ grails.project.dependency.resolution = {
compile 'commons-collections:commons-collections:3.2.1'

// HTSJDK
compile group: 'com.github.samtools', name: 'htsjdk', version: '2.10.0'
compile group: 'com.github.samtools', name: 'htsjdk', version: '2.14.3'

// svg generation
compile group: 'org.apache.xmlgraphics', name: 'batik-svg-dom', version: '1.9'
Expand Down
2 changes: 2 additions & 0 deletions grails-app/conf/UrlMappings.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class UrlMappings {
"/track/cache/clear/${organismName}/${trackName}"(controller: "track", action: "clearTrackCache")
"/track/cache/clear/${organismName}"(controller: "track", action: "clearOrganismCache")

"/vcf/${organismString}/${trackName}/${sequence}:${fmin}..${fmax}.${type}"(controller: "vcf", action: "featuresByLocation",[params:params])

"/sequence/${organismString}/?loc=${sequenceName}:${fmin}..${fmax}"(controller: "sequence", action: "sequenceByLocation",[params:params])
"/sequence/${organismString}/${sequenceName}:${fmin}..${fmax}"(controller: "sequence", action: "sequenceByLocation",[params:params])
"/sequence/${organismString}/${sequenceName}/${featureName}.${type}"(controller: "sequence", action: "sequenceByName",[params:params])
Expand Down
25 changes: 6 additions & 19 deletions grails-app/controllers/org/bbop/apollo/TrackController.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class TrackController {
])
@Transactional
def clearTrackCache(String organismName, String trackName) {
if (!checkPermission(organismName)) return
if (!trackService.checkPermission(request, response, organismName)) return
int removed = TrackCache.executeUpdate("delete from TrackCache tc where tc.organismName = :commonName and tc.trackName = :trackName",[commonName:organismName,trackName: trackName])
render new JSONObject(removed: removed) as JSON
}
Expand All @@ -76,7 +76,7 @@ class TrackController {
render jsonArray as JSON
} else {
log.info "Deleting cache for ${organismName}"
if (!checkPermission(organismName)) return
if (!trackService.checkPermission(request, response, organismName)) return
int removed = TrackCache.executeUpdate("delete from TrackCache tc where tc.organismName = :commonName ",[commonName:organismName])
render new JSONObject(removed: removed) as JSON
}
Expand All @@ -96,7 +96,7 @@ class TrackController {
])
@Transactional
def featuresByName(String organismString, String trackName, String sequence, String featureName, String type) {
if (!checkPermission(organismString)) return
if (!trackService.checkPermission(request, response, organismString)) return

Boolean ignoreCache = params.ignoreCache != null ? Boolean.valueOf(params.ignoreCache) : false
Map paramMap = new TreeMap<>()
Expand Down Expand Up @@ -191,7 +191,7 @@ class TrackController {
])
@Transactional
def featuresByLocation(String organismString, String trackName, String sequence, Long fmin, Long fmax, String type) {
if (!checkPermission(organismString)) return
if (!trackService.checkPermission(request, response, organismString)) return

Set<String> nameSet = getNames(params.name ? params.name : "")
Boolean onlySelected = params.onlySelected != null ? params.onlySelected : false
Expand Down Expand Up @@ -267,7 +267,7 @@ class TrackController {
}

def biolink(String organismString, String trackName, String sequence, Long fmin, Long fmax) {
if (!checkPermission(organismString)) return
if (!trackService.checkPermission(request, response, organismString)) return
JSONArray filteredList = trackService.getNCList(trackName, organismString, sequence, fmin, fmax)
JSONObject renderdObject = trackService.getNCListAsBioLink(filteredList)
render renderdObject as JSON
Expand All @@ -285,22 +285,9 @@ class TrackController {
// TODO: this is just for debuggin
// track < organism ID or name > / <track name > / < sequence name > / min / max
def nclist(String organismString, String trackName, String sequence, Long fmin, Long fmax) {
if (!checkPermission(organismString)) return
if (!trackService.checkPermission(request, response, organismString)) return
JSONArray filteredList = trackService.getNCList(trackName, organismString, sequence, fmin, fmax)
render filteredList as JSON
}

def checkPermission(String organismString) {
Organism organism = preferenceService.getOrganismForToken(organismString)
if (organism && (organism.publicMode || permissionService.checkPermissions(PermissionEnum.READ))) {
return true
} else {
// not accessible to the public
response.status = HttpServletResponse.SC_FORBIDDEN
render ""
return false
}

}

}
73 changes: 73 additions & 0 deletions grails-app/controllers/org/bbop/apollo/VcfController.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.bbop.apollo

import grails.converters.JSON
import htsjdk.variant.vcf.VCFFileReader
import org.bbop.apollo.gwt.shared.FeatureStringEnum
import org.bbop.apollo.gwt.shared.PermissionEnum
import org.codehaus.groovy.grails.web.json.JSONArray
import org.codehaus.groovy.grails.web.json.JSONObject
import org.restapidoc.annotation.RestApi
import org.restapidoc.annotation.RestApiMethod
import org.restapidoc.annotation.RestApiParam
import org.restapidoc.annotation.RestApiParams
import org.restapidoc.pojo.RestApiParamType
import org.restapidoc.pojo.RestApiVerb

import javax.servlet.http.HttpServletResponse

@RestApi(name = "VCF Services", description = "Methods for retrieving VCF track data as JSON")
class VcfController {

def preferenceService
def permissionService
def vcfService
def trackService

@RestApiMethod(description = "Get VCF track data for a given range as JSON", path = "/vcf/<organism_name>/<track_name>/<sequence_name>:<fmin>..<fmax>.<type>?includeGenotypes=<includeGenotypes>&ignoreCache=<ignoreCache>", verb = RestApiVerb.GET)
@RestApiParams(params = [
@RestApiParam(name = "organismString", type = "string", paramType = RestApiParamType.QUERY, description = "Organism common name or ID (required)"),
@RestApiParam(name = "trackName", type = "string", paramType = RestApiParamType.QUERY, description = "Track name by label in trackList.json (required)"),
@RestApiParam(name = "sequence", type = "string", paramType = RestApiParamType.QUERY, description = "Sequence name (required)"),
@RestApiParam(name = "fmin", type = "integer", paramType = RestApiParamType.QUERY, description = "Minimum range (required)"),
@RestApiParam(name = "fmax", type = "integer", paramType = RestApiParamType.QUERY, description = "Maximum range (required)"),
@RestApiParam(name = "type", type = "string", paramType = RestApiParamType.QUERY, description = ".json (required)"),
@RestApiParam(name = "includeGenotypes", type = "boolean", paramType = RestApiParamType.QUERY, description = "(default: false). If true, will include genotypes associated with variants from VCF."),
@RestApiParam(name = "ignoreCache", type = "boolean", paramType = RestApiParamType.QUERY, description = "(default: false). Use cache for request, if available."),
])
def featuresByLocation(String organismString, String trackName, String sequence, Long fmin, Long fmax, String type, boolean includeGenotypes) {
if(!trackService.checkPermission(request, response, organismString)) return

JSONArray featuresArray = new JSONArray()
Organism organism = preferenceService.getOrganismForToken(organismString)
JSONObject trackListObject = trackService.getTrackList(organism.directory)
String trackUrlTemplate
for(JSONObject track : trackListObject.getJSONArray(FeatureStringEnum.TRACKS.value)) {
if(track.getString(FeatureStringEnum.LABEL.value) == trackName) {
trackUrlTemplate = track.urlTemplate
break
}
}

Boolean ignoreCache = params.ignoreCache != null ? Boolean.valueOf(params.ignoreCache) : false
if (!ignoreCache) {
String responseString = trackService.checkCache(organismString, trackName, sequence, fmin, fmax, type, null)
if (responseString) {
render JSON.parse(responseString) as JSON
return
}
}

File file = new File(organism.directory + File.separator + trackUrlTemplate)
try {
VCFFileReader vcfFileReader = new VCFFileReader(file)
featuresArray = vcfService.processVcfRecords(organism, vcfFileReader, sequence, fmin, fmax, includeGenotypes)
}
catch (IOException e) {
log.error(e.stackTrace)
}

trackService.cacheRequest(featuresArray.toString(), organismString, trackName, sequence, fmin, fmax, type, null)
render featuresArray as JSON
}

}
44 changes: 36 additions & 8 deletions grails-app/services/org/bbop/apollo/TrackService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ package org.bbop.apollo
import grails.converters.JSON
import grails.transaction.NotTransactional
import grails.transaction.Transactional
import org.bbop.apollo.gwt.shared.PermissionEnum
import org.bbop.apollo.gwt.shared.track.TrackIndex
import org.bbop.apollo.sequence.SequenceDTO
import org.codehaus.groovy.grails.web.json.JSONArray
import org.codehaus.groovy.grails.web.json.JSONElement
import org.codehaus.groovy.grails.web.json.JSONObject

import javax.servlet.http.HttpServletResponse
import java.util.zip.GZIPInputStream

@Transactional
class TrackService {

def preferenceService
def trackMapperService
def permissionService

public static String TRACK_NAME_SPLITTER = "::"

Expand Down Expand Up @@ -428,15 +431,30 @@ class TrackService {

@Transactional
def cacheRequest(String responseString, String organismString, String trackName, String sequenceName, Long fmin, Long fmax, String type, Map paramMap) {
TrackCache trackCache = new TrackCache(
response: responseString
, organismName: organismString
, trackName: trackName
, sequenceName: sequenceName
, fmin: fmin
, fmax: fmax
, type: type

TrackCache trackCache = TrackCache.findByOrganismNameAndTrackNameAndSequenceNameAndFminAndFmaxAndType(
organismString,
trackName,
sequenceName,
fmin,
fmax,
type
)

if(trackCache){
trackCache.response = responseString
}
else{
trackCache = new TrackCache(
response: responseString
, organismName: organismString
, trackName: trackName
, sequenceName: sequenceName
, fmin: fmin
, fmax: fmax
, type: type
)
}
if (paramMap) {
trackCache.paramMap = (paramMap as JSON).toString()
}
Expand Down Expand Up @@ -555,4 +573,14 @@ class TrackService {
return geneChildren
}

def checkPermission(def request, def response, String organismString) {
Organism organism = preferenceService.getOrganismForToken(organismString)
if (organism && (organism.publicMode || permissionService.checkPermissions(PermissionEnum.READ))) {
return true
} else {
// not accessible to the public
response.status = HttpServletResponse.SC_FORBIDDEN
return false
}
}
}
Loading

0 comments on commit 04d9a6d

Please sign in to comment.