Skip to content

Commit

Permalink
repository search by id (#353)
Browse files Browse the repository at this point in the history
* repository search by id

* apply formatting

* separate InMemory test

* remove redundancy

* search by id update

* search by id update test

* search by id update test script

* reformat

* Fix repository and tests

* Fixes for repo api

---------

Co-authored-by: Jonathan Percival <jonathan.i.percival@gmail.com>
Co-authored-by: JP <jonathan.percival@smilecdr.com>
  • Loading branch information
3 people authored Oct 3, 2023
1 parent a9af518 commit 4e7500e
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ <R extends IBaseResource, P extends IBaseParameters, T extends IBaseResource> R
*/
default <P extends IBaseParameters, T extends IBaseResource> MethodOutcome invoke(
Class<T> resourceType, String name, P parameters) {
return this.invoke(name, parameters, Collections.emptyMap());
return this.invoke(resourceType, name, parameters, Collections.emptyMap());
}

/**
Expand Down Expand Up @@ -520,7 +520,7 @@ <R extends IBaseResource, P extends IBaseParameters, I extends IIdType> R invoke
* @return a MethodOutcome with a status code
*/
default <P extends IBaseParameters, I extends IIdType> MethodOutcome invoke(I id, String name, P parameters) {
return this.invoke(name, parameters, Collections.emptyMap());
return this.invoke(id, name, parameters, Collections.emptyMap());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.BundleBuilder;
import ca.uhn.fhir.util.BundleUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -119,31 +122,56 @@ public <B extends IBaseBundle, T extends IBaseResource> B search(
Map<String, List<IQueryParameterType>> searchParameters,
Map<String, String> headers) {
BundleBuilder builder = new BundleBuilder(this.context);
builder.setType("searchset");

var resourceIdMap = resourceMap.computeIfAbsent(resourceType.getSimpleName(), r -> new HashMap<>());

var resourceList = resourceMap
.computeIfAbsent(resourceType.getSimpleName(), r -> new HashMap<>())
.values();
if (searchParameters == null || searchParameters.isEmpty()) {
resourceList.forEach(builder::addCollectionEntry);
} else {
var resourceMatcher = Repositories.getResourceMatcher(this.context);
for (var resource : resourceList) {
boolean include = true;
for (var nextEntry : searchParameters.entrySet()) {
var paramName = nextEntry.getKey();
if (!resourceMatcher.matches(paramName, nextEntry.getValue(), resource)) {
include = false;
break;
}
resourceIdMap.values().forEach(builder::addCollectionEntry);
return (B) builder.getBundle();
}

Collection<IBaseResource> candidates;
if (searchParameters.containsKey("_id")) {
// We are consuming the _id parameter in this if statement
var idQueries = searchParameters.get("_id");
searchParameters.remove("_id");

// The _id param can be a list of ids
var idResources = new ArrayList<IBaseResource>(idQueries.size());
for (var idQuery : idQueries) {
var idToken = (TokenParam) idQuery;
// Need to construct the equivalent "UnqualifiedVersionless" id that the map is
// indexed by. If an id has a version it won't match. Need apples-to-apples Ids types
var id = Ids.newId(context, resourceType.getSimpleName(), idToken.getValue());
var r = resourceIdMap.get(id);
if (r != null) {
idResources.add(r);
}
}

if (include) {
builder.addCollectionEntry(resource);
candidates = idResources;
} else {
candidates = resourceIdMap.values();
}

// Apply the rest of the filters
var resourceMatcher = Repositories.getResourceMatcher(this.context);
for (var resource : candidates) {
boolean include = true;
for (var nextEntry : searchParameters.entrySet()) {
var paramName = nextEntry.getKey();
if (!resourceMatcher.matches(paramName, nextEntry.getValue(), resource)) {
include = false;
break;
}
}

if (include) {
builder.addCollectionEntry(resource);
}
}

builder.setType("searchset");
return (B) builder.getBundle();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ public static SearchBuilder builder() {
return new SearchBuilder();
}

public static Map<String, List<IQueryParameterType>> byId(String... ids) {
return builder().withTokenParam("_id", ids).build();
}

public static Map<String, List<IQueryParameterType>> byId(String id) {
return builder().withTokenParam("_id", id).build();
}

public static Map<String, List<IQueryParameterType>> byCanonical(String canonical) {
if (canonical.contains("|")) {
var split = canonical.split("|");
Expand Down Expand Up @@ -108,14 +116,12 @@ public SearchBuilder withUriParam(String name, String value) {
return this;
}

SearchBuilder withTokenParam(String name, String value, String... values) {
SearchBuilder withTokenParam(String name, String... values) {
if (this.values == null) {
this.values = new HashMap<>();
}

var params = new ArrayList<IQueryParameterType>(1 + values.length);
params.add(new TokenParam(value));

for (var v : values) {
params.add(new TokenParam(v));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.opencds.cqf.fhir.utility.repository;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Encounter;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Library;
import org.junit.jupiter.api.Test;
import org.opencds.cqf.fhir.api.Repository;
import org.opencds.cqf.fhir.utility.search.Searches;

public class InMemoryRepositoryTest {

Repository repository;

public InMemoryRepositoryTest() {
repository = new InMemoryFhirRepository(FhirContext.forR4Cached());
repository.update(new Library().setId("Library/example1"));
repository.update(new Library().setUrl("http://example.com/123").setId("Library/example2"));
repository.update(new Encounter().setId("Encounter/example3"));
}

@Test
public void testRead() {
IBaseResource res = repository.read(Library.class, new IdType("Library/example1"), null);
assertEquals("example1", res.getIdElement().getIdPart());
}

@Test
public void testSearchWithId() {

var search = Searches.byId("example1");
var resources = repository.search(Bundle.class, Library.class, search);
// The _id parameter will be consumed if the index is being used.
assertTrue(search.isEmpty());
assertEquals(1, resources.getEntry().size());

search = Searches.byId("example3");
resources = repository.search(Bundle.class, Encounter.class, search);
assertTrue(search.isEmpty());
assertEquals(1, resources.getEntry().size());

search = Searches.byId("2345");
resources = repository.search(Bundle.class, Encounter.class, search);
assertTrue(search.isEmpty());
assertEquals(0, resources.getEntry().size());

search = Searches.byId("example1", "example2");
resources = repository.search(Bundle.class, Library.class, search);
assertTrue(search.isEmpty());
assertEquals(2, resources.getEntry().size());
}

@Test
public void testSearchWithUrl() {
var resources = repository.search(Bundle.class, Library.class, Searches.byUrl("http://example.com/123"));
assertEquals(1, resources.getEntry().size());
}
}

0 comments on commit 4e7500e

Please sign in to comment.