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

Add a caching layer to FHIR requests #387

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -138,4 +138,9 @@ protected String paramToProp(@Nonnull String param) {

return super.paramToProp(param);
}

@Override
protected Condition deproxyResult(Condition result) {
return super.deproxyResult(result);
}
}
Expand Up @@ -116,9 +116,9 @@ private List<IBaseResource> get(IBundleProvider results) {
}

@Test
public void shouldGetConditionByUuid() {
public void get_shouldGetConditionByUuid() {
when(dao.get(CONDITION_UUID)).thenReturn(openmrsCondition);
when(conditionTranslator.toFhirResource(openmrsCondition)).thenReturn(fhirCondition);
when(conditionTranslator.toFhirResource(openmrsCondition, null)).thenReturn(fhirCondition);

org.hl7.fhir.r4.model.Condition condition = conditionService.get(CONDITION_UUID);

Expand All @@ -128,7 +128,7 @@ public void shouldGetConditionByUuid() {
}

@Test
public void shouldThrowExceptionWhenGetMissingUuid() {
public void get_shouldThrowExceptionForMissingUuid() {
assertThrows(ResourceNotFoundException.class, () -> conditionService.get(WRONG_CONDITION_UUID));
}

Expand Down Expand Up @@ -160,7 +160,7 @@ public void update_shouldUpdateExistingCondition() {
condition.setId(CONDITION_UUID);

when(dao.get(CONDITION_UUID)).thenReturn(openmrsCondition);
when(conditionTranslator.toFhirResource(openmrsCondition)).thenReturn(condition);
when(conditionTranslator.toFhirResource(openmrsCondition, null)).thenReturn(condition);
when(dao.createOrUpdate(openmrsCondition)).thenReturn(openmrsCondition);
when(conditionTranslator.toOpenmrsType(any(Condition.class), any(org.hl7.fhir.r4.model.Condition.class)))
.thenReturn(openmrsCondition);
Expand Down
4 changes: 4 additions & 0 deletions api/pom.xml
Expand Up @@ -48,6 +48,10 @@
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down
Expand Up @@ -10,10 +10,12 @@
package org.openmrs.module.fhir2.api;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import java.util.Collection;
import java.util.Optional;

import org.openmrs.module.fhir2.api.util.FhirCache;
import org.openmrs.module.fhir2.model.FhirConceptSource;

public interface FhirConceptSourceService {
Expand All @@ -22,5 +24,6 @@ public interface FhirConceptSourceService {

Optional<FhirConceptSource> getFhirConceptSourceByUrl(@Nonnull String url);

Optional<FhirConceptSource> getFhirConceptSourceByConceptSourceName(@Nonnull String sourceName);
Optional<FhirConceptSource> getFhirConceptSourceByConceptSourceName(@Nonnull String sourceName,
@Nullable FhirCache cache);
}
Expand Up @@ -23,7 +23,6 @@
import lombok.AccessLevel;
import lombok.Setter;
import org.hibernate.Criteria;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.openmrs.Obs;
import org.openmrs.annotation.Authorized;
Expand All @@ -32,19 +31,13 @@
import org.openmrs.module.fhir2.api.dao.FhirConditionDao;
import org.openmrs.module.fhir2.api.search.param.SearchParameterMap;
import org.openmrs.util.PrivilegeConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
@Setter(AccessLevel.PUBLIC)
@OpenmrsProfile(openmrsPlatformVersion = "2.0.5 - 2.1.*")
public class FhirConditionDaoImpl extends BaseFhirDao<Obs> implements FhirConditionDao<Obs> {

@Qualifier("sessionFactory")
@Autowired
private SessionFactory sessionFactory;

@Override
@Authorized(PrivilegeConstants.GET_OBS)
public Obs get(@Nonnull String uuid) {
Expand Down Expand Up @@ -131,4 +124,12 @@ protected String paramToProp(@Nonnull String param) {

return super.paramToProp(param);
}

@Override
protected Obs deproxyResult(Obs result) {
Obs obs = super.deproxyResult(result);
obs.setConcept(deproxyObject(obs.getConcept()));
obs.setPerson(deproxyObject(obs.getPerson()));
return obs;
}
}
Expand Up @@ -20,6 +20,8 @@
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import com.google.common.reflect.TypeToken;
import lombok.AccessLevel;
import lombok.Getter;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.openmrs.Auditable;
import org.openmrs.OpenmrsObject;
Expand All @@ -30,14 +32,20 @@
import org.openmrs.module.fhir2.api.dao.FhirDao;
import org.openmrs.module.fhir2.api.translators.OpenmrsFhirTranslator;
import org.openmrs.module.fhir2.api.translators.UpdatableOpenmrsTranslator;
import org.openmrs.module.fhir2.api.util.FhirCache;
import org.openmrs.module.fhir2.api.util.FhirUtils;
import org.openmrs.validator.ValidateUtil;
import org.springframework.beans.factory.annotation.Autowired;

@SuppressWarnings("UnstableApiUsage")
public abstract class BaseFhirService<T extends IAnyResource, U extends OpenmrsObject & Auditable> implements FhirService<T> {

protected final Class<? super T> resourceClass;

@Autowired
@Getter(AccessLevel.PROTECTED)
private FhirCache fhirCache;

protected BaseFhirService() {
// @formatter:off
TypeToken<T> resourceTypeToken = new TypeToken<T>(getClass()) {};
Expand All @@ -61,13 +69,13 @@ public T get(@Nonnull String uuid) {
"Resource of type " + resourceClass.getSimpleName() + " with ID " + uuid + " is gone/deleted");
}

return getTranslator().toFhirResource(openmrsObj);
return getTranslator().toFhirResource(openmrsObj, fhirCache);
}

@Override
public List<T> get(@Nonnull Collection<String> uuids) {
OpenmrsFhirTranslator<U, T> translator = getTranslator();
return getDao().get(uuids).stream().map(translator::toFhirResource).collect(Collectors.toList());
return getDao().get(uuids).stream().map(r -> translator.toFhirResource(r, fhirCache)).collect(Collectors.toList());
}

@Override
Expand Down Expand Up @@ -125,7 +133,9 @@ public T update(@Nonnull String uuid, @Nonnull T updatedResource) {

validateObject(updatedObject);

return translator.toFhirResource(getDao().createOrUpdate(updatedObject));
translator.invalidate(updatedObject, fhirCache);

return translator.toFhirResource(getDao().createOrUpdate(updatedObject), fhirCache);
}

@Override
Expand Down
Expand Up @@ -10,6 +10,7 @@
package org.openmrs.module.fhir2.api.impl;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import java.util.Collection;
import java.util.Optional;
Expand All @@ -18,6 +19,7 @@
import lombok.Setter;
import org.openmrs.module.fhir2.api.FhirConceptSourceService;
import org.openmrs.module.fhir2.api.dao.FhirConceptSourceDao;
import org.openmrs.module.fhir2.api.util.FhirCache;
import org.openmrs.module.fhir2.model.FhirConceptSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
Expand Down Expand Up @@ -45,7 +47,18 @@ public Optional<FhirConceptSource> getFhirConceptSourceByUrl(@Nonnull String url

@Override
@Transactional(readOnly = true)
public Optional<FhirConceptSource> getFhirConceptSourceByConceptSourceName(@Nonnull String sourceName) {
public Optional<FhirConceptSource> getFhirConceptSourceByConceptSourceName(@Nonnull String sourceName,
@Nullable FhirCache cache) {
if (sourceName == null) {
return Optional.empty();
}

if (cache != null) {
String cacheKey = "fhir-concept-source-" + sourceName;
return (Optional<FhirConceptSource>) cache.get(cacheKey,
k -> dao.getFhirConceptSourceByConceptSourceName(sourceName));
}

return dao.getFhirConceptSourceByConceptSourceName(sourceName);
}
}
Expand Up @@ -106,7 +106,7 @@ public List<IBaseResource> getResources(int fromIndex, int toIndex) {

List<U> returnedResourceList = dao
.getSearchResults(searchParameterMap, matchingResourceUuids.subList(firstResult, lastResult)).stream()
.map(translator::toFhirResource).filter(Objects::nonNull).collect(Collectors.toList());
.map(obj -> translator.toFhirResource(obj)).filter(Objects::nonNull).collect(Collectors.toList());

Set<IBaseResource> includedResources = searchQueryInclude.getIncludedResources(returnedResourceList,
this.searchParameterMap);
Expand Down
Expand Up @@ -9,14 +9,9 @@
*/
package org.openmrs.module.fhir2.api.translators;

import javax.annotation.Nonnull;

import org.hl7.fhir.r4.model.Identifier;
import org.openmrs.Order;

public interface OrderIdentifierTranslator extends OpenmrsFhirTranslator<Order, Identifier> {

@Override
public Identifier toFhirResource(@Nonnull Order order);
public interface OrderIdentifierTranslator extends ToFhirTranslator<Order, Identifier> {

}
Expand Up @@ -9,10 +9,16 @@
*/
package org.openmrs.module.fhir2.api.translators;

import javax.annotation.Nonnull;

import java.util.Locale;

import org.hl7.fhir.r4.model.Provenance;
import org.openmrs.OpenmrsObject;
import org.openmrs.module.fhir2.api.util.FhirCache;

/**
* Generic interface for a translator between OpenMRS data and FHIR resources
* Generic interface for a translator between OpenMRS data and FHIR provenance resources
*
* @param <T> OpenMRS data type
*/
Expand All @@ -27,11 +33,60 @@ public interface ProvenanceTranslator<T> {
*/
Provenance getCreateProvenance(T openMrsObject);

default Provenance getCreateProvenance(T openMrsObject, FhirCache cache) {
if (cache != null) {
String cacheKey = getCacheKey(openMrsObject, "create-provenance-");
if (cacheKey != null) {
try {
@SuppressWarnings("unchecked")
Provenance cached = (Provenance) cache.get(((OpenmrsObject) openMrsObject).getUuid(),
(k) -> getCreateProvenance(openMrsObject));
return cached;
}
catch (ClassCastException e) {
// we shouldn't really get here, but...
return getCreateProvenance(openMrsObject);
}
}
}

return getCreateProvenance(openMrsObject);
}

/**
* Maps an OpenMRS Object to a {@link org.hl7.fhir.r4.model.Provenance} resource
*
* @param openMrsObject the OpenMRS object to translate
* @return the corresponding {@link org.hl7.fhir.r4.model.Provenance} resource
*/
Provenance getUpdateProvenance(T openMrsObject);

default Provenance getUpdateProvenance(T openMrsObject, FhirCache cache) {
if (cache != null) {
String cacheKey = getCacheKey(openMrsObject, "update-provenance-");
if (cacheKey != null) {
try {
@SuppressWarnings("unchecked")
Provenance cached = (Provenance) cache.get(((OpenmrsObject) openMrsObject).getUuid(),
(k) -> getUpdateProvenance(openMrsObject));
return cached;
}
catch (ClassCastException e) {
// we shouldn't really get here, but...
return getUpdateProvenance(openMrsObject);
}
}
}

return getUpdateProvenance(openMrsObject);
}

default String getCacheKey(@Nonnull T data, String prefix) {
if (data instanceof OpenmrsObject) {
return prefix + data.getClass().getSimpleName().toLowerCase(Locale.ROOT) + "-"
+ ((OpenmrsObject) data).getUuid();
}

return null;
}
}
Expand Up @@ -10,6 +10,12 @@
package org.openmrs.module.fhir2.api.translators;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import java.util.Locale;

import org.openmrs.OpenmrsObject;
import org.openmrs.module.fhir2.api.util.FhirCache;

/**
* Generic interface for a translator between OpenMRS data and FHIR resources
Expand All @@ -21,9 +27,52 @@ public interface ToFhirTranslator<T, U> extends FhirTranslator {

/**
* Maps an OpenMRS data element to a FHIR resource
*
*
* @param data the OpenMRS data element to translate
* @return the corresponding FHIR resource
*/
U toFhirResource(@Nonnull T data);

/**
* Maps an OpenMRS data element to a FHIR resource
*
* @param data the OpenMRS data element to translate
* @param cache contextual cache
* @return the corresponding FHIR resource
*/
default U toFhirResource(@Nonnull T data, @Nullable FhirCache cache) {
if (cache != null) {
String cacheKey = getCacheKey(data);
if (cacheKey != null) {
try {
@SuppressWarnings("unchecked")
U cached = (U) cache.get(cacheKey, k -> toFhirResource(data));
return cached;
}
catch (ClassCastException e) {
// we shouldn't really get here, but...
return toFhirResource(data);
}
}
}

return toFhirResource(data);
}

default void invalidate(@Nonnull T data, FhirCache fhirCache) {
fhirCache.invalidate(getCacheKey(data));
}

default String getCacheKey(@Nonnull T data) {
if (data == null) {
return null;
}

if (data instanceof OpenmrsObject) {
return this.getClass().getSimpleName().toLowerCase(Locale.ROOT) + "-"
+ data.getClass().getSimpleName().toLowerCase(Locale.ROOT) + "-" + ((OpenmrsObject) data).getUuid();
}

return null;
}
}