From fa86a0d31b8824e244ac3acd8ffa3ffa9f7ded4f Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 7 Mar 2024 16:02:43 -0500 Subject: [PATCH] Update HttpHeaders management in ResourceWebClient Enhancements were made to the management of HttpHeaders in the ResourceWebClient class. The 'search' method in the PicsureSearchService now sends headers to the 'search' method in ResourceWebClient. This modification provides better handling of headers in requests, thereby facilitating more efficient communication with HTTP resources. --- .../service/PicsureSearchService.java | 111 +++-- .../avillach/PicsureSearchServiceTest.java | 40 +- .../avillach/service/ResourceWebClient.java | 210 ++++++---- .../service/ResourceWebClientTest.java | 392 ++++++++---------- 4 files changed, 381 insertions(+), 372 deletions(-) diff --git a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/PicsureSearchService.java b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/PicsureSearchService.java index dad2d9dd..71bf2753 100644 --- a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/PicsureSearchService.java +++ b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/PicsureSearchService.java @@ -20,70 +20,69 @@ public class PicsureSearchService { - private final Logger logger = LoggerFactory.getLogger(PicsureSearchService.class); + private final Logger logger = LoggerFactory.getLogger(PicsureSearchService.class); - private final static ObjectMapper mapper = new ObjectMapper(); + private final static ObjectMapper mapper = new ObjectMapper(); - @Inject - ResourceRepository resourceRepo; + @Inject + ResourceRepository resourceRepo; - @Inject - ResourceWebClient resourceWebClient; + @Inject + ResourceWebClient resourceWebClient; - /** - * Executes a concept search against a target resource - * - * @param resourceId - UUID of target resource - * @param searchQueryRequest - {@link QueryRequest} containing resource specific credentials object - * and resource specific query (could be a string or a json object) - * @param headers - * @return {@link SearchResults} - */ - public SearchResults search(UUID resourceId, QueryRequest searchQueryRequest, HttpHeaders headers) { - if (resourceId == null){ - throw new ProtocolException(ProtocolException.MISSING_RESOURCE_ID); - } - Resource resource = resourceRepo.getById(resourceId); - if (resource == null) { - throw new ProtocolException(ProtocolException.RESOURCE_NOT_FOUND + resourceId.toString()); - } - if (resource.getResourceRSPath() == null){ - throw new ApplicationException(ApplicationException.MISSING_RESOURCE_PATH); - } - if (searchQueryRequest == null){ - throw new ProtocolException(ProtocolException.MISSING_DATA); - } + /** + * Executes a concept search against a target resource + * + * @param resourceId - UUID of target resource + * @param searchQueryRequest - {@link QueryRequest} containing resource specific credentials object and resource specific query (could + * be a string or a json object) + * @param headers + * @return {@link SearchResults} + */ + public SearchResults search(UUID resourceId, QueryRequest searchQueryRequest, HttpHeaders headers) { + if (resourceId == null) { + throw new ProtocolException(ProtocolException.MISSING_RESOURCE_ID); + } + Resource resource = resourceRepo.getById(resourceId); + if (resource == null) { + throw new ProtocolException(ProtocolException.RESOURCE_NOT_FOUND + resourceId.toString()); + } + if (resource.getResourceRSPath() == null) { + throw new ApplicationException(ApplicationException.MISSING_RESOURCE_PATH); + } + if (searchQueryRequest == null) { + throw new ProtocolException(ProtocolException.MISSING_DATA); + } - logger.info("path=/search/{resourceId}, resourceId={}, requestSource={}, searchQueryRequest={}", - resourceId, - Utilities.getRequestSourceFromHeader(headers), - Utilities.convertQueryRequestToString(mapper, searchQueryRequest) - ); + logger.info( + "path=/search/{resourceId}, resourceId={}, requestSource={}, searchQueryRequest={}", resourceId, + Utilities.getRequestSourceFromHeader(headers), Utilities.convertQueryRequestToString(mapper, searchQueryRequest) + ); - if (searchQueryRequest.getResourceCredentials() == null){ - searchQueryRequest.setResourceCredentials(new HashMap()); - } - return resourceWebClient.search(resource.getResourceRSPath(), searchQueryRequest); - } + if (searchQueryRequest.getResourceCredentials() == null) { + searchQueryRequest.setResourceCredentials(new HashMap()); + } + return resourceWebClient.search(resource.getResourceRSPath(), searchQueryRequest, headers); + } - public PaginatedSearchResult searchGenomicConceptValues(UUID resourceId, QueryRequest queryRequest, String conceptPath, String query, Integer page, Integer size - , HttpHeaders headers) { - Resource resource = resourceRepo.getById(resourceId); - if (resource == null){ - throw new ProtocolException(ProtocolException.RESOURCE_NOT_FOUND + resourceId.toString()); - } - if (resource.getResourceRSPath() == null){ - throw new ApplicationException(ApplicationException.MISSING_RESOURCE_PATH); - } + public PaginatedSearchResult searchGenomicConceptValues( + UUID resourceId, QueryRequest queryRequest, String conceptPath, String query, Integer page, Integer size, HttpHeaders headers + ) { + Resource resource = resourceRepo.getById(resourceId); + if (resource == null) { + throw new ProtocolException(ProtocolException.RESOURCE_NOT_FOUND + resourceId.toString()); + } + if (resource.getResourceRSPath() == null) { + throw new ApplicationException(ApplicationException.MISSING_RESOURCE_PATH); + } - logger.info("path=/search/{resourceId}/concept/{conceptPath}, resourceId={}, requestSource={}, queryRequest={}, conceptPath={}, query={}", - resourceId, - Utilities.getRequestSourceFromHeader(headers), - Utilities.convertQueryRequestToString(mapper, queryRequest), - conceptPath, - query); + logger.info( + "path=/search/{resourceId}/concept/{conceptPath}, resourceId={}, requestSource={}, queryRequest={}, conceptPath={}, query={}", + resourceId, Utilities.getRequestSourceFromHeader(headers), Utilities.convertQueryRequestToString(mapper, queryRequest), + conceptPath, query + ); - return resourceWebClient.searchConceptValues(resource.getResourceRSPath(), queryRequest, conceptPath, query, page, size); - } + return resourceWebClient.searchConceptValues(resource.getResourceRSPath(), queryRequest, conceptPath, query, page, size); + } } diff --git a/pic-sure-api-war/src/test/java/edu/harvard/dbmi/avillach/PicsureSearchServiceTest.java b/pic-sure-api-war/src/test/java/edu/harvard/dbmi/avillach/PicsureSearchServiceTest.java index 0abebaa1..e28388ce 100644 --- a/pic-sure-api-war/src/test/java/edu/harvard/dbmi/avillach/PicsureSearchServiceTest.java +++ b/pic-sure-api-war/src/test/java/edu/harvard/dbmi/avillach/PicsureSearchServiceTest.java @@ -53,7 +53,7 @@ public void setUp() { SearchResults results = new SearchResults(); when(resourceRepo.getById(resourceId)).thenReturn(mockResource); when(resourceRepo.getById(not(ArgumentMatchers.same(resourceId)))).thenReturn(null); - when(webClient.search(any(), any())).thenReturn(results); + when(webClient.search(any(), any(), any())).thenReturn(results); } @Test @@ -67,46 +67,58 @@ public void testSearch() { try { SearchResults results = searchService.search(resourceId, searchQueryRequest, null); fail("Missing request data should throw an error"); - } catch (ApplicationException e){ + } catch (ApplicationException e) { assertNotNull(e.getContent()); - assertEquals("Error message should say '" + ApplicationException.MISSING_RESOURCE_PATH + "'", ApplicationException.MISSING_RESOURCE_PATH, e.getContent().toString()); + assertEquals( + "Error message should say '" + ApplicationException.MISSING_RESOURCE_PATH + "'", ApplicationException.MISSING_RESOURCE_PATH, + e.getContent().toString() + ); } when(mockResource.getResourceRSPath()).thenReturn("resourceRsPath"); - //Missing requestdata should throw an error + // Missing requestdata should throw an error try { SearchResults results = searchService.search(resourceId, null, null); fail("Missing request data should throw an error"); - } catch (ProtocolException e){ + } catch (ProtocolException e) { assertNotNull(e.getContent()); - assertEquals("Error message should say '" + ProtocolException.MISSING_DATA + "'", ProtocolException.MISSING_DATA, e.getContent().toString()); + assertEquals( + "Error message should say '" + ProtocolException.MISSING_DATA + "'", ProtocolException.MISSING_DATA, + e.getContent().toString() + ); } - //Missing resourceId should error + // Missing resourceId should error try { SearchResults results = searchService.search(null, searchQueryRequest, null); fail("Missing resourceId should throw an error"); - } catch (ProtocolException e){ + } catch (ProtocolException e) { assertNotNull(e.getContent()); - assertEquals("Error message should say '" + ProtocolException.MISSING_RESOURCE_ID + "'", ProtocolException.MISSING_RESOURCE_ID, e.getContent().toString()); + assertEquals( + "Error message should say '" + ProtocolException.MISSING_RESOURCE_ID + "'", ProtocolException.MISSING_RESOURCE_ID, + e.getContent().toString() + ); } - //Nonexistent resourceId should error + // Nonexistent resourceId should error try { SearchResults results = searchService.search(UUID.randomUUID(), searchQueryRequest, null); fail("Nonexistent resourceId should throw an error"); - } catch (ProtocolException e){ + } catch (ProtocolException e) { assertNotNull(e.getContent()); - assertTrue("Error message should say '" + ProtocolException.RESOURCE_NOT_FOUND + "'", e.getContent().toString().contains(ProtocolException.RESOURCE_NOT_FOUND)); + assertTrue( + "Error message should say '" + ProtocolException.RESOURCE_NOT_FOUND + "'", + e.getContent().toString().contains(ProtocolException.RESOURCE_NOT_FOUND) + ); } - //This should work + // This should work SearchResults results = searchService.search(resourceId, searchQueryRequest, null); assertNotNull("SearchResults should not be null", results); - //There should also be no problem if the resourceCredentials are null + // There should also be no problem if the resourceCredentials are null searchQueryRequest.setResourceCredentials(null); results = searchService.search(resourceId, searchQueryRequest, null); assertNotNull("SearchResults should not be null", results); diff --git a/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/service/ResourceWebClient.java b/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/service/ResourceWebClient.java index 211cf239..9b75e8d5 100644 --- a/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/service/ResourceWebClient.java +++ b/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/service/ResourceWebClient.java @@ -20,17 +20,16 @@ import javax.ws.rs.core.Response; import java.io.IOException; import java.net.URISyntaxException; +import java.util.List; import java.util.Map; import static edu.harvard.dbmi.avillach.util.HttpClientUtil.*; /** - * The ResourceWebClient class implements the client side logic for the endpoints specified in IResourceRS. - - The PicsureInfoService, PicsureQueryService and PicsureSearchService would then use this class - to serve calls from their methods to each configured Resource target url - after looking up the target url from the ResourceRepository. + * The ResourceWebClient class implements the client side logic for the endpoints specified in IResourceRS.

The PicsureInfoService, + * PicsureQueryService and PicsureSearchService would then use this class to serve calls from their methods to each configured Resource + * target url after looking up the target url from the ResourceRepository. */ @ApplicationScoped public class ResourceWebClient { @@ -40,36 +39,39 @@ public class ResourceWebClient { public static final String BEARER_STRING = "Bearer "; public static final String BEARER_TOKEN_KEY = "BEARER_TOKEN"; public static final String QUERY_METADATA_FIELD = "queryMetadata"; - - public ResourceWebClient() { } - public ResourceInfo info(String rsURL, QueryRequest queryRequest){ + public ResourceWebClient() {} + + public ResourceInfo info(String rsURL, QueryRequest queryRequest) { logger.debug("Calling ResourceWebClient info()"); try { - if (queryRequest == null){ + if (queryRequest == null) { throw new ProtocolException(ProtocolException.MISSING_DATA); } - if (queryRequest.getResourceCredentials() == null){ + if (queryRequest.getResourceCredentials() == null) { throw new NotAuthorizedException(NotAuthorizedException.MISSING_CREDENTIALS); } - if (rsURL == null){ + if (rsURL == null) { throw new ApplicationException(ApplicationException.MISSING_RESOURCE_PATH); } logger.debug("Calling /info at ResourceURL: {}", rsURL); String pathName = "/info"; String body = json.writeValueAsString(queryRequest); - HttpResponse resourcesResponse = retrievePostResponse(composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); + HttpResponse resourcesResponse = + retrievePostResponse(composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); if (resourcesResponse.getStatusLine().getStatusCode() != 200) { logger.error("ResourceRS did not return a 200"); throwResponseError(resourcesResponse, rsURL); } return readObjectFromResponse(resourcesResponse, ResourceInfo.class); - } catch (JsonProcessingException e){ + } catch (JsonProcessingException e) { throw new NotAuthorizedException("Unable to encode resource credentials", e); } } - public PaginatedSearchResult searchConceptValues(String rsURL, QueryRequest queryRequest, String conceptPath, String query, Integer page, Integer size) { + public PaginatedSearchResult searchConceptValues( + String rsURL, QueryRequest queryRequest, String conceptPath, String query, Integer page, Integer size + ) { try { logger.debug("Calling /search/values at ResourceURL: {}"); URIBuilder uriBuilder = new URIBuilder(rsURL); @@ -95,168 +97,174 @@ public PaginatedSearchResult searchConceptValues(String rsURL, QueryRequest q } } - public SearchResults search(String rsURL, QueryRequest searchQueryRequest){ + public SearchResults search(String rsURL, QueryRequest searchQueryRequest, HttpHeaders headers) { logger.debug("Calling ResourceWebClient search()"); try { - if (searchQueryRequest == null || searchQueryRequest.getQuery() == null){ + if (searchQueryRequest == null || searchQueryRequest.getQuery() == null) { throw new ProtocolException(ProtocolException.MISSING_DATA); } - if (rsURL == null){ + if (rsURL == null) { throw new ApplicationException(ApplicationException.MISSING_RESOURCE_PATH); } - if (searchQueryRequest.getResourceCredentials() == null){ + if (searchQueryRequest.getResourceCredentials() == null) { throw new NotAuthorizedException(NotAuthorizedException.MISSING_CREDENTIALS); } String pathName = "/search"; String body = json.writeValueAsString(searchQueryRequest); - HttpResponse resourcesResponse = retrievePostResponse(composeURL(rsURL, pathName), createHeaders(searchQueryRequest.getResourceCredentials()), body); + HttpResponse resourcesResponse = retrievePostResponse( + composeURL(rsURL, pathName), createHeaders(searchQueryRequest.getResourceCredentials(), headers), body + ); if (resourcesResponse.getStatusLine().getStatusCode() != 200) { logger.error("ResourceRS did not return a 200"); throwResponseError(resourcesResponse, rsURL); } return readObjectFromResponse(resourcesResponse, SearchResults.class); - } catch (JsonProcessingException e){ + } catch (JsonProcessingException e) { logger.error("Unable to serialize search query"); - //TODO Write custom exception + // TODO Write custom exception throw new ProtocolException("Unable to serialize search query", e); } } - public QueryStatus query(String rsURL, QueryRequest dataQueryRequest){ + public QueryStatus query(String rsURL, QueryRequest dataQueryRequest) { logger.debug("Calling ResourceWebClient query()"); try { - if (rsURL == null){ + if (rsURL == null) { throw new ApplicationException(ApplicationException.MISSING_RESOURCE_PATH); } - if (dataQueryRequest == null){ + if (dataQueryRequest == null) { throw new ProtocolException(ProtocolException.MISSING_DATA); } - if (dataQueryRequest.getResourceCredentials() == null){ + if (dataQueryRequest.getResourceCredentials() == null) { throw new NotAuthorizedException("Missing credentials"); } String pathName = "/query"; String body = json.writeValueAsString(dataQueryRequest); - HttpResponse resourcesResponse = retrievePostResponse(composeURL(rsURL, pathName), createHeaders(dataQueryRequest.getResourceCredentials()), body); + HttpResponse resourcesResponse = + retrievePostResponse(composeURL(rsURL, pathName), createHeaders(dataQueryRequest.getResourceCredentials()), body); if (resourcesResponse.getStatusLine().getStatusCode() != 200) { logger.error("ResourceRS did not return a 200"); throwResponseError(resourcesResponse, rsURL); } return readObjectFromResponse(resourcesResponse, QueryStatus.class); - } catch (JsonProcessingException e){ + } catch (JsonProcessingException e) { logger.error("Unable to encode data query"); throw new ProtocolException("Unable to encode data query", e); } } - public QueryStatus queryStatus(String rsURL, String queryId, QueryRequest queryRequest){ + public QueryStatus queryStatus(String rsURL, String queryId, QueryRequest queryRequest) { logger.debug("Calling ResourceWebClient query()"); try { - if (queryRequest == null){ + if (queryRequest == null) { throw new ProtocolException(ProtocolException.MISSING_DATA); } - if (queryRequest.getResourceCredentials() == null){ + if (queryRequest.getResourceCredentials() == null) { throw new NotAuthorizedException("Missing credentials"); } - if (rsURL == null){ + if (rsURL == null) { throw new ApplicationException(ApplicationException.MISSING_RESOURCE_PATH); } - if (queryId == null){ + if (queryId == null) { throw new ProtocolException("Missing query id"); } String pathName = "/query/" + queryId + "/status"; String body = json.writeValueAsString(queryRequest); logger.debug(composeURL(rsURL, pathName)); logger.debug(body); - HttpResponse resourcesResponse = retrievePostResponse(composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); + HttpResponse resourcesResponse = + retrievePostResponse(composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); if (resourcesResponse.getStatusLine().getStatusCode() != 200) { logger.error("ResourceRS did not return a 200"); throwResponseError(resourcesResponse, rsURL); } return readObjectFromResponse(resourcesResponse, QueryStatus.class); - } catch (JsonProcessingException e){ + } catch (JsonProcessingException e) { logger.error("Unable to encode resource credentials"); throw new ProtocolException("Unable to encode resource credentials", e); } } - public Response queryResult(String rsURL, String queryId, QueryRequest queryRequest){ + public Response queryResult(String rsURL, String queryId, QueryRequest queryRequest) { logger.debug("Calling ResourceWebClient query()"); try { - if (queryRequest == null){ + if (queryRequest == null) { throw new ProtocolException(ProtocolException.MISSING_DATA); } - if (queryRequest.getResourceCredentials() == null){ + if (queryRequest.getResourceCredentials() == null) { throw new NotAuthorizedException("Missing credentials"); } - if (rsURL == null){ + if (rsURL == null) { throw new ApplicationException(ApplicationException.MISSING_RESOURCE_PATH); } - if (queryId == null){ + if (queryId == null) { throw new ProtocolException(ProtocolException.MISSING_QUERY_ID); } String pathName = "/query/" + queryId + "/result"; String body = json.writeValueAsString(queryRequest); - HttpResponse resourcesResponse = retrievePostResponse(composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); + HttpResponse resourcesResponse = + retrievePostResponse(composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); if (resourcesResponse.getStatusLine().getStatusCode() != 200) { logger.error("ResourceRS did not return a 200"); throwResponseError(resourcesResponse, rsURL); } return Response.ok(resourcesResponse.getEntity().getContent()).build(); - } catch (JsonProcessingException e){ + } catch (JsonProcessingException e) { logger.error("Unable to encode resource credentials"); throw new NotAuthorizedException("Unable to encode resource credentials", e); - } catch (IOException e){ + } catch (IOException e) { throw new ResourceInterfaceException("Error getting results", e); } } - - public Response queryFormat(String rsURL, QueryRequest queryRequest){ + + public Response queryFormat(String rsURL, QueryRequest queryRequest) { logger.debug("Calling ResourceWebClient queryFormat()"); try { - if (queryRequest == null){ + if (queryRequest == null) { throw new ProtocolException(ProtocolException.MISSING_DATA); } - if (queryRequest.getResourceCredentials() == null){ + if (queryRequest.getResourceCredentials() == null) { throw new NotAuthorizedException("Missing credentials"); } - if (rsURL == null){ + if (rsURL == null) { throw new ApplicationException(ApplicationException.MISSING_RESOURCE_PATH); } String pathName = "/query/format"; String body = json.writeValueAsString(queryRequest); - HttpResponse resourcesResponse = retrievePostResponse(composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); + HttpResponse resourcesResponse = + retrievePostResponse(composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); int status = resourcesResponse.getStatusLine().getStatusCode(); if (status != 200) { logger.error("Query format request did not return a 200: " + resourcesResponse.getStatusLine().getStatusCode()); return Response.status(status).entity(resourcesResponse.getEntity().getContent()).build(); } return Response.ok(resourcesResponse.getEntity().getContent()).build(); - } catch (JsonProcessingException e){ + } catch (JsonProcessingException e) { logger.error("Unable to encode resource credentials"); throw new NotAuthorizedException("Unable to encode resource credentials", e); - } catch (IOException e){ + } catch (IOException e) { throw new ResourceInterfaceException("Error getting results", e); } } - public Response querySync(String rsURL, QueryRequest queryRequest, String requestSource) { - logger.debug("Calling ResourceWebClient querySync()"); - try { - if (queryRequest == null) { - throw new ProtocolException("Missing query data"); - } - if (queryRequest.getResourceCredentials() == null) { - throw new NotAuthorizedException("Missing credentials"); - } - if (rsURL == null) { - throw new ApplicationException("Missing resource URL"); - } + public Response querySync(String rsURL, QueryRequest queryRequest, String requestSource) { + logger.debug("Calling ResourceWebClient querySync()"); + try { + if (queryRequest == null) { + throw new ProtocolException("Missing query data"); + } + if (queryRequest.getResourceCredentials() == null) { + throw new NotAuthorizedException("Missing credentials"); + } + if (rsURL == null) { + throw new ApplicationException("Missing resource URL"); + } - String pathName = "/query/sync"; - String body = json.writeValueAsString(queryRequest); + String pathName = "/query/sync"; + String body = json.writeValueAsString(queryRequest); Header[] headers = createHeaders(queryRequest.getResourceCredentials()); @@ -271,27 +279,27 @@ public Response querySync(String rsURL, QueryRequest queryRequest, String reques } HttpResponse resourcesResponse = retrievePostResponse(composeURL(rsURL, pathName), headers, body); - if (resourcesResponse.getStatusLine().getStatusCode() != 200) { - throwError(resourcesResponse, rsURL); - } - - if (resourcesResponse.containsHeader(QUERY_METADATA_FIELD)) { - Header metadataHeader = ((Header[]) resourcesResponse.getHeaders(QUERY_METADATA_FIELD))[0]; - return Response.ok(resourcesResponse.getEntity().getContent()) - .header(QUERY_METADATA_FIELD, metadataHeader.getValue()).build(); - } - return Response.ok(resourcesResponse.getEntity().getContent()).build(); - } catch (JsonProcessingException e) { - logger.error("Unable to encode resource credentials"); - throw new NotAuthorizedException("Unable to encode resource credentials", e); - } catch (IOException e) { - throw new ResourceInterfaceException("Error getting results", e); - } - } + if (resourcesResponse.getStatusLine().getStatusCode() != 200) { + throwError(resourcesResponse, rsURL); + } + + if (resourcesResponse.containsHeader(QUERY_METADATA_FIELD)) { + Header metadataHeader = ((Header[]) resourcesResponse.getHeaders(QUERY_METADATA_FIELD))[0]; + return Response.ok(resourcesResponse.getEntity().getContent()).header(QUERY_METADATA_FIELD, metadataHeader.getValue()) + .build(); + } + return Response.ok(resourcesResponse.getEntity().getContent()).build(); + } catch (JsonProcessingException e) { + logger.error("Unable to encode resource credentials"); + throw new NotAuthorizedException("Unable to encode resource credentials", e); + } catch (IOException e) { + throw new ResourceInterfaceException("Error getting results", e); + } + } /** - * This method is used to call the /bin/continuous endpoint on the ResourceRS. The /bin/continuous endpoint is used - * to retrieve binned continuous data from the visualization resource. + * This method is used to call the /bin/continuous endpoint on the ResourceRS. The /bin/continuous endpoint is used to retrieve binned + * continuous data from the visualization resource. * * @param rsURL The URL of the ResourceRS * @param queryRequest The query request object @@ -331,7 +339,7 @@ public Response queryContinuous(String rsURL, QueryRequest queryRequest, String throwError(resourcesResponse, rsURL); } - return Response.ok(resourcesResponse.getEntity().getContent()).build(); + return Response.ok(resourcesResponse.getEntity().getContent()).build(); } catch (JsonProcessingException e) { throw new RuntimeException(e); } catch (IOException e) { @@ -339,15 +347,15 @@ public Response queryContinuous(String rsURL, QueryRequest queryRequest, String } } - private void throwError(HttpResponse response, String baseURL){ + private void throwError(HttpResponse response, String baseURL) { logger.error("ResourceRS did not return a 200"); String errorMessage = baseURL + " " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase(); try { JsonNode responseNode = json.readTree(response.getEntity().getContent()); - if (responseNode != null && responseNode.has("message")){ + if (responseNode != null && responseNode.has("message")) { errorMessage += "/n" + responseNode.get("message").asText(); } - } catch (IOException e ){ + } catch (IOException e) { } if (response.getStatusLine().getStatusCode() == 401) { throw new NotAuthorizedException(errorMessage); @@ -356,11 +364,37 @@ private void throwError(HttpResponse response, String baseURL){ } - private Header[] createHeaders(Map resourceCredentials){ + private Header[] createHeaders(Map resourceCredentials) { Header authorizationHeader = new BasicHeader(HttpHeaders.AUTHORIZATION, BEARER_STRING + resourceCredentials.get(BEARER_TOKEN_KEY)); Header contentTypeHeader = new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json"); Header[] headers = {authorizationHeader, contentTypeHeader}; return headers; } + private Header[] createHeaders(Map resourceCredentials, HttpHeaders headers) { + Header[] headersArray = createHeaders(resourceCredentials); + + if (headers == null) { + return headersArray; + } + + // get the count of additional headers + int additionalHeadersCount = headers.getRequestHeaders().size(); + // create a new array with the size of the original array plus the count of additional headers + Header[] newHeadersArray = new Header[headersArray.length + additionalHeadersCount]; + // copy the original headers into the new array + System.arraycopy(headersArray, 0, newHeadersArray, 0, headersArray.length); + // iterate over the additional headers and add them to the new array + int i = headersArray.length; + + for (Map.Entry> entry : headers.getRequestHeaders().entrySet()) { + for (String value : entry.getValue()) { + newHeadersArray[i] = new BasicHeader(entry.getKey(), value); + i++; + } + } + + return newHeadersArray; + } + } diff --git a/pic-sure-resources/pic-sure-resource-api/src/test/java/edu/harvard/dbmi/avillach/service/ResourceWebClientTest.java b/pic-sure-resources/pic-sure-resource-api/src/test/java/edu/harvard/dbmi/avillach/service/ResourceWebClientTest.java index bac8e462..8460554c 100644 --- a/pic-sure-resources/pic-sure-resource-api/src/test/java/edu/harvard/dbmi/avillach/service/ResourceWebClientTest.java +++ b/pic-sure-resources/pic-sure-resource-api/src/test/java/edu/harvard/dbmi/avillach/service/ResourceWebClientTest.java @@ -33,7 +33,7 @@ public class ResourceWebClientTest { private final static ObjectMapper json = new ObjectMapper(); private final static String token = "testToken"; private final static int port = 8079; - private final static String testURL = "http://localhost:"+port; + private final static String testURL = "http://localhost:" + port; private final ResourceWebClient cut = new ResourceWebClient(); @Rule @@ -42,23 +42,20 @@ public class ResourceWebClientTest { @BeforeClass public static void beforeClass() { - //Need to be able to throw exceptions without container so we can verify correct errors are being thrown + // Need to be able to throw exceptions without container so we can verify correct errors are being thrown RuntimeDelegate runtimeDelegate = new RuntimeDelegateImpl(); RuntimeDelegate.setInstance(runtimeDelegate); } @Test - public void testInfo() throws JsonProcessingException{ + public void testInfo() throws JsonProcessingException { String resourceInfo = json.writeValueAsString(new ResourceInfo()); - wireMockRule.stubFor(any(urlEqualTo("/info")) - .willReturn(aResponse() - .withStatus(200) - .withBody(resourceInfo))); + wireMockRule.stubFor(any(urlEqualTo("/info")).willReturn(aResponse().withStatus(200).withBody(resourceInfo))); - //Any targetURL that matches /info will trigger wiremock + // Any targetURL that matches /info will trigger wiremock String targetURL = "/info"; - //Should throw an error if any parameters are missing + // Should throw an error if any parameters are missing try { cut.info(testURL, null); fail(); @@ -69,8 +66,8 @@ public void testInfo() throws JsonProcessingException{ Map credentials = new HashMap<>(); credentials.put(ResourceWebClient.BEARER_TOKEN_KEY, token); queryRequest.setResourceCredentials(credentials); -// queryRequest.setTargetURL(targetURL); - //Obviously should fail without the rsURL + // queryRequest.setTargetURL(targetURL); + // Obviously should fail without the rsURL try { cut.info(null, queryRequest); fail(); @@ -78,60 +75,52 @@ public void testInfo() throws JsonProcessingException{ assertEquals(ApplicationException.MISSING_RESOURCE_PATH, e.getContent()); } - //Should fail without a targetURL + // Should fail without a targetURL -// queryRequest.setTargetURL(null); -// try { -// cut.info(testURL, queryRequest); -// fail(); -// } catch (ApplicationException e) { -// assertEquals(ApplicationException.MISSING_TARGET_URL, e.getContent()); -// } + // queryRequest.setTargetURL(null); + // try { + // cut.info(testURL, queryRequest); + // fail(); + // } catch (ApplicationException e) { + // assertEquals(ApplicationException.MISSING_TARGET_URL, e.getContent()); + // } - //Assuming everything goes right -// queryRequest.setTargetURL(targetURL); + // Assuming everything goes right + // queryRequest.setTargetURL(targetURL); ResourceInfo result = cut.info(testURL, queryRequest); assertNotNull("Result should not be null", result); - //What if the resource has a problem? - wireMockRule.stubFor(any(urlEqualTo("/info")) - .willReturn(aResponse() - .withStatus(500))); + // What if the resource has a problem? + wireMockRule.stubFor(any(urlEqualTo("/info")).willReturn(aResponse().withStatus(500))); try { cut.info(testURL, queryRequest); fail(); } catch (Exception e) { - assertTrue( e.getMessage().contains("500 Server Error")); + assertTrue(e.getMessage().contains("500 Server Error")); } - //What if resource returns the wrong type of object for some reason? + // What if resource returns the wrong type of object for some reason? String incorrectResponse = json.writeValueAsString(new SearchResults()); - wireMockRule.stubFor(any(urlEqualTo("/info")) - .willReturn(aResponse() - .withStatus(200) - .withBody(incorrectResponse))); + wireMockRule.stubFor(any(urlEqualTo("/info")).willReturn(aResponse().withStatus(200).withBody(incorrectResponse))); try { cut.info(testURL, queryRequest); fail(); } catch (Exception e) { - assertTrue( e.getMessage().contains("Incorrect object type returned")); + assertTrue(e.getMessage().contains("Incorrect object type returned")); } } @Test - public void testSearch() throws JsonProcessingException{ + public void testSearch() throws JsonProcessingException { String searchResults = json.writeValueAsString(new SearchResults()); - wireMockRule.stubFor(any(urlEqualTo("/search")) - .willReturn(aResponse() - .withStatus(200) - .withBody(searchResults))); + wireMockRule.stubFor(any(urlEqualTo("/search")).willReturn(aResponse().withStatus(200).withBody(searchResults))); - //Should throw an error if any parameters are missing + // Should throw an error if any parameters are missing try { - cut.search(testURL, null); + cut.search(testURL, null, null); fail(); } catch (ProtocolException e) { assertEquals(ProtocolException.MISSING_DATA, e.getContent()); @@ -139,7 +128,7 @@ public void testSearch() throws JsonProcessingException{ GeneralQueryRequest request = new GeneralQueryRequest(); try { - cut.search(testURL, request); + cut.search(testURL, request, null); fail(); } catch (ProtocolException e) { assertEquals(ProtocolException.MISSING_DATA, e.getContent()); @@ -147,101 +136,90 @@ public void testSearch() throws JsonProcessingException{ request.setQuery("query"); -// try { -// cut.search(testURL, request); -// fail(); -// } catch (ApplicationException e) { -// assertEquals(ApplicationException.MISSING_TARGET_URL, e.getContent()); -// } + // try { + // cut.search(testURL, request); + // fail(); + // } catch (ApplicationException e) { + // assertEquals(ApplicationException.MISSING_TARGET_URL, e.getContent()); + // } String targetURL = "/search"; -// request.setTargetURL(targetURL); + // request.setTargetURL(targetURL); try { - cut.search(null, request); + cut.search(null, request, null); fail(); } catch (ApplicationException e) { assertEquals(ApplicationException.MISSING_RESOURCE_PATH, e.getContent()); } -// //Should fail if no credentials given -// request.setQuery("query"); -// request.setTargetURL(targetURL); -// try { -// cut.search(testURL, request); -// fail(); -// } catch (Exception e) { -// assertEquals("HTTP 401 Unauthorized", e.getMessage()); -// } - - //With credentials but not search term - /* Map credentials = new HashMap<>(); - credentials.put(ResourceWebClient.BEARER_TOKEN_KEY, token); - request.setQuery(null); - request.setResourceCredentials(credentials); - try { - cut.search(testURL, request); - fail(); - } catch (ProtocolException e) { - assertEquals(ProtocolException.MISSING_DATA, e.getContent()); - }*/ - - //Should fail with no targetURL + // //Should fail if no credentials given + // request.setQuery("query"); + // request.setTargetURL(targetURL); + // try { + // cut.search(testURL, request); + // fail(); + // } catch (Exception e) { + // assertEquals("HTTP 401 Unauthorized", e.getMessage()); + // } + + // With credentials but not search term + /* + * Map credentials = new HashMap<>(); credentials.put(ResourceWebClient.BEARER_TOKEN_KEY, token); + * request.setQuery(null); request.setResourceCredentials(credentials); try { cut.search(testURL, request); fail(); } catch + * (ProtocolException e) { assertEquals(ProtocolException.MISSING_DATA, e.getContent()); } + */ + + // Should fail with no targetURL Map credentials = new HashMap<>(); credentials.put(ResourceWebClient.BEARER_TOKEN_KEY, token); request.setResourceCredentials(credentials); -// request.setTargetURL(null); + // request.setTargetURL(null); request.setQuery("%blood%"); -// try { -// cut.search(testURL, request); -// fail(); -// } catch (ApplicationException e) { -// assertEquals(ApplicationException.MISSING_TARGET_URL, e.getContent()); -// } - -// request.setTargetURL(targetURL); - SearchResults result = cut.search(testURL, request); + // try { + // cut.search(testURL, request); + // fail(); + // } catch (ApplicationException e) { + // assertEquals(ApplicationException.MISSING_TARGET_URL, e.getContent()); + // } + + // request.setTargetURL(targetURL); + SearchResults result = cut.search(testURL, request, null); assertNotNull("Result should not be null", result); - //What if the resource has a problem? - wireMockRule.stubFor(any(urlEqualTo("/search")) - .willReturn(aResponse() - .withStatus(500))); + // What if the resource has a problem? + wireMockRule.stubFor(any(urlEqualTo("/search")).willReturn(aResponse().withStatus(500))); try { - cut.search(testURL, request); + cut.search(testURL, request, null); fail(); } catch (Exception e) { - assertTrue( e.getMessage().contains("500 Server Error")); + assertTrue(e.getMessage().contains("500 Server Error")); } - //What if resource returns the wrong type of object for some reason? + // What if resource returns the wrong type of object for some reason? ResourceInfo incorrectResponse = new ResourceInfo(); incorrectResponse.setName("resource name"); incorrectResponse.setId(new UUID(1L, 1L)); - wireMockRule.stubFor(any(urlEqualTo("/search")) - .willReturn(aResponse() - .withStatus(200) - .withBody(json.writeValueAsString(incorrectResponse)))); + wireMockRule.stubFor( + any(urlEqualTo("/search")).willReturn(aResponse().withStatus(200).withBody(json.writeValueAsString(incorrectResponse))) + ); try { - cut.search(testURL, request); + cut.search(testURL, request, null); fail(); } catch (Exception e) { - assertTrue( e.getMessage().contains("Incorrect object type returned")); + assertTrue(e.getMessage().contains("Incorrect object type returned")); } } @Test - public void testQuery() throws JsonProcessingException{ + public void testQuery() throws JsonProcessingException { String queryResults = json.writeValueAsString(new QueryStatus()); - wireMockRule.stubFor(any(urlEqualTo("/query")) - .willReturn(aResponse() - .withStatus(200) - .withBody(queryResults))); + wireMockRule.stubFor(any(urlEqualTo("/query")).willReturn(aResponse().withStatus(200).withBody(queryResults))); - //Should fail if any parameters are missing + // Should fail if any parameters are missing try { cut.query(testURL, null); fail(); @@ -249,7 +227,7 @@ public void testQuery() throws JsonProcessingException{ assertEquals(ProtocolException.MISSING_DATA, e.getContent()); } GeneralQueryRequest request = new GeneralQueryRequest(); -// request.setTargetURL("/query"); + // request.setTargetURL("/query"); try { cut.query(null, request); @@ -258,148 +236,137 @@ public void testQuery() throws JsonProcessingException{ assertEquals(ApplicationException.MISSING_RESOURCE_PATH, e.getContent()); } - //Should fail if no credentials given -// try { -// cut.query(testURL, request); -// fail(); -// } catch (Exception e) { -// assertEquals("HTTP 401 Unauthorized", e.getMessage()); -// } + // Should fail if no credentials given + // try { + // cut.query(testURL, request); + // fail(); + // } catch (Exception e) { + // assertEquals("HTTP 401 Unauthorized", e.getMessage()); + // } Map credentials = new HashMap<>(); request.setResourceCredentials(credentials); -// request.setTargetURL(null); - //Should fail without a targetURL -// try { -// cut.query(testURL, request); -// fail(); -// } catch (ApplicationException e) { -// assertEquals(ApplicationException.MISSING_TARGET_URL, e.getContent()); -// } - -// request.setTargetURL("/query"); - - //Everything goes correctly + // request.setTargetURL(null); + // Should fail without a targetURL + // try { + // cut.query(testURL, request); + // fail(); + // } catch (ApplicationException e) { + // assertEquals(ApplicationException.MISSING_TARGET_URL, e.getContent()); + // } + + // request.setTargetURL("/query"); + + // Everything goes correctly QueryStatus result = cut.query(testURL, request); assertNotNull("Result should not be null", result); - //What if the resource has a problem? - wireMockRule.stubFor(any(urlEqualTo("/query")) - .willReturn(aResponse() - .withStatus(500))); + // What if the resource has a problem? + wireMockRule.stubFor(any(urlEqualTo("/query")).willReturn(aResponse().withStatus(500))); try { cut.query(testURL, request); fail(); } catch (Exception e) { - assertTrue( e.getMessage().contains("500 Server Error")); + assertTrue(e.getMessage().contains("500 Server Error")); } - //What if resource returns the wrong type of object for some reason? + // What if resource returns the wrong type of object for some reason? ResourceInfo incorrectResponse = new ResourceInfo(); incorrectResponse.setName("resource name"); incorrectResponse.setId(new UUID(1L, 1L)); - wireMockRule.stubFor(any(urlEqualTo("/query")) - .willReturn(aResponse() - .withStatus(200) - .withBody(json.writeValueAsString(incorrectResponse)))); + wireMockRule.stubFor( + any(urlEqualTo("/query")).willReturn(aResponse().withStatus(200).withBody(json.writeValueAsString(incorrectResponse))) + ); try { cut.query(testURL, request); fail(); } catch (Exception e) { - assertTrue( e.getMessage().contains("Incorrect object type returned")); + assertTrue(e.getMessage().contains("Incorrect object type returned")); } } @Test - public void testQueryResult() throws JsonProcessingException{ + public void testQueryResult() throws JsonProcessingException { String testId = "230048"; String mockResult = "Any old response will work"; - wireMockRule.stubFor(any(urlMatching("/query/.*/result")) - .willReturn(aResponse() - .withStatus(200) - .withBody(mockResult))); + wireMockRule.stubFor(any(urlMatching("/query/.*/result")).willReturn(aResponse().withStatus(200).withBody(mockResult))); - //Should fail if missing any parameters -// try { -// cut.queryResult(testURL, testId, null); -// fail(); -// } catch (ProtocolException e) { -// assertEquals(ProtocolException.MISSING_DATA, e.getContent()); -// } + // Should fail if missing any parameters + // try { + // cut.queryResult(testURL, testId, null); + // fail(); + // } catch (ProtocolException e) { + // assertEquals(ProtocolException.MISSING_DATA, e.getContent()); + // } GeneralQueryRequest queryRequest = new GeneralQueryRequest(); Map credentials = new HashMap<>(); credentials.put(ResourceWebClient.BEARER_TOKEN_KEY, token); queryRequest.setResourceCredentials(credentials); -// String targetURL = "/query/13452134/result"; -//// queryRequest.setTargetURL(targetURL); -// -// try { -// cut.queryResult(testURL, null, queryRequest); -// fail(); -// } catch (ProtocolException e) { -// assertEquals(ProtocolException.MISSING_QUERY_ID, e.getContent()); -// } -// try { -// cut.queryResult(null, testId, queryRequest); -// fail(); -// } catch (ApplicationException e) { -// assertEquals(ApplicationException.MISSING_RESOURCE_PATH, e.getContent()); -// } - -//// queryRequest.setTargetURL(null); -// //Should fail without a targetURL -// try { -// cut.queryResult(testURL, testId, queryRequest); -// fail(); -// } catch (ApplicationException e) { -// assertEquals(ApplicationException.MISSING_TARGET_URL, e.getContent()); -// } -// -//// queryRequest.setTargetURL(targetURL); - - - //Everything should work here - Response result = cut.queryResult(testURL,testId, queryRequest); + // String targetURL = "/query/13452134/result"; + //// queryRequest.setTargetURL(targetURL); + // + // try { + // cut.queryResult(testURL, null, queryRequest); + // fail(); + // } catch (ProtocolException e) { + // assertEquals(ProtocolException.MISSING_QUERY_ID, e.getContent()); + // } + // try { + // cut.queryResult(null, testId, queryRequest); + // fail(); + // } catch (ApplicationException e) { + // assertEquals(ApplicationException.MISSING_RESOURCE_PATH, e.getContent()); + // } + + //// queryRequest.setTargetURL(null); + // //Should fail without a targetURL + // try { + // cut.queryResult(testURL, testId, queryRequest); + // fail(); + // } catch (ApplicationException e) { + // assertEquals(ApplicationException.MISSING_TARGET_URL, e.getContent()); + // } + // + //// queryRequest.setTargetURL(targetURL); + + + // Everything should work here + Response result = cut.queryResult(testURL, testId, queryRequest); assertNotNull("Result should not be null", result); try { String resultContent = IOUtils.toString((InputStream) result.getEntity(), "UTF-8"); assertEquals("Result should match " + mockResult, mockResult, resultContent); - } catch (IOException e ){ + } catch (IOException e) { fail("Result content was unreadable"); } - //What if the resource has a problem? - wireMockRule.stubFor(any(urlMatching("/query/.*/result")) - .willReturn(aResponse() - .withStatus(500))); + // What if the resource has a problem? + wireMockRule.stubFor(any(urlMatching("/query/.*/result")).willReturn(aResponse().withStatus(500))); try { cut.queryResult(testURL, testId, queryRequest); fail(); } catch (Exception e) { - assertTrue( e.getMessage().contains("500 Server Error")); + assertTrue(e.getMessage().contains("500 Server Error")); } } @Test - public void testQueryStatus() throws JsonProcessingException{ + public void testQueryStatus() throws JsonProcessingException { String testId = "230048"; QueryStatus testResult = new QueryStatus(); testResult.setStatus(PicSureStatus.PENDING); testResult.setResourceStatus("RUNNING"); String queryStatus = json.writeValueAsString(testResult); - wireMockRule.stubFor(any(urlMatching("/query/.*/status")) - .willReturn(aResponse() - .withStatus(200) - .withBody(queryStatus))); + wireMockRule.stubFor(any(urlMatching("/query/.*/status")).willReturn(aResponse().withStatus(200).withBody(queryStatus))); - //Fails with any missing parameters + // Fails with any missing parameters try { cut.queryStatus(testURL, testId, null); fail(); @@ -410,8 +377,8 @@ public void testQueryStatus() throws JsonProcessingException{ Map credentials = new HashMap<>(); credentials.put(ResourceWebClient.BEARER_TOKEN_KEY, token); queryRequest.setResourceCredentials(credentials); -// String targetURL = "/query/13452134/result"; -// queryRequest.setTargetURL(targetURL); + // String targetURL = "/query/13452134/result"; + // queryRequest.setTargetURL(targetURL); try { cut.queryStatus(testURL, null, queryRequest); @@ -426,55 +393,52 @@ public void testQueryStatus() throws JsonProcessingException{ assertEquals(ApplicationException.MISSING_RESOURCE_PATH, e.getContent()); } -// queryRequest.setTargetURL(null); + // queryRequest.setTargetURL(null); - //Should fail without a targetURL -// try { -// cut.queryStatus(testURL, testId, queryRequest); -// fail(); -// } catch (ApplicationException e) { -// assertEquals(ApplicationException.MISSING_TARGET_URL, e.getContent()); -// } + // Should fail without a targetURL + // try { + // cut.queryStatus(testURL, testId, queryRequest); + // fail(); + // } catch (ApplicationException e) { + // assertEquals(ApplicationException.MISSING_TARGET_URL, e.getContent()); + // } -// queryRequest.setTargetURL(targetURL); + // queryRequest.setTargetURL(targetURL); - //Everything should work here - QueryStatus result = cut.queryStatus(testURL,testId, queryRequest); + // Everything should work here + QueryStatus result = cut.queryStatus(testURL, testId, queryRequest); assertNotNull("Result should not be null", result); - //Make sure all necessary fields are present - assertNotNull("Duration should not be null",result.getDuration()); - assertNotNull("Expiration should not be null",result.getExpiration()); - assertNotNull("ResourceStatus should not be null",result.getResourceStatus()); - assertNotNull("Status should not be null",result.getStatus()); + // Make sure all necessary fields are present + assertNotNull("Duration should not be null", result.getDuration()); + assertNotNull("Expiration should not be null", result.getExpiration()); + assertNotNull("ResourceStatus should not be null", result.getResourceStatus()); + assertNotNull("Status should not be null", result.getStatus()); - //What if the resource has a problem? - wireMockRule.stubFor(any(urlMatching("/query/.*/status")) - .willReturn(aResponse() - .withStatus(500))); + // What if the resource has a problem? + wireMockRule.stubFor(any(urlMatching("/query/.*/status")).willReturn(aResponse().withStatus(500))); try { cut.queryStatus(testURL, testId, queryRequest); fail(); } catch (Exception e) { - assertTrue( e.getMessage().contains("500 Server Error")); + assertTrue(e.getMessage().contains("500 Server Error")); } - //What if resource returns the wrong type of object for some reason? + // What if resource returns the wrong type of object for some reason? ResourceInfo incorrect = new ResourceInfo(); incorrect.setName("resource name"); incorrect.setId(new UUID(1L, 1L)); - wireMockRule.stubFor(any(urlMatching("/query/.*/status")) - .willReturn(aResponse() - .withStatus(200) - .withBody(json.writeValueAsString(incorrect)))); + wireMockRule.stubFor( + any(urlMatching("/query/.*/status")).willReturn(aResponse().withStatus(200).withBody(json.writeValueAsString(incorrect))) + ); try { cut.queryStatus(testURL, testId, queryRequest); fail(); } catch (Exception e) { - assertTrue( e.getMessage().contains("Incorrect object type returned")); + assertTrue(e.getMessage().contains("Incorrect object type returned")); } } }