From f88bc236c6f584b204b80232c34366b1e88500ad Mon Sep 17 00:00:00 2001 From: Luke Sikina Date: Mon, 1 Jan 2024 10:50:35 -0500 Subject: [PATCH] [ALS-0000] Pass url params to proxy --- .../edu/harvard/dbmi/avillach/PicsureRS.java | 473 ++++++++---------- .../dbmi/avillach/service/ProxyWebClient.java | 27 +- .../avillach/service/ProxyWebClientTest.java | 54 +- 3 files changed, 240 insertions(+), 314 deletions(-) diff --git a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/PicsureRS.java b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/PicsureRS.java index e01da7cb..9eb6527e 100644 --- a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/PicsureRS.java +++ b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/PicsureRS.java @@ -5,11 +5,9 @@ import javax.inject.Inject; import javax.ws.rs.*; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.core.*; +import edu.harvard.dbmi.avillach.data.entity.Query; import edu.harvard.dbmi.avillach.domain.*; import edu.harvard.dbmi.avillach.service.*; import io.swagger.v3.oas.annotations.OpenAPIDefinition; @@ -26,280 +24,199 @@ @Produces("application/json") @Consumes("application/json") public class PicsureRS { - - @Inject - PicsureInfoService infoService; - - @Inject - PicsureSearchService searchService; - - @Inject - PicsureQueryService queryService; - @Inject - FormatService formatService; + @Inject + PicsureInfoService infoService; + + @Inject + PicsureSearchService searchService; + + @Inject + PicsureQueryService queryService; + + @Inject + FormatService formatService; + + @Inject + ProxyWebClient proxyWebClient; + + @POST + @Path("/info/{resourceId}") + @Operation( + summary = "Returns information about the provided resource", tags = {"info"}, operationId = "resourceInfo", + responses = {@ApiResponse( + responseCode = "200", description = "Resource information", + content = @Content(schema = @Schema(implementation = ResourceInfo.class)) + )} + ) + public ResourceInfo resourceInfo( + @Parameter(description = "The UUID of the resource to fetch information about") @PathParam("resourceId") String resourceId, + @Parameter QueryRequest credentialsQueryRequest, @Context HttpHeaders headers + ) { + System.out.println("Resource info requested for : " + resourceId); + return infoService.info(UUID.fromString(resourceId), credentialsQueryRequest, headers); + } + + @GET + @Path("/info/resources") + @Operation( + summary = "Returns list of resources available", + responses = {@ApiResponse( + responseCode = "200", description = "Resource information", content = @Content(schema = @Schema(implementation = Map.class)) + )} + ) + public Map resources(@Context HttpHeaders headers) { + return infoService.resources(headers); + } + + @GET + @Path("/search/{resourceId}/values/") + @Consumes("*/*") + public PaginatedSearchResult searchGenomicConceptValues( + @PathParam("resourceId") UUID resourceId, QueryRequest searchQueryRequest, + @QueryParam("genomicConceptPath") String genomicConceptPath, @QueryParam("query") String query, @QueryParam("page") Integer page, + @QueryParam("size") Integer size, @Context HttpHeaders headers + ) { + return searchService.searchGenomicConceptValues(resourceId, searchQueryRequest, genomicConceptPath, query, page, size, headers); + } + + @POST + @Path("/search/{resourceId}") + @Operation( + summary = "Searches for concept paths on the given resource matching the supplied search term", + responses = {@ApiResponse( + responseCode = "200", description = "Search results", content = @Content(schema = @Schema(implementation = SearchResults.class)) + )}, requestBody = @RequestBody(required = true, content = @Content(schema = @Schema(example = "{ \"query\": \"searchTerm\" }"))) + ) + public SearchResults search( + @Parameter(description = "The UUID of the resource to search") @PathParam("resourceId") UUID resourceId, + @Parameter(hidden = true) QueryRequest searchQueryRequest, @Context HttpHeaders headers + ) { + return searchService.search(resourceId, searchQueryRequest, headers); + } + + @POST + @Path("/query") + @Operation( + summary = "Submits a query to the given resource", + responses = {@ApiResponse( + responseCode = "200", description = "Query status", content = @Content(schema = @Schema(implementation = QueryStatus.class)) + )} + ) + public QueryStatus query( + @Parameter QueryRequest dataQueryRequest, + + @Context HttpHeaders headers, + + @Parameter @QueryParam("isInstitute") Boolean isInstitutionQuery, + + @Context SecurityContext context + ) { + if (isInstitutionQuery == null || !isInstitutionQuery) { + return queryService.query(dataQueryRequest, headers); + } else { + String email = context.getUserPrincipal().getName(); + return queryService.institutionalQuery((FederatedQueryRequest) dataQueryRequest, headers, email); + } + } + + @POST + @Path("/query/{queryId}/status") + @Operation( + summary = "Returns the status of the given query", + responses = {@ApiResponse( + responseCode = "200", description = "Query status", content = @Content(schema = @Schema(implementation = QueryStatus.class)) + )} + ) + public QueryStatus queryStatus( + @Parameter( + description = "The UUID of the query to fetch the status of. The UUID is returned by the /query " + + "endpoint as the \"picsureResultId\" in the response object" + ) @PathParam("queryId") UUID queryId, + + @Parameter QueryRequest credentialsQueryRequest, + + @Context HttpHeaders headers, + + @Parameter @QueryParam("isInstitute") Boolean isInstitutionQuery + ) { + if (credentialsQueryRequest instanceof GeneralQueryRequest) { + return queryService.queryStatus(queryId, (GeneralQueryRequest) credentialsQueryRequest, headers); + } else { + return queryService.institutionQueryStatus(queryId, (FederatedQueryRequest) credentialsQueryRequest, headers); + } + } + + @POST + @Path("/query/{queryId}/result") + @Operation( + summary = "Returns result for given query", + responses = {@ApiResponse( + responseCode = "200", description = "Query result", content = @Content(schema = @Schema(implementation = Response.class)) + )} + ) + public Response queryResult( + @Parameter( + description = "The UUID of the query to fetch the status of. The UUID is " + + "returned by the /query endpoint as the \"picsureResultId\" in the response object" + ) @PathParam("queryId") UUID queryId, @Parameter QueryRequest credentialsQueryRequest, @Context HttpHeaders headers + ) { + return queryService.queryResult(queryId, credentialsQueryRequest, headers); + } + + @POST + @Path("/query/sync") + @Operation( + summary = "Returns result for given query", + responses = {@ApiResponse( + responseCode = "200", description = "Query result", content = @Content(schema = @Schema(implementation = Response.class)) + )} + ) + public Response querySync( + @Context HttpHeaders headers, + @Parameter( + description = "Object with field named 'resourceCredentials' which is a key-value map, " + + "key is identifier for resource, value is token for resource" + ) QueryRequest credentialsQueryRequest + ) { + return queryService.querySync(credentialsQueryRequest, headers); + } + + @GET + @Path("/query/{queryId}/metadata") + @Operation( + summary = "Returns metadata for given query", + description = "Generally used to reconstruct a query that was previously submitted. The queryId is " + + "returned by the /query endpoint as the \"picsureResultId\" in the response object", + responses = {@ApiResponse( + responseCode = "200", description = "Query metadata", content = @Content(schema = @Schema(implementation = QueryStatus.class)) + )} + ) + public QueryStatus queryMetadata(@PathParam("queryId") UUID queryId, @Context HttpHeaders headers) { + return queryService.queryMetadata(queryId, headers); + } + + @POST + @Path("/bin/continuous") + public Response generateContinuousBin(QueryRequest continuousData, @Context HttpHeaders headers) { + return formatService.format(continuousData, headers); + } + + + @POST + @Path("/proxy/{container}/{request : .+}") + @Operation(hidden = true) + public Response postProxy( + @PathParam("container") String containerId, @PathParam("request") String request, @Context UriInfo uriInfo, String body + ) { + return proxyWebClient.postProxy(containerId, request, body, uriInfo.getQueryParameters()); + } + + @GET + @Path("/proxy/{container}/{request : .+}") + @Operation(hidden = true) + public Response getProxy(@PathParam("container") String containerId, @PathParam("request") String request, @Context UriInfo uriInfo) { + return proxyWebClient.getProxy(containerId, request, uriInfo.getQueryParameters()); + } - @Inject - ProxyWebClient proxyWebClient; - - @POST - @Path("/info/{resourceId}") - @Operation( - summary = "Returns information about the provided resource", - tags = { "info" }, - operationId = "resourceInfo", - responses = { @ApiResponse( - responseCode = "200", - description = "Resource information", - content = @Content( - schema = @Schema( - implementation = ResourceInfo.class - ) - ) - )} - ) - public ResourceInfo resourceInfo(@Parameter(description="The UUID of the resource to fetch information about") @PathParam("resourceId") String resourceId, - @Parameter QueryRequest credentialsQueryRequest, - @Context HttpHeaders headers) { - System.out.println("Resource info requested for : " + resourceId); - return infoService.info(UUID.fromString(resourceId), credentialsQueryRequest, headers); - } - - @GET - @Path("/info/resources") - @Operation( - summary = "Returns list of resources available", - responses = { - @ApiResponse( - responseCode = "200", - description = "Resource information", - content = @Content( - schema = @Schema( - implementation = Map.class - ) - ) - ) - } - ) - public Map resources(@Context HttpHeaders headers){ - return infoService.resources(headers); - } - - @GET - @Path("/search/{resourceId}/values/") - @Consumes("*/*") - public PaginatedSearchResult searchGenomicConceptValues( - @PathParam("resourceId") UUID resourceId, - QueryRequest searchQueryRequest, - @QueryParam("genomicConceptPath") String genomicConceptPath, - @QueryParam("query") String query, - @QueryParam("page") Integer page, - @QueryParam("size") Integer size, - @Context HttpHeaders headers - ) { - return searchService.searchGenomicConceptValues(resourceId, searchQueryRequest, genomicConceptPath, query, page, size, headers); - } - - @POST - @Path("/search/{resourceId}") - @Operation( - summary = "Searches for concept paths on the given resource matching the supplied search term", - responses = { - @ApiResponse( - responseCode = "200", - description = "Search results", - content = @Content( - schema = @Schema( - implementation = SearchResults.class - ) - ) - )}, - requestBody = @RequestBody( - required = true, - content = @Content( - schema = @Schema( - example = "{ \"query\": \"searchTerm\" }" - ) - ) - ) - ) - public SearchResults search(@Parameter(description="The UUID of the resource to search") @PathParam("resourceId") UUID resourceId, - @Parameter(hidden = true) QueryRequest searchQueryRequest, - @Context HttpHeaders headers) { - return searchService.search(resourceId, searchQueryRequest, headers); - } - - @POST - @Path("/query") - @Operation( - summary = "Submits a query to the given resource", - responses = { - @ApiResponse( - responseCode = "200", - description = "Query status", - content = @Content( - schema = @Schema( - implementation = QueryStatus.class - ) - ) - ) - } - ) - public QueryStatus query( - @Parameter - QueryRequest dataQueryRequest, - - @Context - HttpHeaders headers, - - @Parameter - @QueryParam("isInstitute") - Boolean isInstitutionQuery, - - @Context SecurityContext context - ) { - if (isInstitutionQuery == null || !isInstitutionQuery) { - return queryService.query(dataQueryRequest, headers); - } else { - String email = context.getUserPrincipal().getName(); - return queryService.institutionalQuery((FederatedQueryRequest) dataQueryRequest, headers, email); - } - } - - @POST - @Path("/query/{queryId}/status") - @Operation( - summary = "Returns the status of the given query", - responses = { - @ApiResponse( - responseCode = "200", - description = "Query status", - content = @Content( - schema = @Schema( - implementation = QueryStatus.class - ) - ) - ) - } - ) - public QueryStatus queryStatus( - @Parameter( - description="The UUID of the query to fetch the status of. The UUID is returned by the /query " + - "endpoint as the \"picsureResultId\" in the response object" - ) - @PathParam("queryId") - UUID queryId, - - @Parameter - QueryRequest credentialsQueryRequest, - - @Context - HttpHeaders headers, - - @Parameter - @QueryParam("isInstitute") - Boolean isInstitutionQuery - ) { - if (credentialsQueryRequest instanceof GeneralQueryRequest) { - return queryService.queryStatus(queryId, (GeneralQueryRequest) credentialsQueryRequest, headers); - } else { - return queryService.institutionQueryStatus(queryId, (FederatedQueryRequest) credentialsQueryRequest, headers); - } - } - - @POST - @Path("/query/{queryId}/result") - @Operation( - summary = "Returns result for given query", - responses = { - @ApiResponse( - responseCode = "200", - description = "Query result", - content = @Content( - schema = @Schema( - implementation = Response.class - ) - ) - ) - } - ) - public Response queryResult(@Parameter(description="The UUID of the query to fetch the status of. The UUID is " + - "returned by the /query endpoint as the \"picsureResultId\" in the response object") @PathParam("queryId") UUID queryId, - @Parameter QueryRequest credentialsQueryRequest, - @Context HttpHeaders headers) { - return queryService.queryResult(queryId, credentialsQueryRequest, headers); - } - - @POST - @Path("/query/sync") - @Operation( - summary = "Returns result for given query", - responses = { - @ApiResponse( - responseCode = "200", - description = "Query result", - content = @Content( - schema = @Schema( - implementation = Response.class - ) - ) - ) - } - ) - public Response querySync(@Context HttpHeaders headers, - @Parameter(description="Object with field named 'resourceCredentials' which is a key-value map, " + - "key is identifier for resource, value is token for resource") QueryRequest credentialsQueryRequest) { - return queryService.querySync(credentialsQueryRequest, headers); - } - - @GET - @Path("/query/{queryId}/metadata") - @Operation( - summary = "Returns metadata for given query", - description = "Generally used to reconstruct a query that was previously submitted. The queryId is " + - "returned by the /query endpoint as the \"picsureResultId\" in the response object", - responses = { - @ApiResponse( - responseCode = "200", - description = "Query metadata", - content = @Content( - schema = @Schema( - implementation = QueryStatus.class - ) - ) - ) - } - ) - public QueryStatus queryMetadata(@PathParam("queryId") UUID queryId, @Context HttpHeaders headers){ - return queryService.queryMetadata(queryId, headers); - } - - @POST - @Path("/bin/continuous") - public Response generateContinuousBin(QueryRequest continuousData, @Context HttpHeaders headers) { - return formatService.format(continuousData, headers); - } - - - @POST - @Path("/proxy/{container}/{request : .+}") - @Operation(hidden = true) - public Response postProxy( - @PathParam("container") String containerId, - @PathParam("request") String request, - String body - ) { - return proxyWebClient.postProxy(containerId, request, body); - } - - @GET - @Path("/proxy/{container}/{request : .+}") - @Operation(hidden = true) - public Response getProxy( - @PathParam("container") String containerId, - @PathParam("request") String request - ) { - return proxyWebClient.getProxy(containerId, request); - } - } diff --git a/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/service/ProxyWebClient.java b/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/service/ProxyWebClient.java index 6f0b30f2..6aba4ce8 100644 --- a/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/service/ProxyWebClient.java +++ b/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/service/ProxyWebClient.java @@ -3,22 +3,24 @@ import edu.harvard.dbmi.avillach.data.repository.ResourceRepository; import edu.harvard.dbmi.avillach.util.HttpClientUtil; import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.StringEntity; +import org.apache.http.message.BasicNameValuePair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; +import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.util.regex.Pattern; @ApplicationScoped public class ProxyWebClient { @@ -32,16 +34,13 @@ public ProxyWebClient() { client = HttpClientUtil.getConfiguredHttpClient(); } - public Response postProxy(String containerId, String path, String body) { + public Response postProxy(String containerId, String path, String body, MultivaluedMap queryParams) { if (containerIsNOTAResource(containerId)) { return Response.status(400, "container name not trustworthy").build(); } try { - URI uri = new URIBuilder() - .setScheme("http") - .setHost(containerId) - .setPath(path) - .build(); + URI uri = + new URIBuilder().setScheme("http").setHost(containerId).setPath(path).setParameters(processParams(queryParams)).build(); HttpPost request = new HttpPost(uri); request.setEntity(new StringEntity(body)); request.addHeader("Content-Type", "application/json"); @@ -54,16 +53,13 @@ public Response postProxy(String containerId, String path, String body) { } } - public Response getProxy(String containerId, String path) { + public Response getProxy(String containerId, String path, MultivaluedMap queryParams) { if (containerIsNOTAResource(containerId)) { return Response.status(400, "container name not trustworthy").build(); } try { - URI uri = new URIBuilder() - .setScheme("http") - .setHost(containerId) - .setPath(path) - .build(); + URI uri = + new URIBuilder().setScheme("http").setHost(containerId).setPath(path).setParameters(processParams(queryParams)).build(); HttpGet request = new HttpGet(uri); return getResponse(request); } catch (URISyntaxException e) { @@ -74,6 +70,11 @@ public Response getProxy(String containerId, String path) { } } + private NameValuePair[] processParams(MultivaluedMap params) { + return params.entrySet().stream().flatMap(e -> e.getValue().stream().map(v -> new BasicNameValuePair(e.getKey(), v))) + .toArray(NameValuePair[]::new); + } + private boolean containerIsNOTAResource(String container) { return resourceRepository.getByColumn("name", container).isEmpty(); } diff --git a/pic-sure-resources/pic-sure-resource-api/src/test/java/edu/harvard/dbmi/avillach/service/ProxyWebClientTest.java b/pic-sure-resources/pic-sure-resource-api/src/test/java/edu/harvard/dbmi/avillach/service/ProxyWebClientTest.java index d2852167..70305337 100644 --- a/pic-sure-resources/pic-sure-resource-api/src/test/java/edu/harvard/dbmi/avillach/service/ProxyWebClientTest.java +++ b/pic-sure-resources/pic-sure-resource-api/src/test/java/edu/harvard/dbmi/avillach/service/ProxyWebClientTest.java @@ -15,10 +15,12 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; +import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.Response; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.List; +import java.util.Map; import static org.junit.Assert.*; @@ -42,47 +44,53 @@ public class ProxyWebClientTest { @Test public void shouldPostToProxy() throws IOException { - Mockito.when(client.execute(Mockito.any(HttpPost.class))) - .thenReturn(response); - Mockito.when(response.getEntity()) - .thenReturn(entity); - Mockito.when(entity.getContent()) - .thenReturn(new ByteArrayInputStream("{}".getBytes())); - Mockito.when(resourceRepository.getByColumn("name", "foo")) - .thenReturn(List.of(new Resource())); + Mockito.when(client.execute(Mockito.any(HttpPost.class))).thenReturn(response); + Mockito.when(response.getEntity()).thenReturn(entity); + Mockito.when(entity.getContent()).thenReturn(new ByteArrayInputStream("{}".getBytes())); + Mockito.when(resourceRepository.getByColumn("name", "foo")).thenReturn(List.of(new Resource())); subject.client = client; - Response actual = subject.postProxy("foo", "/my/cool/path", "{}"); + Response actual = subject.postProxy("foo", "/my/cool/path", "{}", new MultivaluedHashMap<>()); Assert.assertEquals(200, actual.getStatus()); } @Test public void shouldGetToProxy() throws IOException { - Mockito.when(client.execute(Mockito.any(HttpGet.class))) - .thenReturn(response); - Mockito.when(response.getEntity()) - .thenReturn(entity); - Mockito.when(entity.getContent()) - .thenReturn(new ByteArrayInputStream("{}".getBytes())); - Mockito.when(resourceRepository.getByColumn("name", "bar")) - .thenReturn(List.of(new Resource())); + Mockito.when(client.execute(Mockito.any(HttpGet.class))).thenReturn(response); + Mockito.when(response.getEntity()).thenReturn(entity); + Mockito.when(entity.getContent()).thenReturn(new ByteArrayInputStream("{}".getBytes())); + Mockito.when(resourceRepository.getByColumn("name", "bar")).thenReturn(List.of(new Resource())); subject.client = client; - Response actual = subject.getProxy("bar", "/my/cool/path"); + Response actual = subject.getProxy("bar", "/my/cool/path", new MultivaluedHashMap<>()); Assert.assertEquals(200, actual.getStatus()); } @Test public void shouldRejectNastyHost() { - Mockito.when(resourceRepository.getByColumn("name", "an.evil.domain")) - .thenReturn(List.of()); + Mockito.when(resourceRepository.getByColumn("name", "an.evil.domain")).thenReturn(List.of()); - Response actual = subject.postProxy("an.evil.domain", "hax", null); + Response actual = subject.postProxy("an.evil.domain", "hax", null, new MultivaluedHashMap<>()); assertEquals(400, actual.getStatus()); - actual = subject.getProxy("an.evil.domain", "hax"); + actual = subject.getProxy("an.evil.domain", "hax", new MultivaluedHashMap<>()); assertEquals(400, actual.getStatus()); } -} \ No newline at end of file + + @Test + public void shouldPostWithParams() throws IOException { + Mockito.when(client.execute(Mockito.any(HttpPost.class))).thenReturn(response); + Mockito.when(response.getEntity()).thenReturn(entity); + Mockito.when(entity.getContent()).thenReturn(new ByteArrayInputStream("{}".getBytes())); + Mockito.when(resourceRepository.getByColumn("name", "foo")).thenReturn(List.of(new Resource())); + subject.client = client; + + MultivaluedHashMap params = new MultivaluedHashMap<>(); + params.put("site", List.of("bch")); + Response actual = subject.postProxy("foo", "/my/cool/path", "{}", params); + + Assert.assertEquals(200, actual.getStatus()); + } +}