From 7b8b020bdd09c69ac38e7193682b77871f6f444c Mon Sep 17 00:00:00 2001 From: lincmba Date: Thu, 21 Mar 2024 16:41:03 +0300 Subject: [PATCH 01/17] Add option to return location hierarchy in a list instead of a tree depending on a parameter set fixes https://github.com/opensrp/fhircore/issues/3161 --- .../LocationHierarchyEndpointHelper.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java index a1c55dc..628f4df 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java @@ -73,6 +73,41 @@ private List getLocationHierarchy(String locationId, Location parentLo return descendants(locationId, parentLocation); } + public List getLocationHierarchyAsList(String locationId) { + Location location = getLocationById(locationId); + if (location != null) { + return getLocationDescendants(locationId, location); + } else { + logger.error("LocationHierarchy with identifier: " + locationId + " not found"); + return new ArrayList<>(); + } + } + + private List getLocationDescendants(String locationId, Location parentLocation) { + List allLocations = new ArrayList<>(); + allLocations.add(parentLocation); + + Bundle childLocationBundle = + getFhirClientForR4() + .search() + .forResource(Location.class) + .where(new ReferenceClientParam(Location.SP_PARTOF).hasAnyOfIds(locationId)) + .returnBundle(Bundle.class) + .execute(); + + if (childLocationBundle != null) { + for (Bundle.BundleEntryComponent childLocation : childLocationBundle.getEntry()) { + Location childLocationEntity = (Location) childLocation.getResource(); + allLocations.addAll( + getLocationDescendants( + childLocationEntity.getIdElement().getIdPart(), + childLocationEntity)); + } + } + + return allLocations; + } + public List descendants(String locationId, Location parentLocation) { Bundle childLocationBundle = From ce9e23846665981a61f7758014deb084edcfb82f Mon Sep 17 00:00:00 2001 From: lincmba Date: Mon, 25 Mar 2024 15:59:26 +0300 Subject: [PATCH 02/17] Refactor methods, remove redundant parameters --- .../LocationHierarchyEndpointHelper.java | 46 ++----------------- 1 file changed, 4 insertions(+), 42 deletions(-) diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java index 628f4df..0f71766 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java @@ -56,7 +56,7 @@ public LocationHierarchy getLocationHierarchyCore(String locationId) { LocationHierarchy locationHierarchy = new LocationHierarchy(); if (location != null) { logger.info("Building Location Hierarchy of Location Id : " + locationId); - locationHierarchyTree.buildTreeFromList(getLocationHierarchy(locationId, location)); + locationHierarchyTree.buildTreeFromList(getDescendants(locationId)); StringType locationIdString = new StringType().setId(locationId).getIdElement(); locationHierarchy.setLocationId(locationIdString); locationHierarchy.setId(LOCATION_RESOURCE + locationId); @@ -69,46 +69,8 @@ public LocationHierarchy getLocationHierarchyCore(String locationId) { return locationHierarchy; } - private List getLocationHierarchy(String locationId, Location parentLocation) { - return descendants(locationId, parentLocation); - } - - public List getLocationHierarchyAsList(String locationId) { - Location location = getLocationById(locationId); - if (location != null) { - return getLocationDescendants(locationId, location); - } else { - logger.error("LocationHierarchy with identifier: " + locationId + " not found"); - return new ArrayList<>(); - } - } - - private List getLocationDescendants(String locationId, Location parentLocation) { - List allLocations = new ArrayList<>(); - allLocations.add(parentLocation); - - Bundle childLocationBundle = - getFhirClientForR4() - .search() - .forResource(Location.class) - .where(new ReferenceClientParam(Location.SP_PARTOF).hasAnyOfIds(locationId)) - .returnBundle(Bundle.class) - .execute(); - - if (childLocationBundle != null) { - for (Bundle.BundleEntryComponent childLocation : childLocationBundle.getEntry()) { - Location childLocationEntity = (Location) childLocation.getResource(); - allLocations.addAll( - getLocationDescendants( - childLocationEntity.getIdElement().getIdPart(), - childLocationEntity)); - } - } - - return allLocations; - } - - public List descendants(String locationId, Location parentLocation) { + public List getDescendants(String locationId) { + Location parentLocation = getLocationById(locationId); Bundle childLocationBundle = getFhirClientForR4() @@ -128,7 +90,7 @@ public List descendants(String locationId, Location parentLocation) { Location childLocationEntity = (Location) childLocation.getResource(); allLocations.add(childLocationEntity); allLocations.addAll( - descendants(childLocationEntity.getIdElement().getIdPart(), null)); + getDescendants(childLocationEntity.getIdElement().getIdPart())); } } From 249fda31eaf57f6a4e068147d97199768ae68ed6 Mon Sep 17 00:00:00 2001 From: lincmba Date: Tue, 26 Mar 2024 15:59:21 +0300 Subject: [PATCH 03/17] Flat Locations Hierarchy support --- .../fhir/gateway/plugins/Constants.java | 2 + .../plugins/LocationHierarchyEndpoint.java | 50 ++++++++++++++----- .../LocationHierarchyEndpointHelper.java | 3 +- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Constants.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Constants.java index 765e1cb..71b95b7 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Constants.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Constants.java @@ -33,6 +33,8 @@ public class Constants { public static final String DEFAULT_RELATED_ENTITY_TAG_URL = "https://smartregister.org/related-entity-location-tag-id"; public static final String ROLE_ALL_LOCATIONS = "ALL_LOCATIONS"; + public static final String MODE = "mode"; + public static final String LIST = "list"; public interface Literals { String EQUALS = "="; diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java index 2d95187..c0997f9 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java @@ -1,13 +1,18 @@ package org.smartregister.fhir.gateway.plugins; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; +import java.util.Objects; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.http.HttpStatus; +import org.hl7.fhir.r4.model.Location; +import org.hl7.fhir.r4.model.Resource; import org.smartregister.model.location.LocationHierarchy; import com.google.fhir.gateway.TokenVerifier; @@ -38,21 +43,42 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) try { RestUtils.checkAuthentication(request, tokenVerifier); String identifier = request.getParameter(Constants.IDENTIFIER); - - LocationHierarchy locationHierarchy = - locationHierarchyEndpointHelper.getLocationHierarchy(identifier); + String mode = request.getParameter(Constants.MODE); String resultContent; + if (Objects.equals(mode, Constants.LIST)) { + List locations = + locationHierarchyEndpointHelper.getDescendants(identifier); + List resourceLocations = new ArrayList<>(locations); + if (locations.isEmpty()) { + resultContent = + fhirR4JsonParser.encodeResourceToString( + createEmptyBundle( + request.getRequestURL() + + "?" + + request.getQueryString())); + } else { + resultContent = + fhirR4JsonParser.encodeResourceToString( + createBundle(resourceLocations)); + } - if (org.smartregister.utils.Constants.LOCATION_RESOURCE_NOT_FOUND.equals( - locationHierarchy.getId())) { - resultContent = - fhirR4JsonParser.encodeResourceToString( - createEmptyBundle( - request.getRequestURL() + "?" + request.getQueryString())); } else { - resultContent = - fhirR4JsonParser.encodeResourceToString( - createBundle(Collections.singletonList(locationHierarchy))); + LocationHierarchy locationHierarchy = + locationHierarchyEndpointHelper.getLocationHierarchy(identifier); + + if (org.smartregister.utils.Constants.LOCATION_RESOURCE_NOT_FOUND.equals( + locationHierarchy.getId())) { + resultContent = + fhirR4JsonParser.encodeResourceToString( + createEmptyBundle( + request.getRequestURL() + + "?" + + request.getQueryString())); + } else { + resultContent = + fhirR4JsonParser.encodeResourceToString( + createBundle(Collections.singletonList(locationHierarchy))); + } } response.setContentType("application/json"); response.getOutputStream().print(resultContent); diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java index 0f71766..b7f77ac 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java @@ -89,8 +89,7 @@ public List getDescendants(String locationId) { for (Bundle.BundleEntryComponent childLocation : childLocationBundle.getEntry()) { Location childLocationEntity = (Location) childLocation.getResource(); allLocations.add(childLocationEntity); - allLocations.addAll( - getDescendants(childLocationEntity.getIdElement().getIdPart())); + allLocations.addAll(getDescendants(childLocationEntity.getIdElement().getIdPart())); } } From beb7197c363a0bfd47015280696ae5c69e9016e2 Mon Sep 17 00:00:00 2001 From: lincmba Date: Wed, 27 Mar 2024 12:29:07 +0300 Subject: [PATCH 04/17] Fix errors. Duplicate location in descendants --- .../gateway/plugins/LocationHierarchyEndpoint.java | 4 +++- .../plugins/LocationHierarchyEndpointHelper.java | 14 +++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java index c0997f9..d18e3e9 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java @@ -46,8 +46,10 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) String mode = request.getParameter(Constants.MODE); String resultContent; if (Objects.equals(mode, Constants.LIST)) { + Location parentLocation = + locationHierarchyEndpointHelper.getLocationById(identifier); List locations = - locationHierarchyEndpointHelper.getDescendants(identifier); + locationHierarchyEndpointHelper.getDescendants(identifier, parentLocation); List resourceLocations = new ArrayList<>(locations); if (locations.isEmpty()) { resultContent = diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java index b7f77ac..c5c46b1 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java @@ -56,7 +56,7 @@ public LocationHierarchy getLocationHierarchyCore(String locationId) { LocationHierarchy locationHierarchy = new LocationHierarchy(); if (location != null) { logger.info("Building Location Hierarchy of Location Id : " + locationId); - locationHierarchyTree.buildTreeFromList(getDescendants(locationId)); + locationHierarchyTree.buildTreeFromList(getDescendants(locationId, location)); StringType locationIdString = new StringType().setId(locationId).getIdElement(); locationHierarchy.setLocationId(locationIdString); locationHierarchy.setId(LOCATION_RESOURCE + locationId); @@ -69,8 +69,11 @@ public LocationHierarchy getLocationHierarchyCore(String locationId) { return locationHierarchy; } - public List getDescendants(String locationId) { - Location parentLocation = getLocationById(locationId); + private List getLocationHierarchy(String locationId, Location parentLocation) { + return getDescendants(locationId, parentLocation); + } + + public List getDescendants(String locationId, Location parentLocation) { Bundle childLocationBundle = getFhirClientForR4() @@ -89,14 +92,15 @@ public List getDescendants(String locationId) { for (Bundle.BundleEntryComponent childLocation : childLocationBundle.getEntry()) { Location childLocationEntity = (Location) childLocation.getResource(); allLocations.add(childLocationEntity); - allLocations.addAll(getDescendants(childLocationEntity.getIdElement().getIdPart())); + allLocations.addAll( + getDescendants(childLocationEntity.getIdElement().getIdPart(), null)); } } return allLocations; } - private @Nullable Location getLocationById(String locationId) { + public @Nullable Location getLocationById(String locationId) { Location location = null; try { location = From 39c322718b3306bc1324aa0617babc2ebb210498 Mon Sep 17 00:00:00 2001 From: lincmba Date: Thu, 4 Apr 2024 13:00:31 +0300 Subject: [PATCH 05/17] Refactor SyncAccessDecision addPaginationLinks to be reusable --- .../gateway/plugins/SyncAccessDecision.java | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java index bf4e9e4..6861a24 100755 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java @@ -357,8 +357,20 @@ private Bundle postProcessModeListEntries( Bundle resultBundle = fhirR4Client.transaction().withBundle(requestBundle).execute(); - // add total - resultBundle.setTotal(requestBundle.getEntry().size()); + StringBuilder urlBuilder = new StringBuilder(request.getFhirServerBase()); + urlBuilder.append("/").append(request.getRequestPath()); + + return addPaginationLinks(urlBuilder, resultBundle, page, totalEntries, count, parameters); + } + + public static Bundle addPaginationLinks( + StringBuilder urlBuilder, + Bundle resultBundle, + int page, + int totalEntries, + int count, + Map parameters) { + resultBundle.setTotal(totalEntries); // add pagination links int nextPage = page < totalEntries / count ? page + 1 : 0; // 0 indicates no next page @@ -366,7 +378,7 @@ private Bundle postProcessModeListEntries( Bundle.BundleLinkComponent selfLink = new Bundle.BundleLinkComponent(); List link = new ArrayList<>(); - String selfUrl = constructUpdatedUrl(request, parameters); + String selfUrl = constructUpdatedUrl(new StringBuilder(urlBuilder), parameters); selfLink.setRelation(IBaseBundle.LINK_SELF); selfLink.setUrl(selfUrl); link.add(selfLink); @@ -375,7 +387,7 @@ private Bundle postProcessModeListEntries( if (nextPage > 0) { parameters.put( Constants.PAGINATION_PAGE_NUMBER, new String[] {String.valueOf(nextPage)}); - String nextUrl = constructUpdatedUrl(request, parameters); + String nextUrl = constructUpdatedUrl(new StringBuilder(urlBuilder), parameters); Bundle.BundleLinkComponent nextLink = new Bundle.BundleLinkComponent(); nextLink.setRelation(IBaseBundle.LINK_NEXT); nextLink.setUrl(nextUrl); @@ -384,13 +396,12 @@ private Bundle postProcessModeListEntries( if (prevPage > 0) { parameters.put( Constants.PAGINATION_PAGE_NUMBER, new String[] {String.valueOf(prevPage)}); - String prevUrl = constructUpdatedUrl(request, parameters); + String prevUrl = constructUpdatedUrl(new StringBuilder(urlBuilder), parameters); Bundle.BundleLinkComponent previousLink = new Bundle.BundleLinkComponent(); previousLink.setRelation(IBaseBundle.LINK_PREV); previousLink.setUrl(prevUrl); resultBundle.addLink(previousLink); } - return resultBundle; } @@ -475,27 +486,23 @@ private boolean isSyncUrl(RequestDetailsReader requestDetailsReader) { } private static String constructUpdatedUrl( - RequestDetailsReader requestDetails, Map parameters) { - StringBuilder updatedUrlBuilder = new StringBuilder(requestDetails.getFhirServerBase()); - - updatedUrlBuilder.append("/").append(requestDetails.getRequestPath()); - - updatedUrlBuilder.append("?"); + StringBuilder urlBuilder, Map parameters) { + urlBuilder.append("?"); for (Map.Entry entry : parameters.entrySet()) { String paramName = entry.getKey(); String[] paramValues = entry.getValue(); for (String paramValue : paramValues) { - updatedUrlBuilder.append(paramName).append("=").append(paramValue).append("&"); + urlBuilder.append(paramName).append("=").append(paramValue).append("&"); } } // Remove the trailing '&' if present - if (updatedUrlBuilder.charAt(updatedUrlBuilder.length() - 1) == '&') { - updatedUrlBuilder.deleteCharAt(updatedUrlBuilder.length() - 1); + if (urlBuilder.charAt(urlBuilder.length() - 1) == '&') { + urlBuilder.deleteCharAt(urlBuilder.length() - 1); } - return updatedUrlBuilder.toString(); + return urlBuilder.toString(); } private boolean isResourceTypeRequest(String requestPath) { From bdb1c92e91c681a5b4068bf4c09a8fafaa70a513 Mon Sep 17 00:00:00 2001 From: lincmba Date: Thu, 4 Apr 2024 13:01:02 +0300 Subject: [PATCH 06/17] Update tests to reflect refactored code --- .../fhir/gateway/plugins/SyncAccessDecisionTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecisionTest.java b/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecisionTest.java index 1900c63..2aea169 100755 --- a/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecisionTest.java +++ b/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecisionTest.java @@ -753,7 +753,7 @@ public void testPostProcessWithListModeHeaderPaginateEntriesBundle() throws IOEx // Verify returned result content from the server request has pagination links Assert.assertNotNull(resultContent); Assert.assertEquals( - "{\"resourceType\":\"Bundle\",\"id\":\"bundle-result-id\",\"type\":\"batch-response\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://test:8080/fhir/List?_page=1&_count=1\"},{\"relation\":\"next\",\"url\":\"http://test:8080/fhir/List?_page=2&_count=1\"}]}", + "{\"resourceType\":\"Bundle\",\"id\":\"bundle-result-id\",\"type\":\"batch-response\",\"total\":2,\"link\":[{\"relation\":\"self\",\"url\":\"http://test:8080/fhir/List?_page=1&_count=1\"},{\"relation\":\"next\",\"url\":\"http://test:8080/fhir/List?_page=2&_count=1\"}]}", resultContent); } @@ -838,7 +838,7 @@ public void testPostProcessWithListModeHeaderSearchByTagPaginateEntriesBundle() // Verify returned result content from the server request, has pagination links Assert.assertNotNull(resultContent); Assert.assertEquals( - "{\"resourceType\":\"Bundle\",\"id\":\"bundle-result-id\",\"type\":\"batch-response\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://test:8080/fhir/List?_page=2&_count=1\"},{\"relation\":\"previous\",\"url\":\"http://test:8080/fhir/List?_page=1&_count=1\"}]}", + "{\"resourceType\":\"Bundle\",\"id\":\"bundle-result-id\",\"type\":\"batch-response\",\"total\":2,\"link\":[{\"relation\":\"self\",\"url\":\"http://test:8080/fhir/List?_page=2&_count=1\"},{\"relation\":\"previous\",\"url\":\"http://test:8080/fhir/List?_page=1&_count=1\"}]}", resultContent); } From 1c7aae4d4c932a4d731fce267818d4f9e6fc9b98 Mon Sep 17 00:00:00 2001 From: lincmba Date: Thu, 4 Apr 2024 13:03:00 +0300 Subject: [PATCH 07/17] Paginate flattened location hierarchy results --- .../plugins/LocationHierarchyEndpoint.java | 37 +++++++++++++++++-- .../gateway/plugins/SyncAccessDecision.java | 14 +++---- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java index d18e3e9..fc6f292 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java @@ -3,7 +3,9 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import javax.servlet.annotation.WebServlet; @@ -11,6 +13,7 @@ import javax.servlet.http.HttpServletResponse; import org.apache.http.HttpStatus; +import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Location; import org.hl7.fhir.r4.model.Resource; import org.smartregister.model.location.LocationHierarchy; @@ -44,6 +47,21 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) RestUtils.checkAuthentication(request, tokenVerifier); String identifier = request.getParameter(Constants.IDENTIFIER); String mode = request.getParameter(Constants.MODE); + String pageSize = request.getParameter(Constants.PAGINATION_PAGE_SIZE); + String pageNumber = request.getParameter(Constants.PAGINATION_PAGE_NUMBER); + Map parameters = new HashMap<>(request.getParameterMap()); + + int count = + pageSize != null + ? Integer.parseInt(pageSize) + : Constants.PAGINATION_DEFAULT_PAGE_SIZE; + int page = + pageNumber != null + ? Integer.parseInt(pageNumber) + : Constants.PAGINATION_DEFAULT_PAGE_NUMBER; + + int start = Math.max(0, (page - 1)) * count; + String resultContent; if (Objects.equals(mode, Constants.LIST)) { Location parentLocation = @@ -51,6 +69,11 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) List locations = locationHierarchyEndpointHelper.getDescendants(identifier, parentLocation); List resourceLocations = new ArrayList<>(locations); + int totalEntries = locations.size(); + int end = Math.min(start + count, resourceLocations.size()); + + List paginatedResourceLocations = resourceLocations.subList(start, end); + if (locations.isEmpty()) { resultContent = fhirR4JsonParser.encodeResourceToString( @@ -59,9 +82,17 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) + "?" + request.getQueryString())); } else { - resultContent = - fhirR4JsonParser.encodeResourceToString( - createBundle(resourceLocations)); + Bundle resultBundle = createBundle(paginatedResourceLocations); + StringBuilder urlBuilder = new StringBuilder(request.getRequestURL()); + resultBundle = + SyncAccessDecision.addPaginationLinks( + urlBuilder, + resultBundle, + page, + totalEntries, + count, + parameters); + resultContent = fhirR4JsonParser.encodeResourceToString(resultBundle); } } else { diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java index 6861a24..7831d39 100755 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java @@ -364,12 +364,12 @@ private Bundle postProcessModeListEntries( } public static Bundle addPaginationLinks( - StringBuilder urlBuilder, - Bundle resultBundle, - int page, - int totalEntries, - int count, - Map parameters) { + StringBuilder urlBuilder, + Bundle resultBundle, + int page, + int totalEntries, + int count, + Map parameters) { resultBundle.setTotal(totalEntries); // add pagination links @@ -486,7 +486,7 @@ private boolean isSyncUrl(RequestDetailsReader requestDetailsReader) { } private static String constructUpdatedUrl( - StringBuilder urlBuilder, Map parameters) { + StringBuilder urlBuilder, Map parameters) { urlBuilder.append("?"); for (Map.Entry entry : parameters.entrySet()) { String paramName = entry.getKey(); From d28722a4d08c50f7c64bee4752690bd581661137 Mon Sep 17 00:00:00 2001 From: lincmba Date: Fri, 5 Apr 2024 13:36:54 +0300 Subject: [PATCH 08/17] Refactor Location Hierarchy pagination. Move method implementation to Helper class --- .../plugins/LocationHierarchyEndpoint.java | 53 ++----------------- .../LocationHierarchyEndpointHelper.java | 43 +++++++++++++++ 2 files changed, 46 insertions(+), 50 deletions(-) diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java index fc6f292..3493fd3 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java @@ -1,11 +1,7 @@ package org.smartregister.fhir.gateway.plugins; import java.io.IOException; -import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.Objects; import javax.servlet.annotation.WebServlet; @@ -14,8 +10,6 @@ import org.apache.http.HttpStatus; import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Location; -import org.hl7.fhir.r4.model.Resource; import org.smartregister.model.location.LocationHierarchy; import com.google.fhir.gateway.TokenVerifier; @@ -47,53 +41,12 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) RestUtils.checkAuthentication(request, tokenVerifier); String identifier = request.getParameter(Constants.IDENTIFIER); String mode = request.getParameter(Constants.MODE); - String pageSize = request.getParameter(Constants.PAGINATION_PAGE_SIZE); - String pageNumber = request.getParameter(Constants.PAGINATION_PAGE_NUMBER); - Map parameters = new HashMap<>(request.getParameterMap()); - - int count = - pageSize != null - ? Integer.parseInt(pageSize) - : Constants.PAGINATION_DEFAULT_PAGE_SIZE; - int page = - pageNumber != null - ? Integer.parseInt(pageNumber) - : Constants.PAGINATION_DEFAULT_PAGE_NUMBER; - - int start = Math.max(0, (page - 1)) * count; String resultContent; if (Objects.equals(mode, Constants.LIST)) { - Location parentLocation = - locationHierarchyEndpointHelper.getLocationById(identifier); - List locations = - locationHierarchyEndpointHelper.getDescendants(identifier, parentLocation); - List resourceLocations = new ArrayList<>(locations); - int totalEntries = locations.size(); - int end = Math.min(start + count, resourceLocations.size()); - - List paginatedResourceLocations = resourceLocations.subList(start, end); - - if (locations.isEmpty()) { - resultContent = - fhirR4JsonParser.encodeResourceToString( - createEmptyBundle( - request.getRequestURL() - + "?" - + request.getQueryString())); - } else { - Bundle resultBundle = createBundle(paginatedResourceLocations); - StringBuilder urlBuilder = new StringBuilder(request.getRequestURL()); - resultBundle = - SyncAccessDecision.addPaginationLinks( - urlBuilder, - resultBundle, - page, - totalEntries, - count, - parameters); - resultContent = fhirR4JsonParser.encodeResourceToString(resultBundle); - } + Bundle resultBundle = + locationHierarchyEndpointHelper.getPaginatedLocations(request); + resultContent = fhirR4JsonParser.encodeResourceToString(resultBundle); } else { LocationHierarchy locationHierarchy = diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java index c5c46b1..a353a06 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java @@ -4,12 +4,16 @@ import static org.smartregister.utils.Constants.LOCATION_RESOURCE_NOT_FOUND; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Location; +import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StringType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -111,4 +115,43 @@ public List getDescendants(String locationId, Location parentLocation) } return location; } + + public Bundle getPaginatedLocations(HttpServletRequest request) { + String identifier = request.getParameter(Constants.IDENTIFIER); + String pageSize = request.getParameter(Constants.PAGINATION_PAGE_SIZE); + String pageNumber = request.getParameter(Constants.PAGINATION_PAGE_NUMBER); + Map parameters = new HashMap<>(request.getParameterMap()); + + int count = + pageSize != null + ? Integer.parseInt(pageSize) + : Constants.PAGINATION_DEFAULT_PAGE_SIZE; + int page = + pageNumber != null + ? Integer.parseInt(pageNumber) + : Constants.PAGINATION_DEFAULT_PAGE_NUMBER; + + int start = Math.max(0, (page - 1)) * count; + Location parentLocation = getLocationById(identifier); + List locations = getDescendants(identifier, parentLocation); + List resourceLocations = new ArrayList<>(locations); + int totalEntries = locations.size(); + + int end = Math.min(start + count, resourceLocations.size()); + List paginatedResourceLocations = resourceLocations.subList(start, end); + Bundle resultBundle; + + if (locations.isEmpty()) { + resultBundle = + BaseEndpoint.createEmptyBundle( + request.getRequestURL() + "?" + request.getQueryString()); + } else { + resultBundle = BaseEndpoint.createBundle(paginatedResourceLocations); + StringBuilder urlBuilder = new StringBuilder(request.getRequestURL()); + SyncAccessDecision.addPaginationLinks( + urlBuilder, resultBundle, page, totalEntries, count, parameters); + } + + return resultBundle; + } } From e3c8fa8c8819cfadda48da5a9be0bd6e5a236528 Mon Sep 17 00:00:00 2001 From: lincmba Date: Fri, 5 Apr 2024 13:37:22 +0300 Subject: [PATCH 09/17] Test Location Hierarchy Flattening --- .../fhir/gateway/plugins/BaseEndpoint.java | 4 +- .../LocationHierarchyEndpointHelperTest.java | 60 +++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/BaseEndpoint.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/BaseEndpoint.java index a335a91..2595253 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/BaseEndpoint.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/BaseEndpoint.java @@ -11,7 +11,7 @@ import org.hl7.fhir.r4.model.Resource; public abstract class BaseEndpoint extends HttpServlet { - public Bundle createBundle(List resourceList) { + public static Bundle createBundle(List resourceList) { Bundle responseBundle = new Bundle(); List bundleEntryComponentList = new ArrayList<>(); @@ -24,7 +24,7 @@ public Bundle createBundle(List resourceList) { return responseBundle; } - public Bundle createEmptyBundle(String requestURL) { + public static Bundle createEmptyBundle(String requestURL) { Bundle responseBundle = new Bundle(); responseBundle.setId(UUID.randomUUID().toString()); Bundle.BundleLinkComponent linkComponent = new Bundle.BundleLinkComponent(); diff --git a/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelperTest.java b/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelperTest.java index 583eaa6..d19059e 100644 --- a/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelperTest.java +++ b/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelperTest.java @@ -5,11 +5,16 @@ import static org.mockito.Mockito.mock; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.Location; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; @@ -55,6 +60,50 @@ public void testGetLocationHierarchyFound() { assertEquals("Location Resource : 12345", locationHierarchy.getId()); } + @Test + public void testGetPaginatedLocationsPaginatesLocations() { + HttpServletRequest request = mock(HttpServletRequest.class); + Mockito.doReturn("12345").when(request).getParameter(Constants.IDENTIFIER); + Mockito.doReturn("2").when(request).getParameter(Constants.PAGINATION_PAGE_SIZE); + Mockito.doReturn("2").when(request).getParameter(Constants.PAGINATION_PAGE_NUMBER); + Mockito.doReturn(new StringBuffer("http://test:8080/LocationHierarchy")) + .when(request) + .getRequestURL(); + + Map parameters = new HashMap<>(); + // Populate the HashMap with the specified parameters + parameters.put(Constants.IDENTIFIER, new String[] {"12345"}); + parameters.put(Constants.PAGINATION_PAGE_SIZE, new String[] {"2"}); + parameters.put(Constants.PAGINATION_PAGE_NUMBER, new String[] {"2"}); + + Mockito.doReturn(parameters).when(request).getParameterMap(); + + LocationHierarchyEndpointHelper mockLocationHierarchyEndpointHelper = + mock(LocationHierarchyEndpointHelper.class); + List locations = createLocationList(5); + + Mockito.doCallRealMethod() + .when(mockLocationHierarchyEndpointHelper) + .getPaginatedLocations(request); + Mockito.doReturn(locations) + .when(mockLocationHierarchyEndpointHelper) + .getDescendants("12345", null); + + Bundle resultBundle = mockLocationHierarchyEndpointHelper.getPaginatedLocations(request); + + Assert.assertTrue(resultBundle.hasEntry()); + Assert.assertTrue(resultBundle.hasLink()); + Assert.assertTrue(resultBundle.hasTotal()); + Assert.assertEquals(2, resultBundle.getEntry().size()); + Assert.assertEquals(2, resultBundle.getLink().size()); + Assert.assertEquals( + "http://test:8080/LocationHierarchy?identifier=12345&_page=1&_count=2", + resultBundle.getLink("previous").getUrl()); + Assert.assertEquals( + "http://test:8080/LocationHierarchy?identifier=12345&_page=2&_count=2", + resultBundle.getLink("self").getUrl()); + } + private Bundle getLocationBundle() { Bundle bundleLocation = new Bundle(); bundleLocation.setId("Location/1234"); @@ -70,4 +119,15 @@ private Bundle getLocationBundle() { bundleLocation.addEntry(bundleEntryComponent); return bundleLocation; } + + private List createLocationList(int numLocations) { + // Create a list of locations + List locations = new ArrayList<>(); + for (int i = 0; i < numLocations; i++) { + Location location = new Location(); + location.setId(Integer.toString(i)); + locations.add(location); + } + return locations; + } } From 1a6bcd00a757e76c104ec128a03fdeba485df736 Mon Sep 17 00:00:00 2001 From: lincmba Date: Fri, 5 Apr 2024 13:47:56 +0300 Subject: [PATCH 10/17] Add next page link for second last page. fixes https://github.com/onaio/fhir-gateway-extension/issues/45 --- .../fhir/gateway/plugins/SyncAccessDecision.java | 2 +- .../gateway/plugins/LocationHierarchyEndpointHelperTest.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java index 7831d39..42662c7 100755 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java @@ -373,7 +373,7 @@ public static Bundle addPaginationLinks( resultBundle.setTotal(totalEntries); // add pagination links - int nextPage = page < totalEntries / count ? page + 1 : 0; // 0 indicates no next page + int nextPage = page < ((float)totalEntries / count) ? page + 1 : 0; // 0 indicates no next page int prevPage = page > 1 ? page - 1 : 0; // 0 indicates no previous page Bundle.BundleLinkComponent selfLink = new Bundle.BundleLinkComponent(); diff --git a/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelperTest.java b/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelperTest.java index d19059e..5efd449 100644 --- a/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelperTest.java +++ b/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelperTest.java @@ -95,13 +95,16 @@ public void testGetPaginatedLocationsPaginatesLocations() { Assert.assertTrue(resultBundle.hasLink()); Assert.assertTrue(resultBundle.hasTotal()); Assert.assertEquals(2, resultBundle.getEntry().size()); - Assert.assertEquals(2, resultBundle.getLink().size()); + Assert.assertEquals(3, resultBundle.getLink().size()); Assert.assertEquals( "http://test:8080/LocationHierarchy?identifier=12345&_page=1&_count=2", resultBundle.getLink("previous").getUrl()); Assert.assertEquals( "http://test:8080/LocationHierarchy?identifier=12345&_page=2&_count=2", resultBundle.getLink("self").getUrl()); + Assert.assertEquals( + "http://test:8080/LocationHierarchy?identifier=12345&_page=3&_count=2", + resultBundle.getLink("next").getUrl()); } private Bundle getLocationBundle() { From 89ff28d52cb2bbe6b9c971ef167b3201230bad04 Mon Sep 17 00:00:00 2001 From: lincmba Date: Fri, 5 Apr 2024 14:03:36 +0300 Subject: [PATCH 11/17] Update documentation. --- README.md | 13 +++++++++++++ .../fhir/gateway/plugins/SyncAccessDecision.java | 4 ++-- .../LocationHierarchyEndpointHelperTest.java | 4 ++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b9b7c77..8baf46a 100755 --- a/README.md +++ b/README.md @@ -308,6 +308,19 @@ Example: [GET] /List?_id=&_count=&_page=&_sort= ``` +##### LocationHierarchy list mode + +The LocationHierarchy endpoint supports two response formats: tree and list. By +default, the response format remains a tree, providing hierarchical location +data. In addition, clients can request the endpoint to return location resources +in a flat list format by providing a request parameter `mode=list`. + +Example: + +``` +[GET] /LocationHierarchy?identifier=&mode=list&_count=&_page=&_sort= +``` + #### Important Note: Developers, please update your client applications accordingly to accommodate diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java index 42662c7..7606e43 100755 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java @@ -372,8 +372,8 @@ public static Bundle addPaginationLinks( Map parameters) { resultBundle.setTotal(totalEntries); - // add pagination links - int nextPage = page < ((float)totalEntries / count) ? page + 1 : 0; // 0 indicates no next page + int nextPage = + page < ((float) totalEntries / count) ? page + 1 : 0; // 0 indicates no next page int prevPage = page > 1 ? page - 1 : 0; // 0 indicates no previous page Bundle.BundleLinkComponent selfLink = new Bundle.BundleLinkComponent(); diff --git a/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelperTest.java b/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelperTest.java index 5efd449..99d03bf 100644 --- a/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelperTest.java +++ b/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelperTest.java @@ -103,8 +103,8 @@ public void testGetPaginatedLocationsPaginatesLocations() { "http://test:8080/LocationHierarchy?identifier=12345&_page=2&_count=2", resultBundle.getLink("self").getUrl()); Assert.assertEquals( - "http://test:8080/LocationHierarchy?identifier=12345&_page=3&_count=2", - resultBundle.getLink("next").getUrl()); + "http://test:8080/LocationHierarchy?identifier=12345&_page=3&_count=2", + resultBundle.getLink("next").getUrl()); } private Bundle getLocationBundle() { From 48bdc3f7c935bbc8d92839f8a5850f3513accd4e Mon Sep 17 00:00:00 2001 From: lincmba Date: Tue, 9 Apr 2024 20:33:44 +0300 Subject: [PATCH 12/17] Make requested changes --- .../fhir/gateway/plugins/BaseEndpoint.java | 35 +------ .../plugins/LocationHierarchyEndpoint.java | 8 +- .../LocationHierarchyEndpointHelper.java | 9 +- .../plugins/PractitionerDetailEndpoint.java | 4 +- .../gateway/plugins/SyncAccessDecision.java | 66 +------------ .../fhir/gateway/plugins/Utils.java | 97 +++++++++++++++++++ 6 files changed, 111 insertions(+), 108 deletions(-) create mode 100644 plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/BaseEndpoint.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/BaseEndpoint.java index 2595253..fe343ec 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/BaseEndpoint.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/BaseEndpoint.java @@ -1,38 +1,5 @@ package org.smartregister.fhir.gateway.plugins; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.UUID; - import javax.servlet.http.HttpServlet; -import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Resource; - -public abstract class BaseEndpoint extends HttpServlet { - public static Bundle createBundle(List resourceList) { - Bundle responseBundle = new Bundle(); - List bundleEntryComponentList = new ArrayList<>(); - - for (Resource resource : resourceList) { - bundleEntryComponentList.add(new Bundle.BundleEntryComponent().setResource(resource)); - } - - responseBundle.setEntry(bundleEntryComponentList); - responseBundle.setTotal(bundleEntryComponentList.size()); - return responseBundle; - } - - public static Bundle createEmptyBundle(String requestURL) { - Bundle responseBundle = new Bundle(); - responseBundle.setId(UUID.randomUUID().toString()); - Bundle.BundleLinkComponent linkComponent = new Bundle.BundleLinkComponent(); - linkComponent.setRelation(Bundle.LINK_SELF); - linkComponent.setUrl(requestURL); - responseBundle.setLink(Collections.singletonList(linkComponent)); - responseBundle.setType(Bundle.BundleType.SEARCHSET); - responseBundle.setTotal(0); - return responseBundle; - } -} +public abstract class BaseEndpoint extends HttpServlet {} diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java index 3493fd3..085ae24 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpoint.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.util.Collections; -import java.util.Objects; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServletRequest; @@ -43,7 +42,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) String mode = request.getParameter(Constants.MODE); String resultContent; - if (Objects.equals(mode, Constants.LIST)) { + if (Constants.LIST.equals(mode)) { Bundle resultBundle = locationHierarchyEndpointHelper.getPaginatedLocations(request); resultContent = fhirR4JsonParser.encodeResourceToString(resultBundle); @@ -56,14 +55,15 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) locationHierarchy.getId())) { resultContent = fhirR4JsonParser.encodeResourceToString( - createEmptyBundle( + Utils.createEmptyBundle( request.getRequestURL() + "?" + request.getQueryString())); } else { resultContent = fhirR4JsonParser.encodeResourceToString( - createBundle(Collections.singletonList(locationHierarchy))); + Utils.createBundle( + Collections.singletonList(locationHierarchy))); } } response.setContentType("application/json"); diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java index a353a06..b217705 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java @@ -73,7 +73,8 @@ public LocationHierarchy getLocationHierarchyCore(String locationId) { return locationHierarchy; } - private List getLocationHierarchy(String locationId, Location parentLocation) { + private List getLocationHierarchyLocations( + String locationId, Location parentLocation) { return getDescendants(locationId, parentLocation); } @@ -143,12 +144,12 @@ public Bundle getPaginatedLocations(HttpServletRequest request) { if (locations.isEmpty()) { resultBundle = - BaseEndpoint.createEmptyBundle( + Utils.createEmptyBundle( request.getRequestURL() + "?" + request.getQueryString()); } else { - resultBundle = BaseEndpoint.createBundle(paginatedResourceLocations); + resultBundle = Utils.createBundle(paginatedResourceLocations); StringBuilder urlBuilder = new StringBuilder(request.getRequestURL()); - SyncAccessDecision.addPaginationLinks( + Utils.addPaginationLinks( urlBuilder, resultBundle, page, totalEntries, count, parameters); } diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PractitionerDetailEndpoint.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PractitionerDetailEndpoint.java index 7e07330..b05d26f 100755 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PractitionerDetailEndpoint.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PractitionerDetailEndpoint.java @@ -47,12 +47,12 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) practitionerDetails.getId())) { resultContent = fhirR4JsonParser.encodeResourceToString( - createEmptyBundle( + Utils.createEmptyBundle( request.getRequestURL() + "?" + request.getQueryString())); } else { resultContent = fhirR4JsonParser.encodeResourceToString( - createBundle(Collections.singletonList(practitionerDetails))); + Utils.createBundle(Collections.singletonList(practitionerDetails))); } response.setContentType("application/json"); diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java index 7606e43..13d3a3a 100755 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java @@ -20,7 +20,6 @@ import org.apache.http.HttpResponse; import org.apache.http.impl.client.BasicResponseHandler; import org.apache.http.util.TextUtils; -import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.ListResource; @@ -360,49 +359,8 @@ private Bundle postProcessModeListEntries( StringBuilder urlBuilder = new StringBuilder(request.getFhirServerBase()); urlBuilder.append("/").append(request.getRequestPath()); - return addPaginationLinks(urlBuilder, resultBundle, page, totalEntries, count, parameters); - } - - public static Bundle addPaginationLinks( - StringBuilder urlBuilder, - Bundle resultBundle, - int page, - int totalEntries, - int count, - Map parameters) { - resultBundle.setTotal(totalEntries); - - int nextPage = - page < ((float) totalEntries / count) ? page + 1 : 0; // 0 indicates no next page - int prevPage = page > 1 ? page - 1 : 0; // 0 indicates no previous page - - Bundle.BundleLinkComponent selfLink = new Bundle.BundleLinkComponent(); - List link = new ArrayList<>(); - String selfUrl = constructUpdatedUrl(new StringBuilder(urlBuilder), parameters); - selfLink.setRelation(IBaseBundle.LINK_SELF); - selfLink.setUrl(selfUrl); - link.add(selfLink); - resultBundle.setLink(link); - - if (nextPage > 0) { - parameters.put( - Constants.PAGINATION_PAGE_NUMBER, new String[] {String.valueOf(nextPage)}); - String nextUrl = constructUpdatedUrl(new StringBuilder(urlBuilder), parameters); - Bundle.BundleLinkComponent nextLink = new Bundle.BundleLinkComponent(); - nextLink.setRelation(IBaseBundle.LINK_NEXT); - nextLink.setUrl(nextUrl); - resultBundle.addLink(nextLink); - } - if (prevPage > 0) { - parameters.put( - Constants.PAGINATION_PAGE_NUMBER, new String[] {String.valueOf(prevPage)}); - String prevUrl = constructUpdatedUrl(new StringBuilder(urlBuilder), parameters); - Bundle.BundleLinkComponent previousLink = new Bundle.BundleLinkComponent(); - previousLink.setRelation(IBaseBundle.LINK_PREV); - previousLink.setUrl(prevUrl); - resultBundle.addLink(previousLink); - } - return resultBundle; + return Utils.addPaginationLinks( + urlBuilder, resultBundle, page, totalEntries, count, parameters); } private String getSyncTagUrl(String syncStrategy) { @@ -485,26 +443,6 @@ private boolean isSyncUrl(RequestDetailsReader requestDetailsReader) { return false; } - private static String constructUpdatedUrl( - StringBuilder urlBuilder, Map parameters) { - urlBuilder.append("?"); - for (Map.Entry entry : parameters.entrySet()) { - String paramName = entry.getKey(); - String[] paramValues = entry.getValue(); - - for (String paramValue : paramValues) { - urlBuilder.append(paramName).append("=").append(paramValue).append("&"); - } - } - - // Remove the trailing '&' if present - if (urlBuilder.charAt(urlBuilder.length() - 1) == '&') { - urlBuilder.deleteCharAt(urlBuilder.length() - 1); - } - - return urlBuilder.toString(); - } - private boolean isResourceTypeRequest(String requestPath) { if (!TextUtils.isEmpty(requestPath)) { String[] sections = requestPath.split(ProxyConstants.HTTP_URL_SEPARATOR); diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java new file mode 100644 index 0000000..bc4addc --- /dev/null +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java @@ -0,0 +1,97 @@ +package org.smartregister.fhir.gateway.plugins; + +import java.util.*; + +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Resource; + +public class Utils { + + public static Bundle addPaginationLinks( + StringBuilder urlBuilder, + Bundle resultBundle, + int page, + int totalEntries, + int count, + Map parameters) { + resultBundle.setTotal(totalEntries); + + int nextPage = + page < ((float) totalEntries / count) ? page + 1 : 0; // 0 indicates no next page + int prevPage = page > 1 ? page - 1 : 0; // 0 indicates no previous page + + Bundle.BundleLinkComponent selfLink = new Bundle.BundleLinkComponent(); + List link = new ArrayList<>(); + String selfUrl = constructUpdatedUrl(new StringBuilder(urlBuilder), parameters); + selfLink.setRelation(IBaseBundle.LINK_SELF); + selfLink.setUrl(selfUrl); + link.add(selfLink); + resultBundle.setLink(link); + + if (nextPage > 0) { + parameters.put( + Constants.PAGINATION_PAGE_NUMBER, new String[] {String.valueOf(nextPage)}); + String nextUrl = constructUpdatedUrl(new StringBuilder(urlBuilder), parameters); + Bundle.BundleLinkComponent nextLink = new Bundle.BundleLinkComponent(); + nextLink.setRelation(IBaseBundle.LINK_NEXT); + nextLink.setUrl(nextUrl); + resultBundle.addLink(nextLink); + } + if (prevPage > 0) { + parameters.put( + Constants.PAGINATION_PAGE_NUMBER, new String[] {String.valueOf(prevPage)}); + String prevUrl = constructUpdatedUrl(new StringBuilder(urlBuilder), parameters); + Bundle.BundleLinkComponent previousLink = new Bundle.BundleLinkComponent(); + previousLink.setRelation(IBaseBundle.LINK_PREV); + previousLink.setUrl(prevUrl); + resultBundle.addLink(previousLink); + } + return resultBundle; + } + + private static String constructUpdatedUrl( + StringBuilder urlBuilder, Map parameters) { + urlBuilder.append("?"); + for (Map.Entry entry : parameters.entrySet()) { + String paramName = entry.getKey(); + String[] paramValues = entry.getValue(); + + for (String paramValue : paramValues) { + urlBuilder.append(paramName).append("=").append(paramValue).append("&"); + } + } + + // Remove the trailing '&' if present + if (urlBuilder.charAt(urlBuilder.length() - 1) == '&') { + urlBuilder.deleteCharAt(urlBuilder.length() - 1); + } + + return urlBuilder.toString(); + } + + public static Bundle createBundle(List resourceList) { + Bundle responseBundle = new Bundle(); + List bundleEntryComponentList = new ArrayList<>(); + + for (Resource resource : resourceList) { + bundleEntryComponentList.add(new Bundle.BundleEntryComponent().setResource(resource)); + } + + responseBundle.setEntry(bundleEntryComponentList); + responseBundle.setTotal(bundleEntryComponentList.size()); + return responseBundle; + } + + public static Bundle createEmptyBundle(String requestURL) { + Bundle responseBundle = new Bundle(); + responseBundle.setId(UUID.randomUUID().toString()); + Bundle.BundleLinkComponent linkComponent = new Bundle.BundleLinkComponent(); + linkComponent.setRelation(Bundle.LINK_SELF); + linkComponent.setUrl(requestURL); + responseBundle.setLink(Collections.singletonList(linkComponent)); + responseBundle.setType(Bundle.BundleType.SEARCHSET); + responseBundle.setTotal(0); + return responseBundle; + } +} From 3171343fc7453cb91a51d198cd35a8281fdade98 Mon Sep 17 00:00:00 2001 From: lincmba Date: Thu, 11 Apr 2024 12:09:16 +0300 Subject: [PATCH 13/17] Cache flattened list --- .../fhir/gateway/plugins/Constants.java | 1 + .../LocationHierarchyEndpointHelper.java | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Constants.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Constants.java index 71b95b7..1adca15 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Constants.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Constants.java @@ -35,6 +35,7 @@ public class Constants { public static final String ROLE_ALL_LOCATIONS = "ALL_LOCATIONS"; public static final String MODE = "mode"; public static final String LIST = "list"; + public static final String FLAT_PREFIX = "flat-"; public interface Literals { String EQUALS = "="; diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java index b217705..3cfb072 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java @@ -12,6 +12,7 @@ import javax.servlet.http.HttpServletRequest; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.DomainResource; import org.hl7.fhir.r4.model.Location; import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StringType; @@ -75,7 +76,21 @@ public LocationHierarchy getLocationHierarchyCore(String locationId) { private List getLocationHierarchyLocations( String locationId, Location parentLocation) { - return getDescendants(locationId, parentLocation); + List descendants; + String flatLocation = Constants.FLAT_PREFIX + locationId; + + if (CacheHelper.INSTANCE.skipCache()) { + descendants = getDescendants(locationId, parentLocation); + } else { + descendants = + (List) + CacheHelper.INSTANCE.resourceCache.get( + flatLocation, + key -> + (DomainResource) + getDescendants(locationId, parentLocation)); + } + return descendants; } public List getDescendants(String locationId, Location parentLocation) { @@ -134,7 +149,7 @@ public Bundle getPaginatedLocations(HttpServletRequest request) { int start = Math.max(0, (page - 1)) * count; Location parentLocation = getLocationById(identifier); - List locations = getDescendants(identifier, parentLocation); + List locations = getLocationHierarchyLocations(identifier, parentLocation); List resourceLocations = new ArrayList<>(locations); int totalEntries = locations.size(); From 70d70c27d5c08b8d61e22e23960643491faac49e Mon Sep 17 00:00:00 2001 From: lincmba Date: Thu, 11 Apr 2024 12:09:53 +0300 Subject: [PATCH 14/17] Pump up version to 1.0.9 --- exec/pom.xml | 4 ++-- plugins/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exec/pom.xml b/exec/pom.xml index 5471d45..67bc688 100755 --- a/exec/pom.xml +++ b/exec/pom.xml @@ -4,7 +4,7 @@ org.smartregister opensrp-gateway-plugin - 1.0.8 + 1.0.9 exec @@ -70,7 +70,7 @@ org.smartregister plugins - 1.0.8 + 1.0.9 diff --git a/plugins/pom.xml b/plugins/pom.xml index f3e02dc..cc0872e 100644 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -4,7 +4,7 @@ org.smartregister opensrp-gateway-plugin - 1.0.8 + 1.0.9 plugins diff --git a/pom.xml b/pom.xml index 2a72cee..5a9e5ab 100755 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ org.smartregister opensrp-gateway-plugin - 1.0.8 + 1.0.9 pom From 2f56784754adfaee13be5c64f4517b246d7efa46 Mon Sep 17 00:00:00 2001 From: lincmba Date: Fri, 12 Apr 2024 16:38:53 +0300 Subject: [PATCH 15/17] Create Cache for ListLocation --- .../fhir/gateway/plugins/CacheHelper.java | 8 ++++++++ .../plugins/LocationHierarchyEndpointHelper.java | 10 ++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/CacheHelper.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/CacheHelper.java index b2e9e44..4709937 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/CacheHelper.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/CacheHelper.java @@ -6,6 +6,7 @@ import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.r4.model.DomainResource; +import org.hl7.fhir.r4.model.Location; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; @@ -16,6 +17,8 @@ public enum CacheHelper { Cache resourceCache; + Cache> locationListCache; + CacheHelper() { cache = Caffeine.newBuilder() @@ -27,6 +30,11 @@ public enum CacheHelper { .expireAfterWrite(getCacheExpiryDurationInSeconds(), TimeUnit.SECONDS) .maximumSize(DEFAULT_CACHE_SIZE) .build(); + locationListCache = + Caffeine.newBuilder() + .expireAfterWrite(getCacheExpiryDurationInSeconds(), TimeUnit.SECONDS) + .maximumSize(DEFAULT_CACHE_SIZE) + .build(); } private int getCacheExpiryDurationInSeconds() { diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java index 3cfb072..545e167 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/LocationHierarchyEndpointHelper.java @@ -12,7 +12,6 @@ import javax.servlet.http.HttpServletRequest; import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.DomainResource; import org.hl7.fhir.r4.model.Location; import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StringType; @@ -77,18 +76,13 @@ public LocationHierarchy getLocationHierarchyCore(String locationId) { private List getLocationHierarchyLocations( String locationId, Location parentLocation) { List descendants; - String flatLocation = Constants.FLAT_PREFIX + locationId; if (CacheHelper.INSTANCE.skipCache()) { descendants = getDescendants(locationId, parentLocation); } else { descendants = - (List) - CacheHelper.INSTANCE.resourceCache.get( - flatLocation, - key -> - (DomainResource) - getDescendants(locationId, parentLocation)); + CacheHelper.INSTANCE.locationListCache.get( + locationId, key -> getDescendants(locationId, parentLocation)); } return descendants; } From 2d07102daded67c49ed02b2de17faaa91465388c Mon Sep 17 00:00:00 2001 From: lincmba Date: Fri, 12 Apr 2024 16:39:35 +0300 Subject: [PATCH 16/17] Refactor code, utilize BaeEndpoint class --- .../smartregister/fhir/gateway/plugins/BaseEndpoint.java | 8 +++++++- .../org/smartregister/fhir/gateway/plugins/Constants.java | 1 - 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/BaseEndpoint.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/BaseEndpoint.java index fe343ec..0a481b6 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/BaseEndpoint.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/BaseEndpoint.java @@ -2,4 +2,10 @@ import javax.servlet.http.HttpServlet; -public abstract class BaseEndpoint extends HttpServlet {} +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.IParser; + +public abstract class BaseEndpoint extends HttpServlet { + protected final FhirContext fhirR4Context = FhirContext.forR4(); + protected final IParser fhirR4JsonParser = fhirR4Context.newJsonParser().setPrettyPrint(true); +} diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Constants.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Constants.java index 1adca15..71b95b7 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Constants.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Constants.java @@ -35,7 +35,6 @@ public class Constants { public static final String ROLE_ALL_LOCATIONS = "ALL_LOCATIONS"; public static final String MODE = "mode"; public static final String LIST = "list"; - public static final String FLAT_PREFIX = "flat-"; public interface Literals { String EQUALS = "="; From 4b90bd6489f9c3c0b9a15aae694c74c0ab81b524 Mon Sep 17 00:00:00 2001 From: lincmba Date: Fri, 12 Apr 2024 16:40:54 +0300 Subject: [PATCH 17/17] Add documentation on create bundle Update PracctionerDetailsEndpoint to use BaseEndpoint --- .../fhir/gateway/plugins/PractitionerDetailEndpoint.java | 4 ---- .../java/org/smartregister/fhir/gateway/plugins/Utils.java | 7 +++++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PractitionerDetailEndpoint.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PractitionerDetailEndpoint.java index b05d26f..11067b1 100755 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PractitionerDetailEndpoint.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PractitionerDetailEndpoint.java @@ -14,15 +14,11 @@ import com.google.fhir.gateway.TokenVerifier; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; @WebServlet("/PractitionerDetail") public class PractitionerDetailEndpoint extends BaseEndpoint { private final TokenVerifier tokenVerifier; - private final FhirContext fhirR4Context = FhirContext.forR4(); - private final IParser fhirR4JsonParser = fhirR4Context.newJsonParser().setPrettyPrint(true); private final PractitionerDetailsEndpointHelper practitionerDetailsEndpointHelper; public PractitionerDetailEndpoint() throws IOException { diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java index bc4addc..145b106 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java @@ -70,6 +70,13 @@ private static String constructUpdatedUrl( return urlBuilder.toString(); } + /** + * Creates a Bundle object containing a list of resources. This method set the total number of + * records in the resourceList + * + * @param resourceList The list of resources to include in the Bundle. + * @return A Bundle object containing the provided resources. + */ public static Bundle createBundle(List resourceList) { Bundle responseBundle = new Bundle(); List bundleEntryComponentList = new ArrayList<>();