-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Jbrowse2 service #258
base: master
Are you sure you want to change the base?
Jbrowse2 service #258
Changes from 7 commits
aa022a7
831ab21
b107127
077e2ac
f19b132
161b896
ce925ca
e676d61
fd08416
faa7bba
775cdb7
2f19f58
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
package org.apidb.apicommon.service.services.jbrowse; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.InputStreamReader; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import javax.sql.DataSource; | ||
import javax.ws.rs.*; | ||
import javax.ws.rs.core.MediaType; | ||
import javax.ws.rs.core.Response; | ||
|
||
import org.gusdb.fgputil.db.runner.SQLRunner; | ||
import org.gusdb.fgputil.db.runner.SQLRunnerException; | ||
import org.gusdb.wdk.model.WdkException; | ||
import org.gusdb.wdk.model.WdkModelException; | ||
import org.gusdb.wdk.model.WdkRuntimeException; | ||
import org.gusdb.wdk.service.service.AbstractWdkService; | ||
import org.json.JSONArray; | ||
import org.json.JSONObject; | ||
|
||
@Path("/jbrowse2") | ||
public class JBrowse2Service extends AbstractWdkService { | ||
private static final String VDI_DATASET_DIR_KEY = "VDI_DATASETS_DIRECTORY"; | ||
private static final String VDI_CONTROL_SCHEMA_KEY ="VDI_CONTROL_SCHEMA"; | ||
private static final String VDI_DATASET_SCHEMA_KEY ="VDI_DATASETS_SCHEMA"; | ||
private static final String WEB_SVC_DIR_KEY ="WEBSERVICEMIRROR"; | ||
|
||
/* | ||
Get config for a single organism. Assumes JSON will easily fit in memory. | ||
*/ | ||
@GET | ||
@Path("orgview/{publicOrganismAbbrev}/config.json") | ||
@Produces(MediaType.APPLICATION_JSON) | ||
public Response getJbrowseSingleOrgTracks(@PathParam("publicOrganismAbbrev") String publicOrganismAbbrev, | ||
@QueryParam("trackSets") String trackSets) throws IOException, WdkException { | ||
|
||
// get static json config, for this organism and set of tracks | ||
String staticConfigJsonString = getStaticConfigJsonString(publicOrganismAbbrev, trackSets); | ||
JSONObject staticConfigJson = new JSONObject(staticConfigJsonString); | ||
|
||
// get similar from user datasets | ||
JSONArray udTracks = getUserDatasetTracks(publicOrganismAbbrev, trackSets); | ||
|
||
// merge UD tracks into static | ||
staticConfigJson.getJSONArray("tracks").putAll(udTracks); | ||
|
||
// send response | ||
String jsonString = staticConfigJson.toString(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A little worried about the size of this. toString() means doubling the size of the completed file before returning. Wondering if we should be using Jackson to stream as Ellie suggested. Though that can probably wait until we know this is giving us what we want. |
||
return Response.ok(jsonString, MediaType.APPLICATION_JSON).build(); | ||
} | ||
|
||
// call out to perl code to produce static config json | ||
String getStaticConfigJsonString(String publicOrganismAbbrev, String trackSets) throws IOException { | ||
|
||
String gusHome = getWdkModel().getGusHome(); | ||
String projectId = getWdkModel().getProjectId(); | ||
String buildNumber = getWdkModel().getBuildNumber(); | ||
|
||
List<String> command = new ArrayList<>(); | ||
command.add(gusHome + "/bin/jbrowse2config"); | ||
command.add("--orgAbbrev"); | ||
command.add(publicOrganismAbbrev); | ||
command.add("--projectId"); | ||
command.add(projectId); | ||
command.add("--buildNumber"); | ||
command.add(buildNumber); | ||
command.add("--webSvcDir"); | ||
command.add(getWdkModel().getProperties().get(WEB_SVC_DIR_KEY)); | ||
command.add("--trackSets"); | ||
command.add(trackSets); | ||
|
||
return stringFromCommand(command); | ||
} | ||
|
||
JSONArray getUserDatasetTracks(String publicOrganismAbbrev, String tracksString) throws WdkModelException { | ||
String buildNumber = getWdkModel().getBuildNumber(); | ||
String projectId = getWdkModel().getProjectId(); | ||
Long userId = getRequestingUser().getUserId(); | ||
String vdiDatasetsDir = getWdkModel().getProperties().get(VDI_DATASET_DIR_KEY); | ||
String vdiDatasetsSchema = getWdkModel().getProperties().get(VDI_DATASET_SCHEMA_KEY); | ||
String vdiControlSchema = getWdkModel().getProperties().get(VDI_CONTROL_SCHEMA_KEY); | ||
|
||
String udDataPathString = String.join("/", vdiDatasetsDir, vdiDatasetsSchema, "build-" + buildNumber, projectId); | ||
JSONArray udTracks = new JSONArray(); | ||
List<String> trackSetList = Arrays.asList(tracksString.split(",")); | ||
|
||
// for now we only have rnaseq UD tracks | ||
if (trackSetList.contains("rnaseq")) { | ||
udTracks.put(getRnaSeqUdTracks(publicOrganismAbbrev, projectId, vdiControlSchema, | ||
udDataPathString, userId)); | ||
} | ||
return udTracks; | ||
} | ||
|
||
JSONArray getRnaSeqUdTracks(String publicOrganismAbbrev, String projectId, String vdiControlSchema, | ||
String udDataPathString, Long userId) throws WdkModelException { | ||
|
||
DataSource appDs = getWdkModel().getAppDb().getDataSource(); | ||
String sql = "select distinct user_dataset_id, name " + | ||
"from " + vdiControlSchema + ".AvailableUserDatasets aud, " + | ||
vdiControlSchema + ".dataset_dependency dd " + | ||
"where project_id = '" + projectId + "' " + | ||
"and (type = 'RnaSeq' or type = 'BigWig') " + | ||
"and ((is_public = 1 and is_owner = 1) or user_id = " + userId + ") " + | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this work? Seems like you would want table refs in these columns. Also, is sharing covered in AvailableUserDatasets? is_public is our community datasets indicator? |
||
"and dd.dataset_id = aud.dataset_id " + | ||
" dd.identifier = '" + publicOrganismAbbrev + "'"; | ||
try { | ||
return new SQLRunner(appDs, sql).executeQuery(rs -> { | ||
JSONArray rnaSeqUdTracks = new JSONArray(); | ||
while (rs.next()) { | ||
String datasetId = rs.getString(1); | ||
String name = rs.getString(2); | ||
List<String> fileNames = getBigwigFileNames(udDataPathString + "/" +datasetId); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, we can't return to the web client the full path to these files. We're returning relative URLs. Right now there's a script in service-jbrowse2 that does this transform using sed (depends on regex matching per line), but we should probably use jq so John can send the JSON in a single line. He mentioned this in one of our meetings. But wonder if we could be doing that here? We can at least set the correct value up front for user datasets. |
||
for (String fileName : fileNames) { | ||
rnaSeqUdTracks.put(createBigwigTrackJson(datasetId, name, fileName, publicOrganismAbbrev, udDataPathString)); | ||
} | ||
} | ||
return rnaSeqUdTracks; | ||
}); | ||
} | ||
catch (SQLRunnerException e) { | ||
throw new WdkModelException("Unable to query VDI tables for RNA seq datasets", e.getCause()); | ||
} | ||
} | ||
|
||
// boilerplate method written by copilot | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't need to advertise this. :) |
||
public static List<String> getBigwigFileNames(String directoryPath) throws SQLRunnerException { | ||
List<String> bwFiles = new ArrayList<>(); | ||
File directory = new File(directoryPath); | ||
|
||
if (directory.isDirectory()) { | ||
File[] files = directory.listFiles(); | ||
if (files != null) { | ||
for (File file : files) { | ||
if (file.isFile() && file.getName().endsWith(".bw")) { | ||
bwFiles.add(file.getName()); | ||
} | ||
} | ||
} | ||
} else { | ||
throw new SQLRunnerException("User Dataset directory not found for path: " + directoryPath); | ||
} | ||
|
||
return bwFiles; | ||
} | ||
|
||
/* | ||
MULTI BIGWIG TRACK EXAMPLE | ||
{ | ||
"assemblyNames": [ | ||
"ORG_ABBREV" | ||
], | ||
"trackId": "VDI_ID", | ||
"name": "VDI_NAME", | ||
"displays": [ | ||
{ | ||
"displayId": "wiggle_ApiCommonModel::Model::JBrowseTrackConfig::MultiBigWigTrackConfig::XY=HASH(0x2249320)", | ||
"maxScore": 1000, | ||
"minScore": 1, | ||
"defaultRendering": "multirowxy", | ||
"type": "MultiLinearWiggleDisplay", | ||
"scaleType": "log" | ||
} | ||
], | ||
"adapter": { | ||
"subadapters": [ | ||
{ | ||
"color": "grey", | ||
"name": "FILE_NAME", | ||
"type": "BigWigAdapter", | ||
"bigWigLocation": { | ||
"locationType": "UriLocation", | ||
"uri": "USER_DATASET_PATH/VDI_ID/FILE_NAME" | ||
} | ||
} | ||
} | ||
} | ||
*/ | ||
JSONObject createBigwigTrackJson(String vdiId, String vdiName, String fileName, String organismAbbrev, String userDatasetsFilePath) { | ||
JSONObject track = new JSONObject(); | ||
track.put("assemblyNames", new JSONArray().put(organismAbbrev)); | ||
track.put("trackId", vdiId); | ||
track.put("name", vdiName); | ||
JSONObject display = new JSONObject(); | ||
display.put("displayId", "wiggle_ApiCommonModel::Model::JBrowseTrackConfig::MultiBigWigTrackConfig::XY=HASH(0x2249320)"); | ||
display.put("maxScore", 1); | ||
display.put("maxScore", 1000); | ||
display.put("defaultRendering", "multirowxy"); | ||
display.put("type", "MultiLinearWiggleDisplay"); | ||
display.put("scaleType", "log"); | ||
JSONArray displays = new JSONArray().put(display); | ||
track.put("displays", displays); | ||
JSONObject subAdapter = new JSONObject(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You forgot to add subAdapter anywhere. A nice feature of org.json is the ability to chain the calls together, so you can avoid misses like this. It also mirrors the structure of the resulting JSON, increasing readability. See below:
|
||
subAdapter.put("color1", "grey"); | ||
subAdapter.put("name", fileName); | ||
subAdapter.put("type", "BigWigAdapter"); | ||
JSONObject location = new JSONObject().put("locationType", "UriLocation"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm.. maybe this is handling the filepath -> relative URL comment I made earlier. Should maybe add comments to talk about what is returned for the "location" of this track? |
||
location.put("uri", String.join("/", userDatasetsFilePath, vdiId, fileName)); | ||
subAdapter.put("bigWigLocation", location); | ||
return track; | ||
} | ||
|
||
String stringFromCommand(List<String> command) throws IOException { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to wait for the process to complete. Also I think the output collection could be simpler. Something like this:
|
||
Process p = processFromCommand(command); | ||
BufferedReader reader = | ||
new BufferedReader(new InputStreamReader(p.getInputStream())); | ||
StringBuilder builder = new StringBuilder(); | ||
String line; | ||
while ( (line = reader.readLine()) != null) { | ||
builder.append(line); | ||
builder.append(System.lineSeparator()); | ||
} | ||
return builder.toString(); | ||
} | ||
|
||
Process processFromCommand (List<String> command) throws IOException { | ||
for (int i = 0; i < command.size(); i++) { | ||
if (command.get(i) == null) | ||
throw new WdkRuntimeException( | ||
"Command part at index " + i + " is null. Could be due to unchecked user input."); | ||
} | ||
ProcessBuilder pb = new ProcessBuilder(command); | ||
Map<String, String> env = pb.environment(); | ||
env.put("GUS_HOME", getWdkModel().getGusHome()); | ||
pb.redirectErrorStream(true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe better to not merge the streams and instead collect error output into a String and check if non-empty. Don't know the characteristics of the subprocess. I see you're not checking return value either, so how do you know if it bombed? Would be nice to log the subprocess output in the case of runtime error- Cristina will definitely ask for this later. |
||
return pb.start(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you use Javadoc here (two **s), our API may eventually pick it up. Doesn't now because we are using hard-coded RAML. :(