Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize location hierarchy #87

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public enum CacheHelper {

Cache<String, String> stringCache;

Cache<String, List<String>> listStringCache;

CacheHelper() {
cache =
Caffeine.newBuilder()
Expand All @@ -42,6 +44,11 @@ public enum CacheHelper {
.expireAfterWrite(getCacheExpiryDurationInSeconds(), TimeUnit.SECONDS)
.maximumSize(DEFAULT_CACHE_SIZE)
.build();
listStringCache =
Caffeine.newBuilder()
.expireAfterWrite(getCacheExpiryDurationInSeconds(), TimeUnit.SECONDS)
.maximumSize(DEFAULT_CACHE_SIZE)
.build();
}

private int getCacheExpiryDurationInSeconds() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
Expand All @@ -16,8 +20,6 @@
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.r4.model.Binary;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Composition;
import org.hl7.fhir.r4.model.ListResource;
import org.hl7.fhir.r4.model.Location;
Expand Down Expand Up @@ -86,7 +88,9 @@ public List<LocationHierarchy> getLocationHierarchies(
List<String> preFetchAdminLevels,
List<String> postFetchAdminLevels,
Boolean filterInventory) {
return locationIds.parallelStream()
Set<String> uniqueLocationIds = new HashSet<>(locationIds);

return uniqueLocationIds.parallelStream()
.map(
locationId ->
getLocationHierarchy(
Expand All @@ -109,7 +113,7 @@ public LocationHierarchy getLocationHierarchyCore(
if (location != null) {
logger.info("Building Location Hierarchy of Location Id : {}", locationId);

List<Location> descendants = getDescendants(locationId, location, preFetchAdminLevels);
List<Location> descendants = getDescendants(locationId, preFetchAdminLevels, location);
if (filterInventory) {
descendants = filterLocationsByInventory(descendants);
}
Expand All @@ -136,12 +140,12 @@ public List<Location> getLocationHierarchyLocations(
List<Location> descendants;

if (CacheHelper.INSTANCE.skipCache()) {
descendants = getDescendants(locationId, parentLocation, preFetchAdminLevels);
descendants = getDescendants(locationId, preFetchAdminLevels, parentLocation);
} else {
descendants =
CacheHelper.INSTANCE.locationListCache.get(
locationId,
key -> getDescendants(locationId, parentLocation, preFetchAdminLevels));
key -> getDescendants(locationId, preFetchAdminLevels, parentLocation));
}
if (filterInventory) {
descendants = filterLocationsByInventory(descendants);
Expand All @@ -150,7 +154,7 @@ public List<Location> getLocationHierarchyLocations(
}

public List<Location> getDescendants(
String locationId, Location parentLocation, List<String> adminLevels) {
String locationId, List<String> adminLevels, Location parentLocation) {
IQuery<IBaseBundle> query =
getFhirClientForR4()
.search()
Expand All @@ -172,28 +176,35 @@ public List<Location> getDescendants(
adminLevelArray));
}

Bundle childLocationBundle =
query.usingStyle(SearchStyleEnum.POST).returnBundle(Bundle.class).execute();
Queue<String> locationQueue = new LinkedList<>();
locationQueue.add(locationId);

List<Location> allLocations = Collections.synchronizedList(new ArrayList<>());
if (parentLocation != null) {
allLocations.add(parentLocation);
}

if (childLocationBundle != null) {

childLocationBundle.getEntry().parallelStream()
.forEach(
childLocation -> {
Location childLocationEntity =
(Location) childLocation.getResource();
allLocations.add(childLocationEntity);
allLocations.addAll(
getDescendants(
childLocationEntity.getIdElement().getIdPart(),
null,
adminLevels));
});
while (!locationQueue.isEmpty()) {
String currentLocationId = locationQueue.poll();
Bundle childLocationBundle =
query.where(
new ReferenceClientParam(Location.SP_PARTOF)
.hasAnyOfIds(currentLocationId))
.usingStyle(SearchStyleEnum.POST)
.returnBundle(Bundle.class)
.execute();

if (childLocationBundle != null) {
childLocationBundle.getEntry().parallelStream()
.forEach(
childLocation -> {
Location childLocationEntity =
(Location) childLocation.getResource();
allLocations.add(childLocationEntity);
locationQueue.add(
childLocationEntity.getIdElement().getIdPart());
});
}
}

return allLocations;
Expand Down Expand Up @@ -259,15 +270,9 @@ public Bundle handleNonIdentifierRequest(
return getPaginatedLocations(request, selectedSyncLocations);

} else {
List<Location> locations =
practitionerDetailsEndpointHelper
.getPractitionerDetailsByKeycloakId(practitionerId)
.getFhirPractitionerDetails()
.getLocations();
List<String> locationIds = new ArrayList<>();
for (Location location : locations) {
locationIds.add(location.getIdElement().getIdPart());
}
List<String> locationIds =
practitionerDetailsEndpointHelper.getPractitionerLocationIdsByByKeycloakId(
practitionerId);
return getPaginatedLocations(request, locationIds);
}

Expand All @@ -287,11 +292,15 @@ public Bundle handleNonIdentifierRequest(
.collect(Collectors.toList());
return Utils.createBundle(resourceList);
} else {
List<String> locationIds =
practitionerDetailsEndpointHelper.getPractitionerLocationIdsByByKeycloakId(
practitionerId);
List<LocationHierarchy> locationHierarchies =
practitionerDetailsEndpointHelper
.getPractitionerDetailsByKeycloakId(practitionerId)
.getFhirPractitionerDetails()
.getLocationHierarchyList();
getLocationHierarchies(
locationIds,
preFetchAdminLevels,
postFetchAdminLevels,
filterInventory);
List<Resource> resourceList =
locationHierarchies.stream()
.map(locationHierarchy -> (Resource) locationHierarchy)
Expand Down Expand Up @@ -358,6 +367,7 @@ public Bundle getPaginatedLocations(HttpServletRequest request, List<String> loc
List<String> postFetchAdminLevels =
generateAdminLevels(administrativeLevelMin, administrativeLevelMax);
Map<String, String[]> parameters = new HashMap<>(request.getParameterMap());
Set<String> uniqueLocationIds = new HashSet<>(locationIds);

int count =
pageSize != null
Expand All @@ -370,18 +380,18 @@ public Bundle getPaginatedLocations(HttpServletRequest request, List<String> loc

int start = Math.max(0, (page - 1)) * count;

List<Resource> resourceLocations = new ArrayList<>();
for (String identifier : locationIds) {
Location parentLocation = getLocationById(identifier);
List<Location> locations =
getLocationHierarchyLocations(
identifier,
parentLocation,
preFetchAdminLevels,
postFetchAdminLevels,
filterInventory);
resourceLocations.addAll(locations);
}
List<Resource> resourceLocations =
uniqueLocationIds.parallelStream()
.flatMap(
identifier ->
getLocationHierarchyLocations(
identifier,
getLocationById(identifier),
preFetchAdminLevels,
postFetchAdminLevels,
filterInventory)
.stream())
.collect(Collectors.toList());
int totalEntries = resourceLocations.size();

int end = Math.min(start + count, resourceLocations.size());
Expand Down Expand Up @@ -443,23 +453,25 @@ public List<String> generateAdminLevels(

public List<Location> filterLocationsByAdminLevels(
List<Location> locations, List<String> postFetchAdminLevels) {
if (postFetchAdminLevels == null) {

if (postFetchAdminLevels == null || postFetchAdminLevels.isEmpty()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolves bug of filtering out all locations when adminlevels is not provided/is empty

return locations;
}
List<Location> allLocations = new ArrayList<>();
for (Location location : locations) {
for (CodeableConcept codeableConcept : location.getType()) {
List<Coding> codings = codeableConcept.getCoding();
for (Coding coding : codings) {
if (coding.getSystem().equals(Constants.DEFAULT_ADMIN_LEVEL_TYPE_URL)) {
if (postFetchAdminLevels.contains(coding.getCode())) {
allLocations.add(location);
}
}
}
}
}
return allLocations;

return locations.stream()
.filter(
location ->
location.getType().stream()
.flatMap(
codeableConcept ->
codeableConcept.getCoding().stream())
.anyMatch(
coding ->
Constants.DEFAULT_ADMIN_LEVEL_TYPE_URL
.equals(coding.getSystem())
&& postFetchAdminLevels.contains(
coding.getCode())))
.collect(Collectors.toList());
}

public List<Location> filterLocationsByInventory(List<Location> locations) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,70 @@ public PractitionerDetails getPractitionerDetailsByPractitioner(Practitioner pra
return practitionerDetails;
}

public List<String> getPractitionerLocationIdsByByKeycloakId(String keycloakUUID) {
logger.info("Searching for Practitioner with user id: " + keycloakUUID);
Practitioner practitioner = getPractitionerByIdentifier(keycloakUUID);
List<String> locationIds = new ArrayList<>();

if (practitioner != null) {
String practitionerId = getPractitionerIdentifier(practitioner);
if (CacheHelper.INSTANCE.skipCache()) {
locationIds = getPractitionerLocationIdsByByKeycloakIdCore(practitionerId);
} else {
locationIds =
CacheHelper.INSTANCE.listStringCache.get(
keycloakUUID,
key ->
getPractitionerLocationIdsByByKeycloakIdCore(
practitionerId));
}

} else {
logger.error("Practitioner with KC identifier : " + keycloakUUID + " not found");
}
return locationIds;
}

@VisibleForTesting
protected List<String> getPractitionerLocationIdsByByKeycloakIdCore(String practitionerId) {

logger.info("Searching for CareTeams with Practitioner Id: " + practitionerId);
Bundle careTeams = getCareTeams(practitionerId);
List<CareTeam> careTeamsList = mapBundleToCareTeams(careTeams);

logger.info(
"Searching for Organizations tied to CareTeams list of size : "
+ careTeamsList.size());
Set<String> careTeamManagingOrganizationIds =
getManagingOrganizationsOfCareTeamIds(careTeamsList);

List<PractitionerRole> practitionerRoleList =
getPractitionerRolesByPractitionerId(practitionerId);
logger.info("Practitioner Roles fetched: " + practitionerRoleList.size());

Set<String> practitionerOrganizationIds =
getOrganizationIdsByPractitionerRoles(practitionerRoleList);

Set<String> organizationIds =
Stream.concat(
careTeamManagingOrganizationIds.stream(),
practitionerOrganizationIds.stream())
.collect(Collectors.toSet());

logger.info("Searching for locations by organizations: " + organizationIds.size());

Bundle organizationAffiliationsBundle =
getOrganizationAffiliationsByOrganizationIdsBundle(organizationIds);

List<OrganizationAffiliation> organizationAffiliations =
mapBundleToOrganizationAffiliation(organizationAffiliationsBundle);

List<String> locationIds =
getLocationIdsByOrganizationAffiliations(organizationAffiliations);

return locationIds;
}

public PractitionerDetails getPractitionerDetailsByPractitionerCore(
String practitionerId, Practitioner practitioner) {

Expand Down Expand Up @@ -367,12 +431,14 @@ public static <T> Predicate<T> distinctByKey(Function<? super T, ?> uniqueKeyExt
return t -> seen.add(uniqueKeyExtractor.apply(t));
}

private List<PractitionerRole> getPractitionerRolesByPractitionerId(String practitionerId) {
@VisibleForTesting
protected List<PractitionerRole> getPractitionerRolesByPractitionerId(String practitionerId) {
Bundle practitionerRoles = getPractitionerRoles(practitionerId);
return mapBundleToPractitionerRolesWithOrganization(practitionerRoles);
}

private Set<String> getOrganizationIdsByPractitionerRoles(
@VisibleForTesting
protected Set<String> getOrganizationIdsByPractitionerRoles(
List<PractitionerRole> practitionerRoles) {
return practitionerRoles.stream()
.filter(PractitionerRole::hasOrganization)
Expand Down Expand Up @@ -427,7 +493,8 @@ protected List<CareTeam> getCareTeamsByOrganizationIds(List<String> organization
.collect(Collectors.toList());
}

private Bundle getCareTeams(String practitionerId) {
@VisibleForTesting
protected Bundle getCareTeams(String practitionerId) {
return getFhirClientForR4()
.search()
.forResource(CareTeam.class)
Expand Down Expand Up @@ -544,7 +611,8 @@ protected Set<String> getManagingOrganizationsOfCareTeamIds(List<CareTeam> careT
.collect(Collectors.toSet());
}

private List<CareTeam> mapBundleToCareTeams(Bundle careTeams) {
@VisibleForTesting
protected List<CareTeam> mapBundleToCareTeams(Bundle careTeams) {
return careTeams != null
? careTeams.getEntry().stream()
.map(bundleEntryComponent -> (CareTeam) bundleEntryComponent.getResource())
Expand All @@ -569,7 +637,8 @@ private List<Group> mapBundleToGroups(Bundle groupsBundle) {
: Collections.emptyList();
}

private List<OrganizationAffiliation> mapBundleToOrganizationAffiliation(
@VisibleForTesting
protected List<OrganizationAffiliation> mapBundleToOrganizationAffiliation(
Bundle organizationAffiliationBundle) {
return organizationAffiliationBundle != null
? organizationAffiliationBundle.getEntry().stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,12 +279,11 @@ public void testGetDescendantsWithAdminLevelFiltersReturnsLocationsWithinAdminLe
Mockito.doReturn(queryMock).when(queryMock).and(any(ICriterion.class));
Mockito.doReturn(queryMock).when(queryMock).usingStyle(SearchStyleEnum.POST);
Mockito.doReturn(queryMock).when(queryMock).returnBundle(Bundle.class);

Mockito.doReturn(firstBundleMock, secondBundleMock).when(queryMock).execute();

List<Location> descendants =
locationHierarchyEndpointHelper.getDescendants(
locationId, parentLocation, adminLevels);
locationId, adminLevels, parentLocation);

Assert.assertNotNull(descendants);
Assert.assertEquals(2, descendants.size());
Expand Down
Loading
Loading