Merge remote-tracking branch 'remotes/origin/master' into im_20200619_mariadb_expand_valueset_fix

This commit is contained in:
ianmarshall 2020-06-24 17:43:34 -04:00
commit 55ff15d039
39 changed files with 1374 additions and 158 deletions

View File

@ -25,6 +25,16 @@ jobs:
inputs:
targetType: 'inline'
script: mkdir -p $(MAVEN_CACHE_FOLDER); pwd; ls -al $(MAVEN_CACHE_FOLDER)
- task: Maven@3
env:
JAVA_HOME_11_X64: /usr/local/openjdk-11
inputs:
goals: 'dependency:go-offline'
# These are Maven CLI options (and show up in the build logs) - "-nsu"=Don't update snapshots. We can remove this when Maven OSS is more healthy
options: '-P ALLMODULES,JACOCO,CI,ERRORPRONE -e -B -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)'
# These are JVM options (and don't show up in the build logs)
mavenOptions: '-Xmx1024m $(MAVEN_OPTS) -Dorg.slf4j.simpleLogger.showDateTime=true -Dorg.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss,SSS -Duser.timezone=America/Toronto'
jdkVersionOption: 1.11
- task: Maven@3
env:
JAVA_HOME_11_X64: /usr/local/openjdk-11

View File

