Merge remote-tracking branch 'remotes/origin/master' into im_20200619_mariadb_expand_valueset_fix
This commit is contained in:
commit
55ff15d039
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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."
|
|
@ -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."
|
|
@ -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."
|
||||
|
|
@ -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.
|
|
@ -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."
|
|
@ -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 -> 29.0-jre</li>
|
||||
</ul>"
|
||||
- item:
|
||||
issue: "1862"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]** – Tests whether a given CodeSystem is supported on the server
|
||||
* **GET [base]/ValueSet?url=[url]** – Tests whether a given ValueSet is supported on the server
|
||||
* **POST [base]/CodeSystem/$validate-code** – Validate codes in fields where no specific ValueSet is bound
|
||||
* **POST [base]/ValueSet/$validate-code** – Validate codes in fields where a specific ValueSet is bound
|
||||
|
||||
# Recipes
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 " +
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"candidateSearchParams" : [{
|
||||
"resourceType" : "Patient",
|
||||
"searchParam" : "foo"
|
||||
"searchParams" : ["foo"]
|
||||
}],
|
||||
"candidateFilterSearchParams" : [],
|
||||
"matchFields" : [],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
4
pom.xml
4
pom.xml
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue