From e710221334f246d9aaad30484df13d5a9a7462d5 Mon Sep 17 00:00:00 2001 From: Luke Sikina Date: Wed, 7 Feb 2024 14:39:23 -0500 Subject: [PATCH 1/5] [ALS-5827] Enable search by CA id - If a uuid does not match a query, instead search for common area UUID - If we have a collision, I'm buying a lottery ticket --- .../data/repository/QueryRepository.java | 17 +++++++++++++++-- .../avillach/service/PicsureQueryService.java | 1 + 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/repository/QueryRepository.java b/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/repository/QueryRepository.java index 12c59bef..0c867ed0 100644 --- a/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/repository/QueryRepository.java +++ b/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/repository/QueryRepository.java @@ -3,12 +3,25 @@ import edu.harvard.dbmi.avillach.data.entity.Query; import javax.enterprise.context.ApplicationScoped; +import javax.persistence.PersistenceException; import javax.transaction.Transactional; import java.util.UUID; @Transactional @ApplicationScoped -public class QueryRepository extends BaseRepository{ +public class QueryRepository extends BaseRepository { - protected QueryRepository() {super(Query.class);} + protected QueryRepository() { + super(Query.class); + } + + public Query getQueryUUIDFromCommonAreaUUID(UUID caID) { + String caIDRegex = "%commonAreaUUID\":\"" + caID + "\"%"; + String query = "SELECT * FROM query WHERE CONVERT(metadata USING utf8) LIKE ?"; + try { + return (Query) em().createNativeQuery(query, Query.class).setParameter(1, caIDRegex).getSingleResult(); + } catch (PersistenceException ignored) { + return null; + } + } } diff --git a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/PicsureQueryService.java b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/PicsureQueryService.java index b0c851bc..ca93d6d8 100644 --- a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/PicsureQueryService.java +++ b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/PicsureQueryService.java @@ -241,6 +241,7 @@ public Response querySync(QueryRequest queryRequest, HttpHeaders headers) { */ public QueryStatus queryMetadata(UUID queryId, HttpHeaders headers) { Query query = queryRepo.getById(queryId); + query = query == null ? queryRepo.getQueryUUIDFromCommonAreaUUID(queryId) : query; if (query == null) { throw new ProtocolException(ProtocolException.QUERY_NOT_FOUND + queryId.toString()); } From 315a78997e7ff2bc04727affec82ee8adb1abe3f Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Tue, 13 Feb 2024 09:10:34 -0500 Subject: [PATCH 2/5] [ALS-5787] Open Access StatViz is not showing "Other" (#180) * Update key length handling in limitKeySize The limitKeySize method in the VisualizationUtil class has been updated to ensure key uniqueness when keys exceed a certain length. A more robust method of shortening the keys has been implemented: if the key is longer than 45 characters, it is cut off and replaced with "..." and additional characters are appended to ensure uniqueness if needed. This helps limit key size while preserving uniqueness. * Add VisualizationUtilTests and refine key shortening logic Added a new test class, VisualizationUtilTests to validate the functionality of the VisualizationUtil class. Adjusted the 'limitKeySize' method to ensure key uniqueness when keys are shortened to a maximum length of 45 characters. If shortened keys are not unique, additional trailing characters are included until uniqueness is achieved. * Add check on length before substring * Update pic-sure-util/src/main/java/edu/harvard/dbmi/avillach/util/VisualizationUtil.java * Update pic-sure-util/src/main/java/edu/harvard/dbmi/avillach/util/VisualizationUtil.java * Update pic-sure-util/src/main/java/edu/harvard/dbmi/avillach/util/VisualizationUtil.java * Update pic-sure-resources/pic-sure-visualization-resource/src/test/java/edu/harvard/hms/dbmi/avillach/resource/visualization/service/VisualizationUtilTests.java Co-authored-by: Luke Sikina * Fix import for modified unit test * Refactor and add test cases for key size limiting function Refactors existing code by simplifying key size limiting logic in VisualizationUtil and added several new unit tests to ensure its correct behavior with different input scenarios including long keys, empty maps, null maps, and uniqueness near middle. * Update null handling in VisualizationUtil's limitKeySize Changed the handling of null input in VisualizationUtil's limitKeySize from returning a new HashMap to throwing an IllegalArgumentException. Also, updated the corresponding test to check for this exception instead of comparing with an empty map. --------- Co-authored-by: Luke Sikina --- .../service/VisualizationUtilTests.java | 94 +++++++++++++++++++ .../dbmi/avillach/util/VisualizationUtil.java | 57 +++++++---- 2 files changed, 135 insertions(+), 16 deletions(-) create mode 100644 pic-sure-resources/pic-sure-visualization-resource/src/test/java/edu/harvard/hms/dbmi/avillach/resource/visualization/service/VisualizationUtilTests.java diff --git a/pic-sure-resources/pic-sure-visualization-resource/src/test/java/edu/harvard/hms/dbmi/avillach/resource/visualization/service/VisualizationUtilTests.java b/pic-sure-resources/pic-sure-visualization-resource/src/test/java/edu/harvard/hms/dbmi/avillach/resource/visualization/service/VisualizationUtilTests.java new file mode 100644 index 00000000..78583922 --- /dev/null +++ b/pic-sure-resources/pic-sure-visualization-resource/src/test/java/edu/harvard/hms/dbmi/avillach/resource/visualization/service/VisualizationUtilTests.java @@ -0,0 +1,94 @@ +package edu.harvard.hms.dbmi.avillach.resource.visualization.service; + +import edu.harvard.dbmi.avillach.util.VisualizationUtil; +import org.junit.Test; +import org.junit.jupiter.api.DisplayName; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class VisualizationUtilTests { + + @Test + @DisplayName("Test limitKeySize") + public void testLimitKeySizeUniqueness() { + Map axisMap = new HashMap<>(Map.of( + "Disease-Specific (Asthma, Allergy and Inflammation, PUB)", 1, + "Disease-Specific (Asthma, Allergy and Inflammation, PUB, NPU)", 1, + "Disease-Specific (Asthma, Allergy and Inflammation, NPU)", 1, + "Disease-Specific (Asthma, Allergy and Inflammation)", 1 + )); + + Map actual = VisualizationUtil.limitKeySize(axisMap); + + Map expected = Map.of( + "Disease-Specific (Asthma, Allergy an..., PUB)", 1, + "Disease-Specific (Asthma, Allergy an...ation)", 1, + "Disease-Specific (Asthma, Allergy an..., NPU)", 1, + "Disease-Specific (Asthma, Allergy a...B, NPU)", 1 + ); + assertEquals(expected, actual); + } + + @Test + @DisplayName("Test Empty Map limitKeySize") + public void testEmptyMapLimitKeySize() { + Map axisMap = new HashMap<>(); + Map actual = VisualizationUtil.limitKeySize(axisMap); + Map expected = new HashMap<>(); + assertEquals(expected, actual); + } + + @Test + @DisplayName("Test null Map limitKeySize") + public void testNullMapLimitKeySize() { + Map axisMap = null; + // this should throw a NullPointerException + try { + VisualizationUtil.limitKeySize(axisMap); + } catch (IllegalArgumentException e) { + assertEquals("axisMap cannot be null", e.getMessage()); + } + } + + @Test + @DisplayName("Test with no long keys limitKeySize") + public void testNoLongKeysLimitKeySize() { + // Test with no long keys + Map axisMap = new HashMap<>(); + for (int i = 0; i < 10; i++) { + axisMap.put("key" + i, 1); + } + Map actual = VisualizationUtil.limitKeySize(axisMap); + Map expected = new HashMap<>(axisMap); + assertEquals(expected, actual); + } + + @Test + @DisplayName("Test with keys of greater than 45 characters and uniqueness is near middle limitKeySize") + public void testKeysOfGreaterLengthAndUniquenessNearMiddleLimitKeySize() { + Map axisMap = new HashMap<>(); + axisMap.put("Hello, this is a long key that is STRING1 greater than 45 characters and is unique", 1); + axisMap.put("Hello, this is a long key that is STRING2 greater than 45 characters and is unique", 1); + axisMap.put("Hello, this is a long key that is STRING3 greater than 45 characters and is unique", 1); + + Map actual = VisualizationUtil.limitKeySize(axisMap); + + // loop through the keys and check if they are less than 45 characters + for (String key : actual.keySet()) { + assertEquals(45, key.length()); + } + + Map expected = Map.of( + "Hello, this is a long key that is ST...unique", 1, + "Hello, this is a long key that is S... unique", 1, + "Hello, this is a long key that is ...s unique", 1 + ); + + assertEquals(expected, actual); + } + + +} diff --git a/pic-sure-util/src/main/java/edu/harvard/dbmi/avillach/util/VisualizationUtil.java b/pic-sure-util/src/main/java/edu/harvard/dbmi/avillach/util/VisualizationUtil.java index 8a7d99a7..ed32a921 100644 --- a/pic-sure-util/src/main/java/edu/harvard/dbmi/avillach/util/VisualizationUtil.java +++ b/pic-sure-util/src/main/java/edu/harvard/dbmi/avillach/util/VisualizationUtil.java @@ -69,25 +69,50 @@ public static Map doProcessResults(Map axisMap } /** - * Replaces long column names with shorter version. + * This method is used to limit the size of the keys in the axisMap to a maximum of 45 characters. If the key is longer + * than 45 characters, it will be shortened to 45 characters and the last 3 characters will be replaced with "...". + * If the shortened key is not unique, we will create a unique one + *