@ -31,6 +31,7 @@ import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -327,13 +328,7 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
}
private String getConformanceResourceUrl(IBaseResource theResource) {
String urlValueString = null;
Optional<IBase> urlValue = getFhirContext().getResourceDefinition(theResource).getChildByName("url").getAccessor().getFirstValueOrNull(theResource);
if (urlValue.isPresent()) {
IPrimitiveType<?> urlValueType = (IPrimitiveType<?>) urlValue.get();
urlValueString = urlValueType.getValueAsString();
}
return urlValueString;
return getConformanceResourceUrl(getFhirContext(), theResource);
}
private List<IBaseResource> parseBundle(InputStreamReader theReader) {
@ -346,6 +341,17 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
}
}
@Nullable
public static String getConformanceResourceUrl(FhirContext theFhirContext, IBaseResource theResource) {
String urlValueString = null;
Optional<IBase> urlValue = theFhirContext.getResourceDefinition(theResource).getChildByName("url").getAccessor().getFirstValueOrNull(theResource);
if (urlValue.isPresent()) {
IPrimitiveType<?> urlValueType = (IPrimitiveType<?>) urlValue.get();
urlValueString = urlValueType.getValueAsString();
}
return urlValueString;
}
static <T extends IBaseResource> List<T> toList(Map<String, IBaseResource> theMap) {
ArrayList<IBaseResource> retVal = new ArrayList<>(theMap.values());
return (List<T>) Collections.unmodifiableList(retVal);

View File

@ -1,5 +1,5 @@
---
type: fix
issue: 1895
title: "HAPI FHIR 5.0.0 introduced a regressin in JPA validator performance, where a number of unnecessary database lookups
title: "HAPI FHIR 5.0.0 introduced a regression in JPA validator performance, where a number of unnecessary database lookups
were introduced. This has been corrected."

View File

@ -0,0 +1,5 @@
---
type: add
issue: 1932
title: "A new configuration bean called ValidationSettings has been added to the JPA server. This can be used
to enable validation of reference target resources if necessary."

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 1933
title: "`BaseValidationSupportWrapper.expandValueSet(...)` and `ValidationSupportChain.expandValueSet(...)` were
incorrectly replacing expansion options (i.e. offset and count) with `null`, causing these parameters to be
ignored when invoking the `ValueSet$expand` operation. This has been corrected."

View File

@ -0,0 +1,8 @@
---
type: add
issue: 1934
title: "The **RemoteTerminologyServiceValidationSupport** validation support module, which is used to connect to
external/remote terminology services, has been significantly enhanced to provide testing for supported
CodeSystems and ValueSets. It will also now validate codes in fields that are not bound to a specific
ValueSet."

View File

@ -0,0 +1,6 @@
---
type: perf
issue: 1937
title: Due to an inefficient SQL statement when performing searches with large numbers of _revincludes where the resources
have tags, a large number of database roundtrips were produced when searching. This has been streamlined, greatly improving
the response times for some searches.

View File

@ -0,0 +1,5 @@
---
type: add
issue: 1939
title: "The JPA server is now able to support _has queries where the linked search expression on the right hand side of the
_has parameter is a second _has query."

View File

@ -4,6 +4,7 @@
title: "The version of a few dependencies have been bumped to the latest versions
(dependent HAPI modules listed in brackets):
<ul>
<li>Guava (JPA): 28.2-jre -&gt; 29.0-jre</li>
</ul>"
- item:
issue: "1862"

View File

@ -11,15 +11,15 @@ Here is an example of a full HAPI EMPI rules json document:
"candidateSearchParams": [
{
"resourceType": "Patient",
"searchParam": "birthdate"
"searchParams": ["given", "family"]
},
{
"resourceType": "*",
"searchParam": "identifier"
"searchParams": ["identifier"]
},
{
"resourceType": "Patient",
"searchParam": "general-practitioner"
"searchParams": ["general-practitioner"]
}
],
"candidateFilterSearchParams": [
@ -56,18 +56,21 @@ Here is an example of a full HAPI EMPI rules json document:
Here is a description of how each section of this document is configured.
* **candidateSearchParams**: These define fields which must have at least one exact match before two resources are considered for matching. This is like a list of "pre-searches" that find potential candidates for matches, to avoid the expensive operation of running a match score calculation on all resources in the system. E.g. you may only wish to consider matching two Patients if they either share at least one identifier in common or have the same birthday. The HAPI FHIR server executes each of these searches separately and then takes the union of the results, so you can think of these as `OR` criteria that cast a wide net for potential candidates. In some EMPI systems, these "pre-searches" are called "blocking" searches (since they identify "blocks" of candidates that will be searched for matches).
### candidateSearchParams
These define fields which must have at least one exact match before two resources are considered for matching. This is like a list of "pre-searches" that find potential candidates for matches, to avoid the expensive operation of running a match score calculation on all resources in the system. E.g. you may only wish to consider matching two Patients if they either share at least one identifier in common or have the same birthday. The HAPI FHIR server executes each of these searches separately and then takes the union of the results, so you can think of these as `OR` criteria that cast a wide net for potential candidates. In some EMPI systems, these "pre-searches" are called "blocking" searches (since they identify "blocks" of candidates that will be searched for matches).
```json
[ {
"resourceType" : "Patient",
"searchParam" : "birthdate"
"searchParams" : ["given", "family"]
}, {
"resourceType" : "Patient",
"searchParam" : "identifier"
} ]
```
* **candidateFilterSearchParams** When searching for match candidates, only resources that match this filter are considered. E.g. you may wish to only search for Patients for which active=true. Another way to think of these filters is all of them are "AND"ed with each candidateSearchParam above.
### candidateFilterSearchParams
When searching for match candidates, only resources that match this filter are considered. E.g. you may wish to only search for Patients for which active=true. Another way to think of these filters is all of them are "AND"ed with each candidateSearchParam above.
```json
[ {
"resourceType" : "Patient",
@ -76,7 +79,35 @@ Here is a description of how each section of this document is configured.
} ]
```
* **matchFields** Once the match candidates have been found, they are then each compared to the incoming Patient resource. This comparison is made across a list of `matchField`s. Each matchField returns `true` or `false` indicating whether the candidate and the incoming Patient match on that field. There are two types of metrics: `Matcher` and `Similarity`. Matcher metrics return a `true` or `false` directly, whereas Similarity metrics return a score between 0.0 (no match) and 1.0 (exact match) and this score is translated to a `true/false` via a `matchThreshold`. E.g. if a `JARO_WINKLER` matchField is configured with a `matchThreshold` of 0.8 then that matchField will return `true` if the `JARO_WINKLER` similarity evaluates to a score >= 8.0.
For example, if the incoming patient looked like this:
```json
{
"resourceType": "Patient",
"id": "example",
"identifier": [{
"system": "urn:oid:1.2.36.146.595.217.0.1",
"value": "12345"
}],
"name": [
{
"family": "Chalmers",
"given": [
"Peter",
"James"
]
}
}
```
then the above `candidateSearchParams` and `candidateFilterSearchParams` would result in the following two consecutive searches for candidates:
* `Patient?given=Peter,James&family=Chalmers&active=true`
* `Patient?identifier=urn:oid:1.2.36.146.595.217.0.1|12345&active=true`
### matchFields
Once the match candidates have been found, they are then each compared to the incoming Patient resource. This comparison is made across a list of `matchField`s. Each matchField returns `true` or `false` indicating whether the candidate and the incoming Patient match on that field. There are two types of metrics: `Matcher` and `Similarity`. Matcher metrics return a `true` or `false` directly, whereas Similarity metrics return a score between 0.0 (no match) and 1.0 (exact match) and this score is translated to a `true/false` via a `matchThreshold`. E.g. if a `JARO_WINKLER` matchField is configured with a `matchThreshold` of 0.8 then that matchField will return `true` if the `JARO_WINKLER` similarity evaluates to a score >= 8.0.
By default, all matchFields have `exact=false` which means that they will have all diacritical marks removed and converted to upper case before matching. `exact=true` can be added to any matchField to compare the strings as they are originally capitalized and accented.
@ -250,7 +281,9 @@ The following metrics are currently supported:
</tbody>
</table>
* **matchResultMap** converts combinations of successful matchFields into an EMPI Match Result for overall matching of a given pair of resources. MATCH results are evaluated take precedence over POSSIBLE_MATCH results.
### matchResultMap
These entries convert combinations of successful matchFields into an EMPI Match Result for overall matching of a given pair of resources. MATCH results are evaluated take precedence over POSSIBLE_MATCH results.
```json
{
@ -261,4 +294,6 @@ The following metrics are currently supported:
}
```
* **eidSystem**: The external EID system that the HAPI EMPI system should expect to see on incoming Patient resources. Must be a valid URI. See [EMPI EID](/hapi-fhir/docs/server_jpa_empi/empi_eid.html) for details on how EIDs are managed by HAPI EMPI.
### eidSystem
The external EID system that the HAPI EMPI system should expect to see on incoming Patient resources. Must be a valid URI. See [EMPI EID](/hapi-fhir/docs/server_jpa_empi/empi_eid.html) for details on how EIDs are managed by HAPI EMPI.

View File

@ -118,6 +118,12 @@ The following table lists vocabulary that is validated by this module:
This module validates codes using a remote FHIR-based terminology server.
This module will invoke the following operations on the remote terminology server:
* **GET [base]/CodeSystem?url=[url]** &ndash; Tests whether a given CodeSystem is supported on the server
* **GET [base]/ValueSet?url=[url]** &ndash; Tests whether a given ValueSet is supported on the server
* **POST [base]/CodeSystem/$validate-code** &ndash; Validate codes in fields where no specific ValueSet is bound
* **POST [base]/ValueSet/$validate-code** &ndash; Validate codes in fields where a specific ValueSet is bound
# Recipes

View File

@ -32,7 +32,9 @@ import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc;
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
import ca.uhn.fhir.jpa.validation.JpaFhirInstanceValidator;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
import ca.uhn.fhir.jpa.validation.ValidationSettings;
import ca.uhn.fhir.validation.IInstanceValidatorModule;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
@ -88,12 +90,17 @@ public abstract class BaseConfigDstu3Plus extends BaseConfig {
@Bean(name = "myInstanceValidator")
@Lazy
public IInstanceValidatorModule instanceValidator() {
FhirInstanceValidator val = new FhirInstanceValidator(fhirContext());
FhirInstanceValidator val = new JpaFhirInstanceValidator(fhirContext());
val.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Warning);
val.setValidationSupport(validationSupportChain());
return val;
}
@Bean
public ValidationSettings validationSettings() {
return new ValidationSettings();
}
@Bean
public abstract ITermReadSvc terminologyService();

View File

@ -32,7 +32,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTag;
public interface IResourceTagDao extends JpaRepository<ResourceTag, Long> {
@Query("" +
"SELECT t FROM ResourceTag t " +
"INNER JOIN TagDefinition td ON (td.myId = t.myTagId) " +
"INNER JOIN FETCH t.myTag td " +
"WHERE t.myResourceId in (:pids)")
Collection<ResourceTag> findByResourceIds(@Param("pids") Collection<Long> pids);

View File

@ -62,6 +62,7 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.param.CompositeParam;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.HasOrListParam;
import ca.uhn.fhir.rest.param.HasParam;
import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.ParameterUtil;
@ -944,30 +945,46 @@ class PredicateBuilderReference extends BasePredicateBuilder {
throw new InvalidRequestException("Invalid resource type: " + targetResourceType);
}
//Ensure that the name of the search param
// (e.g. the `code` in Patient?_has:Observation:subject:code=sys|val)
// exists on the target resource type.
RuntimeSearchParam owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName);
if (owningParameterDef == null) {
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + parameterName);
}
//Ensure that the name of the back-referenced search param on the target (e.g. the `subject` in Patient?_has:Observation:subject:code=sys|val)
//exists on the target resource.
owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramReference);
if (owningParameterDef == null) {
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + paramReference);
}
RuntimeSearchParam paramDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName);
IQueryParameterAnd<IQueryParameterOr<IQueryParameterType>> parsedParam = (IQueryParameterAnd<IQueryParameterOr<IQueryParameterType>>) ParameterUtil.parseQueryParams(myContext, paramDef, paramName, parameters);
ArrayList<IQueryParameterType> orValues = Lists.newArrayList();
for (IQueryParameterOr<IQueryParameterType> next : parsedParam.getValuesAsQueryTokens()) {
orValues.addAll(next.getValuesAsQueryTokens());
if (paramName.startsWith("_has:")) {
ourLog.trace("Handing double _has query: {}", paramName);
String qualifier = paramName.substring(4);
paramName = Constants.PARAM_HAS;
for (IQueryParameterType next : nextOrList) {
HasParam nextHasParam = new HasParam();
nextHasParam.setValueAsQueryToken(myContext, Constants.PARAM_HAS, qualifier, next.getValueAsQueryToken(myContext));
orValues.add(nextHasParam);
}
} else {
//Ensure that the name of the search param
// (e.g. the `code` in Patient?_has:Observation:subject:code=sys|val)
// exists on the target resource type.
RuntimeSearchParam owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName);
if (owningParameterDef == null) {
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + parameterName);
}
//Ensure that the name of the back-referenced search param on the target (e.g. the `subject` in Patient?_has:Observation:subject:code=sys|val)
//exists on the target resource.
owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramReference);
if (owningParameterDef == null) {
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + paramReference);
}
RuntimeSearchParam paramDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName);
IQueryParameterAnd<IQueryParameterOr<IQueryParameterType>> parsedParam = (IQueryParameterAnd<IQueryParameterOr<IQueryParameterType>>) ParameterUtil.parseQueryParams(myContext, paramDef, paramName, parameters);
for (IQueryParameterOr<IQueryParameterType> next : parsedParam.getValuesAsQueryTokens()) {
orValues.addAll(next.getValuesAsQueryTokens());
}
}
//Handle internal chain inside the has.
if (parameterName.contains(".")) {
String chainedPartOfParameter = getChainedPart(parameterName);

View File

@ -0,0 +1,114 @@
package ca.uhn.fhir.jpa.validation;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.JsonParser;
import org.hl7.fhir.r5.utils.IResourceValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Locale;
public class JpaFhirInstanceValidator extends FhirInstanceValidator {
private static final Logger ourLog = LoggerFactory.getLogger(JpaFhirInstanceValidator.class);
private final FhirContext myFhirContext;
@Autowired
private ValidationSettings myValidationSettings;
@Autowired
private DaoRegistry myDaoRegistry;
/**
* Constructor
*/
public JpaFhirInstanceValidator(FhirContext theFhirContext) {
super(theFhirContext);
myFhirContext = theFhirContext;
setValidatorResourceFetcher(new MyValidatorResourceFetcher());
}
private class MyValidatorResourceFetcher implements IResourceValidator.IValidatorResourceFetcher {
@SuppressWarnings("ConstantConditions")
@Override
public Element fetch(Object appContext, String theUrl) throws FHIRException {
IdType id = new IdType(theUrl);
String resourceType = id.getResourceType();
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceType);
IBaseResource target;
try {
target = dao.read(id, (RequestDetails) appContext);
} catch (ResourceNotFoundException e) {
ourLog.info("Failed to resolve local reference: {}", theUrl);
return null;
}
try {
return new JsonParser(provideWorkerContext()).parse(myFhirContext.newJsonParser().encodeResourceToString(target), resourceType);
} catch (Exception e) {
throw new FHIRException(e);
}
}
@Override
public IResourceValidator.ReferenceValidationPolicy validationPolicy(Object appContext, String path, String url) {
int slashIdx = url.indexOf("/");
if (slashIdx > 0 && myFhirContext.getResourceTypes().contains(url.substring(0, slashIdx))) {
return myValidationSettings.getLocalReferenceValidationDefaultPolicy();
}
return IResourceValidator.ReferenceValidationPolicy.IGNORE;
}
@Override
public boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException {
return true;
}
@Override
public byte[] fetchRaw(String url) throws IOException {
return new byte[0];
}
@Override
public void setLocale(Locale locale) {
// ignore
}
}
}

View File

@ -0,0 +1,61 @@
package ca.uhn.fhir.jpa.validation;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.hl7.fhir.r5.utils.IResourceValidator;
import org.thymeleaf.util.Validate;
import javax.annotation.Nonnull;
public class ValidationSettings {
private IResourceValidator.ReferenceValidationPolicy myLocalReferenceValidationDefaultPolicy = IResourceValidator.ReferenceValidationPolicy.IGNORE;
/**
* Supplies a default policy for validating local references. Default is {@literal IResourceValidator.ReferenceValidationPolicy.IGNORE}.
* <p>
* Note that this setting can have a measurable impact on validation performance, as it will cause reference targets
* to be resolved during validation. In other words, if a resource has a reference to (for example) "Patient/123", the
* resource with that ID will be loaded from the database during validation.
* </p>
*
* @since 5.1.0
*/
@Nonnull
public IResourceValidator.ReferenceValidationPolicy getLocalReferenceValidationDefaultPolicy() {
return myLocalReferenceValidationDefaultPolicy;
}
/**
* Supplies a default policy for validating local references. Default is {@literal IResourceValidator.ReferenceValidationPolicy.IGNORE}.
* <p>
* Note that this setting can have a measurable impact on validation performance, as it will cause reference targets
* to be resolved during validation. In other words, if a resource has a reference to (for example) "Patient/123", the
* resource with that ID will be loaded from the database during validation.
* </p>
*
* @since 5.1.0
*/
public void setLocalReferenceValidationDefaultPolicy(@Nonnull IResourceValidator.ReferenceValidationPolicy theLocalReferenceValidationDefaultPolicy) {
Validate.notNull(theLocalReferenceValidationDefaultPolicy, "theLocalReferenceValidationDefaultPolicy must not be null");
myLocalReferenceValidationDefaultPolicy = theLocalReferenceValidationDefaultPolicy;
}
}

View File

