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

issue #3390 - add implicit search/history _type scoping #3504

Merged
merged 6 commits into from
Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions fhir-client/src/main/java/com/ibm/fhir/client/FHIRClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ public interface FHIRClient {
*/
public static final String PROPNAME_TENANT_ID = "fhirclient.tenant.id";

/**
* Returns the default FHIR base URL that is configured for this client instance
* @return the FHIR base URL with scheme, host, and path
*/
String getDefaultBaseUrl();

/**
* Returns a JAX-RS 2.0 WebTarget object associated with the REST API endpoint.
* @return a WebTarget instance that can be used to invoke REST APIs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,11 @@ private Builder addRequestHeaders(Builder builder, FHIRRequestHeader[] headers)
return builder;
}

@Override
public String getDefaultBaseUrl() {
return baseEndpointURL;
}

@Override
public WebTarget getWebTarget() throws Exception {
return client.target(getBaseEndpointURL());
Expand Down Expand Up @@ -973,7 +978,7 @@ private void setClientProperties(Properties clientProperties) {
this.clientProperties = clientProperties;
}

private String getBaseEndpointURL() {
public String getBaseEndpointURL() {
return baseEndpointURL;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,22 @@ public ResourcesConfigAdapter(PropertyGroup resourcesConfig) throws Exception {
}
}

/**
* @return whether the server is configured to prevent searches for one or more resource types
*/
public boolean isSearchRestricted() {
Set<String> searchableResourceTypes = typesByInteraction.get(Interaction.SEARCH);
return searchableResourceTypes == null || searchableResourceTypes.size() < ALL_CONCRETE_TYPES.size();
}

/**
* @return whether the server is configured to prevent history interactions for one or more resource types
*/
public boolean isHistoryRestricted() {
Set<String> resourceTypesSupportingHistory = typesByInteraction.get(Interaction.HISTORY);
return resourceTypesSupportingHistory == null || resourceTypesSupportingHistory.size() < ALL_CONCRETE_TYPES.size();
}

/**
* @return an immutable, non-null set of concrete supported resource types
* @throws Exception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ public Resource insert(Resource resource, List<ExtractedParameterValue> paramet
* @param conn
* @param parameterDao
* @param ifNoneMatch 0 for conditional create-on-update behavior; otherwise null
* @param resourcePayloadKey
* @param outInteractionStatus
* @param outIfNoneMatchVersion
* @return the resource_id for the entry we created
* @throws Exception
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import java.util.Set;
import java.util.logging.Logger;

import org.owasp.encoder.Encode;

import com.ibm.fhir.config.FHIRConfigHelper;
import com.ibm.fhir.config.FHIRConfiguration;
import com.ibm.fhir.config.FHIRRequestContext;
Expand All @@ -39,8 +41,6 @@
import com.ibm.fhir.persistence.context.impl.FHIRSystemHistoryContextImpl;
import com.ibm.fhir.persistence.exception.FHIRPersistenceException;

import org.owasp.encoder.Encode;

public class FHIRPersistenceUtil {
private static final Logger log = Logger.getLogger(FHIRPersistenceUtil.class.getName());

Expand Down Expand Up @@ -104,6 +104,10 @@ public static FHIRSystemHistoryContext parseSystemHistoryParameters(Map<String,
context.setLenient(lenient);
context.setHistorySortOrder(HistorySortOrder.DESC_LAST_UPDATED); // default is most recent first
try {
PropertyGroup pg = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES);
ResourcesConfigAdapter config = new ResourcesConfigAdapter(pg);
Set<String> typesSupportingHistory = config.getSupportedResourceTypes(Interaction.HISTORY);

for (String name : queryParameters.keySet()) {
List<String> values = queryParameters.get(name);
String first = values.get(0);
Expand All @@ -116,14 +120,6 @@ public static FHIRSystemHistoryContext parseSystemHistoryParameters(Map<String,
context.setCount(resourceCount);
}
} else if ("_type".equals(name)) {
if (values.isEmpty()) {
continue;
}

PropertyGroup pg = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES);
ResourcesConfigAdapter config = new ResourcesConfigAdapter(pg);
Set<String> typesSupportingHistory = config.getSupportedResourceTypes(Interaction.HISTORY);

for (String v: values) {
List<String> resourceTypes = Arrays.asList(v.split("\\s*,\\s*"));
for (String resourceType: resourceTypes) {
Expand Down Expand Up @@ -187,6 +183,12 @@ public static FHIRSystemHistoryContext parseSystemHistoryParameters(Map<String,
}
}

// if no _type parameter was passed but the history interaction is only supported for some subset of types
// then we need to set the supported resource types in the context
if (context.getResourceTypes().isEmpty() && config.isHistoryRestricted()) {
typesSupportingHistory.stream().forEach(context::addResourceType);
}

// Grab the return preference from the request context. We add it to the history
// context so we have everything we need in one place
FHIRRequestContext requestContext = FHIRRequestContext.get();
Expand Down Expand Up @@ -240,7 +242,7 @@ public static ResourceResult<Resource> createDeletedResourceResultMarker(String
public static com.ibm.fhir.model.type.Instant getUpdateTime() {
return com.ibm.fhir.model.type.Instant.now(ZoneOffset.UTC);
}

/**
* Creates and returns a copy of the passed resource with the {@code Resource.id}
* {@code Resource.meta.versionId}, and {@code Resource.meta.lastUpdated} elements replaced.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@
import java.util.logging.Logger;
import java.util.stream.Collectors;

import org.owasp.encoder.Encode;

import com.ibm.fhir.config.FHIRConfigHelper;
import com.ibm.fhir.config.FHIRConfiguration;
import com.ibm.fhir.config.FHIRRequestContext;
import com.ibm.fhir.config.Interaction;
import com.ibm.fhir.config.PropertyGroup;
import com.ibm.fhir.config.ResourcesConfigAdapter;
import com.ibm.fhir.config.PropertyGroup.PropertyEntry;
import com.ibm.fhir.config.ResourcesConfigAdapter;
import com.ibm.fhir.core.FHIRConstants;
import com.ibm.fhir.model.resource.CodeSystem;
import com.ibm.fhir.model.resource.CodeSystem.Concept;
Expand Down Expand Up @@ -83,8 +85,6 @@
import com.ibm.fhir.term.util.CodeSystemSupport;
import com.ibm.fhir.term.util.ValueSetSupport;

import org.owasp.encoder.Encode;

/**
* A helper class with methods for working with HL7 FHIR search.
*/
Expand Down Expand Up @@ -404,17 +404,21 @@ public FHIRSearchContext parseQueryParameters(Class<?> resourceType,
throw SearchExceptionUtil.buildNewInvalidSearchException("system search not supported with _include or _revinclude.");
}

// _type parameter is only supported for whole system searches
boolean isSystemSearch = Resource.class.equals(resourceType);
if (queryParameters.containsKey(SearchConstants.RESOURCE_TYPE) && !isSystemSearch) {
manageException("_type parameter is only supported for whole-system search", IssueType.NOT_SUPPORTED, context, false);
}

// Process the _type parameter(s)
if (queryParameters.containsKey(SearchConstants.RESOURCE_TYPE)) {
if (!Resource.class.equals(resourceType)) {
manageException("_type search parameter is only supported with system search", IssueType.NOT_SUPPORTED, context, false);
} else {
if (isSystemSearch) {
PropertyGroup pg = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES);
ResourcesConfigAdapter config = new ResourcesConfigAdapter(pg);
Set<String> searchableTypes = config.getSupportedResourceTypes(Interaction.SEARCH);

if (queryParameters.containsKey(SearchConstants.RESOURCE_TYPE)) {
List<String> values = queryParameters.get(SearchConstants.RESOURCE_TYPE);

PropertyGroup pg = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES);
ResourcesConfigAdapter config = new ResourcesConfigAdapter(pg);
Set<String> searchableTypes = config.getSupportedResourceTypes(Interaction.SEARCH);

for (String v: values) {
List<String> tmpResourceTypes = Arrays.asList(v.split("\\s*,\\s*"));
for (String tmpResourceType: tmpResourceTypes) {
Expand All @@ -430,6 +434,12 @@ public FHIRSearchContext parseQueryParameters(Class<?> resourceType,
}
}
}

// if no _type parameter was passed but the search interaction is only supported for some subset of types
// then we need to set the supported resource types in the context
if (resourceTypes.isEmpty() && config.isSearchRestricted()) {
resourceTypes.addAll(config.getSupportedResourceTypes(Interaction.SEARCH));
}
}

queryParameters.remove(SearchConstants.RESOURCE_TYPE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
import com.ibm.fhir.search.context.FHIRSearchContext;

/**
* This testng test class contains methods that test the parsing of the search result _type parameter in the
* SearchUtil class.
* Test the parsing of the search result _type parameter in the SearchUtil class.
*/
public class TypeParameterParseTest extends BaseSearchTest {

Expand Down Expand Up @@ -74,8 +73,7 @@ public void testTypeNonSystemSearch_strict() throws Exception {
searchHelper.parseQueryParameters(Patient.class, queryParameters, false, true);
} catch(Exception ex) {
isExceptionThrown = true;
assertEquals(ex.getMessage(), "_type search parameter is only supported with system search");

assertEquals(ex.getMessage(), "_type parameter is only supported for whole-system search");
}
assertTrue(isExceptionThrown);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
package com.ibm.fhir.server.test;

import static com.ibm.fhir.model.type.String.string;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertNotNull;
import static org.testng.AssertJUnit.assertTrue;
import static org.testng.AssertJUnit.fail;
Expand All @@ -32,6 +32,7 @@
import com.ibm.fhir.model.format.Format;
import com.ibm.fhir.model.generator.FHIRGenerator;
import com.ibm.fhir.model.resource.Bundle;
import com.ibm.fhir.model.resource.Bundle.Entry;
import com.ibm.fhir.model.resource.CapabilityStatement;
import com.ibm.fhir.model.resource.Immunization;
import com.ibm.fhir.model.resource.Observation;
Expand Down Expand Up @@ -78,7 +79,7 @@ public void testMetadataAPI() throws FHIRPathException, FHIRValidationException
CapabilityStatement conf = response.readEntity(CapabilityStatement.class);
assertNotNull(conf);
assertNotNull(conf.getFormat());
assertEquals(6, conf.getFormat().size());
assertEquals(conf.getFormat().size(), 6);
assertNotNull(conf.getVersion());
assertNotNull(conf.getName());

Expand Down Expand Up @@ -160,12 +161,12 @@ public void testMetadataAPI_XML() {
WebTarget target = getWebTarget();
Response response = target.path("metadata").request(FHIRMediaType.APPLICATION_FHIR_XML).get();
assertResponse(response, Response.Status.OK.getStatusCode());
assertEquals(FHIRMediaType.APPLICATION_FHIR_XML_TYPE, response.getMediaType());
assertEquals(response.getMediaType(), FHIRMediaType.APPLICATION_FHIR_XML_TYPE);

CapabilityStatement conf = response.readEntity(CapabilityStatement.class);
assertNotNull(conf);
assertNotNull(conf.getFormat());
assertEquals(6, conf.getFormat().size());
assertEquals(conf.getFormat().size(), 6);
assertNotNull(conf.getVersion());
assertNotNull(conf.getName());
}
Expand All @@ -180,12 +181,12 @@ public void testMetadataAPI_validFhirVersion() {
Collections.singletonMap(FHIRMediaType.FHIR_VERSION_PARAMETER, "4.0"));
Response response = target.path("metadata").request(mediaType).get();
assertResponse(response, Response.Status.OK.getStatusCode());
assertEquals(mediaType, response.getMediaType());
assertEquals(response.getMediaType(), mediaType);

CapabilityStatement conf = response.readEntity(CapabilityStatement.class);
assertNotNull(conf);
assertNotNull(conf.getFormat());
assertEquals(6, conf.getFormat().size());
assertEquals(conf.getFormat().size(), 6);
assertNotNull(conf.getVersion());
assertNotNull(conf.getName());
}
Expand Down Expand Up @@ -554,22 +555,26 @@ public void testUpdateObservation() throws Exception {
}

/**
* Tests the retrieval of the history for a previously saved and updated
* patient.
* Tests the retrieval of the history for a previously saved and updated patient.
*/
@Test(groups = { "server-basic" }, dependsOnMethods = { "testCreatePatient", "testUpdatePatient" })
public void testHistoryPatient() {
WebTarget target = getWebTarget();

// Call the 'history' API to retrieve both the original and updated versions of
// the patient.
// Call the 'history' API to retrieve both the original and updated versions of the patient.
String targetPath = "Patient/" + savedCreatedPatient.getId() + "/_history";
Response response = target.path(targetPath).request(FHIRMediaType.APPLICATION_FHIR_JSON).get();
assertResponse(response, Response.Status.OK.getStatusCode());

Bundle resources = response.readEntity(Bundle.class);
assertNotNull(resources);
assertEquals(2, resources.getEntry().size());
assertEquals(resources.getEntry().size(), 2);

for (Entry entry : resources.getEntry()) {
String fullUrl = entry.getFullUrl().getValue();
assertEquals(fullUrl, getRestBaseURL() + "/Patient/" + savedCreatedPatient.getId());
}

Patient updatedPatient = (Patient) resources.getEntry().get(0).getResource();
Patient originalPatient = (Patient) resources.getEntry().get(1).getResource();
// Make sure patient ids are equal, and versionIds are NOT equal.
Expand All @@ -583,22 +588,26 @@ public void testHistoryPatient() {
}

/**
* Tests the retrieval of the history for a previously saved and updated
* observation.
* Tests the retrieval of the history for a previously saved and updated observation.
*/
@Test(groups = { "server-basic" }, dependsOnMethods = { "testCreateObservation", "testUpdateObservation" })
public void testHistoryObservation() {
WebTarget target = getWebTarget();

// Call the 'history' API to retrieve both the original and updated versions of
// the observation.
// Call the 'history' API to retrieve both the original and updated versions of the observation.
String targetPath = "Observation/" + savedCreatedObservation.getId() + "/_history";
Response response = target.path(targetPath).request(FHIRMediaType.APPLICATION_FHIR_JSON).get();
assertResponse(response, Response.Status.OK.getStatusCode());

Bundle resources = response.readEntity(Bundle.class);
assertNotNull(resources);
assertEquals(2, resources.getEntry().size());
assertEquals(resources.getEntry().size(), 2);

for (Entry entry : resources.getEntry()) {
String fullUrl = entry.getFullUrl().getValue();
assertEquals(fullUrl, getRestBaseURL() + "/Observation/" + savedCreatedObservation.getId());
}

Observation updatedObservation = (Observation) resources.getEntry().get(0).getResource();
Observation originalObservation = (Observation) resources.getEntry().get(1).getResource();
// Make sure observation ids are equal, and versionIds are NOT equal.
Expand Down Expand Up @@ -728,6 +737,6 @@ public void testSearchObservation() {
assertResponse(response, Response.Status.OK.getStatusCode());
Bundle bundle = response.readEntity(Bundle.class);
assertNotNull(bundle);
assertEquals(1, bundle.getEntry().size());
assertEquals(bundle.getEntry().size(), 1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,7 @@ public void testBatchHistory() throws Exception {
String method = "testBatchHistory";
WebTarget target = getWebTarget();

// Perform a 'read' and a 'vread'.
// Get the history for patientB1
Bundle bundle = buildBundle(BundleType.BATCH);
bundle = addRequestToBundle(null, bundle, HTTPVerb.GET, "Patient/" + patientB1.getId() + "/_history",
null, null);
Expand All @@ -779,12 +779,14 @@ public void testBatchHistory() throws Exception {
assertGoodGetResponse(responseBundle.getEntry().get(0), Status.OK.getStatusCode());

// Take a peek at the result bundle.
Bundle resultSet = (Bundle) responseBundle.getEntry().get(0).getResource();
assertNotNull(resultSet);
assertTrue(resultSet.getEntry().size() > 1);
Bundle historyBundle = (Bundle) responseBundle.getEntry().get(0).getResource();
assertNotNull(historyBundle);

List<Bundle.Entry> resultSet = historyBundle.getEntry();
assertTrue(resultSet.size() > 1);

boolean result = false;
for(Bundle.Entry entry : resultSet.getEntry()) {
for(Bundle.Entry entry : resultSet) {
if(entry.getResponse() != null){
String returnedStatus = entry.getResponse().getStatus().getValue();
assertNotNull(returnedStatus);
Expand Down
Loading