* - * @param axisMap - * @return + * @param axisMap - Map of the categories and their counts + * @return Map - Map of the categories and their counts with the keys limited to 45 characters */ - private static Map limitKeySize(Map axisMap) { - List toRemove = new ArrayList<>(); - Map toAdd = new HashMap<>(); - axisMap.keySet().forEach(key -> { - if (key.length() > MAX_X_LABEL_LINE_LENGTH) { - toRemove.add(key); - toAdd.put( - key.substring(0, MAX_X_LABEL_LINE_LENGTH - 3) + "...", - axisMap.get(key)); - } + public static Map limitKeySize(Map axisMap) { + if (axisMap == null) { + throw new IllegalArgumentException("axisMap cannot be null"); + } + + Map newAxisMap = new HashMap<>(); + HashSet keys = new HashSet<>(); + axisMap.forEach((key, value) -> { + String adjustedKey = key.length() < MAX_X_LABEL_LINE_LENGTH ? key : createAdjustedKey(axisMap, keys, key); + newAxisMap.put(adjustedKey, value); + keys.add(adjustedKey); }); - toRemove.forEach(key -> axisMap.remove(key)); - axisMap.putAll(toAdd); - return axisMap; + return newAxisMap; + } + + private static String createAdjustedKey(Map axisMap, HashSet keys, String key) { + String keyPrefix = key.substring(0, MAX_X_LABEL_LINE_LENGTH); + return isKeyPrefixInAxisMap(axisMap, keyPrefix) ? generateUniqueKey(keys, key) : appendEllipsis(keyPrefix); + } + + private static boolean isKeyPrefixInAxisMap(Map axisMap, String keyPrefix) { + return axisMap.keySet().stream().anyMatch(k -> k.startsWith(keyPrefix)); + } + + private static String generateUniqueKey(HashSet keys, String key) { + int countFromEnd = 6; + String proposedKey; + do { + proposedKey = String.format("%s...%s", key.substring(0, MAX_X_LABEL_LINE_LENGTH - 3 - countFromEnd), key.substring(key.length() - countFromEnd)); + countFromEnd++; + } while (keys.contains(proposedKey)); + return proposedKey; + } + + private static String appendEllipsis(String keyPrefixAdjusted) { + return String.format("%s...", keyPrefixAdjusted); } } From 68f7ac78468d31dfb8be89668ffecd6d38e1ddda Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 29 Feb 2024 15:31:01 -0500 Subject: [PATCH 3/5] Refine the condition for empty return in DataProcessingService (#184) Adjusted the condition under which an empty HashMap is returned in the DataProcessingService. Now, an empty HashMap will only be returned when both the maximum and minimum values in the data are 0, and the number of bins is also 0. This change takes into account situations where the data contains values but the min and max are both 0. --- .../resource/visualization/service/DataProcessingService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pic-sure-resources/pic-sure-visualization-resource/src/main/java/edu/harvard/hms/dbmi/avillach/resource/visualization/service/DataProcessingService.java b/pic-sure-resources/pic-sure-visualization-resource/src/main/java/edu/harvard/hms/dbmi/avillach/resource/visualization/service/DataProcessingService.java index 1988aab4..845909e4 100644 --- a/pic-sure-resources/pic-sure-visualization-resource/src/main/java/edu/harvard/hms/dbmi/avillach/resource/visualization/service/DataProcessingService.java +++ b/pic-sure-resources/pic-sure-visualization-resource/src/main/java/edu/harvard/hms/dbmi/avillach/resource/visualization/service/DataProcessingService.java @@ -158,8 +158,8 @@ private static Map bucketData(Map originalMap) int numBins = calcNumBins(data); double min = data.keySet().stream().min(Double::compareTo).orElse(0.0); double max = data.keySet().stream().max(Double::compareTo).orElse(0.0); - - if ((min == 0.0 && max == 0.0) || numBins == 0) return new HashMap<>(); + // The min and max can both be 0, but we could still have a numBins of 1 if there are values in the data. + if (min == 0.0 && max == 0.0 && numBins == 0) return new HashMap<>(); int binSize = (int) Math.ceil((max - min) / numBins); From 6c716b37a522eecb6f42edadbf05faf155066911 Mon Sep 17 00:00:00 2001 From: ramari16 Date: Tue, 12 Mar 2024 18:13:56 -0300 Subject: [PATCH 4/5] ALS-5387: Remove stack from resources table (#186) * [ALS-5422] Add persistence.xml to visualization resource (#161) (#162) The visualization resource is failing to start due to an error injecting persistence unit into CDI managed bean. It is unable to find a persistence unit named ''. * ALS-5387: Remove stack from resources table * ALS-5387: More robust implementation of feature --------- Co-authored-by: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Co-authored-by: GeorgeC --- .../dbmi/avillach/data/entity/Resource.java | 34 ++++++- .../data/repository/ResourceRepository.java | 2 +- .../avillach/service/PicsureInfoService.java | 88 +++++++++---------- 3 files changed, 77 insertions(+), 47 deletions(-) diff --git a/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/entity/Resource.java b/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/entity/Resource.java index 3faf9ddc..b2cb4367 100644 --- a/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/entity/Resource.java +++ b/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/entity/Resource.java @@ -1,12 +1,12 @@ package edu.harvard.dbmi.avillach.data.entity; import java.io.StringReader; +import java.util.Optional; import javax.json.Json; import javax.json.JsonObject; import javax.json.JsonReader; -import javax.persistence.Column; -import javax.persistence.Entity; +import javax.persistence.*; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; @@ -18,6 +18,9 @@ public class Resource extends BaseEntity{ @Column(length = 8192) private String description; private String targetURL; + + + @Convert(converter = ResourcePathConverter.class) private String resourceRSPath; @Column(length = 8192) @@ -102,4 +105,31 @@ public String toString() { .add("metadata", metadataObj) .build().toString(); } + + /** + * This resource path converter allows resource paths to contain a reference to a specific stack that is + * imputed at runtime based an an environment parameter. This allows multiple stacks to share the same database. + * + * The ___target_stack___ token in any resource path value will be replaced with the TARGET_STACK environment variable + */ + @Converter + public static class ResourcePathConverter implements AttributeConverter { + + public ResourcePathConverter() { + } + + private static final Optional targetStack = Optional.ofNullable(System.getProperty("TARGET_STACK", null)); + + @Override + public String convertToDatabaseColumn(String attribute) { + return attribute; + } + + @Override + public String convertToEntityAttribute(String dbData) { + return targetStack + .map(stack -> dbData.replace("___target_stack___", stack)) + .orElse(dbData); + } + } } diff --git a/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/repository/ResourceRepository.java b/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/repository/ResourceRepository.java index 8bb400f4..f377c9d6 100644 --- a/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/repository/ResourceRepository.java +++ b/pic-sure-api-data/src/main/java/edu/harvard/dbmi/avillach/data/repository/ResourceRepository.java @@ -15,5 +15,5 @@ protected ResourceRepository() { super(Resource.class); } - + } diff --git a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/PicsureInfoService.java b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/PicsureInfoService.java index 922746f8..a64cf005 100644 --- a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/PicsureInfoService.java +++ b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/PicsureInfoService.java @@ -21,55 +21,55 @@ public class PicsureInfoService { - private final Logger logger = LoggerFactory.getLogger(PicsureQueryService.class); + private final Logger logger = LoggerFactory.getLogger(PicsureQueryService.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; - /** - * Retrieve resource info for a specific resource. - * - * @param resourceId - Resource UUID - * @param credentialsQueryRequest - Contains resource specific credentials map - * @return a {@link edu.harvard.dbmi.avillach.domain.ResourceInfo ResourceInfo} - */ - public ResourceInfo info(UUID resourceId, QueryRequest credentialsQueryRequest, 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); - } - if (credentialsQueryRequest == null){ - credentialsQueryRequest = new GeneralQueryRequest(); - } - if (credentialsQueryRequest.getResourceCredentials() == null){ - credentialsQueryRequest.setResourceCredentials(new HashMap()); - } + /** + * Retrieve resource info for a specific resource. + * + * @param resourceId - Resource UUID + * @param credentialsQueryRequest - Contains resource specific credentials map + * @return a {@link edu.harvard.dbmi.avillach.domain.ResourceInfo ResourceInfo} + */ + public ResourceInfo info(UUID resourceId, QueryRequest credentialsQueryRequest, 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); + } + if (credentialsQueryRequest == null) { + credentialsQueryRequest = new GeneralQueryRequest(); + } + if (credentialsQueryRequest.getResourceCredentials() == null) { + credentialsQueryRequest.setResourceCredentials(new HashMap()); + } - logger.info("path=/info/{resourceId}, resourceId={}, requestSource={}, credentialsQueryRequest={}", - resourceId, - Utilities.getRequestSourceFromHeader(headers), - Utilities.convertQueryRequestToString(mapper, credentialsQueryRequest) - ); + logger.info( + "path=/info/{resourceId}, resourceId={}, requestSource={}, credentialsQueryRequest={}", resourceId, + Utilities.getRequestSourceFromHeader(headers), Utilities.convertQueryRequestToString(mapper, credentialsQueryRequest) + ); - credentialsQueryRequest.getResourceCredentials().put(ResourceWebClient.BEARER_TOKEN_KEY, resource.getToken()); - return resourceWebClient.info(resource.getResourceRSPath(), credentialsQueryRequest); - } + credentialsQueryRequest.getResourceCredentials().put(ResourceWebClient.BEARER_TOKEN_KEY, resource.getToken()); + return resourceWebClient.info(resource.getResourceRSPath(), credentialsQueryRequest); + } - /** - * Retrieve a list of all available resources. - * - * @return List containing limited metadata about all available resources and ids. - */ - public Map resources(HttpHeaders headers) { - logger.info("path=/info/resources, requestSource={}", Utilities.getRequestSourceFromHeader(headers)); - return resourceRepo.list().stream().collect(Collectors.toMap(Resource::getUuid, Resource::getName)); - } + /** + * Retrieve a list of all available resources. + * + * @return List containing limited metadata about all available resources and ids. + */ + public Map resources(HttpHeaders headers) { + logger.info("path=/info/resources, requestSource={}", Utilities.getRequestSourceFromHeader(headers)); + return resourceRepo.list().stream().filter(resource -> !resource.getHidden()) + .collect(Collectors.toMap(Resource::getUuid, Resource::getName)); + } } From a4ddf4f1744f89509ab259d50bf0da6e5c50b06f Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Tue, 2 Apr 2024 09:40:45 -0400 Subject: [PATCH 5/5] [ALS-6222] Status endpoints now filters resources (#189) The database contains both open and auth hpds even if it wasn't deployed. This means we attempt to check for a resource that doesn't exist. This results in our service always showing degraded. --- .../harvard/dbmi/avillach/PicSureWarInit.java | 21 +- .../dbmi/avillach/service/SystemService.java | 296 ++++++++++-------- 2 files changed, 173 insertions(+), 144 deletions(-) diff --git a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/PicSureWarInit.java b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/PicSureWarInit.java index 95c07223..43356e1b 100644 --- a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/PicSureWarInit.java +++ b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/PicSureWarInit.java @@ -25,7 +25,10 @@ public class PicSureWarInit { @Resource(mappedName = "java:global/token_introspection_token") private String token_introspection_token; - //to be able to pre modified + @Resource(mappedName = "java:global/defaultApplicationUUID") + private String default_application_uuid; + + // to be able to pre modified public static final ObjectMapper objectMapper = new ObjectMapper(); // check the example from Apache HttpClient official website: @@ -39,14 +42,9 @@ public class PicSureWarInit { static { HTTP_CLIENT_CONNECTION_MANAGER = new PoolingHttpClientConnectionManager(); HTTP_CLIENT_CONNECTION_MANAGER.setMaxTotal(100); - CLOSEABLE_HTTP_CLIENT = HttpClients - .custom() - .setConnectionManager(HTTP_CLIENT_CONNECTION_MANAGER) - .useSystemProperties() - .build(); + CLOSEABLE_HTTP_CLIENT = HttpClients.custom().setConnectionManager(HTTP_CLIENT_CONNECTION_MANAGER).useSystemProperties().build(); } - public String getToken_introspection_url() { return token_introspection_url; } @@ -54,4 +52,13 @@ public String getToken_introspection_url() { public String getToken_introspection_token() { return token_introspection_token; } + + /** + * This method is used to get the default application UUID. This value is either the open or auth hpds resource UUID. + * + * @return the default application UUID + */ + public String getDefaultApplicationUUID() { + return this.default_application_uuid; + } } diff --git a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/SystemService.java b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/SystemService.java index 2d6328d9..a9c8fcb2 100755 --- a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/SystemService.java +++ b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/SystemService.java @@ -1,137 +1,159 @@ -package edu.harvard.dbmi.avillach.service; -import static edu.harvard.dbmi.avillach.util.Utilities.buildHttpClientContext; - -import java.io.IOException; -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.inject.Inject; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; - -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.util.EntityUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import edu.harvard.dbmi.avillach.PicSureWarInit; -import edu.harvard.dbmi.avillach.data.entity.Resource; -import edu.harvard.dbmi.avillach.data.repository.ResourceRepository; -import edu.harvard.dbmi.avillach.domain.GeneralQueryRequest; -import edu.harvard.dbmi.avillach.domain.ResourceInfo; -import edu.harvard.dbmi.avillach.util.exception.ApplicationException; - -@Path("/system") -public class SystemService { - static int max_test_frequency = 60000; - - static final String RUNNING = "RUNNING"; - - static final String ONE_OR_MORE_COMPONENTS_DEGRADED = "ONE OR MORE COMPONENTS DEGRADED"; - - Logger logger = LoggerFactory.getLogger(SystemService.class); - - @Inject - PicSureWarInit picSureWarInit; - - String lastStatus = "UNTESTED"; - long lastStatusCheck = 0l; - - @Inject - ResourceRepository resourceRepo; - - String token_introspection_url; - String token_introspection_token; - - @PostConstruct - public void init() { - token_introspection_url = picSureWarInit.getToken_introspection_url(); - token_introspection_token = picSureWarInit.getToken_introspection_token(); - if(token_introspection_url == null || token_introspection_token == null) { - throw new RuntimeException( - "token_introspection_url and token_introspection_token not configured"); - } - } - - @GET - @Path("/status") - @Produces("text/plain") - public String status() { - // Because there is no auth on this service we limit actually performing the checking to 1 per minute to avoid DOS scenarios. - long timeOfRequest = System.currentTimeMillis(); - if(timeOfRequest-lastStatusCheck < max_test_frequency) { - return lastStatus; - }else { - lastStatusCheck = timeOfRequest; - try{ - List resourcesToTest = resourceRepo.list(); - if( resourcesToTest != null && // This proves the MySQL database is serving queries - !resourcesToTest.isEmpty() && // This proves at least one resources is configured - testPSAMAResponds() && // This proves we can perform token introspection - testResourcesRespond(resourcesToTest) ){ // This proves all resources are at least serving info requests. - lastStatus = RUNNING; - return lastStatus; - }else { - lastStatus = ONE_OR_MORE_COMPONENTS_DEGRADED; - } - }catch(Exception e) { - e.printStackTrace(); - lastStatus = ONE_OR_MORE_COMPONENTS_DEGRADED; - } - return lastStatus; - } - } - - private boolean testPSAMAResponds() throws UnsupportedOperationException, IOException { - CloseableHttpClient client = PicSureWarInit.CLOSEABLE_HTTP_CLIENT; - ObjectMapper json = PicSureWarInit.objectMapper; - - HttpPost post = new HttpPost(token_introspection_url); - post.setEntity(new StringEntity("{}")); - post.setHeader("Content-Type", "application/json"); - //Authorize into the token introspection endpoint - post.setHeader("Authorization", "Bearer " + token_introspection_token); - CloseableHttpResponse response = null; - try { - response = client.execute(post, buildHttpClientContext()); - if (response.getStatusLine().getStatusCode() != 200){ - logger.error("callTokenIntroEndpoint() error back from token intro host server [" - + token_introspection_url + "]: " + EntityUtils.toString(response.getEntity())); - throw new ApplicationException("Token Introspection host server return " + response.getStatusLine().getStatusCode() + - ". Please see the log"); - } - JsonNode responseContent = json.readTree(response.getEntity().getContent()); - if (!responseContent.get("active").asBoolean()){ - // This is actually the expected response as we did not send a token in the token_introspection_request. - return true; - } - - return true; - } finally { - try { - if (response != null) - response.close(); - } catch (IOException ex) { - logger.error("callTokenIntroEndpoint() IOExcpetion when closing http response: " + ex.getMessage()); - } - } - } - - private boolean testResourcesRespond(List resourcesToTest) { - for(Resource resource : resourcesToTest) { - ResourceInfo info = new ResourceWebClient().info(resource.getResourceRSPath(), new GeneralQueryRequest()); - if(info==null) { - return false; - } - } - return true; - } -} - +package edu.harvard.dbmi.avillach.service; + +import static edu.harvard.dbmi.avillach.util.Utilities.buildHttpClientContext; + +import java.io.IOException; +import java.util.List; +import java.util.UUID; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import edu.harvard.dbmi.avillach.PicSureWarInit; +import edu.harvard.dbmi.avillach.data.entity.Resource; +import edu.harvard.dbmi.avillach.data.repository.ResourceRepository; +import edu.harvard.dbmi.avillach.domain.GeneralQueryRequest; +import edu.harvard.dbmi.avillach.domain.ResourceInfo; +import edu.harvard.dbmi.avillach.util.exception.ApplicationException; + +@Path("/system") +public class SystemService { + static int max_test_frequency = 60000; + + static final String RUNNING = "RUNNING"; + + static final String ONE_OR_MORE_COMPONENTS_DEGRADED = "ONE OR MORE COMPONENTS DEGRADED"; + + Logger logger = LoggerFactory.getLogger(SystemService.class); + + @Inject + PicSureWarInit picSureWarInit; + + String lastStatus = "UNTESTED"; + long lastStatusCheck = 0l; + + @Inject + ResourceRepository resourceRepo; + + String token_introspection_url; + String token_introspection_token; + + String defaultApplicationUUID; + + @PostConstruct + public void init() { + token_introspection_url = picSureWarInit.getToken_introspection_url(); + token_introspection_token = picSureWarInit.getToken_introspection_token(); + defaultApplicationUUID = picSureWarInit.getDefaultApplicationUUID(); + if (token_introspection_url == null || token_introspection_token == null) { + throw new RuntimeException("token_introspection_url and token_introspection_token not configured"); + } + } + + @GET + @Path("/status") + @Produces("text/plain") + public String status() { + // Because there is no auth on this service we limit actually performing the checking to 1 per minute to avoid DOS scenarios. + long timeOfRequest = System.currentTimeMillis(); + if (timeOfRequest - lastStatusCheck < max_test_frequency) { + return lastStatus; + } else { + lastStatusCheck = timeOfRequest; + try { + List resourcesToTest = resourceRepo.list(); + if (resourcesToTest == null || resourcesToTest.isEmpty()) { + lastStatus = ONE_OR_MORE_COMPONENTS_DEGRADED; + return lastStatus; + } + + // convert the default application uuid to an uuid object + UUID defaultApplicationUUID = UUID.fromString(this.defaultApplicationUUID); + + // We need to remove open or auth HPDS from the resource list depending on the environment deployed. + // This because both are included in the database, but only one is actually deployed. + // if the name contains hpds and is not the default application uuid, remove it. + resourcesToTest.removeIf( + resource -> resource.getName().toLowerCase().contains("hpds") && !resource.getUuid().equals(defaultApplicationUUID) + ); + + // This proves the MySQL database is serving queries + // This proves at least one resources is configured + // This proves we can perform token introspection + if (testPSAMAResponds() && testResourcesRespond(resourcesToTest)) { // This proves all resources are at least serving info + // requests. + lastStatus = RUNNING; + return lastStatus; + } else { + lastStatus = ONE_OR_MORE_COMPONENTS_DEGRADED; + } + } catch (Exception e) { + e.printStackTrace(); + lastStatus = ONE_OR_MORE_COMPONENTS_DEGRADED; + } + return lastStatus; + } + } + + private boolean testPSAMAResponds() throws UnsupportedOperationException, IOException { + CloseableHttpClient client = PicSureWarInit.CLOSEABLE_HTTP_CLIENT; + ObjectMapper json = PicSureWarInit.objectMapper; + + HttpPost post = new HttpPost(token_introspection_url); + post.setEntity(new StringEntity("{}")); + post.setHeader("Content-Type", "application/json"); + // Authorize into the token introspection endpoint + post.setHeader("Authorization", "Bearer " + token_introspection_token); + CloseableHttpResponse response = null; + try { + response = client.execute(post, buildHttpClientContext()); + if (response.getStatusLine().getStatusCode() != 200) { + logger.error( + "callTokenIntroEndpoint() error back from token intro host server [" + token_introspection_url + "]: " + + EntityUtils.toString(response.getEntity()) + ); + throw new ApplicationException( + "Token Introspection host server return " + response.getStatusLine().getStatusCode() + ". Please see the log" + ); + } + JsonNode responseContent = json.readTree(response.getEntity().getContent()); + if (!responseContent.get("active").asBoolean()) { + // This is actually the expected response as we did not send a token in the token_introspection_request. + return true; + } + + return true; + } finally { + try { + if (response != null) response.close(); + } catch (IOException ex) { + logger.error("callTokenIntroEndpoint() IOExcpetion when closing http response: " + ex.getMessage()); + } + } + } + + private boolean testResourcesRespond(List resourcesToTest) { + for (Resource resource : resourcesToTest) { + ResourceInfo info = new ResourceWebClient().info(resource.getResourceRSPath(), new GeneralQueryRequest()); + if (info == null) { + return false; + } + } + return true; + } +} +