@ -4,11 +4,13 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.SqlQuery;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.ReferenceParam;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CareTeam;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.IdType;
@ -538,6 +540,53 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
}
@Test
public void testSearchOnReverseInclude() {
Patient patient = new Patient();
patient.getMeta().addTag("http://system", "value1", "display");
patient.setId("P1");
patient.getNameFirstRep().setFamily("FAM1");
myPatientDao.update(patient);
patient = new Patient();
patient.setId("P2");
patient.getMeta().addTag("http://system", "value1", "display");
patient.getNameFirstRep().setFamily("FAM2");
myPatientDao.update(patient);
for (int i = 0; i < 3; i++) {
CareTeam ct = new CareTeam();
ct.setId("CT1-" + i);
ct.getMeta().addTag("http://system", "value11", "display");
ct.getSubject().setReference("Patient/P1");
myCareTeamDao.update(ct);
ct = new CareTeam();
ct.setId("CT2-" + i);
ct.getMeta().addTag("http://system", "value22", "display");
ct.getSubject().setReference("Patient/P2");
myCareTeamDao.update(ct);
}
SearchParameterMap map = SearchParameterMap
.newSynchronous()
.addRevInclude(CareTeam.INCLUDE_SUBJECT)
.setSort(new SortSpec(Patient.SP_NAME));
myCaptureQueriesListener.clear();
IBundleProvider outcome = myPatientDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder(
"Patient/P1", "CareTeam/CT1-0", "CareTeam/CT1-1","CareTeam/CT1-2",
"Patient/P2", "CareTeam/CT2-0", "CareTeam/CT2-1","CareTeam/CT2-2"
));
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(4, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
}
@Test
public void testTransactionWithMultipleReferences() {
Bundle input = new Bundle();

View File

@ -876,6 +876,11 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("NOLINK");
obs.setDevice(new Reference(devId));
IIdType obsId = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
DiagnosticReport dr = new DiagnosticReport();
dr.addResult().setReference(obsId.getValue());
dr.setStatus(DiagnosticReport.DiagnosticReportStatus.FINAL);
myObservationDao.create(obs, mySrd);
}
@ -896,6 +901,51 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), empty());
}
@Test
public void testHasParameterDouble() {
// Matching
IIdType pid0;
{
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("00");
patient.addName().setFamily("Tester").addGiven("Joe");
pid0 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("NOLINK");
obs.setSubject(new Reference(pid0));
IIdType obsId = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
DiagnosticReport dr = new DiagnosticReport();
dr.addResult().setReference(obsId.getValue());
dr.setStatus(DiagnosticReport.DiagnosticReportStatus.FINAL);
myDiagnosticReportDao.create(dr, mySrd);
}
// Matching
{
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("001");
patient.addName().setFamily("Tester").addGiven("Joe");
IIdType pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("NOLINK");
obs.setSubject(new Reference(pid1));
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
}
SearchParameterMap params = SearchParameterMap.newSynchronous();
// Double _has
params = new SearchParameterMap();
params.add("_has", new HasParam("Observation", "subject", "_has:DiagnosticReport:result:status", "final"));
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), containsInAnyOrder(pid0.getValue()));
}
@Test
public void testHasParameterChained() {
IIdType pid0;

View File

@ -11,6 +11,7 @@ import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
import ca.uhn.fhir.jpa.validation.ValidationSettings;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
@ -35,6 +36,7 @@ import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Condition;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.Group;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Narrative;
import org.hl7.fhir.r4.model.Observation;
@ -42,6 +44,7 @@ import org.hl7.fhir.r4.model.Observation.ObservationStatus;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Practitioner;
import org.hl7.fhir.r4.model.Questionnaire;
import org.hl7.fhir.r4.model.QuestionnaireResponse;
import org.hl7.fhir.r4.model.Reference;
@ -86,6 +89,8 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
private JpaValidationSupportChain myJpaValidationSupportChain;
@Autowired
private PlatformTransactionManager myTransactionManager;
@Autowired
private ValidationSettings myValidationSettings;
/**
* Create a loinc valueset that expands to more results than the expander is willing to do
@ -187,6 +192,200 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
}
@Test
public void testValidateProfileTargetType_PolicyCheckValid() throws IOException {
myValidationSettings.setLocalReferenceValidationDefaultPolicy(IResourceValidator.ReferenceValidationPolicy.CHECK_VALID);
StructureDefinition profile = loadResourceFromClasspath(StructureDefinition.class, "/r4/profile-vitalsigns-all-loinc.json");
myStructureDefinitionDao.create(profile, mySrd);
ValueSet vs = new ValueSet();
vs.setUrl("http://example.com/fhir/ValueSet/observation-vitalsignresult");
vs.getCompose().addInclude().setSystem("http://loinc.org");
myValueSetDao.create(vs);
CodeSystem cs = new CodeSystem();
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
cs.setUrl("http://loinc.org");
cs.addConcept().setCode("123-4").setDisplay("Code 123 4");
myCodeSystemDao.create(cs);
Group group = new Group();
group.setId("ABC");
group.setActive(true);
myGroupDao.update(group);
Patient patient = new Patient();
patient.setId("DEF");
patient.setActive(true);
myPatientDao.update(patient);
Practitioner practitioner = new Practitioner();
practitioner.setId("P");
practitioner.setActive(true);
myPractitionerDao.update(practitioner);
Observation obs = new Observation();
obs.getMeta().addProfile("http://example.com/fhir/StructureDefinition/vitalsigns-2");
obs.getText().setDivAsString("<div>Hello</div>");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.addPerformer(new Reference("Practitioner/P"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
obs.setValue(new StringType("This is the value"));
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("123-4").setDisplay("Display 3");
// Non-existent target
obs.setSubject(new Reference("Group/123"));
OperationOutcome oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "Unable to resolve resource \"Group/123\"", oo.getIssueFirstRep().getDiagnostics());
// Target of wrong type
obs.setSubject(new Reference("Group/ABC"));
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "Invalid Resource target type. Found Group, but expected one of ([Patient])", oo.getIssueFirstRep().getDiagnostics());
// Target of right type
obs.setSubject(new Reference("Patient/DEF"));
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
}
@Test
public void testValidateProfileTargetType_PolicyCheckExistsAndType() throws IOException {
myValidationSettings.setLocalReferenceValidationDefaultPolicy(IResourceValidator.ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE);
StructureDefinition profile = loadResourceFromClasspath(StructureDefinition.class, "/r4/profile-vitalsigns-all-loinc.json");
myStructureDefinitionDao.create(profile, mySrd);
ValueSet vs = new ValueSet();
vs.setUrl("http://example.com/fhir/ValueSet/observation-vitalsignresult");
vs.getCompose().addInclude().setSystem("http://loinc.org");
myValueSetDao.create(vs);
CodeSystem cs = new CodeSystem();
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
cs.setUrl("http://loinc.org");
cs.addConcept().setCode("123-4").setDisplay("Code 123 4");
myCodeSystemDao.create(cs);
Group group = new Group();
group.setId("ABC");
group.setActive(true);
myGroupDao.update(group);
Patient patient = new Patient();
patient.setId("DEF");
patient.setActive(true);
myPatientDao.update(patient);
Practitioner practitioner = new Practitioner();
practitioner.setId("P");
practitioner.setActive(true);
myPractitionerDao.update(practitioner);
Observation obs = new Observation();
obs.getMeta().addProfile("http://example.com/fhir/StructureDefinition/vitalsigns-2");
obs.getText().setDivAsString("<div>Hello</div>");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.addPerformer(new Reference("Practitioner/P"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
obs.setValue(new StringType("This is the value"));
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("123-4").setDisplay("Display 3");
// Non-existent target
obs.setSubject(new Reference("Group/123"));
OperationOutcome oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "Unable to resolve resource \"Group/123\"", oo.getIssueFirstRep().getDiagnostics());
// Target of wrong type
obs.setSubject(new Reference("Group/ABC"));
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "Unable to find matching profile for Group/ABC (by type) among choices: ; [CanonicalType[http://hl7.org/fhir/StructureDefinition/Patient]]", oo.getIssueFirstRep().getDiagnostics());
// Target of right type
obs.setSubject(new Reference("Patient/DEF"));
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
}
@Test
public void testValidateProfileTargetType_PolicyCheckExists() throws IOException {
myValidationSettings.setLocalReferenceValidationDefaultPolicy(IResourceValidator.ReferenceValidationPolicy.CHECK_EXISTS);
StructureDefinition profile = loadResourceFromClasspath(StructureDefinition.class, "/r4/profile-vitalsigns-all-loinc.json");
myStructureDefinitionDao.create(profile, mySrd);
ValueSet vs = new ValueSet();
vs.setUrl("http://example.com/fhir/ValueSet/observation-vitalsignresult");
vs.getCompose().addInclude().setSystem("http://loinc.org");
myValueSetDao.create(vs);
CodeSystem cs = new CodeSystem();
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
cs.setUrl("http://loinc.org");
cs.addConcept().setCode("123-4").setDisplay("Code 123 4");
myCodeSystemDao.create(cs);
Group group = new Group();
group.setId("ABC");
group.setActive(true);
myGroupDao.update(group);
Patient patient = new Patient();
patient.setId("DEF");
patient.setActive(true);
myPatientDao.update(patient);
Practitioner practitioner = new Practitioner();
practitioner.setId("P");
practitioner.setActive(true);
myPractitionerDao.update(practitioner);
Observation obs = new Observation();
obs.getMeta().addProfile("http://example.com/fhir/StructureDefinition/vitalsigns-2");
obs.getText().setDivAsString("<div>Hello</div>");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.addPerformer(new Reference("Practitioner/P"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
obs.setValue(new StringType("This is the value"));
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("123-4").setDisplay("Display 3");
// Non-existent target
obs.setSubject(new Reference("Group/123"));
OperationOutcome oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "Unable to resolve resource \"Group/123\"", oo.getIssueFirstRep().getDiagnostics());
// Target of wrong type
obs.setSubject(new Reference("Group/ABC"));
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
// Target of right type
obs.setSubject(new Reference("Patient/DEF"));
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
}
/**
* Per: https://chat.fhir.org/#narrow/stream/179166-implementers/topic/Handling.20incomplete.20CodeSystems
* <p>
@ -743,6 +942,8 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
myDaoConfig.setPreExpandValueSets(new DaoConfig().isPreExpandValueSets());
BaseTermReadSvcImpl.setInvokeOnNextCallForUnitTest(null);
myValidationSettings.setLocalReferenceValidationDefaultPolicy(IResourceValidator.ReferenceValidationPolicy.IGNORE);
}
@Test

View File

@ -301,6 +301,103 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
}
@Test
public void testExpandByIdWithPreExpansionWithOffset() throws Exception {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
Parameters respParam = ourClient
.operation()
.onInstance(myExtensionalVsId)
.named("expand")
.withParameter(Parameters.class, "offset", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(1, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1000, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(23, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(0).getSystem());
assertEquals("11378-7", expanded.getExpansion().getContains().get(0).getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContains().get(0).getDisplay());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(1).getSystem());
assertEquals("8493-9", expanded.getExpansion().getContains().get(1).getCode());
assertEquals("Systolic blood pressure 10 hour minimum", expanded.getExpansion().getContains().get(1).getDisplay());
}
@Test
public void testExpandByIdWithPreExpansionWithCount() throws Exception {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
Parameters respParam = ourClient
.operation()
.onInstance(myExtensionalVsId)
.named("expand")
.withParameter(Parameters.class, "count", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(0, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(0, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("8450-9", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure--expiration", expanded.getExpansion().getContainsFirstRep().getDisplay());
}
@Test
public void testExpandByIdWithPreExpansionWithOffsetAndCount() throws Exception {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
Parameters respParam = ourClient
.operation()
.onInstance(myExtensionalVsId)
.named("expand")
.withParameter(Parameters.class, "offset", new IntegerType(1))
.andParameter("count", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(1, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("11378-7", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContainsFirstRep().getDisplay());
}
@Test
public void testExpandByIdWithFilter() throws Exception {
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
@ -402,6 +499,106 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
}
@Test
public void testExpandByUrlWithPreExpansionWithOffset() throws Exception {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
Parameters respParam = ourClient
.operation()
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
.andParameter("offset", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(1, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1000, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(23, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(0).getSystem());
assertEquals("11378-7", expanded.getExpansion().getContains().get(0).getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContains().get(0).getDisplay());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(1).getSystem());
assertEquals("8493-9", expanded.getExpansion().getContains().get(1).getCode());
assertEquals("Systolic blood pressure 10 hour minimum", expanded.getExpansion().getContains().get(1).getDisplay());
}
@Test
public void testExpandByUrlWithPreExpansionWithCount() throws Exception {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
Parameters respParam = ourClient
.operation()
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
.andParameter("count", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(0, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(0, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("8450-9", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure--expiration", expanded.getExpansion().getContainsFirstRep().getDisplay());
}
@Test
public void testExpandByUrlWithPreExpansionWithOffsetAndCount() throws Exception {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
Parameters respParam = ourClient
.operation()
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
.andParameter("offset", new IntegerType(1))
.andParameter("count", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(1, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("11378-7", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContainsFirstRep().getDisplay());
}
@Test
public void testExpandByUrlWithPreExpansionAndBogusUrl() throws Exception {
myDaoConfig.setPreExpandValueSets(true);
@ -448,7 +645,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
public void testExpandByValueSetWithPreExpansion() throws IOException {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystem(HTTPVerb.POST);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
@ -469,6 +666,112 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
}
@Test
public void testExpandByValueSetWithPreExpansionWithOffset() throws IOException {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
Parameters respParam = ourClient
.operation()
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "valueSet", toExpand)
.andParameter("offset", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(1, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1000, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(23, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(0).getSystem());
assertEquals("11378-7", expanded.getExpansion().getContains().get(0).getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContains().get(0).getDisplay());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(1).getSystem());
assertEquals("8493-9", expanded.getExpansion().getContains().get(1).getCode());
assertEquals("Systolic blood pressure 10 hour minimum", expanded.getExpansion().getContains().get(1).getDisplay());
}
@Test
public void testExpandByValueSetWithPreExpansionWithCount() throws IOException {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
Parameters respParam = ourClient
.operation()
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "valueSet", toExpand)
.andParameter("count", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(0, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(0, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("8450-9", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure--expiration", expanded.getExpansion().getContainsFirstRep().getDisplay());
}
@Test
public void testExpandByValueSetWithPreExpansionWithOffsetAndCount() throws IOException {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
Parameters respParam = ourClient
.operation()
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "valueSet", toExpand)
.andParameter("offset", new IntegerType(1))
.andParameter("count", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(1, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("11378-7", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContainsFirstRep().getDisplay());
}
@Test
public void testExpandInlineVsAgainstBuiltInCs() {
createLocalVsPointingAtBuiltInCodeSystem();

View File

@ -37,6 +37,7 @@ import ca.uhn.fhir.jpa.empi.broker.EmpiMessageHandler;
import ca.uhn.fhir.jpa.empi.broker.EmpiQueueConsumerLoader;
import ca.uhn.fhir.jpa.empi.interceptor.EmpiStorageInterceptor;
import ca.uhn.fhir.jpa.empi.interceptor.IEmpiStorageInterceptor;
import ca.uhn.fhir.jpa.empi.svc.EmpiCandidateSearchCriteriaBuilderSvc;
import ca.uhn.fhir.jpa.empi.svc.EmpiCandidateSearchSvc;
import ca.uhn.fhir.jpa.empi.svc.EmpiEidUpdateService;
import ca.uhn.fhir.jpa.empi.svc.EmpiLinkQuerySvcImpl;
@ -157,6 +158,11 @@ public class EmpiConsumerConfig {
return new EmpiCandidateSearchSvc();
}
@Bean
EmpiCandidateSearchCriteriaBuilderSvc empiCriteriaBuilderSvc() {
return new EmpiCandidateSearchCriteriaBuilderSvc();
}
@Bean
EmpiResourceMatcherSvc empiResourceComparatorSvc(FhirContext theFhirContext, IEmpiSettings theEmpiConfig) {
return new EmpiResourceMatcherSvc(theFhirContext, theEmpiConfig);

View File

@ -0,0 +1,45 @@
package ca.uhn.fhir.jpa.empi.svc;
import ca.uhn.fhir.empi.rules.json.EmpiResourceSearchParamJson;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service
public class EmpiCandidateSearchCriteriaBuilderSvc {
@Autowired
private EmpiSearchParamSvc myEmpiSearchParamSvc;
/*
* Given a list of criteria upon which to block, a resource search parameter, and a list of values for that given search parameter,
* build a query url. e.g.
*
* Patient?active=true&name.given=Gary,Grant&name.family=Graham
*/
@Nonnull
public Optional<String> buildResourceQueryString(String theResourceType, IAnyResource theResource, List<String> theFilterCriteria, EmpiResourceSearchParamJson resourceSearchParam) {
List<String> criteria = new ArrayList<>();
resourceSearchParam.iterator().forEachRemaining(searchParam -> {
//to compare it to all known PERSON objects, using the overlapping search parameters that they have.
List<String> valuesFromResourceForSearchParam = myEmpiSearchParamSvc.getValueFromResourceForSearchParam(theResource, searchParam);
if (!valuesFromResourceForSearchParam.isEmpty()) {
criteria.add(buildResourceMatchQuery(searchParam, valuesFromResourceForSearchParam));
}
});
if (criteria.isEmpty()) {
return Optional.empty();
}
criteria.addAll(theFilterCriteria);
return Optional.of(theResourceType + "?" + String.join("&", criteria));
}
private String buildResourceMatchQuery(String theSearchParamName, List<String> theResourceValues) {
return theSearchParamName + "=" + String.join(",", theResourceValues);
}
}

View File

@ -35,13 +35,12 @@ import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import static ca.uhn.fhir.empi.api.EmpiConstants.ALL_RESOURCE_SEARCH_PARAM_TYPE;
@ -58,6 +57,11 @@ public class EmpiCandidateSearchSvc {
private DaoRegistry myDaoRegistry;
@Autowired
private IdHelperService myIdHelperService;
@Autowired
private EmpiCandidateSearchCriteriaBuilderSvc myEmpiCandidateSearchCriteriaBuilderSvc;
public EmpiCandidateSearchSvc() {
}
/**
* Given a target resource, search for all resources that are considered an EMPI match based on defined EMPI rules.
@ -81,13 +85,7 @@ public class EmpiCandidateSearchSvc {
continue;
}
//to compare it to all known PERSON objects, using the overlapping search parameters that they have.
List<String> valuesFromResourceForSearchParam = myEmpiSearchParamSvc.getValueFromResourceForSearchParam(theResource, resourceSearchParam);
if (valuesFromResourceForSearchParam.isEmpty()) {
continue;
}
searchForIdsAndAddToMap(theResourceType, matchedPidsToResources, filterCriteria, resourceSearchParam, valuesFromResourceForSearchParam);
searchForIdsAndAddToMap(theResourceType, theResource, matchedPidsToResources, filterCriteria, resourceSearchParam);
}
//Obviously we don't want to consider the freshly added resource as a potential candidate.
//Sometimes, we are running this function on a resource that has not yet been persisted,
@ -111,9 +109,13 @@ public class EmpiCandidateSearchSvc {
* 4. Store all results in `theMatchedPidsToResources`
*/
@SuppressWarnings("rawtypes")
private void searchForIdsAndAddToMap(String theResourceType, Map<Long, IAnyResource> theMatchedPidsToResources, List<String> theFilterCriteria, EmpiResourceSearchParamJson resourceSearchParam, List<String> theValuesFromResourceForSearchParam) {
private void searchForIdsAndAddToMap(String theResourceType, IAnyResource theResource, Map<Long, IAnyResource> theMatchedPidsToResources, List<String> theFilterCriteria, EmpiResourceSearchParamJson resourceSearchParam) {
//1.
String resourceCriteria = buildResourceQueryString(theResourceType, theFilterCriteria, resourceSearchParam, theValuesFromResourceForSearchParam);
Optional<String> oResourceCriteria = myEmpiCandidateSearchCriteriaBuilderSvc.buildResourceQueryString(theResourceType, theResource, theFilterCriteria, resourceSearchParam);
if (!oResourceCriteria.isPresent()) {
return;
}
String resourceCriteria = oResourceCriteria.get();
ourLog.debug("Searching for {} candidates with {}", theResourceType, resourceCriteria);
//2.
@ -139,24 +141,6 @@ public class EmpiCandidateSearchSvc {
}
}
/*
* Given a list of criteria upon which to block, a resource search parameter, and a list of values for that given search parameter,
* build a query url. e.g.
*
* Patient?active=true&name.given=Gary,Grant
*/
@Nonnull
private String buildResourceQueryString(String theResourceType, List<String> theFilterCriteria, EmpiResourceSearchParamJson resourceSearchParam, List<String> theValuesFromResourceForSearchParam) {
List<String> criteria = new ArrayList<>(theFilterCriteria);
criteria.add(buildResourceMatchQuery(resourceSearchParam.getSearchParam(), theValuesFromResourceForSearchParam));
return theResourceType + "?" + String.join("&", criteria);
}
private String buildResourceMatchQuery(String theSearchParamName, List<String> theResourceValues) {
return theSearchParamName + "=" + String.join(",", theResourceValues);
}
private List<String> buildFilterQuery(List<EmpiFilterSearchParamJson> theFilterSearchParams, String theResourceType) {
return Collections.unmodifiableList(theFilterSearchParams.stream()
.filter(spFilterJson -> paramIsOnCorrectType(theResourceType, spFilterJson))

View File

@ -23,7 +23,6 @@ package ca.uhn.fhir.jpa.empi.svc;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.empi.rules.json.EmpiResourceSearchParamJson;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService;
@ -51,9 +50,9 @@ public class EmpiSearchParamSvc implements ISearchParamRetriever {
return myMatchUrlService.translateMatchUrl(theResourceCriteria, resourceDef);
}
public List<String> getValueFromResourceForSearchParam(IBaseResource theResource, EmpiResourceSearchParamJson theFilterSearchParam) {
public List<String> getValueFromResourceForSearchParam(IBaseResource theResource, String theSearchParam) {
String resourceType = myFhirContext.getResourceType(theResource);
RuntimeSearchParam activeSearchParam = mySearchParamRegistry.getActiveSearchParam(resourceType, theFilterSearchParam.getSearchParam());
RuntimeSearchParam activeSearchParam = mySearchParamRegistry.getActiveSearchParam(resourceType, theSearchParam);
return mySearchParamExtractorService.extractParamValuesAsStrings(activeSearchParam, theResource);
}

View File

@ -0,0 +1,69 @@
package ca.uhn.fhir.jpa.empi.svc;
import ca.uhn.fhir.empi.rules.json.EmpiResourceSearchParamJson;
import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.Patient;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collections;
import java.util.Optional;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class EmpiCandidateSearchCriteriaBuilderSvcTest extends BaseEmpiR4Test {
@Autowired
EmpiCandidateSearchCriteriaBuilderSvc myEmpiCandidateSearchCriteriaBuilderSvc;
@Test
public void testEmptyCase() {
Patient patient = new Patient();
EmpiResourceSearchParamJson searchParamJson = new EmpiResourceSearchParamJson();
searchParamJson.addSearchParam("family");
Optional<String> result = myEmpiCandidateSearchCriteriaBuilderSvc.buildResourceQueryString("Patient", patient, Collections.emptyList(), searchParamJson);
assertFalse(result.isPresent());
}
@Test
public void testSimpleCase() {
Patient patient = new Patient();
patient.addName().setFamily("Fernandez");
EmpiResourceSearchParamJson searchParamJson = new EmpiResourceSearchParamJson();
searchParamJson.addSearchParam("family");
Optional<String> result = myEmpiCandidateSearchCriteriaBuilderSvc.buildResourceQueryString("Patient", patient, Collections.emptyList(), searchParamJson);
assertTrue(result.isPresent());
assertEquals("Patient?family=Fernandez", result.get());
}
@Test
public void testComplexCase() {
Patient patient = new Patient();
HumanName humanName = patient.addName();
humanName.addGiven("Jose");
humanName.addGiven("Martin");
humanName.setFamily("Fernandez");
EmpiResourceSearchParamJson searchParamJson = new EmpiResourceSearchParamJson();
searchParamJson.addSearchParam("given");
searchParamJson.addSearchParam("family");
Optional<String> result = myEmpiCandidateSearchCriteriaBuilderSvc.buildResourceQueryString("Patient", patient, Collections.emptyList(), searchParamJson);
assertTrue(result.isPresent());
assertThat(result.get(), anyOf(equalTo("Patient?given=Jose,Martin&family=Fernandez"), equalTo("Patient?given=Martin,Jose&family=Fernandez")));
}
@Test
public void testIdentifier() {
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:oid:1.2.36.146.595.217.0.1").setValue("12345");
EmpiResourceSearchParamJson searchParamJson = new EmpiResourceSearchParamJson();
searchParamJson.addSearchParam("identifier");
Optional<String> result = myEmpiCandidateSearchCriteriaBuilderSvc.buildResourceQueryString("Patient", patient, Collections.emptyList(), searchParamJson);
assertTrue(result.isPresent());
assertEquals(result.get(), "Patient?identifier=urn:oid:1.2.36.146.595.217.0.1|12345");
}
}

View File

@ -25,7 +25,7 @@ public class EmpiCandidateSearchSvcTest extends BaseEmpiR4Test {
public void testFindCandidates() {
Patient jane = buildJanePatient();
jane.setActive(true);
Patient createdJane = createPatient(jane);
createPatient(jane);
Patient newJane = buildJanePatient();
Collection<IAnyResource> result = myEmpiCandidateSearchSvc.findCandidates("Patient", newJane);

View File

@ -2,15 +2,15 @@
"candidateSearchParams": [
{
"resourceType": "Patient",
"searchParam": "birthdate"
"searchParams": ["birthdate"]
},
{
"resourceType": "*",
"searchParam": "identifier"
"searchParams": ["identifier"]
},
{
"resourceType": "Patient",
"searchParam": "general-practitioner"
"searchParams": ["general-practitioner"]
}
],
"candidateFilterSearchParams": [

View File

@ -42,7 +42,7 @@ public class ResourceTag extends BaseTag {
@Column(name = "PID")
private Long myId;
@ManyToOne(cascade = {})
@ManyToOne(cascade = {}, fetch = FetchType.LAZY)
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_RESTAG_RESOURCE"))
private ResourceTable myResource;

View File

@ -159,8 +159,9 @@ public class SearchParameterMap implements Serializable {
}
}
public void addRevInclude(Include theInclude) {
public SearchParameterMap addRevInclude(Include theInclude) {
getRevIncludes().add(theInclude);
return this;
}
private void addUrlIncludeParams(StringBuilder b, String paramName, Set<Include> theList) {
@ -268,8 +269,9 @@ public class SearchParameterMap implements Serializable {
return mySort;
}
public void setSort(SortSpec theSort) {
public SearchParameterMap setSort(SortSpec theSort) {
mySort = theSort;
return this;
}
/**

View File

@ -67,8 +67,9 @@ public class EmpiRuleValidator {
}
private void validateSearchParams(EmpiRulesJson theEmpiRulesJson) {
for (EmpiResourceSearchParamJson searchParam : theEmpiRulesJson.getCandidateSearchParams()) {
validateSearchParam("candidateSearchParams", searchParam.getResourceType(), searchParam.getSearchParam());
for (EmpiResourceSearchParamJson searchParams : theEmpiRulesJson.getCandidateSearchParams()) {
searchParams.iterator().forEachRemaining(
searchParam -> validateSearchParam("candidateSearchParams", searchParams.getResourceType(), searchParam));
}
for (EmpiFilterSearchParamJson filter : theEmpiRulesJson.getCandidateFilterSearchParams()) {
validateSearchParam("candidateFilterSearchParams", filter.getResourceType(), filter.getSearchParam());
@ -129,7 +130,7 @@ public class EmpiRuleValidator {
private void validatePatientPath(EmpiFieldMatchJson theFieldMatch) {
try {
myTerser.getDefinition(myPatientClass, "Patient." + theFieldMatch.getResourcePath());
} catch (DataFormatException|ConfigurationException e) {
} catch (DataFormatException | ConfigurationException e) {
throw new ConfigurationException("MatchField " +
theFieldMatch.getName() +
" resourceType " +

View File

@ -23,14 +23,18 @@ package ca.uhn.fhir.empi.rules.json;
import ca.uhn.fhir.model.api.IModelJson;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
*
*/
public class EmpiResourceSearchParamJson implements IModelJson {
public class EmpiResourceSearchParamJson implements IModelJson, Iterable<String> {
@JsonProperty(value = "resourceType", required = true)
String myResourceType;
@JsonProperty(value = "searchParam", required = true)
String mySearchParam;
@JsonProperty(value = "searchParams", required = true)
List<String> mySearchParams;
public String getResourceType() {
return myResourceType;
@ -41,12 +45,15 @@ public class EmpiResourceSearchParamJson implements IModelJson {
return this;
}
public String getSearchParam() {
return mySearchParam;
public Iterator<String> iterator() {
return mySearchParams.iterator();
}
public EmpiResourceSearchParamJson setSearchParam(String theSearchParam) {
mySearchParam = theSearchParam;
public EmpiResourceSearchParamJson addSearchParam(String theSearchParam) {
if (mySearchParams == null) {
mySearchParams = new ArrayList<>();
}
mySearchParams.add(theSearchParam);
return this;
}
}

View File

@ -37,10 +37,10 @@ public abstract class BaseEmpiRulesR4Test extends BaseR4Test {
EmpiResourceSearchParamJson patientBirthdayBlocking = new EmpiResourceSearchParamJson()
.setResourceType("Patient")
.setSearchParam(Patient.SP_BIRTHDATE);
.addSearchParam(Patient.SP_BIRTHDATE);
EmpiResourceSearchParamJson patientIdentifierBlocking = new EmpiResourceSearchParamJson()
.setResourceType("Patient")
.setSearchParam(Patient.SP_IDENTIFIER);
.addSearchParam(Patient.SP_IDENTIFIER);
EmpiFieldMatchJson lastNameMatchField = new EmpiFieldMatchJson()

View File

@ -1,7 +1,7 @@
{
"candidateSearchParams" : [{
"resourceType" : "Patient",
"searchParam" : "foo"
"searchParams" : ["foo"]
}],
"candidateFilterSearchParams" : [],
"matchFields" : [],

View File

@ -70,7 +70,7 @@ public class BaseValidationSupportWrapper extends BaseValidationSupport {
@Override
public IValidationSupport.ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) {
return myWrap.expandValueSet(theValidationSupportContext, null, theValueSetToExpand);
return myWrap.expandValueSet(theValidationSupportContext, theExpansionOptions, theValueSetToExpand);
}
@Override

View File

@ -2,13 +2,17 @@ package org.hl7.fhir.common.hapi.validation.support;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.ParametersUtil;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeSystem;
import javax.annotation.Nonnull;
import java.util.ArrayList;
@ -42,6 +46,64 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
return invokeRemoteValidateCode(theCodeSystem, theCode, theDisplay, theValueSetUrl, null);
}
@Override
public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
IBaseResource valueSet = theValueSet;
String valueSetUrl = DefaultProfileValidationSupport.getConformanceResourceUrl(myCtx, valueSet);
if (isNotBlank(valueSetUrl)) {
valueSet = null;
} else {
valueSetUrl = null;
}
return invokeRemoteValidateCode(theCodeSystem, theCode, theDisplay, valueSetUrl, valueSet);
}
@Override
public IBaseResource fetchCodeSystem(String theSystem) {
IGenericClient client = provideClient();
Class<? extends IBaseBundle> bundleType = myCtx.getResourceDefinition("Bundle").getImplementingClass(IBaseBundle.class);
IBaseBundle results = client
.search()
.forResource("CodeSystem")
.where(CodeSystem.URL.matches().value(theSystem))
.returnBundle(bundleType)
.execute();
List<IBaseResource> resultsList = BundleUtil.toListOfResources(myCtx, results);
if (resultsList.size() > 0) {
return resultsList.get(0);
}
return null;
}
@Override
public IBaseResource fetchValueSet(String theValueSetUrl) {
IGenericClient client = provideClient();
Class<? extends IBaseBundle> bundleType = myCtx.getResourceDefinition("Bundle").getImplementingClass(IBaseBundle.class);
IBaseBundle results = client
.search()
.forResource("ValueSet")
.where(CodeSystem.URL.matches().value(theValueSetUrl))
.returnBundle(bundleType)
.execute();
List<IBaseResource> resultsList = BundleUtil.toListOfResources(myCtx, results);
if (resultsList.size() > 0) {
return resultsList.get(0);
}
return null;
}
@Override
public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
return fetchCodeSystem(theSystem) != null;
}
@Override
public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) {
return fetchValueSet(theValueSetUrl) != null;
}
private IGenericClient provideClient() {
IGenericClient retVal = myCtx.newRestfulGenericClient(myBaseUrl);
for (Object next : myClientInterceptors) {
@ -50,11 +112,6 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
return retVal;
}
@Override
public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
return invokeRemoteValidateCode(theCodeSystem, theCode, theDisplay, null, theValueSet);
}
protected CodeValidationResult invokeRemoteValidateCode(String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl, IBaseResource theValueSet) {
if (isBlank(theCode)) {
return null;
@ -64,23 +121,38 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
IBaseParameters input = ParametersUtil.newInstance(getFhirContext());
if (isNotBlank(theValueSetUrl)) {
ParametersUtil.addParameterToParametersUri(getFhirContext(), input, "url", theValueSetUrl);
}
ParametersUtil.addParameterToParametersString(getFhirContext(), input, "code", theCode);
if (isNotBlank(theCodeSystem)) {
ParametersUtil.addParameterToParametersUri(getFhirContext(), input, "system", theCodeSystem);
}
if (isNotBlank(theDisplay)) {
ParametersUtil.addParameterToParametersString(getFhirContext(), input, "display", theDisplay);
}
if (theValueSet != null) {
ParametersUtil.addParameterToParameters(getFhirContext(), input, "valueSet", theValueSet);
String resourceType = "ValueSet";
if (theValueSet == null && theValueSetUrl == null) {
resourceType = "CodeSystem";
ParametersUtil.addParameterToParametersUri(getFhirContext(), input, "url", theCodeSystem);
ParametersUtil.addParameterToParametersString(getFhirContext(), input, "code", theCode);
if (isNotBlank(theDisplay)) {
ParametersUtil.addParameterToParametersString(getFhirContext(), input, "display", theDisplay);
}
} else {
if (isNotBlank(theValueSetUrl)) {
ParametersUtil.addParameterToParametersUri(getFhirContext(), input, "url", theValueSetUrl);
}
ParametersUtil.addParameterToParametersString(getFhirContext(), input, "code", theCode);
if (isNotBlank(theCodeSystem)) {
ParametersUtil.addParameterToParametersUri(getFhirContext(), input, "system", theCodeSystem);
}
if (isNotBlank(theDisplay)) {
ParametersUtil.addParameterToParametersString(getFhirContext(), input, "display", theDisplay);
}
if (theValueSet != null) {
ParametersUtil.addParameterToParameters(getFhirContext(), input, "valueSet", theValueSet);
}
}
IBaseParameters output = client
.operation()
.onType("ValueSet")
.onType(resourceType)
.named("validate-code")
.withParameters(input)
.execute();

View File

@ -134,7 +134,7 @@ public class ValidationSupportChain implements IValidationSupport {
public ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) {
for (IValidationSupport next : myChain) {
// TODO: test if code system is supported?
ValueSetExpansionOutcome expanded = next.expandValueSet(theValidationSupportContext, null, theValueSetToExpand);
ValueSetExpansionOutcome expanded = next.expandValueSet(theValidationSupportContext, theExpansionOptions, theValueSetToExpand);
if (expanded != null) {
return expanded;
}

View File

@ -21,6 +21,7 @@ import org.hl7.fhir.r5.utils.IResourceValidator;
import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -197,6 +198,21 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IInsta
@Override
protected List<ValidationMessage> validate(IValidationContext<?> theValidationCtx) {
VersionSpecificWorkerContextWrapper wrappedWorkerContext = provideWorkerContext();
return new ValidatorWrapper()
.setAnyExtensionsAllowed(isAnyExtensionsAllowed())
.setBestPracticeWarningLevel(getBestPracticeWarningLevel())
.setErrorForUnknownProfiles(isErrorForUnknownProfiles())
.setExtensionDomains(getExtensionDomains())
.setNoTerminologyChecks(isNoTerminologyChecks())
.setValidatorResourceFetcher(getValidatorResourceFetcher())
.setAssumeValidRestReferences(isAssumeValidRestReferences())
.validate(wrappedWorkerContext, theValidationCtx);
}
@Nonnull
protected VersionSpecificWorkerContextWrapper provideWorkerContext() {
VersionSpecificWorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext;
if (wrappedWorkerContext == null) {
VersionSpecificWorkerContextWrapper.IVersionTypeConverter converter;
@ -213,7 +229,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IInsta
org.hl7.fhir.dstu2.model.ValueSet valueSet = (org.hl7.fhir.dstu2.model.ValueSet) nonCanonical;
if (valueSet.hasCodeSystem() && valueSet.getCodeSystem().hasSystem()) {
if (!valueSet.hasCompose()) {
org.hl7.fhir.r5.model.ValueSet valueSetR5 = (org.hl7.fhir.r5.model.ValueSet) retVal;
ValueSet valueSetR5 = (ValueSet) retVal;
valueSetR5.getCompose().addInclude().setSystem(valueSet.getCodeSystem().getSystem());
}
}
@ -257,16 +273,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IInsta
wrappedWorkerContext = new VersionSpecificWorkerContextWrapper(new ValidationSupportContext(myValidationSupport), converter);
}
myWrappedWorkerContext = wrappedWorkerContext;
return new ValidatorWrapper()
.setAnyExtensionsAllowed(isAnyExtensionsAllowed())
.setBestPracticeWarningLevel(getBestPracticeWarningLevel())
.setErrorForUnknownProfiles(isErrorForUnknownProfiles())
.setExtensionDomains(getExtensionDomains())
.setNoTerminologyChecks(isNoTerminologyChecks())
.setValidatorResourceFetcher(getValidatorResourceFetcher())
.setAssumeValidRestReferences(isAssumeValidRestReferences())
.validate(wrappedWorkerContext, theValidationCtx);
return wrappedWorkerContext;
}
private FhirContext getDstu2Context() {

View File

@ -5,9 +5,15 @@ import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.test.utilities.server.RestfulServerRule;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
@ -21,6 +27,9 @@ import org.junit.Test;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@ -37,13 +46,18 @@ public class RemoteTerminologyServiceValidationSupportTest {
@Rule
public RestfulServerRule myRestfulServerRule = new RestfulServerRule(ourCtx);
private MyMockTerminologyServiceProvider myProvider;
private MyValueSetProvider myValueSetProvider;
private RemoteTerminologyServiceValidationSupport mySvc;
private MyCodeSystemProvider myCodeSystemProvider;
@Before
public void before() {
myProvider = new MyMockTerminologyServiceProvider();
myRestfulServerRule.getRestfulServer().registerProvider(myProvider);
myValueSetProvider = new MyValueSetProvider();
myRestfulServerRule.getRestfulServer().registerProvider(myValueSetProvider);
myCodeSystemProvider = new MyCodeSystemProvider();
myRestfulServerRule.getRestfulServer().registerProvider(myCodeSystemProvider);
String baseUrl = "http://localhost:" + myRestfulServerRule.getPort();
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx);
@ -53,7 +67,7 @@ public class RemoteTerminologyServiceValidationSupportTest {
@After
public void after() {
assertThat(myProvider.myInvocationCount, lessThan(2));
assertThat(myValueSetProvider.myInvocationCount, lessThan(2));
}
@Test
@ -64,7 +78,7 @@ public class RemoteTerminologyServiceValidationSupportTest {
@Test
public void testValidateCode_SystemCodeDisplayUrl_Success() {
createNextReturnParameters(true, DISPLAY, null);
createNextValueSetReturnParameters(true, DISPLAY, null);
IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL);
assertEquals(CODE, outcome.getCode());
@ -72,16 +86,16 @@ public class RemoteTerminologyServiceValidationSupportTest {
assertEquals(null, outcome.getSeverity());
assertEquals(null, outcome.getMessage());
assertEquals(CODE, myProvider.myLastCode.getCode());
assertEquals(DISPLAY, myProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, myProvider.myLastSystem.getValue());
assertEquals(VALUE_SET_URL, myProvider.myLastUrl.getValue());
assertEquals(null, myProvider.myLastValueSet);
assertEquals(CODE, myValueSetProvider.myLastCode.getCode());
assertEquals(DISPLAY, myValueSetProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, myValueSetProvider.myLastSystem.getValue());
assertEquals(VALUE_SET_URL, myValueSetProvider.myLastUrl.getValue());
assertEquals(null, myValueSetProvider.myLastValueSet);
}
@Test
public void testValidateCode_SystemCodeDisplayUrl_Error() {
createNextReturnParameters(false, null, ERROR_MESSAGE);
createNextValueSetReturnParameters(false, null, ERROR_MESSAGE);
IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL);
assertEquals(null, outcome.getCode());
@ -89,16 +103,32 @@ public class RemoteTerminologyServiceValidationSupportTest {
assertEquals(IValidationSupport.IssueSeverity.ERROR, outcome.getSeverity());
assertEquals(ERROR_MESSAGE, outcome.getMessage());
assertEquals(CODE, myProvider.myLastCode.getCode());
assertEquals(DISPLAY, myProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, myProvider.myLastSystem.getValue());
assertEquals(VALUE_SET_URL, myProvider.myLastUrl.getValue());
assertEquals(null, myProvider.myLastValueSet);
assertEquals(CODE, myValueSetProvider.myLastCode.getCode());
assertEquals(DISPLAY, myValueSetProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, myValueSetProvider.myLastSystem.getValue());
assertEquals(VALUE_SET_URL, myValueSetProvider.myLastUrl.getValue());
assertEquals(null, myValueSetProvider.myLastValueSet);
}
@Test
public void testValidateCodeInCodeSystem_Good() {
createNextCodeSystemReturnParameters(true, DISPLAY, null);
IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, null);
assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertEquals(null, outcome.getSeverity());
assertEquals(null, outcome.getMessage());
assertEquals(CODE, myCodeSystemProvider.myLastCode.getCode());
assertEquals(DISPLAY, myCodeSystemProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, myCodeSystemProvider.myLastUrl.getValueAsString());
}
@Test
public void testValidateCodeInValueSet_SystemCodeDisplayVS_Good() {
createNextReturnParameters(true, DISPLAY, null);
createNextValueSetReturnParameters(true, DISPLAY, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(VALUE_SET_URL);
@ -109,34 +139,127 @@ public class RemoteTerminologyServiceValidationSupportTest {
assertEquals(null, outcome.getSeverity());
assertEquals(null, outcome.getMessage());
assertEquals(CODE, myProvider.myLastCode.getCode());
assertEquals(DISPLAY, myProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, myProvider.myLastSystem.getValue());
assertEquals(null, myProvider.myLastUrl);
assertEquals(VALUE_SET_URL, myProvider.myLastValueSet.getUrl());
assertEquals(CODE, myValueSetProvider.myLastCode.getCode());
assertEquals(DISPLAY, myValueSetProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, myValueSetProvider.myLastSystem.getValue());
assertEquals(VALUE_SET_URL, myValueSetProvider.myLastUrl.getValueAsString());
assertEquals(null, myValueSetProvider.myLastValueSet);
}
public void createNextReturnParameters(boolean theResult, String theDisplay, String theMessage) {
myProvider.myNextReturn = new Parameters();
myProvider.myNextReturn.addParameter("result", theResult);
myProvider.myNextReturn.addParameter("display", theDisplay);
@Test
public void testIsValueSetSupported_False() {
myValueSetProvider.myNextReturnValueSets = new ArrayList<>();
boolean outcome = mySvc.isValueSetSupported(null, "http://loinc.org/VS");
assertEquals(false, outcome);
assertEquals("http://loinc.org/VS", myValueSetProvider.myLastUrlParam.getValue());
}
@Test
public void testIsValueSetSupported_True() {
myValueSetProvider.myNextReturnValueSets = new ArrayList<>();
myValueSetProvider.myNextReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/123"));
boolean outcome = mySvc.isValueSetSupported(null, "http://loinc.org/VS");
assertEquals(true, outcome);
assertEquals("http://loinc.org/VS", myValueSetProvider.myLastUrlParam.getValue());
}
@Test
public void testIsCodeSystemSupported_False() {
myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>();
boolean outcome = mySvc.isCodeSystemSupported(null, "http://loinc.org");
assertEquals(false, outcome);
assertEquals("http://loinc.org", myCodeSystemProvider.myLastUrlParam.getValue());
}
@Test
public void testIsCodeSystemSupported_True() {
myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>();
myCodeSystemProvider.myNextReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/123"));
boolean outcome = mySvc.isCodeSystemSupported(null, "http://loinc.org");
assertEquals(true, outcome);
assertEquals("http://loinc.org", myCodeSystemProvider.myLastUrlParam.getValue());
}
private void createNextCodeSystemReturnParameters(boolean theResult, String theDisplay, String theMessage) {
myCodeSystemProvider.myNextReturnParams = new Parameters();
myCodeSystemProvider.myNextReturnParams.addParameter("result", theResult);
myCodeSystemProvider.myNextReturnParams.addParameter("display", theDisplay);
if (theMessage != null) {
myProvider.myNextReturn.addParameter("message", theMessage);
myCodeSystemProvider.myNextReturnParams.addParameter("message", theMessage);
}
}
private static class MyMockTerminologyServiceProvider {
private void createNextValueSetReturnParameters(boolean theResult, String theDisplay, String theMessage) {
myValueSetProvider.myNextReturnParams = new Parameters();
myValueSetProvider.myNextReturnParams.addParameter("result", theResult);
myValueSetProvider.myNextReturnParams.addParameter("display", theDisplay);
if (theMessage != null) {
myValueSetProvider.myNextReturnParams.addParameter("message", theMessage);
}
}
private static class MyCodeSystemProvider implements IResourceProvider {
private UriParam myLastUrlParam;
private List<CodeSystem> myNextReturnCodeSystems;
private int myInvocationCount;
private UriType myLastUrl;
private CodeType myLastCode;
private StringType myLastDisplay;
private Parameters myNextReturnParams;
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theCodeSystemUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay
) {
myInvocationCount++;
myLastUrl = theCodeSystemUrl;
myLastCode = theCode;
myLastDisplay = theDisplay;
return myNextReturnParams;
}
@Search
public List<CodeSystem> find(@RequiredParam(name="url") UriParam theUrlParam) {
myLastUrlParam = theUrlParam;
assert myNextReturnCodeSystems != null;
return myNextReturnCodeSystems;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return CodeSystem.class;
}
}
private Parameters myNextReturn;
private static class MyValueSetProvider implements IResourceProvider {
private Parameters myNextReturnParams;
private List<ValueSet> myNextReturnValueSets;
private UriType myLastUrl;
private CodeType myLastCode;
private int myInvocationCount;
private UriType myLastSystem;
private StringType myLastDisplay;
private ValueSet myLastValueSet;
private UriParam myLastUrlParam;
@Operation(name = "validate-code", idempotent = true, typeName = "ValueSet", returnParameters = {
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
@ -156,11 +279,22 @@ public class RemoteTerminologyServiceValidationSupportTest {
myLastSystem = theSystem;
myLastDisplay = theDisplay;
myLastValueSet = theValueSet;
return myNextReturn;
return myNextReturnParams;
}
@Search
public List<ValueSet> find(@RequiredParam(name="url") UriParam theUrlParam) {
myLastUrlParam = theUrlParam;
assert myNextReturnValueSets != null;
return myNextReturnValueSets;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return ValueSet.class;
}
}
}

View File

@ -693,7 +693,7 @@
<!--<derby_version>10.15.1.3</derby_version>-->
<error_prone_annotations_version>2.3.4</error_prone_annotations_version>
<error_prone_core_version>2.3.3</error_prone_core_version>
<guava_version>28.2-jre</guava_version>
<guava_version>29.0-jre</guava_version>
<gson_version>2.8.5</gson_version>
<jaxb_bundle_version>2.2.11_1</jaxb_bundle_version>
<jaxb_api_version>2.3.1</jaxb_api_version>
@ -1669,7 +1669,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<version>3.0.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>