Skip to content

Commit

Permalink
#481: Fixed CDS Hooks service cache (#562)
Browse files Browse the repository at this point in the history
* #481: added interceptor to check update services cache when PlanDefinitions are inserted/updated/deleted ... added test

* #481: added CDS Hooks services cache as a resource listener ... updated tests

* Fixing tests
  • Loading branch information
c-schuler authored Jul 21, 2022
1 parent 4126b70 commit b977daa
Show file tree
Hide file tree
Showing 11 changed files with 728 additions and 65 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package org.opencds.cqf.ruler.cdshooks;

import java.util.concurrent.atomic.AtomicReference;

import com.google.gson.JsonArray;

import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.cache.IResourceChangeListenerRegistry;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import org.opencds.cqf.ruler.cdshooks.providers.ProviderConfiguration;
import org.opencds.cqf.ruler.cql.CqlProperties;
import org.opencds.cqf.ruler.external.annotations.OnDSTU3Condition;
Expand Down Expand Up @@ -33,9 +32,11 @@ public ProviderConfiguration providerConfiguration(CdsHooksProperties cdsPropert
return new ProviderConfiguration(cdsProperties, cqlProperties);
}

@Bean(name = "globalCdsServiceCache")
public AtomicReference<JsonArray> cdsServiceCache() {
return new AtomicReference<>();
@Bean
public CdsServicesCache cdsServiceInterceptor(IResourceChangeListenerRegistry resourceChangeListenerRegistry, DaoRegistry daoRegistry) {
CdsServicesCache listener = new CdsServicesCache(daoRegistry);
resourceChangeListenerRegistry.registerResourceResourceChangeListener("PlanDefinition", SearchParameterMap.newSynchronous(), listener, 1000);
return listener;
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.opencds.cqf.ruler.cdshooks;

import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.cache.IResourceChangeEvent;
import ca.uhn.fhir.jpa.cache.IResourceChangeListener;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.PlanDefinition;
import org.opencds.cqf.ruler.cdshooks.discovery.DiscoveryResolutionR4;
import org.opencds.cqf.ruler.cdshooks.discovery.DiscoveryResolutionStu3;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

public class CdsServicesCache implements IResourceChangeListener {
private static final Logger logger = LoggerFactory.getLogger(CdsServicesCache.class);

private AtomicReference<JsonArray> cdsServiceCache;
private IFhirResourceDao planDefinitionDao;
private DiscoveryResolutionR4 discoveryResolutionR4;
private DiscoveryResolutionStu3 discoveryResolutionStu3;

public CdsServicesCache(DaoRegistry daoRegistry) {
this.planDefinitionDao = daoRegistry.getResourceDao("PlanDefinition");
this.discoveryResolutionR4 = new DiscoveryResolutionR4(daoRegistry);
this.discoveryResolutionStu3 = new DiscoveryResolutionStu3(daoRegistry);
this.cdsServiceCache = new AtomicReference<>(new JsonArray());
}

public AtomicReference<JsonArray> getCdsServiceCache() {
return this.cdsServiceCache;
}

public void clearCache() {
this.cdsServiceCache = new AtomicReference<>(new JsonArray());
}

@Override
public void handleInit(Collection<IIdType> collection) {

}

@Override
public void handleChange(IResourceChangeEvent iResourceChangeEvent) {
if (iResourceChangeEvent == null) return;
if (iResourceChangeEvent.getCreatedResourceIds() != null && !iResourceChangeEvent.getCreatedResourceIds().isEmpty()) {
insert(iResourceChangeEvent.getCreatedResourceIds());
}
if (iResourceChangeEvent.getUpdatedResourceIds() != null && !iResourceChangeEvent.getUpdatedResourceIds().isEmpty()) {
update(iResourceChangeEvent.getUpdatedResourceIds());
}
if (iResourceChangeEvent.getDeletedResourceIds() != null && !iResourceChangeEvent.getDeletedResourceIds().isEmpty()) {
delete(iResourceChangeEvent.getDeletedResourceIds());
}
}

private void insert(List<IIdType> createdIds) {
for (IIdType id : createdIds) {
try {
IBaseResource resource = planDefinitionDao.read(id);
if (resource instanceof PlanDefinition) {
cdsServiceCache.get().add(discoveryResolutionR4.resolveService((PlanDefinition) resource));
} else if (resource instanceof org.hl7.fhir.dstu3.model.PlanDefinition) {
cdsServiceCache.get().add(discoveryResolutionStu3.resolveService((org.hl7.fhir.dstu3.model.PlanDefinition) resource));
}
} catch (Exception e) {
logger.info(String.format("Failed to create service for %s", id.getIdPart()));
}
}
}

private void update(List<IIdType> updatedIds) {
try {
delete(updatedIds);
insert(updatedIds);
} catch (Exception e) {
logger.info(String.format("Failed to update service(s) for %s", updatedIds));
}
}

private void delete(List<IIdType> deletedIds) {
for (IIdType id : deletedIds) {
for (int i = 0; i < cdsServiceCache.get().size(); i++) {
if (((JsonObject) cdsServiceCache.get().get(i)).get("id").getAsString().equals(id.getIdPart())) {
cdsServiceCache.get().remove(i);
break;
}
else logger.info(String.format("Failed to delete service for %s", id.getIdPart()));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.ArrayList;
import java.util.List;

import com.google.gson.JsonObject;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.DataRequirement;
Expand Down Expand Up @@ -198,6 +199,10 @@ public DiscoveryResponse resolve() {
return response;
}

public JsonObject resolveService(PlanDefinition planDefinition) {
return new DiscoveryElementR4(planDefinition, getPrefetchUrlList(planDefinition)).getAsJson();
}

private String mapCodePathToSearchParam(String dataType, String path) {
switch (dataType) {
case "MedicationAdministration":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.ArrayList;
import java.util.List;

import com.google.gson.JsonObject;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.DataRequirement;
Expand Down Expand Up @@ -207,6 +208,10 @@ public DiscoveryResponse resolve() {
return response;
}

public JsonObject resolveService(PlanDefinition planDefinition) {
return new DiscoveryElementStu3(planDefinition, getPrefetchUrlList(planDefinition)).getAsJson();
}

private String mapCodePathToSearchParam(String dataType, String path) {
switch (dataType) {
case "MedicationAdministration":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
Expand All @@ -27,7 +26,7 @@
import org.opencds.cqf.cql.engine.model.ModelResolver;
import org.opencds.cqf.cql.engine.terminology.TerminologyProvider;
import org.opencds.cqf.ruler.behavior.DaoRegistryUser;
import org.opencds.cqf.ruler.cdshooks.discovery.DiscoveryResolutionStu3;
import org.opencds.cqf.ruler.cdshooks.CdsServicesCache;
import org.opencds.cqf.ruler.cdshooks.evaluation.EvaluationContext;
import org.opencds.cqf.ruler.cdshooks.evaluation.Stu3EvaluationContext;
import org.opencds.cqf.ruler.cdshooks.hooks.Hook;
Expand Down Expand Up @@ -98,7 +97,7 @@ public class CdsHooksServlet extends HttpServlet implements DaoRegistryUser {
private ModelResolver modelResolver;

@Autowired
private AtomicReference<JsonArray> services;
CdsServicesCache cdsServicesCache;

protected ProviderConfiguration getProviderConfiguration() {
return this.providerConfiguration;
Expand Down Expand Up @@ -317,21 +316,13 @@ private JsonObject getService(String service) {
}

private JsonArray getServicesArray() {
JsonArray cachedServices = this.services.get();
if (cachedServices == null || cachedServices.size() == 0) {
cachedServices = getServices().get("services").getAsJsonArray();
services.set(cachedServices);
}

return cachedServices;
return this.cdsServicesCache.getCdsServiceCache().get();
}

private JsonObject getServices() {
DiscoveryResolutionStu3 discoveryResolutionStu3 = new DiscoveryResolutionStu3(daoRegistry);

discoveryResolutionStu3.setMaxUriLength(this.getProviderConfiguration().getMaxUriLength());

return discoveryResolutionStu3.resolve().getAsJson();
JsonObject services = new JsonObject();
services.add("services", this.cdsServicesCache.getCdsServiceCache().get());
return services;
}

private String toJsonResponse(List<CdsCard> cards) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
Expand All @@ -27,7 +26,7 @@
import org.opencds.cqf.cql.engine.model.ModelResolver;
import org.opencds.cqf.cql.engine.terminology.TerminologyProvider;
import org.opencds.cqf.ruler.behavior.DaoRegistryUser;
import org.opencds.cqf.ruler.cdshooks.discovery.DiscoveryResolutionR4;
import org.opencds.cqf.ruler.cdshooks.CdsServicesCache;
import org.opencds.cqf.ruler.cdshooks.evaluation.EvaluationContext;
import org.opencds.cqf.ruler.cdshooks.evaluation.R4EvaluationContext;
import org.opencds.cqf.ruler.cdshooks.hooks.Hook;
Expand Down Expand Up @@ -81,6 +80,7 @@ public class CdsHooksServlet extends HttpServlet implements DaoRegistryUser {

@Autowired
private LibraryLoaderFactory libraryLoaderFactory;

@Autowired
private JpaLibraryContentProviderFactory jpaLibraryContentProviderFactory;

Expand All @@ -97,7 +97,7 @@ public class CdsHooksServlet extends HttpServlet implements DaoRegistryUser {
private ModelResolver modelResolver;

@Autowired
private AtomicReference<JsonArray> services;
CdsServicesCache cdsServicesCache;

protected ProviderConfiguration getProviderConfiguration() {
return this.providerConfiguration;
Expand All @@ -119,6 +119,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

logger.info(request.getRequestURI());

if (!request.getRequestURL().toString().endsWith("/cds-services")
&& !request.getRequestURL().toString().endsWith("/cds-services/")) {
logger.error(request.getRequestURI());
Expand All @@ -132,8 +133,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)

@Override
@SuppressWarnings("deprecation")
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
logger.info(request.getRequestURI());

try {
Expand Down Expand Up @@ -317,21 +317,13 @@ private JsonObject getService(String service) {
}

private JsonArray getServicesArray() {
JsonArray cachedServices = this.services.get();
if (cachedServices == null || cachedServices.size() == 0) {
cachedServices = getServices().get("services").getAsJsonArray();
services.set(cachedServices);
}

return cachedServices;
return this.cdsServicesCache.getCdsServiceCache().get();
}

private JsonObject getServices() {
DiscoveryResolutionR4 discoveryResolutionR4 = new DiscoveryResolutionR4(daoRegistry);

discoveryResolutionR4.setMaxUriLength(this.getProviderConfiguration().getMaxUriLength());

return discoveryResolutionR4.resolve().getAsJson();
JsonObject services = new JsonObject();
services.add("services", this.cdsServicesCache.getCdsServiceCache().get());
return services;
}

private String toJsonResponse(List<CdsCard> cards) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.opencds.cqf.ruler.plugin.cdshooks;

import ca.uhn.fhir.jpa.cache.IResourceChangeEvent;
import org.hl7.fhir.instance.model.api.IIdType;

import java.util.List;

public class ResourceChangeEvent implements IResourceChangeEvent {
private List<IIdType> createdResourceIds;
private List<IIdType> updatedResourceIds;
private List<IIdType> deletedResourceIds;

@Override
public List<IIdType> getCreatedResourceIds() {
return this.createdResourceIds;
}

public void setCreatedResourceIds(List<IIdType> createdResourceIds) {
this.createdResourceIds = createdResourceIds;
}

@Override
public List<IIdType> getUpdatedResourceIds() {
return this.updatedResourceIds;
}

public void setUpdatedResourceIds(List<IIdType> updatedResourceIds) {
this.updatedResourceIds = updatedResourceIds;
}

@Override
public List<IIdType> getDeletedResourceIds() {
return this.deletedResourceIds;
}

public void setDeletedResourceIds(List<IIdType> deletedResourceIds) {
this.deletedResourceIds = deletedResourceIds;
}

@Override
public boolean isEmpty() {
return false;
}
}
Loading

0 comments on commit b977daa

Please sign in to comment.