merging in master
This commit is contained in:
commit
78e4eee683
|
@ -440,74 +440,259 @@ public interface IValidationSupport {
|
|||
return "Unknown " + getFhirContext().getVersion().getVersion() + " Validation Support";
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines codes in system <a href="http://hl7.org/fhir/issue-severity">http://hl7.org/fhir/issue-severity</a>.
|
||||
*/
|
||||
/* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */
|
||||
enum IssueSeverity {
|
||||
/**
|
||||
* The issue caused the action to fail, and no further checking could be performed.
|
||||
*/
|
||||
FATAL,
|
||||
FATAL("fatal"),
|
||||
/**
|
||||
* The issue is sufficiently important to cause the action to fail.
|
||||
*/
|
||||
ERROR,
|
||||
ERROR("error"),
|
||||
/**
|
||||
* The issue is not important enough to cause the action to fail, but may cause it to be performed suboptimally or in a way that is not as desired.
|
||||
*/
|
||||
WARNING,
|
||||
WARNING("warning"),
|
||||
/**
|
||||
* The issue has no relation to the degree of success of the action.
|
||||
*/
|
||||
INFORMATION
|
||||
INFORMATION("information"),
|
||||
/**
|
||||
* The operation was successful.
|
||||
*/
|
||||
SUCCESS("success");
|
||||
// the spec for OperationOutcome mentions that a code from http://hl7.org/fhir/issue-severity is required
|
||||
|
||||
private final String myCode;
|
||||
|
||||
IssueSeverity(String theCode) {
|
||||
myCode = theCode;
|
||||
}
|
||||
/**
|
||||
* Provide mapping to a code in system <a href="http://hl7.org/fhir/issue-severity">http://hl7.org/fhir/issue-severity</a>.
|
||||
* @return the code
|
||||
*/
|
||||
public String getCode() {
|
||||
return myCode;
|
||||
}
|
||||
/**
|
||||
* Creates a {@link IssueSeverity} object from the given code.
|
||||
* @return the {@link IssueSeverity}
|
||||
*/
|
||||
public static IssueSeverity fromCode(String theCode) {
|
||||
switch (theCode) {
|
||||
case "fatal":
|
||||
return FATAL;
|
||||
case "error":
|
||||
return ERROR;
|
||||
case "warning":
|
||||
return WARNING;
|
||||
case "information":
|
||||
return INFORMATION;
|
||||
case "success":
|
||||
return SUCCESS;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum CodeValidationIssueCode {
|
||||
NOT_FOUND,
|
||||
CODE_INVALID,
|
||||
INVALID,
|
||||
OTHER
|
||||
}
|
||||
/**
|
||||
* Defines codes in system <a href="http://hl7.org/fhir/issue-type">http://hl7.org/fhir/issue-type</a>.
|
||||
* The binding is enforced as a part of validation logic in the FHIR Core Validation library where an exception is thrown.
|
||||
* Only a sub-set of these codes are defined as constants because they relate to validation,
|
||||
* If there are additional ones that come up, for Remote Terminology they are currently supported via
|
||||
* {@link IValidationSupport.CodeValidationIssue#CodeValidationIssue(String, IssueSeverity, String)}
|
||||
* while for internal validators, more constants can be added to make things easier and consistent.
|
||||
* This maps to resource OperationOutcome.issue.code.
|
||||
*/
|
||||
/* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */
|
||||
class CodeValidationIssueCode {
|
||||
public static final CodeValidationIssueCode NOT_FOUND = new CodeValidationIssueCode("not-found");
|
||||
public static final CodeValidationIssueCode CODE_INVALID = new CodeValidationIssueCode("code-invalid");
|
||||
public static final CodeValidationIssueCode INVALID = new CodeValidationIssueCode("invalid");
|
||||
|
||||
enum CodeValidationIssueCoding {
|
||||
VS_INVALID,
|
||||
NOT_FOUND,
|
||||
NOT_IN_VS,
|
||||
private final String myCode;
|
||||
|
||||
INVALID_CODE,
|
||||
INVALID_DISPLAY,
|
||||
OTHER
|
||||
}
|
||||
|
||||
class CodeValidationIssue {
|
||||
|
||||
private final String myMessage;
|
||||
private final IssueSeverity mySeverity;
|
||||
private final CodeValidationIssueCode myCode;
|
||||
private final CodeValidationIssueCoding myCoding;
|
||||
|
||||
public CodeValidationIssue(
|
||||
String theMessage,
|
||||
IssueSeverity mySeverity,
|
||||
CodeValidationIssueCode theCode,
|
||||
CodeValidationIssueCoding theCoding) {
|
||||
this.myMessage = theMessage;
|
||||
this.mySeverity = mySeverity;
|
||||
this.myCode = theCode;
|
||||
this.myCoding = theCoding;
|
||||
// this is intentionally not exposed
|
||||
CodeValidationIssueCode(String theCode) {
|
||||
myCode = theCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the corresponding code from system <a href="http://hl7.org/fhir/issue-type">http://hl7.org/fhir/issue-type</a>.
|
||||
* @return the code
|
||||
*/
|
||||
public String getCode() {
|
||||
return myCode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds information about the details of a {@link CodeValidationIssue}.
|
||||
* This maps to resource OperationOutcome.issue.details.
|
||||
*/
|
||||
/* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */
|
||||
class CodeValidationIssueDetails {
|
||||
private final String myText;
|
||||
private List<CodeValidationIssueCoding> myCodings;
|
||||
|
||||
public CodeValidationIssueDetails(String theText) {
|
||||
myText = theText;
|
||||
}
|
||||
|
||||
// intentionally not exposed
|
||||
void addCoding(CodeValidationIssueCoding theCoding) {
|
||||
getCodings().add(theCoding);
|
||||
}
|
||||
|
||||
public CodeValidationIssueDetails addCoding(String theSystem, String theCode) {
|
||||
if (myCodings == null) {
|
||||
myCodings = new ArrayList<>();
|
||||
}
|
||||
myCodings.add(new CodeValidationIssueCoding(theSystem, theCode));
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return myText;
|
||||
}
|
||||
|
||||
public List<CodeValidationIssueCoding> getCodings() {
|
||||
if (myCodings == null) {
|
||||
myCodings = new ArrayList<>();
|
||||
}
|
||||
return myCodings;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines codes that can be part of the details of an issue.
|
||||
* There are some constants available (pre-defined) for codes for system <a href="http://hl7.org/fhir/tools/CodeSystem/tx-issue-type">http://hl7.org/fhir/tools/CodeSystem/tx-issue-type</a>.
|
||||
* This maps to resource OperationOutcome.issue.details.coding[0].code.
|
||||
*/
|
||||
class CodeValidationIssueCoding {
|
||||
public static String TX_ISSUE_SYSTEM = "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type";
|
||||
public static CodeValidationIssueCoding VS_INVALID =
|
||||
new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "vs-invalid");
|
||||
public static final CodeValidationIssueCoding NOT_FOUND =
|
||||
new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "not-found");
|
||||
public static final CodeValidationIssueCoding NOT_IN_VS =
|
||||
new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "not-in-vs");
|
||||
public static final CodeValidationIssueCoding INVALID_CODE =
|
||||
new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "invalid-code");
|
||||
public static final CodeValidationIssueCoding INVALID_DISPLAY =
|
||||
new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "vs-display");
|
||||
private final String mySystem, myCode;
|
||||
|
||||
// this is intentionally not exposed
|
||||
CodeValidationIssueCoding(String theSystem, String theCode) {
|
||||
mySystem = theSystem;
|
||||
myCode = theCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the corresponding code for the details of a validation issue.
|
||||
* @return the code
|
||||
*/
|
||||
public String getCode() {
|
||||
return myCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the system for the details of a validation issue.
|
||||
* @return the system
|
||||
*/
|
||||
public String getSystem() {
|
||||
return mySystem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a hapi-fhir internal version agnostic object holding information about a validation issue.
|
||||
* An alternative (which requires significant refactoring) would be to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult instead.
|
||||
*/
|
||||
class CodeValidationIssue {
|
||||
private final String myDiagnostics;
|
||||
private final IssueSeverity mySeverity;
|
||||
private final CodeValidationIssueCode myCode;
|
||||
private CodeValidationIssueDetails myDetails;
|
||||
|
||||
public CodeValidationIssue(
|
||||
String theDiagnostics, IssueSeverity theSeverity, CodeValidationIssueCode theTypeCode) {
|
||||
this(theDiagnostics, theSeverity, theTypeCode, null);
|
||||
}
|
||||
|
||||
public CodeValidationIssue(String theDiagnostics, IssueSeverity theSeverity, String theTypeCode) {
|
||||
this(theDiagnostics, theSeverity, new CodeValidationIssueCode(theTypeCode), null);
|
||||
}
|
||||
|
||||
public CodeValidationIssue(
|
||||
String theDiagnostics,
|
||||
IssueSeverity theSeverity,
|
||||
CodeValidationIssueCode theType,
|
||||
CodeValidationIssueCoding theDetailsCoding) {
|
||||
myDiagnostics = theDiagnostics;
|
||||
mySeverity = theSeverity;
|
||||
myCode = theType;
|
||||
// reuse the diagnostics message as a detail text message
|
||||
myDetails = new CodeValidationIssueDetails(theDiagnostics);
|
||||
myDetails.addCoding(theDetailsCoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Please use {@link #getDiagnostics()} instead.
|
||||
*/
|
||||
@Deprecated(since = "7.4.6")
|
||||
public String getMessage() {
|
||||
return myMessage;
|
||||
return getDiagnostics();
|
||||
}
|
||||
|
||||
public String getDiagnostics() {
|
||||
return myDiagnostics;
|
||||
}
|
||||
|
||||
public IssueSeverity getSeverity() {
|
||||
return mySeverity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Please use {@link #getType()} instead.
|
||||
*/
|
||||
@Deprecated(since = "7.4.6")
|
||||
public CodeValidationIssueCode getCode() {
|
||||
return getType();
|
||||
}
|
||||
|
||||
public CodeValidationIssueCode getType() {
|
||||
return myCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Please use {@link #getDetails()} instead. That has support for multiple codings.
|
||||
*/
|
||||
@Deprecated(since = "7.4.6")
|
||||
public CodeValidationIssueCoding getCoding() {
|
||||
return myCoding;
|
||||
return myDetails != null
|
||||
? myDetails.getCodings().stream().findFirst().orElse(null)
|
||||
: null;
|
||||
}
|
||||
|
||||
public void setDetails(CodeValidationIssueDetails theDetails) {
|
||||
this.myDetails = theDetails;
|
||||
}
|
||||
|
||||
public CodeValidationIssueDetails getDetails() {
|
||||
return myDetails;
|
||||
}
|
||||
|
||||
public boolean hasIssueDetailCode(@Nonnull String theCode) {
|
||||
// this method is system agnostic at the moment but it can be restricted if needed
|
||||
return myDetails.getCodings().stream().anyMatch(coding -> theCode.equals(coding.getCode()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -671,6 +856,10 @@ public interface IValidationSupport {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a hapi-fhir internal version agnostic object holding information about the validation result.
|
||||
* An alternative (which requires significant refactoring) would be to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult.
|
||||
*/
|
||||
class CodeValidationResult {
|
||||
public static final String SOURCE_DETAILS = "sourceDetails";
|
||||
public static final String RESULT = "result";
|
||||
|
@ -686,7 +875,7 @@ public interface IValidationSupport {
|
|||
private String myDisplay;
|
||||
private String mySourceDetails;
|
||||
|
||||
private List<CodeValidationIssue> myCodeValidationIssues;
|
||||
private List<CodeValidationIssue> myIssues;
|
||||
|
||||
public CodeValidationResult() {
|
||||
super();
|
||||
|
@ -771,20 +960,45 @@ public interface IValidationSupport {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Please use method {@link #getIssues()} instead.
|
||||
*/
|
||||
@Deprecated(since = "7.4.6")
|
||||
public List<CodeValidationIssue> getCodeValidationIssues() {
|
||||
if (myCodeValidationIssues == null) {
|
||||
myCodeValidationIssues = new ArrayList<>();
|
||||
}
|
||||
return myCodeValidationIssues;
|
||||
return getIssues();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Please use method {@link #setIssues(List)} instead.
|
||||
*/
|
||||
@Deprecated(since = "7.4.6")
|
||||
public CodeValidationResult setCodeValidationIssues(List<CodeValidationIssue> theCodeValidationIssues) {
|
||||
myCodeValidationIssues = new ArrayList<>(theCodeValidationIssues);
|
||||
return setIssues(theCodeValidationIssues);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Please use method {@link #addIssue(CodeValidationIssue)} instead.
|
||||
*/
|
||||
@Deprecated(since = "7.4.6")
|
||||
public CodeValidationResult addCodeValidationIssue(CodeValidationIssue theCodeValidationIssue) {
|
||||
getCodeValidationIssues().add(theCodeValidationIssue);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeValidationResult addCodeValidationIssue(CodeValidationIssue theCodeValidationIssue) {
|
||||
getCodeValidationIssues().add(theCodeValidationIssue);
|
||||
public List<CodeValidationIssue> getIssues() {
|
||||
if (myIssues == null) {
|
||||
myIssues = new ArrayList<>();
|
||||
}
|
||||
return myIssues;
|
||||
}
|
||||
|
||||
public CodeValidationResult setIssues(List<CodeValidationIssue> theIssues) {
|
||||
myIssues = new ArrayList<>(theIssues);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeValidationResult addIssue(CodeValidationIssue theCodeValidationIssue) {
|
||||
getIssues().add(theCodeValidationIssue);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -811,17 +1025,19 @@ public interface IValidationSupport {
|
|||
public String getSeverityCode() {
|
||||
String retVal = null;
|
||||
if (getSeverity() != null) {
|
||||
retVal = getSeverity().name().toLowerCase();
|
||||
retVal = getSeverity().getCode();
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an issue severity as a string code. Value must be the name of
|
||||
* one of the enum values in {@link IssueSeverity}. Value is case-insensitive.
|
||||
* Sets an issue severity using a severity code. Please use method {@link #setSeverity(IssueSeverity)} instead.
|
||||
* @param theSeverityCode the code
|
||||
* @return the current {@link CodeValidationResult} instance
|
||||
*/
|
||||
public CodeValidationResult setSeverityCode(@Nonnull String theIssueSeverity) {
|
||||
setSeverity(IssueSeverity.valueOf(theIssueSeverity.toUpperCase()));
|
||||
@Deprecated(since = "7.4.6")
|
||||
public CodeValidationResult setSeverityCode(@Nonnull String theSeverityCode) {
|
||||
setSeverity(IssueSeverity.fromCode(theSeverityCode));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -838,6 +1054,11 @@ public interface IValidationSupport {
|
|||
if (isNotBlank(getSourceDetails())) {
|
||||
ParametersUtil.addParameterToParametersString(theContext, retVal, SOURCE_DETAILS, getSourceDetails());
|
||||
}
|
||||
/*
|
||||
should translate issues as well, except that is version specific code, so it requires more refactoring
|
||||
or replace the current class with org.hl7.fhir.r5.terminologies.utilities.ValidationResult
|
||||
@see VersionSpecificWorkerContextWrapper#getIssuesForCodeValidation
|
||||
*/
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
|
|
@ -105,7 +105,6 @@ public abstract class BaseParser implements IParser {
|
|||
private static final Set<String> notEncodeForContainedResource =
|
||||
new HashSet<>(Arrays.asList("security", "versionId", "lastUpdated"));
|
||||
|
||||
private FhirTerser.ContainedResources myContainedResources;
|
||||
private boolean myEncodeElementsAppliesToChildResourcesOnly;
|
||||
private final FhirContext myContext;
|
||||
private Collection<String> myDontEncodeElements;
|
||||
|
@ -183,12 +182,15 @@ public abstract class BaseParser implements IParser {
|
|||
}
|
||||
|
||||
private String determineReferenceText(
|
||||
IBaseReference theRef, CompositeChildElement theCompositeChildElement, IBaseResource theResource) {
|
||||
IBaseReference theRef,
|
||||
CompositeChildElement theCompositeChildElement,
|
||||
IBaseResource theResource,
|
||||
EncodeContext theContext) {
|
||||
IIdType ref = theRef.getReferenceElement();
|
||||
if (isBlank(ref.getIdPart())) {
|
||||
String reference = ref.getValue();
|
||||
if (theRef.getResource() != null) {
|
||||
IIdType containedId = getContainedResources().getResourceId(theRef.getResource());
|
||||
IIdType containedId = theContext.getContainedResources().getResourceId(theRef.getResource());
|
||||
if (containedId != null && !containedId.isEmpty()) {
|
||||
if (containedId.isLocal()) {
|
||||
reference = containedId.getValue();
|
||||
|
@ -262,7 +264,8 @@ public abstract class BaseParser implements IParser {
|
|||
@Override
|
||||
public final void encodeResourceToWriter(IBaseResource theResource, Writer theWriter)
|
||||
throws IOException, DataFormatException {
|
||||
EncodeContext encodeContext = new EncodeContext(this, myContext.getParserOptions());
|
||||
EncodeContext encodeContext =
|
||||
new EncodeContext(this, myContext.getParserOptions(), new FhirTerser.ContainedResources());
|
||||
encodeResourceToWriter(theResource, theWriter, encodeContext);
|
||||
}
|
||||
|
||||
|
@ -285,7 +288,8 @@ public abstract class BaseParser implements IParser {
|
|||
} else if (theElement instanceof IPrimitiveType) {
|
||||
theWriter.write(((IPrimitiveType<?>) theElement).getValueAsString());
|
||||
} else {
|
||||
EncodeContext encodeContext = new EncodeContext(this, myContext.getParserOptions());
|
||||
EncodeContext encodeContext =
|
||||
new EncodeContext(this, myContext.getParserOptions(), new FhirTerser.ContainedResources());
|
||||
encodeToWriter(theElement, theWriter, encodeContext);
|
||||
}
|
||||
}
|
||||
|
@ -404,10 +408,6 @@ public abstract class BaseParser implements IParser {
|
|||
return elementId;
|
||||
}
|
||||
|
||||
FhirTerser.ContainedResources getContainedResources() {
|
||||
return myContainedResources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getDontStripVersionsFromReferencesAtPaths() {
|
||||
return myDontStripVersionsFromReferencesAtPaths;
|
||||
|
@ -539,10 +539,11 @@ public abstract class BaseParser implements IParser {
|
|||
return mySuppressNarratives;
|
||||
}
|
||||
|
||||
protected boolean isChildContained(BaseRuntimeElementDefinition<?> childDef, boolean theIncludedResource) {
|
||||
protected boolean isChildContained(
|
||||
BaseRuntimeElementDefinition<?> childDef, boolean theIncludedResource, EncodeContext theContext) {
|
||||
return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES
|
||||
|| childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST)
|
||||
&& getContainedResources().isEmpty() == false
|
||||
&& theContext.getContainedResources().isEmpty() == false
|
||||
&& theIncludedResource == false;
|
||||
}
|
||||
|
||||
|
@ -788,7 +789,8 @@ public abstract class BaseParser implements IParser {
|
|||
*/
|
||||
if (next instanceof IBaseReference) {
|
||||
IBaseReference nextRef = (IBaseReference) next;
|
||||
String refText = determineReferenceText(nextRef, theCompositeChildElement, theResource);
|
||||
String refText =
|
||||
determineReferenceText(nextRef, theCompositeChildElement, theResource, theEncodeContext);
|
||||
if (!StringUtils.equals(refText, nextRef.getReferenceElement().getValue())) {
|
||||
|
||||
if (retVal == theValues) {
|
||||
|
@ -980,7 +982,7 @@ public abstract class BaseParser implements IParser {
|
|||
return true;
|
||||
}
|
||||
|
||||
protected void containResourcesInReferences(IBaseResource theResource) {
|
||||
protected void containResourcesInReferences(IBaseResource theResource, EncodeContext theContext) {
|
||||
|
||||
/*
|
||||
* If a UUID is present in Bundle.entry.fullUrl but no value is present
|
||||
|
@ -1003,7 +1005,7 @@ public abstract class BaseParser implements IParser {
|
|||
}
|
||||
}
|
||||
|
||||
myContainedResources = getContext().newTerser().containResources(theResource);
|
||||
theContext.setContainedResources(getContext().newTerser().containResources(theResource));
|
||||
}
|
||||
|
||||
static class ChildNameAndDef {
|
||||
|
@ -1034,8 +1036,12 @@ public abstract class BaseParser implements IParser {
|
|||
private final List<EncodeContextPath> myEncodeElementPaths;
|
||||
private final Set<String> myEncodeElementsAppliesToResourceTypes;
|
||||
private final List<EncodeContextPath> myDontEncodeElementPaths;
|
||||
private FhirTerser.ContainedResources myContainedResources;
|
||||
|
||||
public EncodeContext(BaseParser theParser, ParserOptions theParserOptions) {
|
||||
public EncodeContext(
|
||||
BaseParser theParser,
|
||||
ParserOptions theParserOptions,
|
||||
FhirTerser.ContainedResources theContainedResources) {
|
||||
Collection<String> encodeElements = theParser.myEncodeElements;
|
||||
Collection<String> dontEncodeElements = theParser.myDontEncodeElements;
|
||||
if (isSummaryMode()) {
|
||||
|
@ -1058,6 +1064,8 @@ public abstract class BaseParser implements IParser {
|
|||
dontEncodeElements.stream().map(EncodeContextPath::new).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
myContainedResources = theContainedResources;
|
||||
|
||||
myEncodeElementsAppliesToResourceTypes =
|
||||
ParserUtil.determineApplicableResourceTypesForTerserPaths(myEncodeElementPaths);
|
||||
}
|
||||
|
@ -1065,6 +1073,14 @@ public abstract class BaseParser implements IParser {
|
|||
private Map<Key, List<BaseParser.CompositeChildElement>> getCompositeChildrenCache() {
|
||||
return myCompositeChildrenCache;
|
||||
}
|
||||
|
||||
public FhirTerser.ContainedResources getContainedResources() {
|
||||
return myContainedResources;
|
||||
}
|
||||
|
||||
public void setContainedResources(FhirTerser.ContainedResources theContainedResources) {
|
||||
myContainedResources = theContainedResources;
|
||||
}
|
||||
}
|
||||
|
||||
protected class CompositeChildElement {
|
||||
|
|
|
@ -54,6 +54,7 @@ import ca.uhn.fhir.parser.json.JsonLikeStructure;
|
|||
import ca.uhn.fhir.parser.json.jackson.JacksonStructure;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.util.ElementUtil;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.text.WordUtils;
|
||||
|
@ -386,12 +387,14 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
case CONTAINED_RESOURCE_LIST:
|
||||
case CONTAINED_RESOURCES: {
|
||||
List<IBaseResource> containedResources = getContainedResources().getContainedResources();
|
||||
List<IBaseResource> containedResources =
|
||||
theEncodeContext.getContainedResources().getContainedResources();
|
||||
if (containedResources.size() > 0) {
|
||||
beginArray(theEventWriter, theChildName);
|
||||
|
||||
for (IBaseResource next : containedResources) {
|
||||
IIdType resourceId = getContainedResources().getResourceId(next);
|
||||
IIdType resourceId =
|
||||
theEncodeContext.getContainedResources().getResourceId(next);
|
||||
String value = resourceId.getValue();
|
||||
encodeResourceToJsonStreamWriter(
|
||||
theResDef,
|
||||
|
@ -554,7 +557,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
|
||||
if (nextValue == null || nextValue.isEmpty()) {
|
||||
if (nextValue instanceof BaseContainedDt) {
|
||||
if (theContainedResource || getContainedResources().isEmpty()) {
|
||||
if (theContainedResource
|
||||
|| theEncodeContext.getContainedResources().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
|
@ -838,7 +842,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
+ theResource.getStructureFhirVersionEnum());
|
||||
}
|
||||
|
||||
EncodeContext encodeContext = new EncodeContext(this, getContext().getParserOptions());
|
||||
EncodeContext encodeContext =
|
||||
new EncodeContext(this, getContext().getParserOptions(), new FhirTerser.ContainedResources());
|
||||
String resourceName = getContext().getResourceType(theResource);
|
||||
encodeContext.pushPath(resourceName, true);
|
||||
doEncodeResourceToJsonLikeWriter(theResource, theJsonLikeWriter, encodeContext);
|
||||
|
@ -894,7 +899,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
|
||||
if (!theContainedResource) {
|
||||
containResourcesInReferences(theResource);
|
||||
containResourcesInReferences(theResource, theEncodeContext);
|
||||
}
|
||||
|
||||
RuntimeResourceDefinition resDef = getContext().getResourceDefinition(theResource);
|
||||
|
|
|
@ -191,7 +191,7 @@ public class RDFParser extends BaseParser {
|
|||
}
|
||||
|
||||
if (!containedResource) {
|
||||
containResourcesInReferences(resource);
|
||||
containResourcesInReferences(resource, encodeContext);
|
||||
}
|
||||
|
||||
if (!(resource instanceof IAnyResource)) {
|
||||
|
@ -354,7 +354,7 @@ public class RDFParser extends BaseParser {
|
|||
try {
|
||||
|
||||
if (element == null || element.isEmpty()) {
|
||||
if (!isChildContained(childDef, includedResource)) {
|
||||
if (!isChildContained(childDef, includedResource, theEncodeContext)) {
|
||||
return rdfModel;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -295,7 +295,7 @@ public class XmlParser extends BaseParser {
|
|||
try {
|
||||
|
||||
if (theElement == null || theElement.isEmpty()) {
|
||||
if (isChildContained(childDef, theIncludedResource)) {
|
||||
if (isChildContained(childDef, theIncludedResource, theEncodeContext)) {
|
||||
// We still want to go in..
|
||||
} else {
|
||||
return;
|
||||
|
@ -359,8 +359,10 @@ public class XmlParser extends BaseParser {
|
|||
* theEventWriter.writeStartElement("contained"); encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(next.getId().getValue()));
|
||||
* theEventWriter.writeEndElement(); }
|
||||
*/
|
||||
for (IBaseResource next : getContainedResources().getContainedResources()) {
|
||||
IIdType resourceId = getContainedResources().getResourceId(next);
|
||||
for (IBaseResource next :
|
||||
theEncodeContext.getContainedResources().getContainedResources()) {
|
||||
IIdType resourceId =
|
||||
theEncodeContext.getContainedResources().getResourceId(next);
|
||||
theEventWriter.writeStartElement("contained");
|
||||
String value = resourceId.getValue();
|
||||
encodeResourceToXmlStreamWriter(
|
||||
|
@ -682,7 +684,7 @@ public class XmlParser extends BaseParser {
|
|||
}
|
||||
|
||||
if (!theContainedResource) {
|
||||
containResourcesInReferences(theResource);
|
||||
containResourcesInReferences(theResource, theEncodeContext);
|
||||
}
|
||||
|
||||
theEventWriter.writeStartElement(resDef.getName());
|
||||
|
|
|
@ -28,6 +28,8 @@ import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
|
|||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
import org.hl7.fhir.instance.model.api.IBaseConformance;
|
||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||
|
@ -231,6 +233,23 @@ public interface Repository {
|
|||
|
||||
// Querying starts here
|
||||
|
||||
/**
|
||||
* Searches this repository
|
||||
*
|
||||
* @see <a href="https://www.hl7.org/fhir/http.html#search">FHIR search</a>
|
||||
*
|
||||
* @param <B> a Bundle type
|
||||
* @param <T> a Resource type
|
||||
* @param bundleType the class of the Bundle type to return
|
||||
* @param resourceType the class of the Resource type to search
|
||||
* @param searchParameters the searchParameters for this search
|
||||
* @return a Bundle with the results of the search
|
||||
*/
|
||||
default <B extends IBaseBundle, T extends IBaseResource> B search(
|
||||
Class<B> bundleType, Class<T> resourceType, Multimap<String, List<IQueryParameterType>> searchParameters) {
|
||||
return this.search(bundleType, resourceType, searchParameters, Collections.emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches this repository
|
||||
*
|
||||
|
@ -264,9 +283,32 @@ public interface Repository {
|
|||
<B extends IBaseBundle, T extends IBaseResource> B search(
|
||||
Class<B> bundleType,
|
||||
Class<T> resourceType,
|
||||
Map<String, List<IQueryParameterType>> searchParameters,
|
||||
Multimap<String, List<IQueryParameterType>> searchParameters,
|
||||
Map<String, String> headers);
|
||||
|
||||
/**
|
||||
* Searches this repository
|
||||
*
|
||||
* @see <a href="https://www.hl7.org/fhir/http.html#search">FHIR search</a>
|
||||
*
|
||||
* @param <B> a Bundle type
|
||||
* @param <T> a Resource type
|
||||
* @param bundleType the class of the Bundle type to return
|
||||
* @param resourceType the class of the Resource type to search
|
||||
* @param searchParameters the searchParameters for this search
|
||||
* @param headers headers for this request, typically key-value pairs of HTTP headers
|
||||
* @return a Bundle with the results of the search
|
||||
*/
|
||||
default <B extends IBaseBundle, T extends IBaseResource> B search(
|
||||
Class<B> bundleType,
|
||||
Class<T> resourceType,
|
||||
Map<String, List<IQueryParameterType>> searchParameters,
|
||||
Map<String, String> headers) {
|
||||
ArrayListMultimap<String, List<IQueryParameterType>> multimap = ArrayListMultimap.create();
|
||||
searchParameters.forEach(multimap::put);
|
||||
return this.search(bundleType, resourceType, multimap, headers);
|
||||
}
|
||||
|
||||
// Paging starts here
|
||||
|
||||
/**
|
||||
|
|
|
@ -61,6 +61,7 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
|
@ -83,10 +84,23 @@ public class FhirTerser {
|
|||
|
||||
private static final Pattern COMPARTMENT_MATCHER_PATH =
|
||||
Pattern.compile("([a-zA-Z.]+)\\.where\\(resolve\\(\\) is ([a-zA-Z]+)\\)");
|
||||
|
||||
private static final String USER_DATA_KEY_CONTAIN_RESOURCES_COMPLETED =
|
||||
FhirTerser.class.getName() + "_CONTAIN_RESOURCES_COMPLETED";
|
||||
|
||||
private final FhirContext myContext;
|
||||
|
||||
/**
|
||||
* This comparator sorts IBaseReferences, and places any that are missing an ID at the end. Those with an ID go to the front.
|
||||
*/
|
||||
private static final Comparator<IBaseReference> REFERENCES_WITH_IDS_FIRST =
|
||||
Comparator.nullsLast(Comparator.comparing(ref -> {
|
||||
if (ref.getResource() == null) return true;
|
||||
if (ref.getResource().getIdElement() == null) return true;
|
||||
if (ref.getResource().getIdElement().getValue() == null) return true;
|
||||
return false;
|
||||
}));
|
||||
|
||||
public FhirTerser(FhirContext theContext) {
|
||||
super();
|
||||
myContext = theContext;
|
||||
|
@ -1418,6 +1432,13 @@ public class FhirTerser {
|
|||
private void containResourcesForEncoding(
|
||||
ContainedResources theContained, IBaseResource theResource, boolean theModifyResource) {
|
||||
List<IBaseReference> allReferences = getAllPopulatedChildElementsOfType(theResource, IBaseReference.class);
|
||||
|
||||
// Note that we process all contained resources that have arrived here with an ID contained resources first, so
|
||||
// that we don't accidentally auto-assign an ID
|
||||
// which may collide with a resource we have yet to process.
|
||||
// See: https://github.com/hapifhir/hapi-fhir/issues/6403
|
||||
allReferences.sort(REFERENCES_WITH_IDS_FIRST);
|
||||
|
||||
for (IBaseReference next : allReferences) {
|
||||
IBaseResource resource = next.getResource();
|
||||
if (resource == null && next.getReferenceElement().isLocal()) {
|
||||
|
@ -1437,11 +1458,11 @@ public class FhirTerser {
|
|||
IBaseResource resource = next.getResource();
|
||||
if (resource != null) {
|
||||
if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) {
|
||||
if (theContained.getResourceId(resource) != null) {
|
||||
// Prevent infinite recursion if there are circular loops in the contained resources
|
||||
|
||||
IIdType id = theContained.addContained(resource);
|
||||
if (id == null) {
|
||||
continue;
|
||||
}
|
||||
IIdType id = theContained.addContained(resource);
|
||||
if (theModifyResource) {
|
||||
getContainedResourceList(theResource).add(resource);
|
||||
next.setReference(id.getValue());
|
||||
|
@ -1769,7 +1790,6 @@ public class FhirTerser {
|
|||
|
||||
public static class ContainedResources {
|
||||
private long myNextContainedId = 1;
|
||||
|
||||
private List<IBaseResource> myResourceList;
|
||||
private IdentityHashMap<IBaseResource, IIdType> myResourceToIdMap;
|
||||
private Map<String, IBaseResource> myExistingIdToContainedResourceMap;
|
||||
|
@ -1782,6 +1802,11 @@ public class FhirTerser {
|
|||
}
|
||||
|
||||
public IIdType addContained(IBaseResource theResource) {
|
||||
if (this.getResourceId(theResource) != null) {
|
||||
// Prevent infinite recursion if there are circular loops in the contained resources
|
||||
return null;
|
||||
}
|
||||
|
||||
IIdType existing = getResourceToIdMap().get(theResource);
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
|
@ -1796,7 +1821,10 @@ public class FhirTerser {
|
|||
if (substring(idPart, 0, 1).equals("#")) {
|
||||
idPart = idPart.substring(1);
|
||||
if (StringUtils.isNumeric(idPart)) {
|
||||
myNextContainedId = Long.parseLong(idPart) + 1;
|
||||
// If there is a user-supplied numeric contained ID, our auto-assigned IDs should exceed the
|
||||
// largest
|
||||
// client-assigned contained ID.
|
||||
myNextContainedId = Math.max(myNextContainedId, Long.parseLong(idPart) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1864,6 +1892,7 @@ public class FhirTerser {
|
|||
}
|
||||
|
||||
public void assignIdsToContainedResources() {
|
||||
// TODO JA: Dead code?
|
||||
|
||||
if (!getContainedResources().isEmpty()) {
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package ca.uhn.fhir.util.adapters;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class AdapterManager implements IAdapterManager {
|
||||
public static final AdapterManager INSTANCE = new AdapterManager();
|
||||
|
||||
Set<IAdapterFactory> myAdapterFactories = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Hidden to force shared use of the public INSTANCE.
|
||||
*/
|
||||
AdapterManager() {}
|
||||
|
||||
public <T> @Nonnull Optional<T> getAdapter(Object theObject, Class<T> theTargetType) {
|
||||
// todo this can be sped up with a cache of type->Factory.
|
||||
return myAdapterFactories.stream()
|
||||
.filter(nextFactory -> nextFactory.getAdapters().stream().anyMatch(theTargetType::isAssignableFrom))
|
||||
.flatMap(nextFactory -> {
|
||||
var adapter = nextFactory.getAdapter(theObject, theTargetType);
|
||||
// can't use Optional.stream() because of our Android target is API level 26/JDK 8.
|
||||
if (adapter.isPresent()) {
|
||||
return Stream.of(adapter.get());
|
||||
} else {
|
||||
return Stream.empty();
|
||||
}
|
||||
})
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public void registerFactory(@Nonnull IAdapterFactory theFactory) {
|
||||
myAdapterFactories.add(theFactory);
|
||||
}
|
||||
|
||||
public void unregisterFactory(@Nonnull IAdapterFactory theFactory) {
|
||||
myAdapterFactories.remove(theFactory);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package ca.uhn.fhir.util.adapters;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class AdapterUtils {
|
||||
|
||||
/**
|
||||
* Main entry point for adapter calls.
|
||||
* Implements three conversions: cast to the target type, use IAdaptable if present, or lastly try the AdapterManager.INSTANCE.
|
||||
* @param theObject the object to be adapted
|
||||
* @param theTargetType the type of the adapter requested
|
||||
*/
|
||||
static <T> Optional<T> adapt(Object theObject, Class<T> theTargetType) {
|
||||
if (theTargetType.isInstance(theObject)) {
|
||||
//noinspection unchecked
|
||||
return Optional.of((T) theObject);
|
||||
}
|
||||
|
||||
if (theObject instanceof IAdaptable) {
|
||||
IAdaptable adaptable = (IAdaptable) theObject;
|
||||
var adapted = adaptable.getAdapter(theTargetType);
|
||||
if (adapted.isPresent()) {
|
||||
return adapted;
|
||||
}
|
||||
}
|
||||
|
||||
return AdapterManager.INSTANCE.getAdapter(theObject, theTargetType);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package ca.uhn.fhir.util.adapters;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Generic version of Eclipse IAdaptable interface.
|
||||
*/
|
||||
public interface IAdaptable {
|
||||
/**
|
||||
* Get an adapter of requested type.
|
||||
* @param theTargetType the desired type of the adapter
|
||||
* @return an adapter of theTargetType if possible, or empty.
|
||||
*/
|
||||
default <T> @Nonnull Optional<T> getAdapter(@Nonnull Class<T> theTargetType) {
|
||||
return AdapterUtils.adapt(this, theTargetType);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package ca.uhn.fhir.util.adapters;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Interface for external service that builds adaptors for targets.
|
||||
*/
|
||||
public interface IAdapterFactory {
|
||||
/**
|
||||
* Build an adaptor for the target.
|
||||
* May return empty() even if the target type is listed in getAdapters() when
|
||||
* the factory fails to convert a particular instance.
|
||||
*
|
||||
* @param theObject the object to be adapted.
|
||||
* @param theAdapterType the target type
|
||||
* @return the adapter, if possible.
|
||||
*/
|
||||
<T> Optional<T> getAdapter(Object theObject, Class<T> theAdapterType);
|
||||
|
||||
/**
|
||||
* @return the collection of adapter target types handled by this factory.
|
||||
*/
|
||||
Collection<Class<?>> getAdapters();
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package ca.uhn.fhir.util.adapters;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Get an adaptor
|
||||
*/
|
||||
public interface IAdapterManager {
|
||||
<T> Optional<T> getAdapter(Object theTarget, Class<T> theAdapter);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Implements the Adapter pattern to allow external classes to extend/adapt existing classes.
|
||||
* Useful for extending interfaces that are closed to modification, or restricted for classpath reasons.
|
||||
* <p>
|
||||
* For clients, the main entry point is {@link ca.uhn.fhir.util.adapters.AdapterUtils#adapt(java.lang.Object, java.lang.Class)}
|
||||
* which will attempt to cast to the target type, or build an adapter of the target type.
|
||||
* </p>
|
||||
* <p>
|
||||
* For implementors, you can support adaptation via two mechanisms:
|
||||
* <ul>
|
||||
* <li>by implementing {@link ca.uhn.fhir.util.adapters.IAdaptable} directly on a class to provide supported adapters,
|
||||
* <li>or when the class is closed to direct modification, you can implement
|
||||
* an instance of {@link ca.uhn.fhir.util.adapters.IAdapterFactory} and register
|
||||
* it with the public {@link ca.uhn.fhir.util.adapters.AdapterManager#INSTANCE}.</li>
|
||||
* </ul>
|
||||
* The AdapterUtils.adapt() supports both of these.
|
||||
* </p>
|
||||
* Inspired by the Eclipse runtime.
|
||||
*/
|
||||
package ca.uhn.fhir.util.adapters;
|
|
@ -0,0 +1,78 @@
|
|||
package ca.uhn.fhir.util.adapters;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class AdapterManagerTest {
|
||||
AdapterManager myAdapterManager = new AdapterManager();
|
||||
|
||||
@AfterAll
|
||||
static void tearDown() {
|
||||
assertThat(AdapterManager.INSTANCE.myAdapterFactories)
|
||||
.withFailMessage("Don't dirty the public instance").isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegisterFactory_providesAdapter() {
|
||||
// given
|
||||
myAdapterManager.registerFactory(new StringToIntFactory());
|
||||
|
||||
// when
|
||||
var result = myAdapterManager.getAdapter("22", Integer.class);
|
||||
|
||||
// then
|
||||
assertThat(result).contains(22);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegisterFactory_wrongTypeStillEmpty() {
|
||||
// given
|
||||
myAdapterManager.registerFactory(new StringToIntFactory());
|
||||
|
||||
// when
|
||||
var result = myAdapterManager.getAdapter("22", Float.class);
|
||||
|
||||
// then
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnregisterFactory_providesEmpty() {
|
||||
// given active factory, now gone.
|
||||
StringToIntFactory factory = new StringToIntFactory();
|
||||
myAdapterManager.registerFactory(factory);
|
||||
myAdapterManager.getAdapter("22", Integer.class);
|
||||
myAdapterManager.unregisterFactory(factory);
|
||||
|
||||
// when
|
||||
var result = myAdapterManager.getAdapter("22", Integer.class);
|
||||
|
||||
// then
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
|
||||
static class StringToIntFactory implements IAdapterFactory {
|
||||
@Override
|
||||
public <T> Optional<T> getAdapter(Object theObject, Class<T> theAdapterType) {
|
||||
if (theObject instanceof String s) {
|
||||
if (theAdapterType.isAssignableFrom(Integer.class)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T i = (T) Integer.valueOf(s);
|
||||
return Optional.of(i);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Collection<Class<?>> getAdapters() {
|
||||
return List.of(Integer.class);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package ca.uhn.fhir.util.adapters;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class AdapterUtilsTest {
|
||||
|
||||
final private IAdapterFactory myTestFactory = new TestAdaptorFactory();
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
AdapterManager.INSTANCE.unregisterFactory(myTestFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNullDoesNotAdapt() {
|
||||
|
||||
// when
|
||||
var adapted = AdapterUtils.adapt(null, InterfaceA.class);
|
||||
|
||||
// then
|
||||
assertThat(adapted).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAdaptObjectImplementingInterface() {
|
||||
// given
|
||||
var object = new ClassB();
|
||||
|
||||
// when
|
||||
var adapted = AdapterUtils.adapt(object, InterfaceA.class);
|
||||
|
||||
// then
|
||||
assertThat(adapted)
|
||||
.isPresent()
|
||||
.get().isInstanceOf(InterfaceA.class);
|
||||
assertThat(adapted.get()).withFailMessage("Use object since it implements interface").isSameAs(object);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAdaptObjectImplementingAdaptorSupportingInterface() {
|
||||
// given
|
||||
var object = new SelfAdaptableClass();
|
||||
|
||||
// when
|
||||
var adapted = AdapterUtils.adapt(object, InterfaceA.class);
|
||||
|
||||
// then
|
||||
assertThat(adapted)
|
||||
.isPresent()
|
||||
.get().isInstanceOf(InterfaceA.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAdaptObjectViaAdapterManager() {
|
||||
// given
|
||||
var object = new ManagerAdaptableClass();
|
||||
AdapterManager.INSTANCE.registerFactory(myTestFactory);
|
||||
|
||||
// when
|
||||
var adapted = AdapterUtils.adapt(object, InterfaceA.class);
|
||||
|
||||
// then
|
||||
assertThat(adapted)
|
||||
.isPresent()
|
||||
.get().isInstanceOf(InterfaceA.class);
|
||||
}
|
||||
|
||||
interface InterfaceA {
|
||||
|
||||
}
|
||||
|
||||
static class ClassB implements InterfaceA {
|
||||
|
||||
}
|
||||
|
||||
/** class that can adapt itself to IAdaptable */
|
||||
static class SelfAdaptableClass implements IAdaptable {
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public <T> Optional<T> getAdapter(@Nonnull Class<T> theTargetType) {
|
||||
if (theTargetType.isAssignableFrom(InterfaceA.class)) {
|
||||
T value = theTargetType.cast(buildInterfaceAWrapper(this));
|
||||
return Optional.of(value);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nonnull InterfaceA buildInterfaceAWrapper(Object theObject) {
|
||||
return new InterfaceA() {};
|
||||
}
|
||||
|
||||
/** Class that relies on an external IAdapterFactory */
|
||||
static class ManagerAdaptableClass {
|
||||
}
|
||||
|
||||
|
||||
static class TestAdaptorFactory implements IAdapterFactory {
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getAdapter(Object theObject, Class<T> theAdapterType) {
|
||||
if (theObject instanceof ManagerAdaptableClass && theAdapterType == InterfaceA.class) {
|
||||
T adapter = theAdapterType.cast(buildInterfaceAWrapper(theObject));
|
||||
return Optional.of(adapter);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<?>> getAdapters() {
|
||||
return Set.of(InterfaceA.class);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: fix
|
||||
jira: SMILE-8969
|
||||
title: "Fixed a rare bug in the JSON Parser, wherein client-assigned contained resource IDs could collide with server-assigned contained IDs. For example if a
|
||||
resource had a client-assigned contained ID of `#2`, and a contained resource with no ID, then depending on the processing order, the parser could occasionally
|
||||
provide duplicate contained resource IDs, leading to non-deterministic behaviour."
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 6404
|
||||
title: "Searches using fulltext search that combined `_lastUpdated` query parameter with
|
||||
any other (supported) fulltext query parameter would find no matches, even
|
||||
if matches existed.
|
||||
This has been corrected.
|
||||
"
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 6419
|
||||
title: "Previously, on Postgres, the `$reindex` operation with `optimizeStorage` set to `ALL_VERSIONS` would process
|
||||
only a subset of versions if there were more than 100 versions to be processed for a resource. This has been fixed
|
||||
so that all versions of the resource are now processed."
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 6420
|
||||
title: "Previously, when the `$reindex` operation is run for a single FHIR resource with `optimizeStorage` set to
|
||||
`ALL_VERSIONS`, none of the versions of the resource were processed in `hfj_res_ver` table. This has been fixed."
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 6422
|
||||
title: "Previously, since 7.4.4 the validation issue detail codes were not translated correctly for Remote Terminology
|
||||
validateCode calls. The detail code used was `invalid-code` for all use-cases which resulted in profile binding strength
|
||||
not being applied to the issue severity as expected when validating resources against a profile.
|
||||
This has been fixed and issue detail codes are translated correctly."
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 6440
|
||||
title: "Previously, if an `IInterceptorBroadcaster` was set in a `RequestDetails` object,
|
||||
`STORAGE_PRECHECK_FOR_CACHED_SEARCH` hooks that were registered to that `IInterceptorBroadcaster` were not
|
||||
called. Also, if an `IInterceptorBroadcaster` was set in the `RequestDetails` object, the boolean return value of the hooks
|
||||
registered to that `IInterceptorBroadcaster` were not taken into account. This second issue existed for all pointcuts
|
||||
that returned a boolean type, not just for `STORAGE_PRECHECK_FOR_CACHED_SEARCH`. These issues have now been fixed."
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
type: add
|
||||
issue: 6445
|
||||
title: "Add Multimap versions of the search() methods to Repository to support queries like `Patient?_tag=a&_tag=b`"
|
|
@ -23,3 +23,8 @@ If `true`, this will return summarized subject bundle with only detectedIssue.
|
|||
|
||||
The `subject` parameter of the `Questionnaire/$populate` operation has been changed to expect a `Reference` as specified
|
||||
in the SDC IG.
|
||||
|
||||
# Fulltext Search with _lastUpdated Filter
|
||||
|
||||
Fulltext searches have been updated to support `_lastUpdated` search parameter. A reindexing of Search Parameters
|
||||
is required to migrate old data to support the `_lastUpdated` search parameter.
|
||||
|
|
|
@ -48,24 +48,24 @@ Additional parameters have been added to support CQL evaluation.
|
|||
|
||||
The following parameters are supported for the `Questionnaire/$populate` operation:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|---------------------|---------------|-------------|
|
||||
| questionnaire | Questionnaire | The Questionnaire to populate. Used when the operation is invoked at the 'type' level. |
|
||||
| canonical | canonical | The canonical identifier for the Questionnaire (optionally version-specific). |
|
||||
| url | uri | Canonical URL of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters. |
|
||||
| version | string | Version of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters. |
|
||||
| subject | Reference | The resource that is to be the QuestionnaireResponse.subject. The QuestionnaireResponse instance will reference the provided subject. |
|
||||
| context | | Resources containing information to be used to help populate the QuestionnaireResponse. |
|
||||
| context.name | string | The name of the launchContext or root Questionnaire variable the passed content should be used as for population purposes. The name SHALL correspond to a launchContext or variable delared at the root of the Questionnaire. |
|
||||
| context.reference | Reference | The actual resource (or resources) to use as the value of the launchContext or variable. |
|
||||
| local | boolean | Whether the server should use what resources and other knowledge it has about the referenced subject when pre-populating answers to questions. |
|
||||
| launchContext | Extension | The [Questionnaire Launch Context](https://hl7.org/fhir/uv/sdc/StructureDefinition-sdc-questionnaire-launchContext.html) extension containing Resources that provide context for form processing logic (pre-population) when creating/displaying/editing a QuestionnaireResponse. |
|
||||
| parameters | Parameters | Any input parameters defined in libraries referenced by the Questionnaire. |
|
||||
| useServerData | boolean | Whether to use data from the server performing the evaluation. |
|
||||
| data | Bundle | Data to be made available during CQL evaluation. |
|
||||
| dataEndpoint | Endpoint | An endpoint to use to access data referenced by retrieve operations in libraries referenced by the Questionnaire. |
|
||||
| contentEndpoint | Endpoint | An endpoint to use to access content (i.e. libraries) referenced by the Questionnaire. |
|
||||
| terminologyEndpoint | Endpoint | An endpoint to use to access terminology (i.e. valuesets, codesystems, and membership testing) referenced by the Questionnaire. |
|
||||
| Parameter | Type | Description |
|
||||
|---------------------|--------------------|-------------|
|
||||
| questionnaire | Questionnaire | The Questionnaire to populate. Used when the operation is invoked at the 'type' level. |
|
||||
| canonical | canonical | The canonical identifier for the Questionnaire (optionally version-specific). |
|
||||
| url | uri | Canonical URL of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters. |
|
||||
| version | string | Version of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters. |
|
||||
| subject | Reference | The resource that is to be the QuestionnaireResponse.subject. The QuestionnaireResponse instance will reference the provided subject. |
|
||||
| context | | Resources containing information to be used to help populate the QuestionnaireResponse. |
|
||||
| context.name | string | The name of the launchContext or root Questionnaire variable the passed content should be used as for population purposes. The name SHALL correspond to a launchContext or variable declared at the root of the Questionnaire. |
|
||||
| context.content | Reference/Resource | The actual resource (or reference) to use as the value of the launchContext or variable. |
|
||||
| local | boolean | Whether the server should use what resources and other knowledge it has about the referenced subject when pre-populating answers to questions. |
|
||||
| launchContext | Extension | The [Questionnaire Launch Context](https://hl7.org/fhir/uv/sdc/StructureDefinition-sdc-questionnaire-launchContext.html) extension containing Resources that provide context for form processing logic (pre-population) when creating/displaying/editing a QuestionnaireResponse. |
|
||||
| parameters | Parameters | Any input parameters defined in libraries referenced by the Questionnaire. |
|
||||
| useServerData | boolean | Whether to use data from the server performing the evaluation. |
|
||||
| data | Bundle | Data to be made available during CQL evaluation. |
|
||||
| dataEndpoint | Endpoint | An endpoint to use to access data referenced by retrieve operations in libraries referenced by the Questionnaire. |
|
||||
| contentEndpoint | Endpoint | An endpoint to use to access content (i.e. libraries) referenced by the Questionnaire. |
|
||||
| terminologyEndpoint | Endpoint | An endpoint to use to access terminology (i.e. valuesets, codesystems, and membership testing) referenced by the Questionnaire. |
|
||||
|
||||
## Extract
|
||||
|
||||
|
|
|
@ -981,12 +981,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
entity.setIndexStatus(INDEX_STATUS_INDEXED);
|
||||
}
|
||||
|
||||
if (myFulltextSearchSvc != null && !myFulltextSearchSvc.isDisabled()) {
|
||||
if (myFulltextSearchSvc != null && !myFulltextSearchSvc.isDisabled() && changed.isChanged()) {
|
||||
populateFullTextFields(myContext, theResource, entity, newParams);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
entity.setUpdated(theTransactionDetails.getTransactionDate());
|
||||
entity.setIndexStatus(null);
|
||||
|
||||
|
@ -1018,16 +1016,19 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
* Save the resource itself
|
||||
*/
|
||||
if (entity.getId() == null) {
|
||||
// create
|
||||
myEntityManager.persist(entity);
|
||||
|
||||
postPersist(entity, (T) theResource, theRequest);
|
||||
|
||||
} else if (entity.getDeleted() != null) {
|
||||
// delete
|
||||
entity = myEntityManager.merge(entity);
|
||||
|
||||
postDelete(entity);
|
||||
|
||||
} else {
|
||||
// update
|
||||
entity = myEntityManager.merge(entity);
|
||||
|
||||
postUpdate(entity, (T) theResource, theRequest);
|
||||
|
@ -1679,7 +1680,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
theEntity.setContentText(parseContentTextIntoWords(theContext, theResource));
|
||||
if (myStorageSettings.isAdvancedHSearchIndexing()) {
|
||||
ExtendedHSearchIndexData hSearchIndexData =
|
||||
myFulltextSearchSvc.extractLuceneIndexData(theResource, theNewParams);
|
||||
myFulltextSearchSvc.extractLuceneIndexData(theResource, theEntity, theNewParams);
|
||||
theEntity.setLuceneIndexData(hSearchIndexData);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,6 +133,7 @@ import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -1693,9 +1694,15 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
if (theOptimizeStorageMode == ReindexParameters.OptimizeStorageModeEnum.ALL_VERSIONS) {
|
||||
int pageSize = 100;
|
||||
for (int page = 0; ((long) page * pageSize) < entity.getVersion(); page++) {
|
||||
|
||||
// We need to sort the pages, because we are updating the same data we are paging through.
|
||||
// If not sorted explicitly, a database like Postgres returns the same data multiple times on
|
||||
// different pages as the underlying data gets updated.
|
||||
PageRequest pageRequest = PageRequest.of(page, pageSize, Sort.by("myId"));
|
||||
Slice<ResourceHistoryTable> historyEntities =
|
||||
myResourceHistoryTableDao.findForResourceIdAndReturnEntitiesAndFetchProvenance(
|
||||
PageRequest.of(page, pageSize), entity.getId(), historyEntity.getVersion());
|
||||
pageRequest, entity.getId(), historyEntity.getVersion());
|
||||
|
||||
for (ResourceHistoryTable next : historyEntities) {
|
||||
reindexOptimizeStorageHistoryEntity(entity, next);
|
||||
}
|
||||
|
|
|
@ -205,7 +205,7 @@ public abstract class BaseHapiFhirSystemDao<T extends IBaseBundle, MT> extends B
|
|||
*
|
||||
* However, for realistic average workloads, this should reduce the number of round trips.
|
||||
*/
|
||||
if (idChunk.size() >= 2) {
|
||||
if (!idChunk.isEmpty()) {
|
||||
List<ResourceTable> entityChunk = prefetchResourceTableHistoryAndProvenance(idChunk);
|
||||
|
||||
if (thePreFetchIndexes) {
|
||||
|
|
|
@ -135,13 +135,13 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
|
|||
|
||||
@Override
|
||||
public ExtendedHSearchIndexData extractLuceneIndexData(
|
||||
IBaseResource theResource, ResourceIndexedSearchParams theNewParams) {
|
||||
IBaseResource theResource, ResourceTable theEntity, ResourceIndexedSearchParams theNewParams) {
|
||||
String resourceType = myFhirContext.getResourceType(theResource);
|
||||
ResourceSearchParams activeSearchParams = mySearchParamRegistry.getActiveSearchParams(
|
||||
resourceType, ISearchParamRegistry.SearchParamLookupContextEnum.SEARCH);
|
||||
ExtendedHSearchIndexExtractor extractor = new ExtendedHSearchIndexExtractor(
|
||||
myStorageSettings, myFhirContext, activeSearchParams, mySearchParamExtractor);
|
||||
return extractor.extract(theResource, theNewParams);
|
||||
return extractor.extract(theResource, theEntity, theNewParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -89,7 +89,7 @@ public interface IFulltextSearchSvc {
|
|||
boolean isDisabled();
|
||||
|
||||
ExtendedHSearchIndexData extractLuceneIndexData(
|
||||
IBaseResource theResource, ResourceIndexedSearchParams theNewParams);
|
||||
IBaseResource theResource, ResourceTable theEntity, ResourceIndexedSearchParams theNewParams);
|
||||
|
||||
/**
|
||||
* Returns true if the parameter map can be handled for hibernate search.
|
||||
|
|
|
@ -392,7 +392,7 @@ public class ExtendedHSearchClauseBuilder {
|
|||
|
||||
/**
|
||||
* Create date clause from date params. The date lower and upper bounds are taken
|
||||
* into considertion when generating date query ranges
|
||||
* into consideration when generating date query ranges
|
||||
*
|
||||
* <p>Example 1 ('eq' prefix/empty): <code>http://fhirserver/Observation?date=eq2020</code>
|
||||
* would generate the following search clause
|
||||
|
|
|
@ -25,6 +25,8 @@ import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
|||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
|
||||
import ca.uhn.fhir.jpa.model.search.CompositeSearchIndexData;
|
||||
import ca.uhn.fhir.jpa.model.search.DateSearchIndexData;
|
||||
import ca.uhn.fhir.jpa.model.search.ExtendedHSearchIndexData;
|
||||
|
@ -37,6 +39,7 @@ import ca.uhn.fhir.rest.server.util.ResourceSearchParams;
|
|||
import ca.uhn.fhir.util.MetaUtil;
|
||||
import com.google.common.base.Strings;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseCoding;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
@ -73,9 +76,10 @@ public class ExtendedHSearchIndexExtractor {
|
|||
mySearchParamExtractor = theSearchParamExtractor;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public ExtendedHSearchIndexData extract(IBaseResource theResource, ResourceIndexedSearchParams theNewParams) {
|
||||
ExtendedHSearchIndexData retVal = new ExtendedHSearchIndexData(myContext, myJpaStorageSettings, theResource);
|
||||
public ExtendedHSearchIndexData extract(
|
||||
IBaseResource theResource, ResourceTable theEntity, ResourceIndexedSearchParams theNewParams) {
|
||||
ExtendedHSearchIndexData retVal =
|
||||
new ExtendedHSearchIndexData(myContext, myJpaStorageSettings, theResource, theEntity);
|
||||
|
||||
if (myJpaStorageSettings.isStoreResourceInHSearchIndex()) {
|
||||
retVal.setRawResourceData(myContext.newJsonParser().encodeResourceToString(theResource));
|
||||
|
@ -113,11 +117,27 @@ public class ExtendedHSearchIndexExtractor {
|
|||
.filter(nextParam -> !nextParam.isMissing())
|
||||
.forEach(nextParam -> retVal.addUriIndexData(nextParam.getParamName(), nextParam.getUri()));
|
||||
|
||||
theResource.getMeta().getTag().forEach(tag -> retVal.addTokenIndexData("_tag", tag));
|
||||
theEntity.getTags().forEach(tag -> {
|
||||
TagDefinition td = tag.getTag();
|
||||
|
||||
theResource.getMeta().getSecurity().forEach(sec -> retVal.addTokenIndexData("_security", sec));
|
||||
|
||||
theResource.getMeta().getProfile().forEach(prof -> retVal.addUriIndexData("_profile", prof.getValue()));
|
||||
IBaseCoding coding = (IBaseCoding) myContext.getVersion().newCodingDt();
|
||||
coding.setVersion(td.getVersion());
|
||||
coding.setDisplay(td.getDisplay());
|
||||
coding.setCode(td.getCode());
|
||||
coding.setSystem(td.getSystem());
|
||||
coding.setUserSelected(ObjectUtils.defaultIfNull(td.getUserSelected(), false));
|
||||
switch (td.getTagType()) {
|
||||
case TAG:
|
||||
retVal.addTokenIndexData("_tag", coding);
|
||||
break;
|
||||
case PROFILE:
|
||||
retVal.addUriIndexData("_profile", coding.getCode());
|
||||
break;
|
||||
case SECURITY_LABEL:
|
||||
retVal.addTokenIndexData("_security", coding);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
String source = MetaUtil.getSource(myContext, theResource.getMeta());
|
||||
if (isNotBlank(source)) {
|
||||
|
@ -127,20 +147,14 @@ public class ExtendedHSearchIndexExtractor {
|
|||
theNewParams.myCompositeParams.forEach(nextParam ->
|
||||
retVal.addCompositeIndexData(nextParam.getSearchParamName(), buildCompositeIndexData(nextParam)));
|
||||
|
||||
if (theResource.getMeta().getLastUpdated() != null) {
|
||||
int ordinal = ResourceIndexedSearchParamDate.calculateOrdinalValue(
|
||||
theResource.getMeta().getLastUpdated())
|
||||
if (theEntity.getUpdated() != null && !theEntity.getUpdated().isEmpty()) {
|
||||
int ordinal = ResourceIndexedSearchParamDate.calculateOrdinalValue(theEntity.getUpdatedDate())
|
||||
.intValue();
|
||||
retVal.addDateIndexData(
|
||||
"_lastUpdated",
|
||||
theResource.getMeta().getLastUpdated(),
|
||||
ordinal,
|
||||
theResource.getMeta().getLastUpdated(),
|
||||
ordinal);
|
||||
"_lastUpdated", theEntity.getUpdatedDate(), ordinal, theEntity.getUpdatedDate(), ordinal);
|
||||
}
|
||||
|
||||
if (!theNewParams.myLinks.isEmpty()) {
|
||||
|
||||
// awkwardly, links are indexed by jsonpath, not by search param.
|
||||
// so we re-build the linkage.
|
||||
Map<String, List<String>> linkPathToParamName = new HashMap<>();
|
||||
|
|
|
@ -28,6 +28,7 @@ import ca.uhn.fhir.rest.api.Constants;
|
|||
import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
|
||||
import ca.uhn.fhir.rest.param.CompositeParam;
|
||||
import ca.uhn.fhir.rest.param.DateParam;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import ca.uhn.fhir.rest.param.NumberParam;
|
||||
import ca.uhn.fhir.rest.param.QuantityParam;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
|
@ -89,19 +90,29 @@ public class ExtendedHSearchSearchBuilder {
|
|||
* be inaccurate and wrong.
|
||||
*/
|
||||
public boolean canUseHibernateSearch(
|
||||
String theResourceType, SearchParameterMap myParams, ISearchParamRegistry theSearchParamRegistry) {
|
||||
String theResourceType, SearchParameterMap theParams, ISearchParamRegistry theSearchParamRegistry) {
|
||||
boolean canUseHibernate = false;
|
||||
|
||||
ResourceSearchParams resourceActiveSearchParams = theSearchParamRegistry.getActiveSearchParams(
|
||||
theResourceType, ISearchParamRegistry.SearchParamLookupContextEnum.SEARCH);
|
||||
for (String paramName : myParams.keySet()) {
|
||||
theResourceType, ISearchParamRegistry.SearchParamLookupContextEnum.SEARCH);
|
||||
|
||||
// special SearchParam handling:
|
||||
// _lastUpdated
|
||||
if (theParams.getLastUpdated() != null) {
|
||||
canUseHibernate = !illegalForHibernateSearch(Constants.PARAM_LASTUPDATED, resourceActiveSearchParams);
|
||||
if (!canUseHibernate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (String paramName : theParams.keySet()) {
|
||||
// is this parameter supported?
|
||||
if (illegalForHibernateSearch(paramName, resourceActiveSearchParams)) {
|
||||
canUseHibernate = false;
|
||||
} else {
|
||||
// are the parameter values supported?
|
||||
canUseHibernate =
|
||||
myParams.get(paramName).stream()
|
||||
theParams.get(paramName).stream()
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList())
|
||||
.stream()
|
||||
|
@ -136,6 +147,7 @@ public class ExtendedHSearchSearchBuilder {
|
|||
|
||||
// not yet supported in HSearch
|
||||
myParams.getSearchContainedMode() == SearchContainedModeEnum.FALSE
|
||||
&& supportsLastUpdated(myParams)
|
||||
&& // ???
|
||||
myParams.entrySet().stream()
|
||||
.filter(e -> !ourUnsafeSearchParmeters.contains(e.getKey()))
|
||||
|
@ -145,6 +157,19 @@ public class ExtendedHSearchSearchBuilder {
|
|||
.allMatch(this::isParamTypeSupported);
|
||||
}
|
||||
|
||||
private boolean supportsLastUpdated(SearchParameterMap theMap) {
|
||||
if (theMap.getLastUpdated() == null || theMap.getLastUpdated().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DateRangeParam lastUpdated = theMap.getLastUpdated();
|
||||
|
||||
return lastUpdated.getLowerBound() != null
|
||||
&& isParamTypeSupported(lastUpdated.getLowerBound())
|
||||
&& lastUpdated.getUpperBound() != null
|
||||
&& isParamTypeSupported(lastUpdated.getUpperBound());
|
||||
}
|
||||
|
||||
/**
|
||||
* Do we support this query param type+modifier?
|
||||
* <p>
|
||||
|
|
|
@ -605,12 +605,12 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
|
|||
.add(SearchParameterMap.class, theParams)
|
||||
.add(RequestDetails.class, theRequestDetails)
|
||||
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
|
||||
Object outcome = CompositeInterceptorBroadcaster.doCallHooksAndReturnObject(
|
||||
boolean canUseCache = CompositeInterceptorBroadcaster.doCallHooks(
|
||||
myInterceptorBroadcaster,
|
||||
theRequestDetails,
|
||||
Pointcut.STORAGE_PRECHECK_FOR_CACHED_SEARCH,
|
||||
params);
|
||||
if (Boolean.FALSE.equals(outcome)) {
|
||||
if (!canUseCache) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -1044,7 +1044,8 @@ public class TermReadSvcImpl implements ITermReadSvc, IHasScheduledJobs {
|
|||
if (theExpansionOptions != null
|
||||
&& !theExpansionOptions.isFailOnMissingCodeSystem()
|
||||
// Code system is unknown, therefore NOT_FOUND
|
||||
&& e.getCodeValidationIssue().getCoding() == CodeValidationIssueCoding.NOT_FOUND) {
|
||||
&& e.getCodeValidationIssue()
|
||||
.hasIssueDetailCode(CodeValidationIssueCoding.NOT_FOUND.getCode())) {
|
||||
return;
|
||||
}
|
||||
throw new InternalErrorException(Msg.code(888) + e);
|
||||
|
@ -2190,7 +2191,7 @@ public class TermReadSvcImpl implements ITermReadSvc, IHasScheduledJobs {
|
|||
.setSeverity(IssueSeverity.ERROR)
|
||||
.setCodeSystemVersion(theCodeSystemVersion)
|
||||
.setMessage(theMessage)
|
||||
.addCodeValidationIssue(new CodeValidationIssue(
|
||||
.addIssue(new CodeValidationIssue(
|
||||
theMessage,
|
||||
IssueSeverity.ERROR,
|
||||
CodeValidationIssueCode.CODE_INVALID,
|
||||
|
|
|
@ -121,7 +121,6 @@ import java.io.IOException;
|
|||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -130,13 +129,11 @@ import java.util.stream.Collectors;
|
|||
|
||||
import static ca.uhn.fhir.jpa.model.util.UcumServiceUtil.UCUM_CODESYSTEM_URL;
|
||||
import static ca.uhn.fhir.rest.api.Constants.CHARSET_UTF8;
|
||||
import static ca.uhn.fhir.rest.api.Constants.HEADER_CACHE_CONTROL;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
|
@ -294,6 +291,20 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest impl
|
|||
}
|
||||
}
|
||||
|
||||
@Test //TODO LS : This test fails, and did not before.
|
||||
public void testNoOpUpdateDoesNotModifyLastUpdated() throws InterruptedException {
|
||||
myStorageSettings.setAdvancedHSearchIndexing(true);
|
||||
Patient patient = new Patient();
|
||||
patient.getNameFirstRep().setFamily("graham").addGiven("gary");
|
||||
|
||||
patient = (Patient) myPatientDao.create(patient).getResource();
|
||||
Date originalLastUpdated = patient.getMeta().getLastUpdated();
|
||||
|
||||
patient = (Patient) myPatientDao.update(patient).getResource();
|
||||
Date newLastUpdated = patient.getMeta().getLastUpdated();
|
||||
|
||||
assertThat(originalLastUpdated).isEqualTo(newLastUpdated);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullTextSearchesArePerformanceLogged() {
|
||||
|
|
|
@ -108,7 +108,6 @@ public class ValueSetExpansionR4ElasticsearchIT extends BaseJpaTest implements I
|
|||
when(mySrd.getUserData().getOrDefault(MAKE_LOADING_VERSION_CURRENT, Boolean.TRUE)).thenReturn(Boolean.TRUE);
|
||||
}
|
||||
|
||||
|
||||
@AfterEach
|
||||
public void after() {
|
||||
myStorageSettings.setMaximumExpansionSize(JpaStorageSettings.DEFAULT_MAX_EXPANSION_SIZE);
|
||||
|
@ -217,7 +216,6 @@ public class ValueSetExpansionR4ElasticsearchIT extends BaseJpaTest implements I
|
|||
}
|
||||
myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd(CS_URL, additions);
|
||||
|
||||
|
||||
// Codes available exceeds the max
|
||||
myStorageSettings.setMaximumExpansionSize(50);
|
||||
ValueSet vs = new ValueSet();
|
||||
|
@ -236,7 +234,6 @@ public class ValueSetExpansionR4ElasticsearchIT extends BaseJpaTest implements I
|
|||
include.setSystem(CS_URL);
|
||||
ValueSet outcome = myTermSvc.expandValueSet(null, vs);
|
||||
assertThat(outcome.getExpansion().getContains()).hasSize(109);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ca.uhn.fhir.jpa.mdm.helper;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
|
@ -23,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import java.util.function.Supplier;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
|
@ -78,6 +80,7 @@ public abstract class BaseMdmHelper implements BeforeEachCallback, AfterEachCall
|
|||
//they are coming from an external HTTP Request.
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(myMockSrd.getInterceptorBroadcaster()).thenReturn(myMockInterceptorBroadcaster);
|
||||
when(myMockInterceptorBroadcaster.callHooks(any(Pointcut.class), any(HookParams.class))).thenReturn(true);
|
||||
when(myMockSrd.getServletRequest()).thenReturn(myMockServletRequest);
|
||||
when(myMockSrd.getServer()).thenReturn(myMockRestfulServer);
|
||||
when(myMockSrd.getRequestId()).thenReturn("MOCK_REQUEST");
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package ca.uhn.fhir.jpa.model.search;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.model.entity.StorageSettings;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
|
@ -57,12 +58,17 @@ public class ExtendedHSearchIndexData {
|
|||
private String myForcedId;
|
||||
private String myResourceJSON;
|
||||
private IBaseResource myResource;
|
||||
private ResourceTable myEntity;
|
||||
|
||||
public ExtendedHSearchIndexData(
|
||||
FhirContext theFhirContext, StorageSettings theStorageSettings, IBaseResource theResource) {
|
||||
FhirContext theFhirContext,
|
||||
StorageSettings theStorageSettings,
|
||||
IBaseResource theResource,
|
||||
ResourceTable theEntity) {
|
||||
this.myFhirContext = theFhirContext;
|
||||
this.myStorageSettings = theStorageSettings;
|
||||
myResource = theResource;
|
||||
myEntity = theEntity;
|
||||
}
|
||||
|
||||
private <V> BiConsumer<String, V> ifNotContained(BiConsumer<String, V> theIndexWriter) {
|
||||
|
|
|
@ -62,6 +62,9 @@ public abstract class BaseComboParamsR4Test extends BaseJpaR4Test {
|
|||
myMessages.add("REUSING CACHED SEARCH");
|
||||
return null;
|
||||
});
|
||||
|
||||
// allow searches to use cached results
|
||||
when(myInterceptorBroadcaster.callHooks(eq(Pointcut.STORAGE_PRECHECK_FOR_CACHED_SEARCH), ArgumentMatchers.any(HookParams.class))).thenReturn(true);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ca.uhn.fhir.jpa.dao.r4;
|
||||
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
|
@ -23,6 +24,8 @@ import org.hl7.fhir.r4.model.Reference;
|
|||
import org.hl7.fhir.r4.model.ServiceRequest;
|
||||
import org.hl7.fhir.r4.model.ServiceRequest.ServiceRequestIntent;
|
||||
import org.hl7.fhir.r4.model.ServiceRequest.ServiceRequestStatus;
|
||||
import org.hl7.fhir.r4.model.Specimen;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
|
|
@ -2549,7 +2549,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
|||
ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
|
||||
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
myCaptureQueriesListener.logInsertQueriesForCurrentThread();
|
||||
assertEquals(2, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
|
||||
|
|
|
@ -428,7 +428,7 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
|
|||
IdType observationId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
|
||||
|
||||
// Make sure we're not introducing any extra DB operations
|
||||
assertThat(myCaptureQueriesListener.logSelectQueries()).hasSize(3);
|
||||
assertThat(myCaptureQueriesListener.logSelectQueries()).hasSize(2);
|
||||
|
||||
// Read back and verify that reference is now versioned
|
||||
observation = myObservationDao.read(observationId);
|
||||
|
@ -463,7 +463,7 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
|
|||
IdType observationId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
|
||||
|
||||
// Make sure we're not introducing any extra DB operations
|
||||
assertThat(myCaptureQueriesListener.logSelectQueries()).hasSize(4);
|
||||
assertThat(myCaptureQueriesListener.logSelectQueries()).hasSize(3);
|
||||
|
||||
// Read back and verify that reference is now versioned
|
||||
observation = myObservationDao.read(observationId);
|
||||
|
|
|
@ -14,9 +14,6 @@ import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
|
|||
import ca.uhn.fhir.rest.param.StringAndListParam;
|
||||
import ca.uhn.fhir.rest.param.StringOrListParam;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import org.hl7.fhir.r4.model.Organization;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
|
@ -29,7 +26,6 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
|
|||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -304,169 +300,4 @@ public class FhirSearchDaoR4Test extends BaseJpaR4Test implements IR4SearchIndex
|
|||
assertThat(JpaPid.toLongList(found)).isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchNarrativeWithLuceneSearch() {
|
||||
final int numberOfPatientsToCreate = SearchBuilder.getMaximumPageSize() + 10;
|
||||
List<String> expectedActivePatientIds = new ArrayList<>(numberOfPatientsToCreate);
|
||||
|
||||
for (int i = 0; i < numberOfPatientsToCreate; i++) {
|
||||
Patient patient = new Patient();
|
||||
patient.getText().setDivAsString("<div>AAAS<p>FOO</p> CCC </div>");
|
||||
expectedActivePatientIds.add(myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getIdPart());
|
||||
}
|
||||
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.getText().setDivAsString("<div>AAAB<p>FOO</p> CCC </div>");
|
||||
myPatientDao.create(patient, mySrd);
|
||||
}
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.getText().setDivAsString("<div>ZZYZXY</div>");
|
||||
myPatientDao.create(patient, mySrd);
|
||||
}
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true);
|
||||
map.add(Constants.PARAM_TEXT, new StringParam("AAAS"));
|
||||
|
||||
IBundleProvider searchResultBundle = myPatientDao.search(map, mySrd);
|
||||
List<String> resourceIdsFromSearchResult = searchResultBundle.getAllResourceIds();
|
||||
|
||||
assertThat(resourceIdsFromSearchResult).containsExactlyInAnyOrderElementsOf(expectedActivePatientIds);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void searchLuceneAndJPA_withLuceneMatchingButJpaNot_returnsNothing() {
|
||||
// setup
|
||||
int numToCreate = 2 * SearchBuilder.getMaximumPageSize() + 10;
|
||||
|
||||
// create resources
|
||||
for (int i = 0; i < numToCreate; i++) {
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
patient.addIdentifier()
|
||||
.setSystem("http://fhir.com")
|
||||
.setValue("ZYX");
|
||||
patient.getText().setDivAsString("<div>ABC</div>");
|
||||
myPatientDao.create(patient, mySrd);
|
||||
}
|
||||
|
||||
// test
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.setLoadSynchronous(true);
|
||||
map.setSearchTotalMode(SearchTotalModeEnum.ACCURATE);
|
||||
TokenAndListParam tokenAndListParam = new TokenAndListParam();
|
||||
tokenAndListParam.addAnd(new TokenOrListParam().addOr(new TokenParam().setValue("true")));
|
||||
map.add("active", tokenAndListParam);
|
||||
map.add(Constants.PARAM_TEXT, new StringParam("ABC"));
|
||||
map.add("identifier", new TokenParam(null, "not found"));
|
||||
IBundleProvider provider = myPatientDao.search(map, mySrd);
|
||||
|
||||
// verify
|
||||
assertEquals(0, provider.getAllResources().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void searchLuceneAndJPA_withLuceneBroadAndJPASearchNarrow_returnsFoundResults() {
|
||||
// setup
|
||||
int numToCreate = 2 * SearchBuilder.getMaximumPageSize() + 10;
|
||||
String identifierToFind = "bcde";
|
||||
|
||||
// create patients
|
||||
for (int i = 0; i < numToCreate; i++) {
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
String identifierVal = i == numToCreate - 10 ? identifierToFind:
|
||||
"abcd";
|
||||
patient.addIdentifier()
|
||||
.setSystem("http://fhir.com")
|
||||
.setValue(identifierVal);
|
||||
|
||||
patient.getText().setDivAsString(
|
||||
"<div>FINDME</div>"
|
||||
);
|
||||
myPatientDao.create(patient, mySrd);
|
||||
}
|
||||
|
||||
// test
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.setLoadSynchronous(true);
|
||||
map.setSearchTotalMode(SearchTotalModeEnum.ACCURATE);
|
||||
TokenAndListParam tokenAndListParam = new TokenAndListParam();
|
||||
tokenAndListParam.addAnd(new TokenOrListParam().addOr(new TokenParam().setValue("true")));
|
||||
map.add("active", tokenAndListParam);
|
||||
map.add(Constants.PARAM_TEXT, new StringParam("FINDME"));
|
||||
map.add("identifier", new TokenParam(null, identifierToFind));
|
||||
IBundleProvider provider = myPatientDao.search(map, mySrd);
|
||||
|
||||
// verify
|
||||
List<String> ids = provider.getAllResourceIds();
|
||||
assertEquals(1, ids.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLuceneNarrativeSearchQueryIntersectingJpaQuery() {
|
||||
final int numberOfPatientsToCreate = SearchBuilder.getMaximumPageSize() + 10;
|
||||
List<String> expectedActivePatientIds = new ArrayList<>(numberOfPatientsToCreate);
|
||||
|
||||
// create active and non-active patients with the same narrative
|
||||
for (int i = 0; i < numberOfPatientsToCreate; i++) {
|
||||
Patient activePatient = new Patient();
|
||||
activePatient.getText().setDivAsString("<div>AAAS<p>FOO</p> CCC </div>");
|
||||
activePatient.setActive(true);
|
||||
String patientId = myPatientDao.create(activePatient, mySrd).getId().toUnqualifiedVersionless().getIdPart();
|
||||
expectedActivePatientIds.add(patientId);
|
||||
|
||||
Patient nonActivePatient = new Patient();
|
||||
nonActivePatient.getText().setDivAsString("<div>AAAS<p>FOO</p> CCC </div>");
|
||||
nonActivePatient.setActive(false);
|
||||
myPatientDao.create(nonActivePatient, mySrd);
|
||||
}
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true);
|
||||
|
||||
TokenAndListParam tokenAndListParam = new TokenAndListParam();
|
||||
tokenAndListParam.addAnd(new TokenOrListParam().addOr(new TokenParam().setValue("true")));
|
||||
|
||||
map.add("active", tokenAndListParam);
|
||||
map.add(Constants.PARAM_TEXT, new StringParam("AAAS"));
|
||||
|
||||
IBundleProvider searchResultBundle = myPatientDao.search(map, mySrd);
|
||||
List<String> resourceIdsFromSearchResult = searchResultBundle.getAllResourceIds();
|
||||
|
||||
assertThat(resourceIdsFromSearchResult).containsExactlyInAnyOrderElementsOf(expectedActivePatientIds);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLuceneContentSearchQueryIntersectingJpaQuery() {
|
||||
final int numberOfPatientsToCreate = SearchBuilder.getMaximumPageSize() + 10;
|
||||
final String patientFamilyName = "Flanders";
|
||||
List<String> expectedActivePatientIds = new ArrayList<>(numberOfPatientsToCreate);
|
||||
|
||||
// create active and non-active patients with the same narrative
|
||||
for (int i = 0; i < numberOfPatientsToCreate; i++) {
|
||||
Patient activePatient = new Patient();
|
||||
activePatient.addName().setFamily(patientFamilyName);
|
||||
activePatient.setActive(true);
|
||||
String patientId = myPatientDao.create(activePatient, mySrd).getId().toUnqualifiedVersionless().getIdPart();
|
||||
expectedActivePatientIds.add(patientId);
|
||||
|
||||
Patient nonActivePatient = new Patient();
|
||||
nonActivePatient.addName().setFamily(patientFamilyName);
|
||||
nonActivePatient.setActive(false);
|
||||
myPatientDao.create(nonActivePatient, mySrd);
|
||||
}
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true);
|
||||
TokenAndListParam tokenAndListParam = new TokenAndListParam();
|
||||
tokenAndListParam.addAnd(new TokenOrListParam().addOr(new TokenParam().setValue("true")));
|
||||
map.add("active", tokenAndListParam);
|
||||
map.add(Constants.PARAM_CONTENT, new StringParam(patientFamilyName));
|
||||
|
||||
IBundleProvider searchResultBundle = myPatientDao.search(map, mySrd);
|
||||
List<String> resourceIdsFromSearchResult = searchResultBundle.getAllResourceIds();
|
||||
|
||||
assertThat(resourceIdsFromSearchResult).containsExactlyInAnyOrderElementsOf(expectedActivePatientIds);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -263,6 +263,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
|||
myStorageSettings.setSearchPreFetchThresholds(new JpaStorageSettings().getSearchPreFetchThresholds());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testParameterWithNoValueThrowsError_InvalidChainOnCustomSearch() throws IOException {
|
||||
SearchParameter searchParameter = new SearchParameter();
|
||||
|
|
|
@ -180,6 +180,59 @@ public class ReindexTaskTest extends BaseJpaR4Test {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptimizeStorage_AllVersions_SingleResourceWithMultipleVersion() {
|
||||
|
||||
// this difference of this test from testOptimizeStorage_AllVersions is that this one has only 1 resource
|
||||
// (with multiple versions) in the db. There was a bug where if only one resource were being re-indexed, the
|
||||
// resource wasn't processed for optimize storage.
|
||||
|
||||
// Setup
|
||||
IIdType patientId = createPatient(withActiveTrue());
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Patient p = new Patient();
|
||||
p.setId(patientId.toUnqualifiedVersionless());
|
||||
p.setActive(true);
|
||||
p.addIdentifier().setValue(String.valueOf(i));
|
||||
myPatientDao.update(p, mySrd);
|
||||
}
|
||||
|
||||
// Move resource text to compressed storage, which we don't write to anymore but legacy
|
||||
// data may exist that was previously stored there, so we're simulating that.
|
||||
List<ResourceHistoryTable> allHistoryEntities = runInTransaction(() -> myResourceHistoryTableDao.findAll());
|
||||
allHistoryEntities.forEach(t->relocateResourceTextToCompressedColumn(t.getResourceId(), t.getVersion()));
|
||||
|
||||
runInTransaction(()->{
|
||||
assertEquals(11, myResourceHistoryTableDao.count());
|
||||
for (ResourceHistoryTable history : myResourceHistoryTableDao.findAll()) {
|
||||
assertNull(history.getResourceTextVc());
|
||||
assertNotNull(history.getResource());
|
||||
}
|
||||
});
|
||||
|
||||
// execute
|
||||
JobInstanceStartRequest startRequest = new JobInstanceStartRequest();
|
||||
startRequest.setJobDefinitionId(JOB_REINDEX);
|
||||
startRequest.setParameters(
|
||||
new ReindexJobParameters()
|
||||
.setOptimizeStorage(ReindexParameters.OptimizeStorageModeEnum.ALL_VERSIONS)
|
||||
.setReindexSearchParameters(ReindexParameters.ReindexSearchParametersEnum.NONE)
|
||||
);
|
||||
Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(mySrd, startRequest);
|
||||
myBatch2JobHelper.awaitJobCompletion(startResponse);
|
||||
|
||||
// validate
|
||||
runInTransaction(()->{
|
||||
assertEquals(11, myResourceHistoryTableDao.count());
|
||||
for (ResourceHistoryTable history : myResourceHistoryTableDao.findAll()) {
|
||||
assertNotNull(history.getResourceTextVc());
|
||||
assertNull(history.getResource());
|
||||
}
|
||||
});
|
||||
Patient patient = myPatientDao.read(patientId, mySrd);
|
||||
assertTrue(patient.getActive());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptimizeStorage_AllVersions_CopyProvenanceEntityData() {
|
||||
// Setup
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package ca.uhn.fhir.jpa.search;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.jpa.dao.search.ExtendedHSearchResourceProjection;
|
||||
|
@ -12,6 +11,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import static ca.uhn.fhir.jpa.dao.search.ExtendedHSearchResourceProjection.RESOURCE_NOT_STORED_ERROR;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class ExtendedHSearchResourceProjectionTest {
|
||||
|
@ -57,8 +57,4 @@ class ExtendedHSearchResourceProjectionTest {
|
|||
() -> new ExtendedHSearchResourceProjection(22, null, ""));
|
||||
assertEquals(Msg.code(2130) + RESOURCE_NOT_STORED_ERROR + "22", ex.getMessage());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,29 +1,21 @@
|
|||
package ca.uhn.fhir.jpa.provider.r4;
|
||||
package ca.uhn.fhir.jpa.validation;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.config.JpaConfig;
|
||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
|
||||
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.param.UriParam;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import ca.uhn.fhir.test.utilities.validation.IValidationProviders;
|
||||
import ca.uhn.fhir.test.utilities.validation.IValidationProvidersR4;
|
||||
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
|
||||
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||
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.Coding;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.Parameters;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.hl7.fhir.r4.model.UriType;
|
||||
import org.hl7.fhir.r4.model.ValueSet;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
|
@ -33,9 +25,7 @@ import org.junit.jupiter.api.extension.RegisterExtension;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_VALIDATE_CODE;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -43,15 +33,15 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
|||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
/*
|
||||
/**
|
||||
* This set of integration tests that instantiates and injects an instance of
|
||||
* {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport}
|
||||
* into the ValidationSupportChain, which tests the logic of dynamically selecting the correct Remote Terminology
|
||||
* implementation. It also exercises the code found in
|
||||
* {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport#invokeRemoteValidateCode}
|
||||
* implementation. It also exercises the validateCode output translation code found in
|
||||
* {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport}
|
||||
*/
|
||||
public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResourceProviderR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateCodeOperationWithRemoteTerminologyR4Test.class);
|
||||
public class ValidateCodeWithRemoteTerminologyR4Test extends BaseResourceProviderR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateCodeWithRemoteTerminologyR4Test.class);
|
||||
private static final String DISPLAY = "DISPLAY";
|
||||
private static final String DISPLAY_BODY_MASS_INDEX = "Body mass index (BMI) [Ratio]";
|
||||
private static final String CODE_BODY_MASS_INDEX = "39156-5";
|
||||
|
@ -64,8 +54,8 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
|
|||
protected static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);
|
||||
|
||||
private RemoteTerminologyServiceValidationSupport mySvc;
|
||||
private MyCodeSystemProvider myCodeSystemProvider;
|
||||
private MyValueSetProvider myValueSetProvider;
|
||||
private IValidationProviders.MyValidationProvider<CodeSystem> myCodeSystemProvider;
|
||||
private IValidationProviders.MyValidationProvider<ValueSet> myValueSetProvider;
|
||||
|
||||
@Autowired
|
||||
@Qualifier(JpaConfig.JPA_VALIDATION_SUPPORT_CHAIN)
|
||||
|
@ -76,8 +66,8 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
|
|||
String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
|
||||
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl);
|
||||
myValidationSupportChain.addValidationSupport(0, mySvc);
|
||||
myCodeSystemProvider = new MyCodeSystemProvider();
|
||||
myValueSetProvider = new MyValueSetProvider();
|
||||
myCodeSystemProvider = new IValidationProvidersR4.MyCodeSystemProviderR4();
|
||||
myValueSetProvider = new IValidationProvidersR4.MyValueSetProviderR4();
|
||||
ourRestfulServerExtension.registerProvider(myCodeSystemProvider);
|
||||
ourRestfulServerExtension.registerProvider(myValueSetProvider);
|
||||
}
|
||||
|
@ -103,11 +93,11 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
|
|||
|
||||
@Test
|
||||
public void validateCodeOperationOnCodeSystem_byCodingAndUrl_usingBuiltInCodeSystems() {
|
||||
myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>();
|
||||
myCodeSystemProvider.myReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/v2-0247"));
|
||||
myCodeSystemProvider.myReturnParams = new Parameters();
|
||||
myCodeSystemProvider.myReturnParams.addParameter("result", true);
|
||||
myCodeSystemProvider.myReturnParams.addParameter("display", DISPLAY);
|
||||
final String code = "P";
|
||||
final String system = CODE_SYSTEM_V2_0247_URI;;
|
||||
|
||||
Parameters params = new Parameters().addParameter("result", true).addParameter("display", DISPLAY);
|
||||
setupCodeSystemValidateCode(system, code, params);
|
||||
|
||||
logAllConcepts();
|
||||
|
||||
|
@ -115,8 +105,8 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
|
|||
.operation()
|
||||
.onType(CodeSystem.class)
|
||||
.named(JpaConstants.OPERATION_VALIDATE_CODE)
|
||||
.withParameter(Parameters.class, "coding", new Coding().setSystem(CODE_SYSTEM_V2_0247_URI).setCode("P"))
|
||||
.andParameter("url", new UriType(CODE_SYSTEM_V2_0247_URI))
|
||||
.withParameter(Parameters.class, "coding", new Coding().setSystem(system).setCode(code))
|
||||
.andParameter("url", new UriType(system))
|
||||
.execute();
|
||||
|
||||
String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
|
||||
|
@ -128,7 +118,7 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
|
|||
|
||||
@Test
|
||||
public void validateCodeOperationOnCodeSystem_byCodingAndUrlWhereCodeSystemIsUnknown_returnsFalse() {
|
||||
myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>();
|
||||
myCodeSystemProvider.setShouldThrowExceptionForResourceNotFound(false);
|
||||
|
||||
Parameters respParam = myClient
|
||||
.operation()
|
||||
|
@ -166,21 +156,21 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
|
|||
|
||||
@Test
|
||||
public void validateCodeOperationOnValueSet_byUrlAndSystem_usingBuiltInCodeSystems() {
|
||||
myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>();
|
||||
myCodeSystemProvider.myReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/list-example-use-codes"));
|
||||
myValueSetProvider.myReturnValueSets = new ArrayList<>();
|
||||
myValueSetProvider.myReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/list-example-codes"));
|
||||
myValueSetProvider.myReturnParams = new Parameters();
|
||||
myValueSetProvider.myReturnParams.addParameter("result", true);
|
||||
myValueSetProvider.myReturnParams.addParameter("display", DISPLAY);
|
||||
final String code = "alerts";
|
||||
final String system = "http://terminology.hl7.org/CodeSystem/list-example-use-codes";
|
||||
final String valueSetUrl = "http://hl7.org/fhir/ValueSet/list-example-codes";
|
||||
|
||||
Parameters params = new Parameters().addParameter("result", true).addParameter("display", DISPLAY);
|
||||
setupValueSetValidateCode(valueSetUrl, system, code, params);
|
||||
setupCodeSystemValidateCode(system, code, params);
|
||||
|
||||
Parameters respParam = myClient
|
||||
.operation()
|
||||
.onType(ValueSet.class)
|
||||
.named(JpaConstants.OPERATION_VALIDATE_CODE)
|
||||
.withParameter(Parameters.class, "code", new CodeType("alerts"))
|
||||
.andParameter("system", new UriType("http://terminology.hl7.org/CodeSystem/list-example-use-codes"))
|
||||
.andParameter("url", new UriType("http://hl7.org/fhir/ValueSet/list-example-codes"))
|
||||
.withParameter(Parameters.class, "code", new CodeType(code))
|
||||
.andParameter("system", new UriType(system))
|
||||
.andParameter("url", new UriType(valueSetUrl))
|
||||
.useHttpGet()
|
||||
.execute();
|
||||
|
||||
|
@ -193,21 +183,20 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
|
|||
|
||||
@Test
|
||||
public void validateCodeOperationOnValueSet_byUrlSystemAndCode() {
|
||||
myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>();
|
||||
myCodeSystemProvider.myReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/list-example-use-codes"));
|
||||
myValueSetProvider.myReturnValueSets = new ArrayList<>();
|
||||
myValueSetProvider.myReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/list-example-codes"));
|
||||
myValueSetProvider.myReturnParams = new Parameters();
|
||||
myValueSetProvider.myReturnParams.addParameter("result", true);
|
||||
myValueSetProvider.myReturnParams.addParameter("display", DISPLAY_BODY_MASS_INDEX);
|
||||
final String code = CODE_BODY_MASS_INDEX;
|
||||
final String system = "http://terminology.hl7.org/CodeSystem/list-example-use-codes";
|
||||
final String valueSetUrl = "http://hl7.org/fhir/ValueSet/list-example-codes";
|
||||
|
||||
Parameters params = new Parameters().addParameter("result", true).addParameter("display", DISPLAY_BODY_MASS_INDEX);
|
||||
setupValueSetValidateCode(valueSetUrl, system, code, params);
|
||||
|
||||
Parameters respParam = myClient
|
||||
.operation()
|
||||
.onType(ValueSet.class)
|
||||
.named(JpaConstants.OPERATION_VALIDATE_CODE)
|
||||
.withParameter(Parameters.class, "code", new CodeType(CODE_BODY_MASS_INDEX))
|
||||
.andParameter("url", new UriType("https://loinc.org"))
|
||||
.andParameter("system", new UriType("http://loinc.org"))
|
||||
.withParameter(Parameters.class, "code", new CodeType(code))
|
||||
.andParameter("url", new UriType(valueSetUrl))
|
||||
.andParameter("system", new UriType(system))
|
||||
.execute();
|
||||
|
||||
String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
|
||||
|
@ -219,7 +208,7 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
|
|||
|
||||
@Test
|
||||
public void validateCodeOperationOnValueSet_byCodingAndUrlWhereValueSetIsUnknown_returnsFalse() {
|
||||
myValueSetProvider.myReturnValueSets = new ArrayList<>();
|
||||
myValueSetProvider.setShouldThrowExceptionForResourceNotFound(false);
|
||||
|
||||
Parameters respParam = myClient
|
||||
.operation()
|
||||
|
@ -238,70 +227,18 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
|
|||
" - Unknown or unusable ValueSet[" + UNKNOWN_VALUE_SYSTEM_URI + "]");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class MyCodeSystemProvider implements IResourceProvider {
|
||||
private List<CodeSystem> myReturnCodeSystems;
|
||||
private Parameters myReturnParams;
|
||||
private void setupValueSetValidateCode(String theUrl, String theSystem, String theCode, IBaseParameters theResponseParams) {
|
||||
ValueSet valueSet = myValueSetProvider.addTerminologyResource(theUrl);
|
||||
myValueSetProvider.addTerminologyResource(theSystem);
|
||||
myValueSetProvider.addTerminologyResponse(OPERATION_VALIDATE_CODE, valueSet.getUrl(), theCode, theResponseParams);
|
||||
|
||||
@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
|
||||
) {
|
||||
return myReturnParams;
|
||||
}
|
||||
|
||||
@Search
|
||||
public List<CodeSystem> find(@RequiredParam(name = "url") UriParam theUrlParam) {
|
||||
assert myReturnCodeSystems != null;
|
||||
return myReturnCodeSystems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return CodeSystem.class;
|
||||
}
|
||||
// we currently do this because VersionSpecificWorkerContextWrapper has logic to infer the system when missing
|
||||
// based on the ValueSet by calling ValidationSupportUtils#extractCodeSystemForCode.
|
||||
valueSet.getCompose().addInclude().setSystem(theSystem);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class MyValueSetProvider implements IResourceProvider {
|
||||
private Parameters myReturnParams;
|
||||
private List<ValueSet> myReturnValueSets;
|
||||
|
||||
@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 theValueSetUrl,
|
||||
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
|
||||
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
|
||||
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
|
||||
@OperationParam(name = "valueSet") ValueSet theValueSet
|
||||
) {
|
||||
return myReturnParams;
|
||||
}
|
||||
|
||||
@Search
|
||||
public List<ValueSet> find(@RequiredParam(name = "url") UriParam theUrlParam) {
|
||||
assert myReturnValueSets != null;
|
||||
return myReturnValueSets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return ValueSet.class;
|
||||
}
|
||||
|
||||
private void setupCodeSystemValidateCode(String theUrl, String theCode, IBaseParameters theResponseParams) {
|
||||
CodeSystem codeSystem = myCodeSystemProvider.addTerminologyResource(theUrl);
|
||||
myCodeSystemProvider.addTerminologyResponse(OPERATION_VALIDATE_CODE, codeSystem.getUrl(), theCode, theResponseParams);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
package ca.uhn.fhir.jpa.validation;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.config.JpaConfig;
|
||||
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
|
||||
import ca.uhn.fhir.test.utilities.validation.IValidationProviders;
|
||||
import ca.uhn.fhir.test.utilities.validation.IValidationProvidersR4;
|
||||
import ca.uhn.fhir.util.ClasspathUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
|
||||
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.CodeSystem;
|
||||
import org.hl7.fhir.r4.model.Encounter;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.OperationOutcome;
|
||||
import org.hl7.fhir.r4.model.Procedure;
|
||||
import org.hl7.fhir.r4.model.Reference;
|
||||
import org.hl7.fhir.r4.model.StructureDefinition;
|
||||
import org.hl7.fhir.r4.model.ValueSet;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_VALIDATE_CODE;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests resource validation with Remote Terminology bindings.
|
||||
* To create a new test, you need to do 3 things:
|
||||
* (1) the resource profile, if any custom one is needed should be stored in the FHIR repository
|
||||
* (2) all the CodeSystem and ValueSet terminology resources need to be added to the corresponding resource provider.
|
||||
* At the moment only placeholder CodeSystem/ValueSet resources are returned with id and url populated. For the moment
|
||||
* there was no need to load the full resource, but that can be done if there is logic run which requires it.
|
||||
* This is a minimal setup.
|
||||
* (3) the Remote Terminology operation responses that are needed for the test need to be added to the corresponding
|
||||
* resource provider. The intention is to record and use the responses of an actual terminology server
|
||||
* e.g. <a href="https://r4.ontoserver.csiro.au/fhir/">OntoServer</a>.
|
||||
* This is done as a result of the fact that unit test cannot always catch bugs which are introduced as a result of
|
||||
* changes in the OntoServer or FHIR Validator library, or both.
|
||||
* @see #setupValueSetValidateCode
|
||||
* @see #setupCodeSystemValidateCode
|
||||
* The responses are in Parameters resource format where issues is an OperationOutcome resource.
|
||||
*/
|
||||
public class ValidateWithRemoteTerminologyTest extends BaseResourceProviderR4Test {
|
||||
private static final FhirContext ourCtx = FhirContext.forR4Cached();
|
||||
|
||||
@RegisterExtension
|
||||
protected static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);
|
||||
private RemoteTerminologyServiceValidationSupport mySvc;
|
||||
@Autowired
|
||||
@Qualifier(JpaConfig.JPA_VALIDATION_SUPPORT_CHAIN)
|
||||
private ValidationSupportChain myValidationSupportChain;
|
||||
private IValidationProviders.MyValidationProvider<CodeSystem> myCodeSystemProvider;
|
||||
private IValidationProviders.MyValidationProvider<ValueSet> myValueSetProvider;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
|
||||
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl);
|
||||
myValidationSupportChain.addValidationSupport(0, mySvc);
|
||||
myCodeSystemProvider = new IValidationProvidersR4.MyCodeSystemProviderR4();
|
||||
myValueSetProvider = new IValidationProvidersR4.MyValueSetProviderR4();
|
||||
ourRestfulServerExtension.registerProvider(myCodeSystemProvider);
|
||||
ourRestfulServerExtension.registerProvider(myValueSetProvider);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void after() {
|
||||
myValidationSupportChain.removeValidationSupport(mySvc);
|
||||
ourRestfulServerExtension.getRestfulServer().getInterceptorService().unregisterAllInterceptors();
|
||||
ourRestfulServerExtension.unregisterProvider(myCodeSystemProvider);
|
||||
ourRestfulServerExtension.unregisterProvider(myValueSetProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validate_withProfileWithValidCodesFromAllBindingTypes_returnsNoErrors() {
|
||||
// setup
|
||||
final StructureDefinition profileEncounter = ClasspathUtil.loadResource(ourCtx, StructureDefinition.class, "validation/encounter/profile-encounter-custom.json");
|
||||
myClient.update().resource(profileEncounter).execute();
|
||||
|
||||
final String statusCode = "planned";
|
||||
final String classCode = "IMP";
|
||||
final String identifierTypeCode = "VN";
|
||||
|
||||
final String statusSystem = "http://hl7.org/fhir/encounter-status"; // implied system
|
||||
final String classSystem = "http://terminology.hl7.org/CodeSystem/v3-ActCode";
|
||||
final String identifierTypeSystem = "http://terminology.hl7.org/CodeSystem/v2-0203";
|
||||
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/encounter-status", "http://hl7.org/fhir/encounter-status", statusCode, "validation/encounter/validateCode-ValueSet-encounter-status.json");
|
||||
setupValueSetValidateCode("http://terminology.hl7.org/ValueSet/v3-ActEncounterCode", "http://terminology.hl7.org/CodeSystem/v3-ActCode", classCode, "validation/encounter/validateCode-ValueSet-v3-ActEncounterCode.json");
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/identifier-type", "http://hl7.org/fhir/identifier-type", identifierTypeCode, "validation/encounter/validateCode-ValueSet-identifier-type.json");
|
||||
|
||||
setupCodeSystemValidateCode(statusSystem, statusCode, "validation/encounter/validateCode-CodeSystem-encounter-status.json");
|
||||
setupCodeSystemValidateCode(classSystem, classCode, "validation/encounter/validateCode-CodeSystem-v3-ActCode.json");
|
||||
setupCodeSystemValidateCode(identifierTypeSystem, identifierTypeCode, "validation/encounter/validateCode-CodeSystem-v2-0203.json");
|
||||
|
||||
Encounter encounter = new Encounter();
|
||||
encounter.getMeta().addProfile("http://example.ca/fhir/StructureDefinition/profile-encounter");
|
||||
|
||||
// required binding
|
||||
encounter.setStatus(Encounter.EncounterStatus.fromCode(statusCode));
|
||||
|
||||
// preferred binding
|
||||
encounter.getClass_()
|
||||
.setSystem(classSystem)
|
||||
.setCode(classCode)
|
||||
.setDisplay("inpatient encounter");
|
||||
|
||||
// extensible binding
|
||||
encounter.addIdentifier()
|
||||
.getType().addCoding()
|
||||
.setSystem(identifierTypeSystem)
|
||||
.setCode(identifierTypeCode)
|
||||
.setDisplay("Visit number");
|
||||
|
||||
// execute
|
||||
List<String> errors = getValidationErrors(encounter);
|
||||
|
||||
// verify
|
||||
assertThat(errors).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validate_withInvalidCode_returnsErrors() {
|
||||
// setup
|
||||
final String statusCode = "final";
|
||||
final String code = "10xx";
|
||||
|
||||
final String statusSystem = "http://hl7.org/fhir/observation-status";
|
||||
final String loincSystem = "http://loinc.org";
|
||||
final String system = "http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM";
|
||||
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/observation-status", statusSystem, statusCode, "validation/observation/validateCode-ValueSet-observation-status.json");
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/observation-codes", loincSystem, statusCode, "validation/observation/validateCode-ValueSet-codes.json");
|
||||
|
||||
setupCodeSystemValidateCode(statusSystem, statusCode, "validation/observation/validateCode-CodeSystem-observation-status.json");
|
||||
setupCodeSystemValidateCode(system, code, "validation/observation/validateCode-CodeSystem-ICD9CM.json");
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.setStatus(Observation.ObservationStatus.fromCode(statusCode));
|
||||
obs.getCode().addCoding().setCode(code).setSystem(system);
|
||||
|
||||
// execute
|
||||
List<String> errors = getValidationErrors(obs);
|
||||
assertThat(errors).hasSize(1);
|
||||
|
||||
// verify
|
||||
assertThat(errors.get(0))
|
||||
.contains("Unknown code '10xx' in the CodeSystem 'http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validate_withProfileWithInvalidCode_returnsErrors() {
|
||||
// setup
|
||||
String profile = "http://example.ca/fhir/StructureDefinition/profile-procedure";
|
||||
StructureDefinition profileProcedure = ClasspathUtil.loadResource(myFhirContext, StructureDefinition.class, "validation/procedure/profile-procedure.json");
|
||||
myClient.update().resource(profileProcedure).execute();
|
||||
|
||||
final String statusCode = "completed";
|
||||
final String procedureCode1 = "417005";
|
||||
final String procedureCode2 = "xx417005";
|
||||
|
||||
final String statusSystem = "http://hl7.org/fhir/event-status";
|
||||
final String snomedSystem = "http://snomed.info/sct";
|
||||
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/event-status", statusSystem, statusCode, "validation/procedure/validateCode-ValueSet-event-status.json");
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/procedure-code", snomedSystem, procedureCode1, "validation/procedure/validateCode-ValueSet-procedure-code-valid.json");
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/procedure-code", snomedSystem, procedureCode2, "validation/procedure/validateCode-ValueSet-procedure-code-invalid.json");
|
||||
|
||||
setupCodeSystemValidateCode(statusSystem, statusCode, "validation/procedure/validateCode-CodeSystem-event-status.json");
|
||||
setupCodeSystemValidateCode(snomedSystem, procedureCode1, "validation/procedure/validateCode-CodeSystem-snomed-valid.json");
|
||||
setupCodeSystemValidateCode(snomedSystem, procedureCode2, "validation/procedure/validateCode-CodeSystem-snomed-invalid.json");
|
||||
|
||||
Procedure procedure = new Procedure();
|
||||
procedure.setSubject(new Reference("Patient/P1"));
|
||||
procedure.setStatus(Procedure.ProcedureStatus.fromCode(statusCode));
|
||||
procedure.getCode().addCoding().setSystem(snomedSystem).setCode(procedureCode1);
|
||||
procedure.getCode().addCoding().setSystem(snomedSystem).setCode(procedureCode2);
|
||||
procedure.getMeta().addProfile(profile);
|
||||
|
||||
// execute
|
||||
List<String> errors = getValidationErrors(procedure);
|
||||
// TODO: there is currently some duplication in the errors returned. This needs to be investigated and fixed.
|
||||
// assertThat(errors).hasSize(1);
|
||||
|
||||
// verify
|
||||
// note that we're not selecting an explicit versions (using latest) so the message verification does not include it.
|
||||
assertThat(StringUtils.join("", errors))
|
||||
.contains("Unknown code 'xx417005' in the CodeSystem 'http://snomed.info/sct'")
|
||||
.doesNotContain("The provided code 'http://snomed.info/sct#xx417005' was not found in the value set 'http://hl7.org/fhir/ValueSet/procedure-code")
|
||||
.doesNotContain("http://snomed.info/sct#417005");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validate_withProfileWithSlicingWithValidCode_returnsNoErrors() {
|
||||
// setup
|
||||
String profile = "http://example.ca/fhir/StructureDefinition/profile-procedure-with-slicing";
|
||||
StructureDefinition profileProcedure = ClasspathUtil.loadResource(myFhirContext, StructureDefinition.class, "validation/procedure/profile-procedure-slicing.json");
|
||||
myClient.update().resource(profileProcedure).execute();
|
||||
|
||||
final String statusCode = "completed";
|
||||
final String procedureCode = "no-procedure-info";
|
||||
|
||||
final String statusSystem = "http://hl7.org/fhir/event-status";
|
||||
final String snomedSystem = "http://snomed.info/sct";
|
||||
final String absentUnknownSystem = "http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips";
|
||||
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/event-status", statusSystem, statusCode, "validation/procedure/validateCode-ValueSet-event-status.json");
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/procedure-code", snomedSystem, procedureCode, "validation/procedure/validateCode-ValueSet-procedure-code-invalid-slice.json");
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/uv/ips/ValueSet/absent-or-unknown-procedures-uv-ips", absentUnknownSystem, procedureCode, "validation/procedure/validateCode-ValueSet-absent-or-unknown-procedure.json");
|
||||
|
||||
setupCodeSystemValidateCode(statusSystem, statusCode, "validation/procedure/validateCode-CodeSystem-event-status.json");
|
||||
setupCodeSystemValidateCode(absentUnknownSystem, procedureCode, "validation/procedure/validateCode-CodeSystem-absent-or-unknown.json");
|
||||
|
||||
Procedure procedure = new Procedure();
|
||||
procedure.setSubject(new Reference("Patient/P1"));
|
||||
procedure.setStatus(Procedure.ProcedureStatus.fromCode(statusCode));
|
||||
procedure.getCode().addCoding().setSystem(absentUnknownSystem).setCode(procedureCode);
|
||||
procedure.getMeta().addProfile(profile);
|
||||
|
||||
// execute
|
||||
List<String> errors = getValidationErrors(procedure);
|
||||
assertThat(errors).hasSize(0);
|
||||
}
|
||||
|
||||
private void setupValueSetValidateCode(String theUrl, String theSystem, String theCode, String theTerminologyResponseFile) {
|
||||
ValueSet valueSet = myValueSetProvider.addTerminologyResource(theUrl);
|
||||
myCodeSystemProvider.addTerminologyResource(theSystem);
|
||||
myValueSetProvider.addTerminologyResponse(OPERATION_VALIDATE_CODE, valueSet.getUrl(), theCode, ourCtx, theTerminologyResponseFile);
|
||||
|
||||
// we currently do this because VersionSpecificWorkerContextWrapper has logic to infer the system when missing
|
||||
// based on the ValueSet by calling ValidationSupportUtils#extractCodeSystemForCode.
|
||||
valueSet.getCompose().addInclude().setSystem(theSystem);
|
||||
|
||||
// you will notice each of these calls require also a call to setupCodeSystemValidateCode
|
||||
// that is necessary because VersionSpecificWorkerContextWrapper#validateCodeInValueSet
|
||||
// which also attempts a validateCode against the CodeSystem after the validateCode against the ValueSet
|
||||
}
|
||||
|
||||
private void setupCodeSystemValidateCode(String theUrl, String theCode, String theTerminologyResponseFile) {
|
||||
CodeSystem codeSystem = myCodeSystemProvider.addTerminologyResource(theUrl);
|
||||
myCodeSystemProvider.addTerminologyResponse(OPERATION_VALIDATE_CODE, codeSystem.getUrl(), theCode, ourCtx, theTerminologyResponseFile);
|
||||
}
|
||||
|
||||
private List<String> getValidationErrors(IBaseResource theResource) {
|
||||
MethodOutcome resultProcedure = myClient.validate().resource(theResource).execute();
|
||||
OperationOutcome operationOutcome = (OperationOutcome) resultProcedure.getOperationOutcome();
|
||||
return operationOutcome.getIssue().stream()
|
||||
.filter(issue -> issue.getSeverity() == OperationOutcome.IssueSeverity.ERROR)
|
||||
.map(OperationOutcome.OperationOutcomeIssueComponent::getDiagnostics)
|
||||
.toList();
|
||||
}
|
||||
}
|
|
@ -31,7 +31,7 @@
|
|||
<logger name="ca.uhn.fhir.jpa.bulk" level="info"/>
|
||||
|
||||
<!-- more debugging -->
|
||||
<!--
|
||||
<!--
|
||||
<logger name="org.elasticsearch.client" level="trace"/>
|
||||
<logger name="org.hibernate.search.elasticsearch.request" level="TRACE"/>
|
||||
<logger name="ca.uhn.fhir.jpa.model.search" level="debug"/>
|
||||
|
@ -39,7 +39,7 @@
|
|||
<logger name="org.hibernate.search" level="debug"/>
|
||||
<logger name="org.hibernate.search.query" level="TRACE"/>
|
||||
<logger name="org.hibernate.search.elasticsearch.request" level="TRACE"/>
|
||||
-->
|
||||
-->
|
||||
<!-- See https://docs.jboss.org/hibernate/stable/search/reference/en-US/html_single/#backend-lucene-io-writer-infostream for lucene logging
|
||||
<logger name="org.hibernate.search.backend.lucene.infostream" level="TRACE"/> -->
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"resourceType": "StructureDefinition",
|
||||
"id": "profile-encounter",
|
||||
"url": "http://example.ca/fhir/StructureDefinition/profile-encounter",
|
||||
"version": "0.11.0",
|
||||
"name": "EncounterProfile",
|
||||
"title": "Encounter Profile",
|
||||
"status": "active",
|
||||
"date": "2022-10-15T12:00:00+00:00",
|
||||
"publisher": "Example Organization",
|
||||
"fhirVersion": "4.0.1",
|
||||
"kind": "resource",
|
||||
"abstract": false,
|
||||
"type": "Encounter",
|
||||
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Encounter",
|
||||
"derivation": "constraint",
|
||||
"differential": {
|
||||
"element": [
|
||||
{
|
||||
"id": "Encounter.identifier.type.coding",
|
||||
"path": "Encounter.identifier.type.coding",
|
||||
"min": 1,
|
||||
"max": "1",
|
||||
"mustSupport": true
|
||||
},
|
||||
{
|
||||
"id": "Encounter.identifier.type.coding.system",
|
||||
"path": "Encounter.identifier.type.coding.system",
|
||||
"min": 1,
|
||||
"fixedUri": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"mustSupport": true
|
||||
},
|
||||
{
|
||||
"id": "Encounter.identifier.type.coding.code",
|
||||
"path": "Encounter.identifier.type.coding.code",
|
||||
"min": 1,
|
||||
"fixedCode": "VN",
|
||||
"mustSupport": true
|
||||
},
|
||||
{
|
||||
"id": "Encounter.identifier.type.coding.display",
|
||||
"path": "Encounter.identifier.type.coding.display",
|
||||
"min": 1,
|
||||
"fixedString": "Visit number",
|
||||
"mustSupport": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": true
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "planned"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://hl7.org/fhir/encounter-status"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"valueString": "5.0.0-ballot"
|
||||
},
|
||||
{
|
||||
"name": "display",
|
||||
"valueString": "Planned"
|
||||
},
|
||||
{
|
||||
"name": "issues",
|
||||
"resource": {
|
||||
"resourceType": "OperationOutcome",
|
||||
"issue": [
|
||||
{
|
||||
"severity": "information",
|
||||
"code": "business-rule",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "status-check"
|
||||
}
|
||||
],
|
||||
"text": "Reference to trial-use CodeSystem http://hl7.org/fhir/encounter-status|5.0.0-ballot"
|
||||
}
|
||||
},
|
||||
{
|
||||
"severity": "information",
|
||||
"code": "business-rule",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "status-check"
|
||||
}
|
||||
],
|
||||
"text": "Reference to draft CodeSystem http://hl7.org/fhir/encounter-status|5.0.0-ballot"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": true
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "VN"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://terminology.hl7.org/CodeSystem/v2-0203"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"valueString": "3.0.0"
|
||||
},
|
||||
{
|
||||
"name": "display",
|
||||
"valueString": "Visit number"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": true
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "IMP"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://terminology.hl7.org/CodeSystem/v3-ActCode"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"valueString": "2018-08-12"
|
||||
},
|
||||
{
|
||||
"name": "display",
|
||||
"valueString": "inpatient encounter"
|
||||
},
|
||||
{
|
||||
"name": "issues",
|
||||
"resource": {
|
||||
"resourceType": "OperationOutcome",
|
||||
"issue": [
|
||||
{
|
||||
"severity": "information",
|
||||
"code": "business-rule",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "status-check"
|
||||
}
|
||||
],
|
||||
"text": "Reference to draft CodeSystem http://terminology.hl7.org/CodeSystem/v3-ActCode|2018-08-12"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": true
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "planned"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://hl7.org/fhir/encounter-status"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"valueString": "5.0.0-ballot"
|
||||
},
|
||||
{
|
||||
"name": "display",
|
||||
"valueString": "Planned"
|
||||
},
|
||||
{
|
||||
"name": "issues",
|
||||
"resource": {
|
||||
"resourceType": "OperationOutcome",
|
||||
"issue": [
|
||||
{
|
||||
"severity": "information",
|
||||
"code": "business-rule",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "status-check"
|
||||
}
|
||||
],
|
||||
"text": "Reference to trial-use CodeSystem http://hl7.org/fhir/encounter-status|5.0.0-ballot"
|
||||
}
|
||||
},
|
||||
{
|
||||
"severity": "information",
|
||||
"code": "business-rule",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "status-check"
|
||||
}
|
||||
],
|
||||
"text": "Reference to draft CodeSystem http://hl7.org/fhir/encounter-status|5.0.0-ballot"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": false
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "VN"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://terminology.hl7.org/CodeSystem/v2-0203"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"valueString": "3.0.0"
|
||||
},
|
||||
{
|
||||
"name": "issues",
|
||||
"resource": {
|
||||
"resourceType": "OperationOutcome",
|
||||
"issue": [
|
||||
{
|
||||
"severity": "error",
|
||||
"code": "code-invalid",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "not-in-vs"
|
||||
}
|
||||
],
|
||||
"text": "The provided code 'http://terminology.hl7.org/CodeSystem/v2-0203#VN' was not found in the value set 'http://hl7.org/fhir/ValueSet/identifier-type|5.0.0-ballot'"
|
||||
},
|
||||
"location": [
|
||||
"code"
|
||||
],
|
||||
"expression": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"valueString": "The provided code 'http://terminology.hl7.org/CodeSystem/v2-0203#VN' was not found in the value set 'http://hl7.org/fhir/ValueSet/identifier-type|5.0.0-ballot'"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": true
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "IMP"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://terminology.hl7.org/CodeSystem/v3-ActCode"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"valueString": "2018-08-12"
|
||||
},
|
||||
{
|
||||
"name": "display",
|
||||
"valueString": "inpatient encounter"
|
||||
},
|
||||
{
|
||||
"name": "issues",
|
||||
"resource": {
|
||||
"resourceType": "OperationOutcome",
|
||||
"issue": [
|
||||
{
|
||||
"severity": "information",
|
||||
"code": "business-rule",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "status-check"
|
||||
}
|
||||
],
|
||||
"text": "Reference to trial-use ValueSet http://terminology.hl7.org/ValueSet/v3-ActEncounterCode|2014-03-26"
|
||||
}
|
||||
},
|
||||
{
|
||||
"severity": "information",
|
||||
"code": "business-rule",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "status-check"
|
||||
}
|
||||
],
|
||||
"text": "Reference to draft CodeSystem http://terminology.hl7.org/CodeSystem/v3-ActCode|2018-08-12"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": false
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "10xx"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM"
|
||||
},
|
||||
{
|
||||
"name": "issues",
|
||||
"resource": {
|
||||
"resourceType": "OperationOutcome",
|
||||
"issue": [
|
||||
{
|
||||
"severity": "error",
|
||||
"code": "code-invalid",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "invalid-code"
|
||||
}
|
||||
],
|
||||
"text": "Unknown code '10xx' in the CodeSystem 'http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM' version '0.1.0'"
|
||||
},
|
||||
"location": [
|
||||
"code"
|
||||
],
|
||||
"expression": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"valueString": "Unknown code '10xx' in the CodeSystem 'http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM' version '0.1.0'"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": true
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "final"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://hl7.org/fhir/observation-status"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"valueString": "5.0.0-ballot"
|
||||
},
|
||||
{
|
||||
"name": "display",
|
||||
"valueString": "Final"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": false
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "10xx"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM"
|
||||
},
|
||||
{
|
||||
"name": "issues",
|
||||
"resource": {
|
||||
"resourceType": "OperationOutcome",
|
||||
"issue": [
|
||||
{
|
||||
"severity": "error",
|
||||
"code": "code-invalid",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "not-in-vs"
|
||||
}
|
||||
],
|
||||
"text": "The provided code 'http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM#10xx' was not found in the value set 'http://hl7.org/fhir/ValueSet/observation-codes|5.0.0-ballot'"
|
||||
},
|
||||
"location": [
|
||||
"code"
|
||||
],
|
||||
"expression": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"valueString": "The provided code 'http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM#10xx' was not found in the value set 'http://hl7.org/fhir/ValueSet/observation-codes|5.0.0-ballot'"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": true
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "final"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://hl7.org/fhir/observation-status"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"valueString": "5.0.0-ballot"
|
||||
},
|
||||
{
|
||||
"name": "display",
|
||||
"valueString": "Final"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
{
|
||||
"resourceType": "StructureDefinition",
|
||||
"id": "profile-procedure-with-slicing",
|
||||
"url": "http://example.ca/fhir/StructureDefinition/profile-procedure-with-slicing",
|
||||
"version": "0.11.0",
|
||||
"name": "ProcedureProfile",
|
||||
"title": "Procedure Profile",
|
||||
"status": "active",
|
||||
"date": "2022-10-15T12:00:00+00:00",
|
||||
"publisher": "Example Organization",
|
||||
"fhirVersion": "4.0.1",
|
||||
"kind": "resource",
|
||||
"abstract": false,
|
||||
"type": "Procedure",
|
||||
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Procedure",
|
||||
"derivation": "constraint",
|
||||
"differential": {
|
||||
"element": [
|
||||
{
|
||||
"id": "Procedure.code.coding",
|
||||
"path": "Procedure.code.coding",
|
||||
"slicing": {
|
||||
"discriminator": [
|
||||
{
|
||||
"type": "pattern",
|
||||
"path": "$this"
|
||||
}
|
||||
],
|
||||
"description": "Discriminated by the bound value set",
|
||||
"rules": "open"
|
||||
},
|
||||
"mustSupport": true,
|
||||
"binding": {
|
||||
"strength": "preferred",
|
||||
"valueSet": "http://hl7.org/fhir/ValueSet/procedure-code"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Procedure.code.coding.display.extension:translation",
|
||||
"path": "Procedure.code.coding.display.extension",
|
||||
"sliceName": "translation"
|
||||
},
|
||||
{
|
||||
"id": "Procedure.code.coding.display.extension:translation.extension",
|
||||
"path": "Procedure.code.coding.display.extension.extension",
|
||||
"min": 2
|
||||
},
|
||||
{
|
||||
"id": "Procedure.code.coding:absentOrUnknownProcedure",
|
||||
"path": "Procedure.code.coding",
|
||||
"sliceName": "absentOrUnknownProcedure",
|
||||
"short": "Optional slice for representing a code for absent problem or for unknown procedure",
|
||||
"definition": "Code representing the statement \"absent problem\" or the statement \"procedures unknown\"",
|
||||
"mustSupport": true,
|
||||
"binding": {
|
||||
"extension": [
|
||||
{
|
||||
"url": "http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName",
|
||||
"valueString": "absentOrUnknownProcedure"
|
||||
}
|
||||
],
|
||||
"strength": "required",
|
||||
"description": "A code to identify absent or unknown procedures",
|
||||
"valueSet": "http://hl7.org/fhir/uv/ips/ValueSet/absent-or-unknown-procedures-uv-ips"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Procedure.code.coding:absentOrUnknownProcedure.display.extension:translation",
|
||||
"path": "Procedure.code.coding.display.extension",
|
||||
"sliceName": "translation"
|
||||
},
|
||||
{
|
||||
"id": "Procedure.code.coding:absentOrUnknownProcedure.display.extension:translation.extension",
|
||||
"path": "Procedure.code.coding.display.extension.extension",
|
||||
"min": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"resourceType": "StructureDefinition",
|
||||
"id": "profile-procedure",
|
||||
"url": "http://example.ca/fhir/StructureDefinition/profile-procedure",
|
||||
"version": "0.11.0",
|
||||
"name": "ProcedureProfile",
|
||||
"title": "Procedure Profile",
|
||||
"status": "active",
|
||||
"date": "2022-10-15T12:00:00+00:00",
|
||||
"publisher": "Example Organization",
|
||||
"fhirVersion": "4.0.1",
|
||||
"kind": "resource",
|
||||
"abstract": false,
|
||||
"type": "Procedure",
|
||||
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Procedure",
|
||||
"derivation": "constraint",
|
||||
"differential": {
|
||||
"element": [
|
||||
{
|
||||
"id": "Procedure.code.coding",
|
||||
"path": "Procedure.code.coding",
|
||||
"slicing": {
|
||||
"discriminator": [
|
||||
{
|
||||
"type": "pattern",
|
||||
"path": "$this"
|
||||
}
|
||||
],
|
||||
"description": "Discriminated by the bound value set",
|
||||
"rules": "open"
|
||||
},
|
||||
"mustSupport": true,
|
||||
"binding": {
|
||||
"strength": "preferred",
|
||||
"valueSet": "http://hl7.org/fhir/ValueSet/procedure-code"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Procedure.code.coding.display.extension:translation",
|
||||
"path": "Procedure.code.coding.display.extension",
|
||||
"sliceName": "translation"
|
||||
},
|
||||
{
|
||||
"id": "Procedure.code.coding.display.extension:translation.extension",
|
||||
"path": "Procedure.code.coding.display.extension.extension",
|
||||
"min": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": true
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "no-procedure-info"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"valueString": "1.1.0"
|
||||
},
|
||||
{
|
||||
"name": "display",
|
||||
"valueString": "No information about past history of procedures"
|
||||
},
|
||||
{
|
||||
"name": "issues",
|
||||
"resource": {
|
||||
"resourceType": "OperationOutcome",
|
||||
"issue": [
|
||||
{
|
||||
"severity": "information",
|
||||
"code": "business-rule",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "status-check"
|
||||
}
|
||||
],
|
||||
"text": "Reference to trial-use CodeSystem http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips|1.1.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": true
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "completed"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://hl7.org/fhir/event-status"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"valueString": "5.0.0-ballot"
|
||||
},
|
||||
{
|
||||
"name": "display",
|
||||
"valueString": "Completed"
|
||||
},
|
||||
{
|
||||
"name": "issues",
|
||||
"resource": {
|
||||
"resourceType": "OperationOutcome",
|
||||
"issue": [
|
||||
{
|
||||
"severity": "information",
|
||||
"code": "business-rule",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "status-check"
|
||||
}
|
||||
],
|
||||
"text": "Reference to trial-use CodeSystem http://hl7.org/fhir/event-status|5.0.0-ballot"
|
||||
}
|
||||
},
|
||||
{
|
||||
"severity": "information",
|
||||
"code": "business-rule",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "status-check"
|
||||
}
|
||||
],
|
||||
"text": "Reference to experimental CodeSystem http://hl7.org/fhir/event-status|5.0.0-ballot"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": false
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "xx417005"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://snomed.info/sct"
|
||||
},
|
||||
{
|
||||
"name": "issues",
|
||||
"resource": {
|
||||
"resourceType": "OperationOutcome",
|
||||
"issue": [
|
||||
{
|
||||
"severity": "error",
|
||||
"code": "code-invalid",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "invalid-code"
|
||||
}
|
||||
],
|
||||
"text": "Unknown code 'xx417005' in the CodeSystem 'http://snomed.info/sct' version 'http://snomed.info/sct/32506021000036107/version/20241031'"
|
||||
},
|
||||
"location": [
|
||||
"code"
|
||||
],
|
||||
"expression": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"valueString": "Unknown code 'xx417005' in the CodeSystem 'http://snomed.info/sct' version 'http://snomed.info/sct/32506021000036107/version/20241031'"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": true
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "417005"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://snomed.info/sct"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"valueString": "http://snomed.info/sct/32506021000036107/version/20241031"
|
||||
},
|
||||
{
|
||||
"name": "display",
|
||||
"valueString": "Hospital re-admission"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": true
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "no-procedure-info"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"valueString": "1.1.0"
|
||||
},
|
||||
{
|
||||
"name": "display",
|
||||
"valueString": "No information about past history of procedures"
|
||||
},
|
||||
{
|
||||
"name": "issues",
|
||||
"resource": {
|
||||
"resourceType": "OperationOutcome",
|
||||
"issue": [
|
||||
{
|
||||
"severity": "information",
|
||||
"code": "business-rule",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "status-check"
|
||||
}
|
||||
],
|
||||
"text": "Reference to trial-use CodeSystem http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips|1.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"severity": "information",
|
||||
"code": "business-rule",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "status-check"
|
||||
}
|
||||
],
|
||||
"text": "Reference to trial-use ValueSet http://hl7.org/fhir/uv/ips/ValueSet/absent-or-unknown-procedures-uv-ips|1.1.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": true
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "final"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://hl7.org/fhir/procedure-status"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"valueString": "5.0.0-ballot"
|
||||
},
|
||||
{
|
||||
"name": "display",
|
||||
"valueString": "Final"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": false
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "no-procedure-info"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips"
|
||||
},
|
||||
{
|
||||
"name": "issues",
|
||||
"resource": {
|
||||
"resourceType": "OperationOutcome",
|
||||
"issue": [
|
||||
{
|
||||
"severity": "error",
|
||||
"code": "code-invalid",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "not-in-vs"
|
||||
}
|
||||
],
|
||||
"text": "The provided code 'http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips#no-procedure-info' was not found in the value set 'http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot'"
|
||||
},
|
||||
"location": [
|
||||
"code"
|
||||
],
|
||||
"expression": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"valueString": "The provided code 'http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips#no-procedure-info' was not found in the value set 'http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot'"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": false
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "xx417005"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://snomed.info/sct"
|
||||
},
|
||||
{
|
||||
"name": "issues",
|
||||
"resource": {
|
||||
"resourceType": "OperationOutcome",
|
||||
"issue": [
|
||||
{
|
||||
"severity": "error",
|
||||
"code": "code-invalid",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "not-in-vs"
|
||||
}
|
||||
],
|
||||
"text": "The provided code 'http://snomed.info/sct#xx417005' was not found in the value set 'http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot'"
|
||||
},
|
||||
"location": [
|
||||
"code"
|
||||
],
|
||||
"expression": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"severity": "error",
|
||||
"code": "code-invalid",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "invalid-code"
|
||||
}
|
||||
],
|
||||
"text": "Unknown code 'xx417005' in the CodeSystem 'http://snomed.info/sct' version 'http://snomed.info/sct/32506021000036107/version/20241031'"
|
||||
},
|
||||
"location": [
|
||||
"code"
|
||||
],
|
||||
"expression": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"valueString": "Unknown code 'xx417005' in the CodeSystem 'http://snomed.info/sct' version 'http://snomed.info/sct/32506021000036107/version/20241031'; The provided code 'http://snomed.info/sct#xx417005' was not found in the value set 'http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot'"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [
|
||||
{
|
||||
"name": "result",
|
||||
"valueBoolean": true
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"valueCode": "417005"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"valueUri": "http://snomed.info/sct"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"valueString": "http://snomed.info/sct/32506021000036107/version/20241031"
|
||||
},
|
||||
{
|
||||
"name": "display",
|
||||
"valueString": "Hospital re-admission"
|
||||
},
|
||||
{
|
||||
"name": "issues",
|
||||
"resource": {
|
||||
"resourceType": "OperationOutcome",
|
||||
"issue": [
|
||||
{
|
||||
"severity": "information",
|
||||
"code": "business-rule",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "status-check"
|
||||
}
|
||||
],
|
||||
"text": "Reference to draft ValueSet http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot"
|
||||
}
|
||||
},
|
||||
{
|
||||
"severity": "information",
|
||||
"code": "business-rule",
|
||||
"details": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code": "status-check"
|
||||
}
|
||||
],
|
||||
"text": "Reference to experimental ValueSet http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -2,19 +2,29 @@ package ca.uhn.fhir.jpa.dao.r4;
|
|||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
||||
import ca.uhn.fhir.jpa.bulk.export.api.IBulkDataExportJobSchedulingHelper;
|
||||
import ca.uhn.fhir.jpa.dao.TestDaoSearch;
|
||||
import ca.uhn.fhir.jpa.search.BaseSourceSearchParameterTestCases;
|
||||
import ca.uhn.fhir.jpa.search.CompositeSearchParameterTestCases;
|
||||
import ca.uhn.fhir.jpa.search.QuantitySearchParameterTestCases;
|
||||
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
|
||||
import ca.uhn.fhir.jpa.test.BaseJpaTest;
|
||||
import ca.uhn.fhir.jpa.test.config.TestR4Config;
|
||||
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.storage.test.BaseDateSearchDaoTests;
|
||||
import ca.uhn.fhir.storage.test.DaoTestDataBuilder;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.HumanName;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.Meta;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Practitioner;
|
||||
import org.hl7.fhir.r4.model.PractitionerRole;
|
||||
import org.hl7.fhir.r4.model.Reference;
|
||||
|
@ -32,15 +42,15 @@ import org.springframework.transaction.PlatformTransactionManager;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = {
|
||||
TestR4Config.class,
|
||||
DaoTestDataBuilder.Config.class,
|
||||
TestDaoSearch.Config.class
|
||||
})
|
||||
public class FhirResourceDaoR4StandardQueriesLuceneTest extends BaseJpaTest {
|
||||
public class FhirResourceDaoR4StandardQueriesLuceneTest extends BaseJpaTest
|
||||
implements ILuceneSearchR4Test {
|
||||
|
||||
FhirContext myFhirContext = FhirContext.forR4Cached();
|
||||
@Autowired
|
||||
PlatformTransactionManager myTxManager;
|
||||
|
@ -53,6 +63,19 @@ public class FhirResourceDaoR4StandardQueriesLuceneTest extends BaseJpaTest {
|
|||
@Qualifier("myObservationDaoR4")
|
||||
IFhirResourceDao<Observation> myObservationDao;
|
||||
@Autowired
|
||||
@Qualifier("myPatientDaoR4")
|
||||
protected IFhirResourceDaoPatient<Patient> myPatientDao;
|
||||
@Autowired
|
||||
private IFhirSystemDao<Bundle, Meta> mySystemDao;
|
||||
@Autowired
|
||||
private IResourceReindexingSvc myResourceReindexingSvc;
|
||||
@Autowired
|
||||
protected ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
||||
@Autowired
|
||||
protected ISearchParamRegistry mySearchParamRegistry;
|
||||
@Autowired
|
||||
private IBulkDataExportJobSchedulingHelper myBulkDataScheduleHelper;
|
||||
@Autowired
|
||||
IFhirResourceDao<Practitioner> myPractitionerDao;
|
||||
@Autowired
|
||||
IFhirResourceDao<PractitionerRole> myPractitionerRoleDao;
|
||||
|
@ -60,6 +83,7 @@ public class FhirResourceDaoR4StandardQueriesLuceneTest extends BaseJpaTest {
|
|||
// todo mb create an extension to restore via clone or xstream + BeanUtils.copyProperties().
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
purgeDatabase(myStorageSettings, mySystemDao, myResourceReindexingSvc, mySearchCoordinatorSvc, mySearchParamRegistry, myBulkDataScheduleHelper);
|
||||
myStorageSettings.setAdvancedHSearchIndexing(true);
|
||||
}
|
||||
|
||||
|
@ -79,6 +103,11 @@ public class FhirResourceDaoR4StandardQueriesLuceneTest extends BaseJpaTest {
|
|||
return myTxManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DaoRegistry getDaoRegistry() {
|
||||
return myDaoRegistry;
|
||||
}
|
||||
|
||||
@Nested
|
||||
public class DateSearchTests extends BaseDateSearchDaoTests {
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,317 @@
|
|||
package ca.uhn.fhir.jpa.dao.r4;
|
||||
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.util.DateRangeUtil;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public interface ILuceneSearchR4Test {
|
||||
|
||||
DaoRegistry getDaoRegistry();
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private IFhirResourceDao getResourceDao(String theResourceType) {
|
||||
return getDaoRegistry()
|
||||
.getResourceDao(theResourceType);
|
||||
}
|
||||
|
||||
void runInTransaction(Runnable theRunnable);
|
||||
|
||||
@Test
|
||||
default void testNoOpUpdateDoesNotModifyLastUpdated() throws InterruptedException {
|
||||
IFhirResourceDao<Patient> patientDao = getResourceDao("Patient");
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.getNameFirstRep().setFamily("graham").addGiven("gary");
|
||||
|
||||
patient = (Patient) patientDao.create(patient).getResource();
|
||||
Date originalLastUpdated = patient.getMeta().getLastUpdated();
|
||||
|
||||
patient = (Patient) patientDao.update(patient).getResource();
|
||||
Date newLastUpdated = patient.getMeta().getLastUpdated();
|
||||
|
||||
assertThat(originalLastUpdated).isEqualTo(newLastUpdated);
|
||||
}
|
||||
|
||||
@Test
|
||||
default void luceneSearch_forTagsAndLastUpdated_shouldReturn() {
|
||||
// setup
|
||||
SystemRequestDetails requestDeatils = new SystemRequestDetails();
|
||||
String system = "http://fhir";
|
||||
String code = "cv";
|
||||
Date start = Date.from(Instant.now().minus(1, ChronoUnit.SECONDS).truncatedTo(ChronoUnit.SECONDS));
|
||||
Date end = Date.from(Instant.now().plus(10, ChronoUnit.SECONDS).truncatedTo(ChronoUnit.SECONDS));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
IFhirResourceDao<Patient> patientDao = getResourceDao("Patient");
|
||||
|
||||
// create a patient with some tag
|
||||
Patient patient = new Patient();
|
||||
patient.getMeta()
|
||||
.addTag(system, code, "");
|
||||
patient.addName().addGiven("homer")
|
||||
.setFamily("simpson");
|
||||
patient.addAddress()
|
||||
.setCity("springfield")
|
||||
.addLine("742 evergreen terrace");
|
||||
Long id = patientDao.create(patient, requestDeatils).getId().toUnqualifiedVersionless().getIdPartAsLong();
|
||||
|
||||
// create base search map
|
||||
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true);
|
||||
TokenOrListParam goldenRecordStatusToken = new TokenOrListParam(system, code);
|
||||
map.add(Constants.PARAM_TAG, goldenRecordStatusToken);
|
||||
DateRangeParam lastUpdated = DateRangeUtil.narrowDateRange(map.getLastUpdated(), start, end);
|
||||
map.setLastUpdated(lastUpdated);
|
||||
|
||||
runInTransaction(() -> {
|
||||
Stream<JpaPid> stream;
|
||||
List<JpaPid> list;
|
||||
Optional<JpaPid> first;
|
||||
|
||||
// tag search only; should return our resource
|
||||
map.setLastUpdated(null);
|
||||
stream = patientDao.searchForIdStream(map, new SystemRequestDetails(), null);
|
||||
list = stream.toList();
|
||||
assertEquals(1, list.size());
|
||||
first = list.stream().findFirst();
|
||||
assertTrue(first.isPresent());
|
||||
assertEquals(id, first.get().getId());
|
||||
|
||||
// last updated search only; should return our resource
|
||||
map.setLastUpdated(lastUpdated);
|
||||
map.remove(Constants.PARAM_TAG);
|
||||
stream = patientDao.searchForIdStream(map, new SystemRequestDetails(), null);
|
||||
list = stream.toList();
|
||||
assertEquals(1, list.size());
|
||||
first = list.stream().findFirst();
|
||||
assertTrue(first.isPresent());
|
||||
assertEquals(id, first.get().getId());
|
||||
|
||||
// both last updated and tags; should return our resource
|
||||
map.add(Constants.PARAM_TAG, goldenRecordStatusToken);
|
||||
stream = patientDao.searchForIdStream(map, new SystemRequestDetails(), null);
|
||||
list = stream.toList();
|
||||
assertEquals(1, list.size());
|
||||
first = list.stream().findFirst();
|
||||
assertTrue(first.isPresent());
|
||||
assertEquals(id, first.get().getId());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
default void searchLuceneAndJPA_withLuceneMatchingButJpaNot_returnsNothing() {
|
||||
// setup
|
||||
int numToCreate = 2 * SearchBuilder.getMaximumPageSize() + 10;
|
||||
SystemRequestDetails requestDetails = new SystemRequestDetails();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
IFhirResourceDao<Patient> patientDao = getResourceDao("Patient");
|
||||
|
||||
// create resources
|
||||
for (int i = 0; i < numToCreate; i++) {
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
patient.addIdentifier()
|
||||
.setSystem("http://fhir.com")
|
||||
.setValue("ZYX");
|
||||
patient.getText().setDivAsString("<div>ABC</div>");
|
||||
patientDao.create(patient, requestDetails);
|
||||
}
|
||||
|
||||
// test
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.setLoadSynchronous(true);
|
||||
map.setSearchTotalMode(SearchTotalModeEnum.ACCURATE);
|
||||
TokenAndListParam tokenAndListParam = new TokenAndListParam();
|
||||
tokenAndListParam.addAnd(new TokenOrListParam().addOr(new TokenParam().setValue("true")));
|
||||
map.add("active", tokenAndListParam);
|
||||
map.add(Constants.PARAM_TEXT, new StringParam("ABC"));
|
||||
map.add("identifier", new TokenParam(null, "not found"));
|
||||
IBundleProvider provider = patientDao.search(map, requestDetails);
|
||||
|
||||
// verify
|
||||
assertEquals(0, provider.getAllResources().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
default void testLuceneNarrativeSearchQueryIntersectingJpaQuery() {
|
||||
final int numberOfPatientsToCreate = SearchBuilder.getMaximumPageSize() + 10;
|
||||
List<String> expectedActivePatientIds = new ArrayList<>(numberOfPatientsToCreate);
|
||||
|
||||
SystemRequestDetails requestDetails = new SystemRequestDetails();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
IFhirResourceDao<Patient> patientDao = getResourceDao("Patient");
|
||||
|
||||
|
||||
// create active and non-active patients with the same narrative
|
||||
for (int i = 0; i < numberOfPatientsToCreate; i++) {
|
||||
Patient activePatient = new Patient();
|
||||
activePatient.getText().setDivAsString("<div>AAAS<p>FOO</p> CCC </div>");
|
||||
activePatient.setActive(true);
|
||||
String patientId = patientDao.create(activePatient, requestDetails).getId().toUnqualifiedVersionless().getIdPart();
|
||||
expectedActivePatientIds.add(patientId);
|
||||
|
||||
Patient nonActivePatient = new Patient();
|
||||
nonActivePatient.getText().setDivAsString("<div>AAAS<p>FOO</p> CCC </div>");
|
||||
nonActivePatient.setActive(false);
|
||||
patientDao.create(nonActivePatient, requestDetails);
|
||||
}
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true);
|
||||
|
||||
TokenAndListParam tokenAndListParam = new TokenAndListParam();
|
||||
tokenAndListParam.addAnd(new TokenOrListParam().addOr(new TokenParam().setValue("true")));
|
||||
|
||||
map.add("active", tokenAndListParam);
|
||||
map.add(Constants.PARAM_TEXT, new StringParam("AAAS"));
|
||||
|
||||
IBundleProvider searchResultBundle = patientDao.search(map, requestDetails);
|
||||
List<String> resourceIdsFromSearchResult = searchResultBundle.getAllResourceIds();
|
||||
|
||||
assertThat(resourceIdsFromSearchResult).containsExactlyInAnyOrderElementsOf(expectedActivePatientIds);
|
||||
}
|
||||
|
||||
@Test
|
||||
default void testLuceneContentSearchQueryIntersectingJpaQuery() {
|
||||
final int numberOfPatientsToCreate = SearchBuilder.getMaximumPageSize() + 10;
|
||||
final String patientFamilyName = "Flanders";
|
||||
List<String> expectedActivePatientIds = new ArrayList<>(numberOfPatientsToCreate);
|
||||
|
||||
SystemRequestDetails requestDetails = new SystemRequestDetails();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
IFhirResourceDao<Patient> patientDao = getResourceDao("Patient");
|
||||
|
||||
|
||||
// create active and non-active patients with the same narrative
|
||||
for (int i = 0; i < numberOfPatientsToCreate; i++) {
|
||||
Patient activePatient = new Patient();
|
||||
activePatient.addName().setFamily(patientFamilyName);
|
||||
activePatient.setActive(true);
|
||||
String patientId = patientDao.create(activePatient, requestDetails).getId().toUnqualifiedVersionless().getIdPart();
|
||||
expectedActivePatientIds.add(patientId);
|
||||
|
||||
Patient nonActivePatient = new Patient();
|
||||
nonActivePatient.addName().setFamily(patientFamilyName);
|
||||
nonActivePatient.setActive(false);
|
||||
patientDao.create(nonActivePatient, requestDetails);
|
||||
}
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true);
|
||||
TokenAndListParam tokenAndListParam = new TokenAndListParam();
|
||||
tokenAndListParam.addAnd(new TokenOrListParam().addOr(new TokenParam().setValue("true")));
|
||||
map.add("active", tokenAndListParam);
|
||||
map.add(Constants.PARAM_CONTENT, new StringParam(patientFamilyName));
|
||||
|
||||
IBundleProvider searchResultBundle = patientDao.search(map, requestDetails);
|
||||
List<String> resourceIdsFromSearchResult = searchResultBundle.getAllResourceIds();
|
||||
|
||||
assertThat(resourceIdsFromSearchResult).containsExactlyInAnyOrderElementsOf(expectedActivePatientIds);
|
||||
}
|
||||
|
||||
@Test
|
||||
default void searchLuceneAndJPA_withLuceneBroadAndJPASearchNarrow_returnsFoundResults() {
|
||||
// setup
|
||||
int numToCreate = 2 * SearchBuilder.getMaximumPageSize() + 10;
|
||||
String identifierToFind = "bcde";
|
||||
SystemRequestDetails requestDetails = new SystemRequestDetails();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
IFhirResourceDao<Patient> patientDao = getResourceDao("Patient");
|
||||
|
||||
// create patients
|
||||
for (int i = 0; i < numToCreate; i++) {
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
String identifierVal = i == numToCreate - 10 ? identifierToFind:
|
||||
"abcd";
|
||||
patient.addIdentifier()
|
||||
.setSystem("http://fhir.com")
|
||||
.setValue(identifierVal);
|
||||
|
||||
patient.getText().setDivAsString(
|
||||
"<div>FINDME</div>"
|
||||
);
|
||||
patientDao.create(patient, requestDetails);
|
||||
}
|
||||
|
||||
// test
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.setLoadSynchronous(true);
|
||||
map.setSearchTotalMode(SearchTotalModeEnum.ACCURATE);
|
||||
TokenAndListParam tokenAndListParam = new TokenAndListParam();
|
||||
tokenAndListParam.addAnd(new TokenOrListParam().addOr(new TokenParam().setValue("true")));
|
||||
map.add("active", tokenAndListParam);
|
||||
map.add(Constants.PARAM_TEXT, new StringParam("FINDME"));
|
||||
map.add("identifier", new TokenParam(null, identifierToFind));
|
||||
IBundleProvider provider = patientDao.search(map, requestDetails);
|
||||
|
||||
// verify
|
||||
List<String> ids = provider.getAllResourceIds();
|
||||
assertEquals(1, ids.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
default void testSearchNarrativeWithLuceneSearch() {
|
||||
final int numberOfPatientsToCreate = SearchBuilder.getMaximumPageSize() + 10;
|
||||
List<String> expectedActivePatientIds = new ArrayList<>(numberOfPatientsToCreate);
|
||||
|
||||
SystemRequestDetails requestDetails = new SystemRequestDetails();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
IFhirResourceDao<Patient> patientDao = getResourceDao("Patient");
|
||||
|
||||
|
||||
for (int i = 0; i < numberOfPatientsToCreate; i++) {
|
||||
Patient patient = new Patient();
|
||||
patient.getText().setDivAsString("<div>AAAS<p>FOO</p> CCC </div>");
|
||||
expectedActivePatientIds.add(patientDao.create(patient, requestDetails).getId().toUnqualifiedVersionless().getIdPart());
|
||||
}
|
||||
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.getText().setDivAsString("<div>AAAB<p>FOO</p> CCC </div>");
|
||||
patientDao.create(patient, requestDetails);
|
||||
}
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.getText().setDivAsString("<div>ZZYZXY</div>");
|
||||
patientDao.create(patient, requestDetails);
|
||||
}
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true);
|
||||
map.add(Constants.PARAM_TEXT, new StringParam("AAAS"));
|
||||
|
||||
IBundleProvider searchResultBundle = patientDao.search(map, requestDetails);
|
||||
List<String> resourceIdsFromSearchResult = searchResultBundle.getAllResourceIds();
|
||||
|
||||
assertThat(resourceIdsFromSearchResult).containsExactlyInAnyOrderElementsOf(expectedActivePatientIds);
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
|||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.model.search.CompositeSearchIndexData;
|
||||
import ca.uhn.fhir.jpa.model.search.DateSearchIndexData;
|
||||
import ca.uhn.fhir.jpa.model.search.ExtendedHSearchIndexData;
|
||||
|
@ -55,7 +56,7 @@ class ExtendedHSearchIndexExtractorTest implements ITestDataBuilder.WithSupport
|
|||
ResourceSearchParams activeSearchParams = mySearchParamRegistry.getActiveSearchParams("Observation", ISearchParamRegistry.SearchParamLookupContextEnum.SEARCH);
|
||||
ExtendedHSearchIndexExtractor extractor = new ExtendedHSearchIndexExtractor(
|
||||
myJpaStorageSettings, myFhirContext, activeSearchParams, mySearchParamExtractor);
|
||||
ExtendedHSearchIndexData indexData = extractor.extract(new Observation(), extractedParams);
|
||||
ExtendedHSearchIndexData indexData = extractor.extract(new Observation(), new ResourceTable(), extractedParams);
|
||||
|
||||
// validate
|
||||
Set<CompositeSearchIndexData> spIndexData = indexData.getSearchParamComposites().get("component-code-value-concept");
|
||||
|
@ -78,14 +79,13 @@ class ExtendedHSearchIndexExtractorTest implements ITestDataBuilder.WithSupport
|
|||
ResourceSearchParams activeSearchParams = mySearchParamRegistry.getActiveSearchParams("Patient", ISearchParamRegistry.SearchParamLookupContextEnum.SEARCH);
|
||||
ExtendedHSearchIndexExtractor extractor = new ExtendedHSearchIndexExtractor(
|
||||
myJpaStorageSettings, myFhirContext, activeSearchParams, mySearchParamExtractor);
|
||||
ExtendedHSearchIndexData indexData = extractor.extract(new SearchParameter(), searchParams);
|
||||
ExtendedHSearchIndexData indexData = extractor.extract(new SearchParameter(), new ResourceTable(), searchParams);
|
||||
|
||||
// validate
|
||||
Set<DateSearchIndexData> dIndexData = indexData.getDateIndexData().get("Date");
|
||||
assertThat(dIndexData).hasSize(0);
|
||||
Set<QuantitySearchIndexData> qIndexData = indexData.getQuantityIndexData().get("Quantity");
|
||||
assertThat(qIndexData).hasSize(0);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -81,7 +81,7 @@ public class CompositeInterceptorBroadcaster {
|
|||
}
|
||||
if (theRequestDetails != null && theRequestDetails.getInterceptorBroadcaster() != null && retVal) {
|
||||
IInterceptorBroadcaster interceptorBroadcaster = theRequestDetails.getInterceptorBroadcaster();
|
||||
interceptorBroadcaster.callHooks(thePointcut, theParams);
|
||||
retVal = interceptorBroadcaster.callHooks(thePointcut, theParams);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
package ca.uhn.fhir.rest.server.util;
|
||||
|
||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CompositeInterceptorBroadcasterTest {
|
||||
|
||||
@Mock
|
||||
private IInterceptorBroadcaster myModuleBroadcasterMock;
|
||||
@Mock
|
||||
private IInterceptorBroadcaster myReqDetailsBroadcasterMock;
|
||||
@Mock
|
||||
private Pointcut myPointcutMock;
|
||||
@Mock
|
||||
private HookParams myHookParamsMock;
|
||||
@Mock
|
||||
private RequestDetails myRequestDetailsMock;
|
||||
|
||||
|
||||
@Test
|
||||
void doCallHooks_WhenModuleBroadcasterReturnsTrue_And_RequestDetailsBroadcasterReturnsTrue_ThenReturnsTrue() {
|
||||
when(myRequestDetailsMock.getInterceptorBroadcaster()).thenReturn(myReqDetailsBroadcasterMock);
|
||||
|
||||
when(myModuleBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(true);
|
||||
when(myReqDetailsBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(true);
|
||||
|
||||
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(myModuleBroadcasterMock, myRequestDetailsMock,
|
||||
myPointcutMock, myHookParamsMock);
|
||||
|
||||
assertThat(retVal).isTrue();
|
||||
|
||||
verify(myModuleBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
|
||||
verify(myReqDetailsBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
void doCallHooks_WhenModuleBroadcasterReturnsTrue_And_RequestDetailsBroadcasterReturnsFalse_ThenReturnsFalse() {
|
||||
when(myRequestDetailsMock.getInterceptorBroadcaster()).thenReturn(myReqDetailsBroadcasterMock);
|
||||
|
||||
when(myModuleBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(true);
|
||||
when(myReqDetailsBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(false);
|
||||
|
||||
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(myModuleBroadcasterMock, myRequestDetailsMock,
|
||||
myPointcutMock, myHookParamsMock);
|
||||
|
||||
assertThat(retVal).isFalse();
|
||||
|
||||
verify(myModuleBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
|
||||
verify(myReqDetailsBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
void doCallHooks_WhenModuleBroadcasterReturnsFalse_ThenSkipsBroadcasterInRequestDetails_And_ReturnsFalse() {
|
||||
when(myRequestDetailsMock.getInterceptorBroadcaster()).thenReturn(myReqDetailsBroadcasterMock);
|
||||
|
||||
when(myModuleBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(false);
|
||||
|
||||
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(myModuleBroadcasterMock, myRequestDetailsMock,
|
||||
myPointcutMock, myHookParamsMock);
|
||||
|
||||
assertThat(retVal).isFalse();
|
||||
|
||||
verify(myModuleBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
|
||||
verify(myReqDetailsBroadcasterMock, never()).callHooks(myPointcutMock, myHookParamsMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
void doCallHooks_WhenModuleBroadcasterReturnsTrue_And_NullRequestDetailsBroadcaster_ThenReturnsTrue() {
|
||||
|
||||
when(myModuleBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(true);
|
||||
when(myRequestDetailsMock.getInterceptorBroadcaster()).thenReturn(null);
|
||||
|
||||
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(myModuleBroadcasterMock, myRequestDetailsMock, myPointcutMock,
|
||||
myHookParamsMock);
|
||||
|
||||
assertThat(retVal).isTrue();
|
||||
|
||||
verify(myModuleBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
void doCallHooks_WhenModuleBroadcasterReturnsFalse_And_NullRequestDetailsBroadcaster_ThenReturnsFalse() {
|
||||
|
||||
when(myModuleBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(false);
|
||||
when(myRequestDetailsMock.getInterceptorBroadcaster()).thenReturn(null);
|
||||
|
||||
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(myModuleBroadcasterMock, myRequestDetailsMock, myPointcutMock,
|
||||
myHookParamsMock);
|
||||
|
||||
assertThat(retVal).isFalse();
|
||||
|
||||
verify(myModuleBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
void doCallHooks_WhenModuleBroadcasterReturnsTrue_And_NullRequestDetails_ThenReturnsTrue() {
|
||||
|
||||
when(myModuleBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(true);
|
||||
|
||||
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(myModuleBroadcasterMock, null, myPointcutMock, myHookParamsMock);
|
||||
|
||||
assertThat(retVal).isTrue();
|
||||
|
||||
verify(myModuleBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
void doCallHooks_WhenModuleBroadcasterReturnsFalse_And_NullRequestDetails_ThenReturnsFalse() {
|
||||
|
||||
when(myModuleBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(false);
|
||||
|
||||
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(myModuleBroadcasterMock, null, myPointcutMock, myHookParamsMock);
|
||||
|
||||
assertThat(retVal).isFalse();
|
||||
|
||||
verify(myModuleBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
void doCallHooks_WhenNullModuleBroadcaster_And_NullRequestDetails_ThenReturnsTrue() {
|
||||
|
||||
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(null, null, myPointcutMock, myHookParamsMock);
|
||||
|
||||
assertThat(retVal).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void doCallHooks_WhenNullModuleBroadcaster_And_RequestDetailsBroadcasterReturnsTrue_ThenReturnsTrue() {
|
||||
when(myRequestDetailsMock.getInterceptorBroadcaster()).thenReturn(myReqDetailsBroadcasterMock);
|
||||
when(myReqDetailsBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(true);
|
||||
|
||||
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(null, myRequestDetailsMock, myPointcutMock, myHookParamsMock);
|
||||
|
||||
assertThat(retVal).isTrue();
|
||||
verify(myReqDetailsBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void doCallHooks_WhenNullModuleBroadcaster_And_RequestDetailsBroadcasterReturnsFalse_ThenReturnsFalse() {
|
||||
when(myRequestDetailsMock.getInterceptorBroadcaster()).thenReturn(myReqDetailsBroadcasterMock);
|
||||
when(myReqDetailsBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(false);
|
||||
|
||||
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(null, myRequestDetailsMock, myPointcutMock, myHookParamsMock);
|
||||
|
||||
assertThat(retVal).isFalse();
|
||||
verify(myReqDetailsBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
|
||||
}
|
||||
}
|
|
@ -92,7 +92,7 @@ public class ReindexV1Config {
|
|||
"Load IDs of resources to reindex",
|
||||
ResourceIdListWorkChunkJson.class,
|
||||
myReindexLoadIdsStep)
|
||||
.addLastStep("reindex-start", "Start the resource reindex", reindexStepV1())
|
||||
.addLastStep("reindex", "Start the resource reindex", reindexStepV1())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ 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.api.server.SystemRequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
|
||||
import ca.uhn.fhir.test.utilities.ITestDataBuilder;
|
||||
import ca.uhn.fhir.util.BundleBuilder;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
|
@ -66,18 +67,42 @@ public class DaoTestDataBuilder implements ITestDataBuilder.WithSupport, ITestDa
|
|||
}
|
||||
//noinspection rawtypes
|
||||
IFhirResourceDao dao = myDaoRegistry.getResourceDao(theResource.getClass());
|
||||
|
||||
// manipulate the transaction details to provide a fake transaction date
|
||||
TransactionDetails details = null;
|
||||
if (theResource.getMeta() != null && theResource.getMeta().getLastUpdated() != null) {
|
||||
details = new TransactionDetails(theResource.getMeta().getLastUpdated());
|
||||
} else {
|
||||
details = new TransactionDetails();
|
||||
}
|
||||
|
||||
//noinspection unchecked
|
||||
IIdType id = dao.create(theResource, mySrd).getId().toUnqualifiedVersionless();
|
||||
IIdType id = dao.create(theResource, null, true, mySrd, details)
|
||||
.getId().toUnqualifiedVersionless();
|
||||
myIds.put(theResource.fhirType(), id);
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIdType doUpdateResource(IBaseResource theResource) {
|
||||
// manipulate the transaction details to provdie a fake transaction date
|
||||
TransactionDetails details = null;
|
||||
if (theResource.getMeta() != null && theResource.getMeta().getLastUpdated() != null) {
|
||||
details = new TransactionDetails(theResource.getMeta().getLastUpdated());
|
||||
} else {
|
||||
details = new TransactionDetails();
|
||||
}
|
||||
|
||||
//noinspection rawtypes
|
||||
IFhirResourceDao dao = myDaoRegistry.getResourceDao(theResource.getClass());
|
||||
|
||||
//noinspection unchecked
|
||||
IIdType id = dao.update(theResource, mySrd).getId().toUnqualifiedVersionless();
|
||||
IIdType id = dao.update(theResource,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
mySrd,
|
||||
details).getId().toUnqualifiedVersionless();
|
||||
myIds.put(theResource.fhirType(), id);
|
||||
return id;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.hl7.fhir.r4.model.Composition;
|
|||
import org.hl7.fhir.r4.model.DateTimeType;
|
||||
import org.hl7.fhir.r4.model.DecimalType;
|
||||
import org.hl7.fhir.r4.model.Device;
|
||||
import org.hl7.fhir.r4.model.DiagnosticReport;
|
||||
import org.hl7.fhir.r4.model.DocumentReference;
|
||||
import org.hl7.fhir.r4.model.Encounter;
|
||||
import org.hl7.fhir.r4.model.Extension;
|
||||
|
@ -43,6 +44,7 @@ import org.hl7.fhir.r4.model.PrimitiveType;
|
|||
import org.hl7.fhir.r4.model.Quantity;
|
||||
import org.hl7.fhir.r4.model.QuestionnaireResponse;
|
||||
import org.hl7.fhir.r4.model.Reference;
|
||||
import org.hl7.fhir.r4.model.Specimen;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.hl7.fhir.r4.model.Type;
|
||||
import org.hl7.fhir.r4.model.codesystems.DataAbsentReason;
|
||||
|
@ -55,6 +57,8 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import org.testcontainers.shaded.com.trilead.ssh2.packets.PacketDisconnect;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
@ -261,7 +265,81 @@ public class JsonParserR4Test extends BaseTest {
|
|||
|
||||
idx = encoded.indexOf("\"Medication\"", idx + 1);
|
||||
assertEquals(-1, idx);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicateContainedResourcesAcrossABundleAreReplicated() {
|
||||
Bundle b = new Bundle();
|
||||
Specimen specimen = new Specimen();
|
||||
Practitioner practitioner = new Practitioner();
|
||||
DiagnosticReport report = new DiagnosticReport();
|
||||
report.addSpecimen(new Reference(specimen));
|
||||
b.addEntry().setResource(report).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("/DiagnosticReport");
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.addPerformer(new Reference(practitioner));
|
||||
obs.setSpecimen(new Reference(specimen));
|
||||
|
||||
b.addEntry().setResource(obs).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("/Observation");
|
||||
|
||||
String encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(b);
|
||||
//Then: Diag should contain one local contained specimen
|
||||
assertThat(encoded).contains("[{\"resource\":{\"resourceType\":\"DiagnosticReport\",\"contained\":[{\"resourceType\":\"Specimen\",\"id\":\"1\"}]");
|
||||
//Then: Obs should contain one local contained specimen, and one local contained pract
|
||||
assertThat(encoded).contains("\"resource\":{\"resourceType\":\"Observation\",\"contained\":[{\"resourceType\":\"Specimen\",\"id\":\"1\"},{\"resourceType\":\"Practitioner\",\"id\":\"2\"}]");
|
||||
assertThat(encoded).contains("\"performer\":[{\"reference\":\"#2\"}],\"specimen\":{\"reference\":\"#1\"}");
|
||||
|
||||
//Also, reverting the operation should work too!
|
||||
Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, encoded);
|
||||
IBaseResource resource1 = ((DiagnosticReport) bundle.getEntry().get(0).getResource()).getSpecimenFirstRep().getResource();
|
||||
IBaseResource resource = ((Observation) bundle.getEntry().get(1).getResource()).getSpecimen().getResource();
|
||||
assertThat(resource1.getIdElement().getIdPart()).isEqualTo(resource.getIdElement().getIdPart());
|
||||
assertThat(resource1).isNotSameAs(resource);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoAssignedContainedCollisionOrderDependent() {
|
||||
{
|
||||
Specimen specimen = new Specimen();
|
||||
Practitioner practitioner = new Practitioner();
|
||||
DiagnosticReport report = new DiagnosticReport();
|
||||
report.addSpecimen(new Reference(specimen));
|
||||
|
||||
Observation obs = new Observation();
|
||||
//When: The practitioner (which is parsed first, has an assigned id that will collide with auto-assigned
|
||||
practitioner.setId("#1");
|
||||
obs.addPerformer(new Reference(practitioner));
|
||||
obs.setSpecimen(new Reference(specimen));
|
||||
|
||||
|
||||
String encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(obs);
|
||||
assertThat(encoded).contains("\"contained\":[{\"resourceType\":\"Practitioner\",\"id\":\"1\"},{\"resourceType\":\"Specimen\",\"id\":\"2\"}]");
|
||||
assertThat(encoded).contains("\"performer\":[{\"reference\":\"#1\"}]");
|
||||
assertThat(encoded).contains("\"specimen\":{\"reference\":\"#2\"}}");
|
||||
ourLog.info(encoded);
|
||||
}
|
||||
|
||||
{
|
||||
Specimen specimen = new Specimen();
|
||||
Practitioner practitioner = new Practitioner();
|
||||
DiagnosticReport report = new DiagnosticReport();
|
||||
report.addSpecimen(new Reference(specimen));
|
||||
|
||||
Observation obs = new Observation();
|
||||
|
||||
//When: The specimen (which is parsed second, has an assigned id that will collide with auto-assigned practitioner
|
||||
specimen.setId("#1");
|
||||
obs.addPerformer(new Reference(practitioner));
|
||||
obs.setSpecimen(new Reference(specimen));
|
||||
|
||||
|
||||
String encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(obs);
|
||||
assertThat(encoded).contains("\"contained\":[{\"resourceType\":\"Specimen\",\"id\":\"1\"},{\"resourceType\":\"Practitioner\",\"id\":\"2\"}]");
|
||||
assertThat(encoded).contains("\"performer\":[{\"reference\":\"#2\"}]");
|
||||
assertThat(encoded).contains("\"specimen\":{\"reference\":\"#1\"}}");
|
||||
ourLog.info(encoded);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -6,7 +6,10 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.model.api.annotation.Block;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
import ca.uhn.fhir.parser.JsonParser;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.jena.base.Sys;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseExtension;
|
||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||
|
@ -47,6 +50,8 @@ import org.junit.jupiter.api.Test;
|
|||
import org.mockito.ArgumentCaptor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.testcontainers.shaded.com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
|
@ -1545,23 +1550,29 @@ public class FhirTerserR4Test {
|
|||
|
||||
@Test
|
||||
void copyingAndParsingCreatesDuplicateContainedResources() {
|
||||
var input = new Library();
|
||||
var library = new Library();
|
||||
var params = new Parameters();
|
||||
var id = "#expansion-parameters-ecr";
|
||||
params.setId(id);
|
||||
params.addParameter("system-version", new StringType("test2"));
|
||||
var paramsExt = new Extension();
|
||||
|
||||
paramsExt.setUrl("test").setValue(new Reference(id));
|
||||
input.addContained(params);
|
||||
input.addExtension(paramsExt);
|
||||
library.addContained(params);
|
||||
library.addExtension(paramsExt);
|
||||
|
||||
final var parser = FhirContext.forR4Cached().newJsonParser();
|
||||
var stringified = parser.encodeResourceToString(input);
|
||||
var stringified = parser.encodeResourceToString(library);
|
||||
|
||||
|
||||
var parsed = parser.parseResource(stringified);
|
||||
var copy = ((Library) parsed).copy();
|
||||
|
||||
assertEquals(1, copy.getContained().size());
|
||||
var stringifiedCopy = parser.encodeResourceToString(copy);
|
||||
var parsedCopy = parser.parseResource(stringifiedCopy);
|
||||
assertEquals(1, ((Library) parsedCopy).getContained().size());
|
||||
|
||||
String stringifiedCopy = FhirContext.forR4Cached().newJsonParser().encodeResourceToString(copy);
|
||||
Library parsedCopy = (Library) parser.parseResource(stringifiedCopy);
|
||||
assertEquals(1, parsedCopy.getContained().size());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -209,6 +209,12 @@ public interface ITestDataBuilder {
|
|||
return t -> ((IBaseResource)t).getMeta().setLastUpdated(theLastUpdated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a _lastUpdated value.
|
||||
*
|
||||
* This value will also be used to control the transaction time, which is what determines
|
||||
* what the Updated date is.
|
||||
*/
|
||||
default ICreationArgument withLastUpdated(String theIsoDate) {
|
||||
return t -> ((IBaseResource)t).getMeta().setLastUpdated(new InstantType(theIsoDate).getValue());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Test Utilities
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
|
||||
* %%
|
||||
* 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%
|
||||
*/
|
||||
package ca.uhn.fhir.test.utilities.validation;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.param.UriParam;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.util.ClasspathUtil;
|
||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||
import org.hl7.fhir.instance.model.api.IDomainResource;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface IValidationProviders {
|
||||
String CODE_SYSTEM = "http://code.system/url";
|
||||
String CODE_SYSTEM_VERSION = "1.0.0";
|
||||
String CODE_SYSTEM_NAME = "Test Code System";
|
||||
String CODE = "CODE";
|
||||
String VALUE_SET_URL = "http://value.set/url";
|
||||
String DISPLAY = "Explanation for code TestCode.";
|
||||
String LANGUAGE = "en";
|
||||
String ERROR_MESSAGE = "This is an error message";
|
||||
|
||||
interface IMyValidationProvider extends IResourceProvider {
|
||||
void addException(String theOperation, String theUrl, String theCode, Exception theException);
|
||||
<P extends IBaseParameters> void addTerminologyResponse(String theOperation, String theUrl, String theCode, P theReturnParams);
|
||||
IBaseParameters addTerminologyResponse(String theOperation, String theUrl, String theCode, FhirContext theFhirContext, String theTerminologyResponseFile);
|
||||
}
|
||||
|
||||
abstract class MyValidationProvider<T extends IDomainResource> implements IMyValidationProvider {
|
||||
private final Map<String, Exception> myExceptionMap = new HashMap<>();
|
||||
private boolean myShouldThrowExceptionForResourceNotFound = true;
|
||||
private final Map<String, IBaseParameters> myTerminologyResponseMap = new HashMap<>();
|
||||
private final Map<String, T> myTerminologyResourceMap = new HashMap<>();
|
||||
|
||||
static String getInputKey(String theOperation, String theUrl, String theCode) {
|
||||
return theOperation + "-" + theUrl + "#" + theCode;
|
||||
}
|
||||
|
||||
public void setShouldThrowExceptionForResourceNotFound(boolean theShouldThrowExceptionForResourceNotFound) {
|
||||
myShouldThrowExceptionForResourceNotFound = theShouldThrowExceptionForResourceNotFound;
|
||||
}
|
||||
|
||||
public void addException(String theOperation, String theUrl, String theCode, Exception theException) {
|
||||
String inputKey = getInputKey(theOperation, theUrl, theCode);
|
||||
myExceptionMap.put(inputKey, theException);
|
||||
}
|
||||
|
||||
abstract Class<? extends IBaseParameters> getParameterType();
|
||||
|
||||
@Override
|
||||
public <P extends IBaseParameters> void addTerminologyResponse(String theOperation, String theUrl, String theCode, P theReturnParams) {
|
||||
myTerminologyResponseMap.put(getInputKey(theOperation, theUrl, theCode), theReturnParams);
|
||||
}
|
||||
|
||||
public IBaseParameters addTerminologyResponse(String theOperation, String theUrl, String theCode, FhirContext theFhirContext, String theTerminologyResponseFile) {
|
||||
IBaseParameters responseParams = ClasspathUtil.loadResource(theFhirContext, getParameterType(), theTerminologyResponseFile);
|
||||
addTerminologyResponse(theOperation, theUrl, theCode, responseParams);
|
||||
return responseParams;
|
||||
}
|
||||
|
||||
protected void addTerminologyResource(String theUrl, T theResource) {
|
||||
myTerminologyResourceMap.put(theUrl, theResource);
|
||||
}
|
||||
|
||||
public abstract T addTerminologyResource(String theUrl);
|
||||
|
||||
protected IBaseParameters getTerminologyResponse(String theOperation, String theUrl, String theCode) throws Exception {
|
||||
String inputKey = getInputKey(theOperation, theUrl, theCode);
|
||||
if (myExceptionMap.containsKey(inputKey)) {
|
||||
throw myExceptionMap.get(inputKey);
|
||||
}
|
||||
IBaseParameters params = myTerminologyResponseMap.get(inputKey);
|
||||
if (params == null) {
|
||||
throw new IllegalStateException("Test setup incomplete. Missing return params for " + inputKey);
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
protected T getTerminologyResource(UriParam theUrlParam) {
|
||||
if (theUrlParam.isEmpty()) {
|
||||
throw new IllegalStateException("CodeSystem url should not be null.");
|
||||
}
|
||||
String urlValue = theUrlParam.getValue();
|
||||
if (!myTerminologyResourceMap.containsKey(urlValue) && myShouldThrowExceptionForResourceNotFound) {
|
||||
throw new IllegalStateException("Test setup incomplete. CodeSystem not found " + urlValue);
|
||||
}
|
||||
return myTerminologyResourceMap.get(urlValue);
|
||||
}
|
||||
|
||||
@Search
|
||||
public List<T> find(@RequiredParam(name = "url") UriParam theUrlParam) {
|
||||
T resource = getTerminologyResource(theUrlParam);
|
||||
return resource != null ? List.of(resource) : List.of();
|
||||
}
|
||||
}
|
||||
|
||||
interface IMyLookupCodeProvider extends IResourceProvider {
|
||||
void setLookupCodeResult(IValidationSupport.LookupCodeResult theLookupCodeResult);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Test Utilities
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
|
||||
* %%
|
||||
* 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%
|
||||
*/
|
||||
package ca.uhn.fhir.test.utilities.validation;
|
||||
|
||||
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.api.server.RequestDetails;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.hl7.fhir.dstu3.model.BooleanType;
|
||||
import org.hl7.fhir.dstu3.model.CodeSystem;
|
||||
import org.hl7.fhir.dstu3.model.CodeType;
|
||||
import org.hl7.fhir.dstu3.model.Coding;
|
||||
import org.hl7.fhir.dstu3.model.IdType;
|
||||
import org.hl7.fhir.dstu3.model.Parameters;
|
||||
import org.hl7.fhir.dstu3.model.StringType;
|
||||
import org.hl7.fhir.dstu3.model.UriType;
|
||||
import org.hl7.fhir.dstu3.model.ValueSet;
|
||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IValidationProvidersDstu3 {
|
||||
@SuppressWarnings("unused")
|
||||
class MyCodeSystemProviderDstu3 extends IValidationProviders.MyValidationProvider<CodeSystem> {
|
||||
@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 IBaseParameters 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
|
||||
) throws Exception {
|
||||
String url = theCodeSystemUrl != null ? theCodeSystemUrl.getValue() : null;
|
||||
String code = theCode != null ? theCode.getValue() : null;
|
||||
return getTerminologyResponse("$validate-code", url, code);
|
||||
}
|
||||
|
||||
@Operation(name = "$lookup", idempotent = true, returnParameters= {
|
||||
@OperationParam(name = "name", type = StringType.class, min = 1),
|
||||
@OperationParam(name = "version", type = StringType.class),
|
||||
@OperationParam(name = "display", type = StringType.class, min = 1),
|
||||
@OperationParam(name = "abstract", type = BooleanType.class, min = 1),
|
||||
@OperationParam(name = "property", type = StringType.class, min = 0, max = OperationParam.MAX_UNLIMITED)
|
||||
})
|
||||
public IBaseParameters lookup(
|
||||
HttpServletRequest theServletRequest,
|
||||
@OperationParam(name = "code", max = 1) CodeType theCode,
|
||||
@OperationParam(name = "system",max = 1) UriType theSystem,
|
||||
@OperationParam(name = "coding", max = 1) Coding theCoding,
|
||||
@OperationParam(name = "version", max = 1) StringType theVersion,
|
||||
@OperationParam(name = "displayLanguage", max = 1) CodeType theDisplayLanguage,
|
||||
@OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List<CodeType> thePropertyNames,
|
||||
RequestDetails theRequestDetails
|
||||
) throws Exception {
|
||||
String url = theSystem != null ? theSystem.getValue() : null;
|
||||
String code = theCode != null ? theCode.getValue() : null;
|
||||
return getTerminologyResponse("$lookup", url, code);
|
||||
}
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return CodeSystem.class;
|
||||
}
|
||||
@Override
|
||||
Class<Parameters> getParameterType() {
|
||||
return Parameters.class;
|
||||
}
|
||||
@Override
|
||||
public CodeSystem addTerminologyResource(String theUrl) {
|
||||
CodeSystem codeSystem = new CodeSystem();
|
||||
codeSystem.setId(theUrl.substring(0, theUrl.lastIndexOf("/")));
|
||||
codeSystem.setUrl(theUrl);
|
||||
addTerminologyResource(theUrl, codeSystem);
|
||||
return codeSystem;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
class MyValueSetProviderDstu3 extends IValidationProviders.MyValidationProvider<ValueSet> {
|
||||
@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 IBaseParameters validateCode(
|
||||
HttpServletRequest theServletRequest,
|
||||
@IdParam(optional = true) IdType theId,
|
||||
@OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl,
|
||||
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
|
||||
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
|
||||
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
|
||||
@OperationParam(name = "valueSet") ValueSet theValueSet
|
||||
) throws Exception {
|
||||
String url = theValueSetUrl != null ? theValueSetUrl.getValue() : null;
|
||||
String code = theCode != null ? theCode.getValue() : null;
|
||||
return getTerminologyResponse("$validate-code", url, code);
|
||||
}
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return ValueSet.class;
|
||||
}
|
||||
@Override
|
||||
Class<Parameters> getParameterType() {
|
||||
return Parameters.class;
|
||||
}
|
||||
@Override
|
||||
public ValueSet addTerminologyResource(String theUrl) {
|
||||
ValueSet valueSet = new ValueSet();
|
||||
valueSet.setId(theUrl.substring(0, theUrl.lastIndexOf("/")));
|
||||
valueSet.setUrl(theUrl);
|
||||
addTerminologyResource(theUrl, valueSet);
|
||||
return valueSet;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,29 @@
|
|||
package org.hl7.fhir.r4.validation;
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Test Utilities
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
|
||||
* %%
|
||||
* 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%
|
||||
*/
|
||||
package ca.uhn.fhir.test.utilities.validation;
|
||||
|
||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||
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.api.server.RequestDetails;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
|
||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.BooleanType;
|
||||
|
@ -21,38 +38,29 @@ import org.hl7.fhir.r4.model.ValueSet;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
public interface IValidateCodeProvidersR4 {
|
||||
public interface IValidationProvidersR4 {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
class MyCodeSystemProviderR4 implements IValidationProviders.IMyCodeSystemProvider {
|
||||
private UriType mySystemUrl;
|
||||
private CodeType myCode;
|
||||
private StringType myDisplay;
|
||||
private Exception myException;
|
||||
private Parameters myReturnParams;
|
||||
class MyCodeSystemProviderR4 extends IValidationProviders.MyValidationProvider<CodeSystem> {
|
||||
|
||||
@Operation(name = "validate-code", idempotent = true, 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)
|
||||
})
|
||||
public Parameters validateCode(
|
||||
public IBaseParameters 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
|
||||
) throws Exception {
|
||||
mySystemUrl = theCodeSystemUrl;
|
||||
myCode = theCode;
|
||||
myDisplay = theDisplay;
|
||||
if (myException != null) {
|
||||
throw myException;
|
||||
}
|
||||
return myReturnParams;
|
||||
String url = theCodeSystemUrl != null ? theCodeSystemUrl.getValue() : null;
|
||||
String code = theCode != null ? theCode.getValue() : null;
|
||||
return getTerminologyResponse("$validate-code", url, code);
|
||||
}
|
||||
|
||||
@Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= {
|
||||
@Operation(name = "$lookup", idempotent = true, returnParameters= {
|
||||
@OperationParam(name = "name", type = StringType.class, min = 1),
|
||||
@OperationParam(name = "version", type = StringType.class),
|
||||
@OperationParam(name = "display", type = StringType.class, min = 1),
|
||||
|
@ -69,54 +77,39 @@ public interface IValidateCodeProvidersR4 {
|
|||
@OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List<CodeType> thePropertyNames,
|
||||
RequestDetails theRequestDetails
|
||||
) throws Exception {
|
||||
mySystemUrl = theSystem;
|
||||
myCode = theCode;
|
||||
if (myException != null) {
|
||||
throw myException;
|
||||
}
|
||||
return myReturnParams;
|
||||
String url = theSystem != null ? theSystem.getValue() : null;
|
||||
String code = theCode != null ? theCode.getValue() : null;
|
||||
return getTerminologyResponse("$lookup", url, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return CodeSystem.class;
|
||||
}
|
||||
|
||||
public void setException(Exception theException) {
|
||||
myException = theException;
|
||||
}
|
||||
@Override
|
||||
public void setReturnParams(IBaseParameters theParameters) {
|
||||
myReturnParams = (Parameters) theParameters;
|
||||
Class<Parameters> getParameterType() {
|
||||
return Parameters.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return myCode != null ? myCode.getValueAsString() : null;
|
||||
}
|
||||
@Override
|
||||
public String getSystem() {
|
||||
return mySystemUrl != null ? mySystemUrl.getValueAsString() : null;
|
||||
}
|
||||
public String getDisplay() {
|
||||
return myDisplay != null ? myDisplay.getValue() : null;
|
||||
public CodeSystem addTerminologyResource(String theUrl) {
|
||||
CodeSystem codeSystem = new CodeSystem();
|
||||
codeSystem.setId(theUrl.substring(0, theUrl.lastIndexOf("/")));
|
||||
codeSystem.setUrl(theUrl);
|
||||
addTerminologyResource(theUrl, codeSystem);
|
||||
return codeSystem;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
class MyValueSetProviderR4 implements IValidationProviders.IMyValueSetProvider {
|
||||
private Exception myException;
|
||||
private Parameters myReturnParams;
|
||||
private UriType mySystemUrl;
|
||||
private UriType myValueSetUrl;
|
||||
private CodeType myCode;
|
||||
private StringType myDisplay;
|
||||
class MyValueSetProviderR4 extends IValidationProviders.MyValidationProvider<ValueSet> {
|
||||
|
||||
@Operation(name = "validate-code", idempotent = true, 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)
|
||||
})
|
||||
public Parameters validateCode(
|
||||
public IBaseParameters validateCode(
|
||||
HttpServletRequest theServletRequest,
|
||||
@IdParam(optional = true) IdType theId,
|
||||
@OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl,
|
||||
|
@ -125,41 +118,25 @@ public interface IValidateCodeProvidersR4 {
|
|||
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
|
||||
@OperationParam(name = "valueSet") ValueSet theValueSet
|
||||
) throws Exception {
|
||||
mySystemUrl = theSystem;
|
||||
myValueSetUrl = theValueSetUrl;
|
||||
myCode = theCode;
|
||||
myDisplay = theDisplay;
|
||||
if (myException != null) {
|
||||
throw myException;
|
||||
}
|
||||
return myReturnParams;
|
||||
String url = theValueSetUrl != null ? theValueSetUrl.getValue() : null;
|
||||
String code = theCode != null ? theCode.getValue() : null;
|
||||
return getTerminologyResponse("$validate-code", url, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return ValueSet.class;
|
||||
}
|
||||
public void setException(Exception theException) {
|
||||
myException = theException;
|
||||
@Override
|
||||
Class<Parameters> getParameterType() {
|
||||
return Parameters.class;
|
||||
}
|
||||
@Override
|
||||
public void setReturnParams(IBaseParameters theParameters) {
|
||||
myReturnParams = (Parameters) theParameters;
|
||||
}
|
||||
@Override
|
||||
public String getCode() {
|
||||
return myCode != null ? myCode.getValueAsString() : null;
|
||||
}
|
||||
@Override
|
||||
public String getSystem() {
|
||||
return mySystemUrl != null ? mySystemUrl.getValueAsString() : null;
|
||||
}
|
||||
@Override
|
||||
public String getValueSet() {
|
||||
return myValueSetUrl != null ? myValueSetUrl.getValueAsString() : null;
|
||||
}
|
||||
public String getDisplay() {
|
||||
return myDisplay != null ? myDisplay.getValue() : null;
|
||||
public ValueSet addTerminologyResource(String theUrl) {
|
||||
ValueSet valueSet = new ValueSet();
|
||||
valueSet.setId(theUrl.substring(0, theUrl.lastIndexOf("/")));
|
||||
valueSet.setUrl(theUrl);
|
||||
addTerminologyResource(theUrl, valueSet);
|
||||
return valueSet;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -190,7 +190,7 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport {
|
|||
return new CodeValidationResult()
|
||||
.setSeverity(IssueSeverity.ERROR)
|
||||
.setMessage(theMessage)
|
||||
.setCodeValidationIssues(Collections.singletonList(new CodeValidationIssue(
|
||||
.setIssues(Collections.singletonList(new CodeValidationIssue(
|
||||
theMessage,
|
||||
IssueSeverity.ERROR,
|
||||
CodeValidationIssueCode.INVALID,
|
||||
|
|
|
@ -28,7 +28,6 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
|||
import org.hl7.fhir.r5.model.CanonicalType;
|
||||
import org.hl7.fhir.r5.model.CodeSystem;
|
||||
import org.hl7.fhir.r5.model.Enumerations;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -258,7 +257,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
|
|||
theValidationSupportContext, theValueSet, theCodeSystemUrlAndVersion, theCode);
|
||||
} catch (ExpansionCouldNotBeCompletedInternallyException e) {
|
||||
CodeValidationResult codeValidationResult = new CodeValidationResult();
|
||||
codeValidationResult.setSeverityCode("error");
|
||||
codeValidationResult.setSeverity(IssueSeverity.ERROR);
|
||||
|
||||
String msg = "Failed to expand ValueSet '" + vsUrl + "' (in-memory). Could not validate code "
|
||||
+ theCodeSystemUrlAndVersion + "#" + theCode;
|
||||
|
@ -267,7 +266,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
|
|||
}
|
||||
|
||||
codeValidationResult.setMessage(msg);
|
||||
codeValidationResult.addCodeValidationIssue(e.getCodeValidationIssue());
|
||||
codeValidationResult.addIssue(e.getCodeValidationIssue());
|
||||
return codeValidationResult;
|
||||
}
|
||||
|
||||
|
@ -551,18 +550,18 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
|
|||
if (valueSetResult != null) {
|
||||
codeValidationResult = valueSetResult;
|
||||
} else {
|
||||
ValidationMessage.IssueSeverity severity;
|
||||
IValidationSupport.IssueSeverity severity;
|
||||
String message;
|
||||
CodeValidationIssueCode issueCode = CodeValidationIssueCode.CODE_INVALID;
|
||||
CodeValidationIssueCoding issueCoding = CodeValidationIssueCoding.INVALID_CODE;
|
||||
if ("fragment".equals(codeSystemResourceContentMode)) {
|
||||
severity = ValidationMessage.IssueSeverity.WARNING;
|
||||
severity = IValidationSupport.IssueSeverity.WARNING;
|
||||
message = "Unknown code in fragment CodeSystem '"
|
||||
+ getFormattedCodeSystemAndCodeForMessage(
|
||||
theCodeSystemUrlAndVersionToValidate, theCodeToValidate)
|
||||
+ "'";
|
||||
} else {
|
||||
severity = ValidationMessage.IssueSeverity.ERROR;
|
||||
severity = IValidationSupport.IssueSeverity.ERROR;
|
||||
message = "Unknown code '"
|
||||
+ getFormattedCodeSystemAndCodeForMessage(
|
||||
theCodeSystemUrlAndVersionToValidate, theCodeToValidate)
|
||||
|
@ -574,10 +573,9 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
|
|||
}
|
||||
|
||||
codeValidationResult = new CodeValidationResult()
|
||||
.setSeverityCode(severity.toCode())
|
||||
.setSeverity(severity)
|
||||
.setMessage(message)
|
||||
.addCodeValidationIssue(new CodeValidationIssue(
|
||||
message, getIssueSeverityFromCodeValidationIssue(severity), issueCode, issueCoding));
|
||||
.addIssue(new CodeValidationIssue(message, severity, issueCode, issueCoding));
|
||||
}
|
||||
|
||||
return codeValidationResult;
|
||||
|
@ -589,19 +587,6 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
|
|||
+ theCodeToValidate;
|
||||
}
|
||||
|
||||
private IValidationSupport.IssueSeverity getIssueSeverityFromCodeValidationIssue(
|
||||
ValidationMessage.IssueSeverity theSeverity) {
|
||||
switch (theSeverity) {
|
||||
case ERROR:
|
||||
return IValidationSupport.IssueSeverity.ERROR;
|
||||
case WARNING:
|
||||
return IValidationSupport.IssueSeverity.WARNING;
|
||||
case INFORMATION:
|
||||
return IValidationSupport.IssueSeverity.INFORMATION;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private CodeValidationResult findCodeInExpansion(
|
||||
String theCodeToValidate,
|
||||
String theDisplayToValidate,
|
||||
|
@ -1123,8 +1108,8 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
|
|||
new CodeValidationIssue(
|
||||
theMessage,
|
||||
IssueSeverity.ERROR,
|
||||
CodeValidationIssueCode.OTHER,
|
||||
CodeValidationIssueCoding.OTHER));
|
||||
CodeValidationIssueCode.INVALID,
|
||||
CodeValidationIssueCoding.VS_INVALID));
|
||||
}
|
||||
for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next :
|
||||
subExpansion.getExpansion().getContains()) {
|
||||
|
@ -1376,7 +1361,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
|
|||
.setCodeSystemVersion(theCodeSystemVersion)
|
||||
.setDisplay(theExpectedDisplay);
|
||||
if (issueSeverity != null) {
|
||||
codeValidationResult.setCodeValidationIssues(Collections.singletonList(new CodeValidationIssue(
|
||||
codeValidationResult.setIssues(Collections.singletonList(new CodeValidationIssue(
|
||||
message,
|
||||
theIssueSeverityForCodeDisplayMismatch,
|
||||
CodeValidationIssueCode.INVALID,
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
|
|||
import org.hl7.fhir.r4.model.Base;
|
||||
import org.hl7.fhir.r4.model.CodeSystem;
|
||||
import org.hl7.fhir.r4.model.CodeType;
|
||||
import org.hl7.fhir.r4.model.CodeableConcept;
|
||||
import org.hl7.fhir.r4.model.Coding;
|
||||
import org.hl7.fhir.r4.model.OperationOutcome;
|
||||
import org.hl7.fhir.r4.model.Parameters;
|
||||
|
@ -631,7 +632,7 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
|
|||
return new CodeValidationResult()
|
||||
.setSeverity(severity)
|
||||
.setMessage(theMessage)
|
||||
.addCodeValidationIssue(new CodeValidationIssue(
|
||||
.addIssue(new CodeValidationIssue(
|
||||
theMessage, severity, theIssueCode, CodeValidationIssueCoding.INVALID_CODE));
|
||||
}
|
||||
|
||||
|
@ -680,13 +681,13 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
|
|||
createCodeValidationIssues(
|
||||
(IBaseOperationOutcome) issuesValue.get(),
|
||||
fhirContext.getVersion().getVersion())
|
||||
.ifPresent(i -> i.forEach(result::addCodeValidationIssue));
|
||||
.ifPresent(i -> i.forEach(result::addIssue));
|
||||
} else {
|
||||
// create a validation issue out of the message
|
||||
// this is a workaround to overcome an issue in the FHIR Validator library
|
||||
// where ValueSet bindings are only reading issues but not messages
|
||||
// @see https://github.com/hapifhir/org.hl7.fhir.core/issues/1766
|
||||
result.addCodeValidationIssue(createCodeValidationIssue(result.getMessage()));
|
||||
result.addIssue(createCodeValidationIssue(result.getMessage()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -717,23 +718,42 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
|
|||
|
||||
private static Collection<CodeValidationIssue> createCodeValidationIssuesR4(OperationOutcome theOperationOutcome) {
|
||||
return theOperationOutcome.getIssue().stream()
|
||||
.map(issueComponent ->
|
||||
createCodeValidationIssue(issueComponent.getDetails().getText()))
|
||||
.map(issueComponent -> {
|
||||
String diagnostics = issueComponent.getDiagnostics();
|
||||
IssueSeverity issueSeverity =
|
||||
IssueSeverity.fromCode(issueComponent.getSeverity().toCode());
|
||||
String issueTypeCode = issueComponent.getCode().toCode();
|
||||
CodeableConcept details = issueComponent.getDetails();
|
||||
CodeValidationIssue issue = new CodeValidationIssue(diagnostics, issueSeverity, issueTypeCode);
|
||||
CodeValidationIssueDetails issueDetails = new CodeValidationIssueDetails(details.getText());
|
||||
details.getCoding().forEach(coding -> issueDetails.addCoding(coding.getSystem(), coding.getCode()));
|
||||
issue.setDetails(issueDetails);
|
||||
return issue;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static Collection<CodeValidationIssue> createCodeValidationIssuesDstu3(
|
||||
org.hl7.fhir.dstu3.model.OperationOutcome theOperationOutcome) {
|
||||
return theOperationOutcome.getIssue().stream()
|
||||
.map(issueComponent ->
|
||||
createCodeValidationIssue(issueComponent.getDetails().getText()))
|
||||
.map(issueComponent -> {
|
||||
String diagnostics = issueComponent.getDiagnostics();
|
||||
IssueSeverity issueSeverity =
|
||||
IssueSeverity.fromCode(issueComponent.getSeverity().toCode());
|
||||
String issueTypeCode = issueComponent.getCode().toCode();
|
||||
org.hl7.fhir.dstu3.model.CodeableConcept details = issueComponent.getDetails();
|
||||
CodeValidationIssue issue = new CodeValidationIssue(diagnostics, issueSeverity, issueTypeCode);
|
||||
CodeValidationIssueDetails issueDetails = new CodeValidationIssueDetails(details.getText());
|
||||
details.getCoding().forEach(coding -> issueDetails.addCoding(coding.getSystem(), coding.getCode()));
|
||||
issue.setDetails(issueDetails);
|
||||
return issue;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static CodeValidationIssue createCodeValidationIssue(String theMessage) {
|
||||
return new CodeValidationIssue(
|
||||
theMessage,
|
||||
// assume issue type is OperationOutcome.IssueType#CODEINVALID as it is the only match
|
||||
IssueSeverity.ERROR,
|
||||
CodeValidationIssueCode.INVALID,
|
||||
CodeValidationIssueCoding.INVALID_CODE);
|
||||
|
|
|
@ -87,7 +87,7 @@ public class UnknownCodeSystemWarningValidationSupport extends BaseValidationSup
|
|||
result.setSeverity(null);
|
||||
result.setMessage(null);
|
||||
} else {
|
||||
result.addCodeValidationIssue(new CodeValidationIssue(
|
||||
result.addIssue(new CodeValidationIssue(
|
||||
theMessage,
|
||||
myNonExistentCodeSystemSeverity,
|
||||
CodeValidationIssueCode.NOT_FOUND,
|
||||
|
|
|
@ -11,6 +11,17 @@ public final class ValidationSupportUtils {
|
|||
|
||||
private ValidationSupportUtils() {}
|
||||
|
||||
/**
|
||||
* This method extracts a code system that can be (potentially) associated with a code when
|
||||
* performing validation against a ValueSet. This method was created for internal purposes.
|
||||
* Please use this method with care because it will only cover some
|
||||
* use-cases (e.g. standard bindings) while for others it may not return correct results or return null.
|
||||
* An incorrect result could be considered if the resource declares a code with a system, and you're calling
|
||||
* this method to check a binding against a ValueSet that has nothing to do with that system.
|
||||
* @param theValueSet the valueSet
|
||||
* @param theCode the code
|
||||
* @return the system which can be associated with the code
|
||||
*/
|
||||
public static String extractCodeSystemForCode(IBaseResource theValueSet, String theCode) {
|
||||
if (theValueSet instanceof org.hl7.fhir.dstu3.model.ValueSet) {
|
||||
return extractCodeSystemForCodeDSTU3((org.hl7.fhir.dstu3.model.ValueSet) theValueSet, theCode);
|
||||
|
|
|
@ -62,6 +62,7 @@ import java.util.Map;
|
|||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static ca.uhn.fhir.context.support.IValidationSupport.CodeValidationIssueCoding.INVALID_DISPLAY;
|
||||
import static java.util.stream.Collectors.collectingAndThen;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
@ -296,7 +297,7 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
|
|||
theResult.getCodeSystemVersion(),
|
||||
conceptDefinitionComponent,
|
||||
display,
|
||||
getIssuesForCodeValidation(theResult.getCodeValidationIssues()));
|
||||
getIssuesForCodeValidation(theResult.getIssues()));
|
||||
}
|
||||
|
||||
if (retVal == null) {
|
||||
|
@ -307,73 +308,36 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
|
|||
}
|
||||
|
||||
private List<OperationOutcome.OperationOutcomeIssueComponent> getIssuesForCodeValidation(
|
||||
List<IValidationSupport.CodeValidationIssue> codeValidationIssues) {
|
||||
List<OperationOutcome.OperationOutcomeIssueComponent> issues = new ArrayList<>();
|
||||
List<IValidationSupport.CodeValidationIssue> theIssues) {
|
||||
List<OperationOutcome.OperationOutcomeIssueComponent> issueComponents = new ArrayList<>();
|
||||
|
||||
for (IValidationSupport.CodeValidationIssue codeValidationIssue : codeValidationIssues) {
|
||||
for (IValidationSupport.CodeValidationIssue issue : theIssues) {
|
||||
OperationOutcome.IssueSeverity severity =
|
||||
OperationOutcome.IssueSeverity.fromCode(issue.getSeverity().getCode());
|
||||
OperationOutcome.IssueType issueType =
|
||||
OperationOutcome.IssueType.fromCode(issue.getType().getCode());
|
||||
String diagnostics = issue.getDiagnostics();
|
||||
|
||||
CodeableConcept codeableConcept = new CodeableConcept().setText(codeValidationIssue.getMessage());
|
||||
codeableConcept.addCoding(
|
||||
"http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
getIssueCodingFromCodeValidationIssue(codeValidationIssue),
|
||||
null);
|
||||
IValidationSupport.CodeValidationIssueDetails details = issue.getDetails();
|
||||
CodeableConcept codeableConcept = new CodeableConcept().setText(details.getText());
|
||||
details.getCodings().forEach(detailCoding -> codeableConcept
|
||||
.addCoding()
|
||||
.setSystem(detailCoding.getSystem())
|
||||
.setCode(detailCoding.getCode()));
|
||||
|
||||
OperationOutcome.OperationOutcomeIssueComponent issue =
|
||||
OperationOutcome.OperationOutcomeIssueComponent issueComponent =
|
||||
new OperationOutcome.OperationOutcomeIssueComponent()
|
||||
.setSeverity(getIssueSeverityFromCodeValidationIssue(codeValidationIssue))
|
||||
.setCode(getIssueTypeFromCodeValidationIssue(codeValidationIssue))
|
||||
.setDetails(codeableConcept);
|
||||
issue.getDetails().setText(codeValidationIssue.getMessage());
|
||||
issue.addExtension()
|
||||
.setSeverity(severity)
|
||||
.setCode(issueType)
|
||||
.setDetails(codeableConcept)
|
||||
.setDiagnostics(diagnostics);
|
||||
issueComponent
|
||||
.addExtension()
|
||||
.setUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id")
|
||||
.setValue(new StringType("Terminology_PassThrough_TX_Message"));
|
||||
issues.add(issue);
|
||||
issueComponents.add(issueComponent);
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
|
||||
private String getIssueCodingFromCodeValidationIssue(IValidationSupport.CodeValidationIssue codeValidationIssue) {
|
||||
switch (codeValidationIssue.getCoding()) {
|
||||
case VS_INVALID:
|
||||
return "vs-invalid";
|
||||
case NOT_FOUND:
|
||||
return "not-found";
|
||||
case NOT_IN_VS:
|
||||
return "not-in-vs";
|
||||
case INVALID_CODE:
|
||||
return "invalid-code";
|
||||
case INVALID_DISPLAY:
|
||||
return "invalid-display";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private OperationOutcome.IssueType getIssueTypeFromCodeValidationIssue(
|
||||
IValidationSupport.CodeValidationIssue codeValidationIssue) {
|
||||
switch (codeValidationIssue.getCode()) {
|
||||
case NOT_FOUND:
|
||||
return OperationOutcome.IssueType.NOTFOUND;
|
||||
case CODE_INVALID:
|
||||
return OperationOutcome.IssueType.CODEINVALID;
|
||||
case INVALID:
|
||||
return OperationOutcome.IssueType.INVALID;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private OperationOutcome.IssueSeverity getIssueSeverityFromCodeValidationIssue(
|
||||
IValidationSupport.CodeValidationIssue codeValidationIssue) {
|
||||
switch (codeValidationIssue.getSeverity()) {
|
||||
case FATAL:
|
||||
return OperationOutcome.IssueSeverity.FATAL;
|
||||
case ERROR:
|
||||
return OperationOutcome.IssueSeverity.ERROR;
|
||||
case WARNING:
|
||||
return OperationOutcome.IssueSeverity.WARNING;
|
||||
case INFORMATION:
|
||||
return OperationOutcome.IssueSeverity.INFORMATION;
|
||||
}
|
||||
return null;
|
||||
return issueComponents;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -851,25 +815,22 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
|
|||
.getRootValidationSupport()
|
||||
.validateCodeInValueSet(
|
||||
myValidationSupportContext, theValidationOptions, theSystem, theCode, theDisplay, theValueSet);
|
||||
if (result != null) {
|
||||
if (result != null && theSystem != null) {
|
||||
/* We got a value set result, which could be successful, or could contain errors/warnings. The code
|
||||
might also be invalid in the code system, so we will check that as well and add those issues
|
||||
to our result.
|
||||
*/
|
||||
IValidationSupport.CodeValidationResult codeSystemResult =
|
||||
validateCodeInCodeSystem(theValidationOptions, theSystem, theCode, theDisplay);
|
||||
final boolean valueSetResultContainsInvalidDisplay = result.getCodeValidationIssues().stream()
|
||||
.anyMatch(codeValidationIssue -> codeValidationIssue.getCoding()
|
||||
== IValidationSupport.CodeValidationIssueCoding.INVALID_DISPLAY);
|
||||
final boolean valueSetResultContainsInvalidDisplay = result.getIssues().stream()
|
||||
.anyMatch(VersionSpecificWorkerContextWrapper::hasInvalidDisplayDetailCode);
|
||||
if (codeSystemResult != null) {
|
||||
for (IValidationSupport.CodeValidationIssue codeValidationIssue :
|
||||
codeSystemResult.getCodeValidationIssues()) {
|
||||
for (IValidationSupport.CodeValidationIssue codeValidationIssue : codeSystemResult.getIssues()) {
|
||||
/* Value set validation should already have checked the display name. If we get INVALID_DISPLAY
|
||||
issues from code system validation, they will only repeat what was already caught.
|
||||
*/
|
||||
if (codeValidationIssue.getCoding() != IValidationSupport.CodeValidationIssueCoding.INVALID_DISPLAY
|
||||
|| !valueSetResultContainsInvalidDisplay) {
|
||||
result.addCodeValidationIssue(codeValidationIssue);
|
||||
if (!hasInvalidDisplayDetailCode(codeValidationIssue) || !valueSetResultContainsInvalidDisplay) {
|
||||
result.addIssue(codeValidationIssue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -877,6 +838,10 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
|
|||
return result;
|
||||
}
|
||||
|
||||
private static boolean hasInvalidDisplayDetailCode(IValidationSupport.CodeValidationIssue theIssue) {
|
||||
return theIssue.hasIssueDetailCode(INVALID_DISPLAY.getCode());
|
||||
}
|
||||
|
||||
private IValidationSupport.CodeValidationResult validateCodeInCodeSystem(
|
||||
ConceptValidationOptions theValidationOptions, String theSystem, String theCode, String theDisplay) {
|
||||
return myValidationSupportContext
|
||||
|
|
|
@ -9,6 +9,7 @@ import ca.uhn.fhir.context.support.IValidationSupport.LookupCodeResult;
|
|||
import ca.uhn.fhir.context.support.IValidationSupport.StringConceptProperty;
|
||||
import ca.uhn.fhir.context.support.LookupCodeRequest;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.test.utilities.validation.IValidationProviders;
|
||||
import org.hl7.fhir.instance.model.api.IBaseDatatype;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -21,12 +22,12 @@ import static ca.uhn.fhir.context.support.IValidationSupport.TYPE_GROUP;
|
|||
import static ca.uhn.fhir.context.support.IValidationSupport.TYPE_STRING;
|
||||
import static java.util.stream.IntStream.range;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE;
|
||||
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM;
|
||||
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM_NAME;
|
||||
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM_VERSION;
|
||||
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.DISPLAY;
|
||||
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.LANGUAGE;
|
||||
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE;
|
||||
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM;
|
||||
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_NAME;
|
||||
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_VERSION;
|
||||
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.DISPLAY;
|
||||
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.LANGUAGE;
|
||||
import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.createConceptProperty;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
@ -189,8 +190,6 @@ public interface ILookupCodeTest {
|
|||
|
||||
// verify
|
||||
assertNotNull(outcome);
|
||||
assertEquals(theRequest.getCode(), getLookupCodeProvider().getCode());
|
||||
assertEquals(theRequest.getSystem(), getLookupCodeProvider().getSystem());
|
||||
assertEquals(theExpectedResult.isFound(), outcome.isFound());
|
||||
assertEquals(theExpectedResult.getErrorMessage(), outcome.getErrorMessage());
|
||||
assertEquals(theExpectedResult.getCodeSystemDisplayName(), outcome.getCodeSystemDisplayName());
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.hl7.fhir.common.hapi.validation;
|
|||
|
||||
import ca.uhn.fhir.context.support.IValidationSupport.LookupCodeResult;
|
||||
import ca.uhn.fhir.context.support.LookupCodeRequest;
|
||||
import ca.uhn.fhir.test.utilities.validation.IValidationProviders;
|
||||
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
|
|
@ -1,17 +1,76 @@
|
|||
package org.hl7.fhir.common.hapi.validation;
|
||||
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.createCodeValidationIssues;
|
||||
|
||||
public interface IRemoteTerminologyValidateCodeTest extends IValidateCodeTest {
|
||||
default List<IValidationSupport.CodeValidationIssue> getCodeValidationIssues(IBaseOperationOutcome theOperationOutcome) {
|
||||
// this method should be removed once support for issues is fully implemented across all validator types
|
||||
Optional<Collection<IValidationSupport.CodeValidationIssue>> issues = RemoteTerminologyServiceValidationSupport.createCodeValidationIssues(theOperationOutcome, getService().getFhirContext().getVersion().getVersion());
|
||||
return issues.map(theCodeValidationIssues -> theCodeValidationIssues.stream().toList()).orElseGet(List::of);
|
||||
}
|
||||
|
||||
@Test
|
||||
default void createCodeValidationIssues_withCodeSystemOutcomeForInvalidCode_returnsAsExpected() {
|
||||
IBaseOperationOutcome outcome = getCodeSystemInvalidCodeOutcome();
|
||||
FhirVersionEnum versionEnum = getService().getFhirContext().getVersion().getVersion();
|
||||
Optional<Collection<IValidationSupport.CodeValidationIssue>> issuesOptional = createCodeValidationIssues(outcome, versionEnum);
|
||||
assertThat(issuesOptional).isPresent();
|
||||
assertThat(issuesOptional.get()).hasSize(1);
|
||||
IValidationSupport.CodeValidationIssue issue = issuesOptional.get().iterator().next();
|
||||
assertThat(issue.getType().getCode()).isEqualTo("code-invalid");
|
||||
assertThat(issue.getSeverity().getCode()).isEqualTo("error");
|
||||
assertThat(issue.getDetails().getCodings()).hasSize(1);
|
||||
IValidationSupport.CodeValidationIssueCoding issueCoding = issue.getDetails().getCodings().get(0);
|
||||
assertThat(issueCoding.getSystem()).isEqualTo("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type");
|
||||
assertThat(issueCoding.getCode()).isEqualTo("invalid-code");
|
||||
assertThat(issue.getDetails().getText()).isEqualTo("Unknown code 'CODE' in the CodeSystem 'http://code.system/url' version '1.0.0'");
|
||||
assertThat(issue.getDiagnostics()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
default void createCodeValidationIssues_withValueSetOutcomeForInvalidCode_returnsAsExpected() {
|
||||
IBaseOperationOutcome outcome = getValueSetInvalidCodeOutcome();
|
||||
FhirVersionEnum versionEnum = getService().getFhirContext().getVersion().getVersion();
|
||||
Optional<Collection<IValidationSupport.CodeValidationIssue>> issuesOptional = createCodeValidationIssues(outcome, versionEnum);
|
||||
assertThat(issuesOptional).isPresent();
|
||||
assertThat(issuesOptional.get()).hasSize(2);
|
||||
IValidationSupport.CodeValidationIssue issue = issuesOptional.get().iterator().next();
|
||||
assertThat(issue.getType().getCode()).isEqualTo("code-invalid");
|
||||
assertThat(issue.getSeverity().getCode()).isEqualTo("error");
|
||||
assertThat(issue.getDetails().getCodings()).hasSize(1);
|
||||
IValidationSupport.CodeValidationIssueCoding issueCoding = issue.getDetails().getCodings().get(0);
|
||||
assertThat(issueCoding.getSystem()).isEqualTo("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type");
|
||||
assertThat(issueCoding.getCode()).isEqualTo("not-in-vs");
|
||||
assertThat(issue.getDetails().getText()).isEqualTo("The provided code 'http://code.system/url#CODE' was not found in the value set 'http://value.set/url%7C1.0.0'");
|
||||
assertThat(issue.getDiagnostics()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
default void createCodeValidationIssues_withValueSetOutcomeWithCustomDetailCode_returnsAsExpected() {
|
||||
IBaseOperationOutcome outcome = getValueSetCustomDetailCodeOutcome();
|
||||
FhirVersionEnum versionEnum = getService().getFhirContext().getVersion().getVersion();
|
||||
Optional<Collection<IValidationSupport.CodeValidationIssue>> issuesOptional = createCodeValidationIssues(outcome, versionEnum);
|
||||
assertThat(issuesOptional).isPresent();
|
||||
assertThat(issuesOptional.get()).hasSize(1);
|
||||
IValidationSupport.CodeValidationIssue issue = issuesOptional.get().iterator().next();
|
||||
assertThat(issue.getType().getCode()).isEqualTo("processing");
|
||||
assertThat(issue.getSeverity().getCode()).isEqualTo("information");
|
||||
assertThat(issue.getDetails().getCodings()).hasSize(1);
|
||||
IValidationSupport.CodeValidationIssueCoding issueCoding = issue.getDetails().getCodings().get(0);
|
||||
assertThat(issueCoding.getSystem()).isEqualTo("http://example.com/custom-issue-type");
|
||||
assertThat(issueCoding.getCode()).isEqualTo("valueset-is-draft");
|
||||
assertThat(issue.getDetails().getText()).isNull();
|
||||
assertThat(issue.getDiagnostics()).isEqualTo("The ValueSet status is marked as draft.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ import ca.uhn.fhir.context.support.IValidationSupport;
|
|||
import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.test.utilities.validation.IValidationProviders;
|
||||
import ca.uhn.fhir.util.ClasspathUtil;
|
||||
import org.hl7.fhir.dstu3.model.OperationOutcome;
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
@ -16,12 +19,13 @@ import java.util.List;
|
|||
import java.util.stream.Stream;
|
||||
|
||||
import static ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity.ERROR;
|
||||
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE;
|
||||
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM;
|
||||
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM_VERSION;
|
||||
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.DISPLAY;
|
||||
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.ERROR_MESSAGE;
|
||||
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.VALUE_SET_URL;
|
||||
import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_VALIDATE_CODE;
|
||||
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE;
|
||||
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM;
|
||||
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_VERSION;
|
||||
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.DISPLAY;
|
||||
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.ERROR_MESSAGE;
|
||||
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.VALUE_SET_URL;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
@ -31,21 +35,35 @@ import static org.junit.jupiter.api.Assertions.fail;
|
|||
|
||||
public interface IValidateCodeTest {
|
||||
|
||||
IValidationProviders.IMyCodeSystemProvider getCodeSystemProvider();
|
||||
IValidationProviders.IMyValueSetProvider getValueSetProvider();
|
||||
IValidationProviders.IMyValidationProvider getCodeSystemProvider();
|
||||
IValidationProviders.IMyValidationProvider getValueSetProvider();
|
||||
IValidationSupport getService();
|
||||
IBaseParameters createParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource);
|
||||
String getCodeSystemError();
|
||||
String getValueSetError();
|
||||
IBaseOperationOutcome getCodeSystemInvalidCodeOutcome();
|
||||
IBaseOperationOutcome getValueSetInvalidCodeOutcome();
|
||||
IBaseOperationOutcome getValueSetCustomDetailCodeOutcome();
|
||||
|
||||
default IBaseOperationOutcome getCodeSystemInvalidCodeOutcome(Class<? extends IBaseOperationOutcome> theResourceClass) {
|
||||
return getOutcome(theResourceClass, "/terminology/OperationOutcome-CodeSystem-invalid-code.json");
|
||||
}
|
||||
default IBaseOperationOutcome getValueSetInvalidCodeOutcome(Class<? extends IBaseOperationOutcome> theResourceClass) {
|
||||
return getOutcome(theResourceClass, "/terminology/OperationOutcome-ValueSet-invalid-code.json");
|
||||
}
|
||||
default IBaseOperationOutcome getValueSetCustomDetailCodeOutcome(Class<? extends IBaseOperationOutcome> theResourceClass) {
|
||||
return getOutcome(theResourceClass, "/terminology/OperationOutcome-ValueSet-custom-issue-detail.json");
|
||||
}
|
||||
default IBaseOperationOutcome getOutcome(Class<? extends IBaseOperationOutcome> theResourceClass, String theFile) {
|
||||
return ClasspathUtil.loadResource(getService().getFhirContext(), theResourceClass, theFile);
|
||||
}
|
||||
|
||||
default void createCodeSystemReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) {
|
||||
getCodeSystemProvider().setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource));
|
||||
getCodeSystemProvider().addTerminologyResponse(OPERATION_VALIDATE_CODE, CODE_SYSTEM, CODE, createParameters(theResult, theDisplay, theMessage, theIssuesResource));
|
||||
}
|
||||
|
||||
default void createValueSetReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) {
|
||||
getValueSetProvider().setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource));
|
||||
getValueSetProvider().addTerminologyResponse(OPERATION_VALIDATE_CODE, VALUE_SET_URL, CODE, createParameters(theResult, theDisplay, theMessage, theIssuesResource));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -91,8 +109,8 @@ public interface IValidateCodeTest {
|
|||
String theValidationMessage,
|
||||
String theCodeSystem,
|
||||
String theValueSetUrl) {
|
||||
getCodeSystemProvider().setException(theException);
|
||||
getValueSetProvider().setException(theException);
|
||||
getCodeSystemProvider().addException(OPERATION_VALIDATE_CODE, theCodeSystem, CODE, theException);
|
||||
getValueSetProvider().addException(OPERATION_VALIDATE_CODE, theValueSetUrl, CODE, theException);
|
||||
CodeValidationResult outcome = getService().validateCode(null, null, theCodeSystem, CODE, DISPLAY, theValueSetUrl);
|
||||
|
||||
verifyErrorResultFromException(outcome, theValidationMessage, theServerMessage);
|
||||
|
@ -105,7 +123,7 @@ public interface IValidateCodeTest {
|
|||
for (String message : theMessages) {
|
||||
assertTrue(outcome.getMessage().contains(message));
|
||||
}
|
||||
assertFalse(outcome.getCodeValidationIssues().isEmpty());
|
||||
assertFalse(outcome.getIssues().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -130,11 +148,7 @@ public interface IValidateCodeTest {
|
|||
assertEquals(DISPLAY, outcome.getDisplay());
|
||||
assertNull(outcome.getSeverity());
|
||||
assertNull(outcome.getMessage());
|
||||
assertTrue(outcome.getCodeValidationIssues().isEmpty());
|
||||
|
||||
assertEquals(CODE, getValueSetProvider().getCode());
|
||||
assertEquals(DISPLAY, getValueSetProvider().getDisplay());
|
||||
assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet());
|
||||
assertTrue(outcome.getIssues().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -147,9 +161,7 @@ public interface IValidateCodeTest {
|
|||
assertEquals(DISPLAY, outcome.getDisplay());
|
||||
assertNull(outcome.getSeverity());
|
||||
assertNull(outcome.getMessage());
|
||||
assertTrue(outcome.getCodeValidationIssues().isEmpty());
|
||||
|
||||
assertEquals(CODE, getCodeSystemProvider().getCode());
|
||||
assertTrue(outcome.getIssues().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -165,10 +177,7 @@ public interface IValidateCodeTest {
|
|||
assertNull(outcome.getDisplay());
|
||||
assertNull(outcome.getSeverity());
|
||||
assertNull(outcome.getMessage());
|
||||
assertTrue(outcome.getCodeValidationIssues().isEmpty());
|
||||
|
||||
assertEquals(CODE, getCodeSystemProvider().getCode());
|
||||
assertEquals(CODE_SYSTEM, getCodeSystemProvider().getSystem());
|
||||
assertTrue(outcome.getIssues().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -184,15 +193,11 @@ public interface IValidateCodeTest {
|
|||
assertEquals(DISPLAY, outcome.getDisplay());
|
||||
assertNull(outcome.getSeverity());
|
||||
assertNull(outcome.getMessage());
|
||||
assertTrue(outcome.getCodeValidationIssues().isEmpty());
|
||||
|
||||
assertEquals(CODE, getCodeSystemProvider().getCode());
|
||||
assertEquals(DISPLAY, getCodeSystemProvider().getDisplay());
|
||||
assertEquals(CODE_SYSTEM, getCodeSystemProvider().getSystem());
|
||||
assertTrue(outcome.getIssues().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
default void validateCode_withCodeSystemError_returnsCorrectly() {
|
||||
default void validateCode_withCodeSystemErrorWithDiagnosticsWithIssues_returnsCorrectly() {
|
||||
IBaseOperationOutcome invalidCodeOutcome = getCodeSystemInvalidCodeOutcome();
|
||||
createCodeSystemReturnParameters(false, null, ERROR_MESSAGE, invalidCodeOutcome);
|
||||
|
||||
|
@ -204,12 +209,12 @@ public interface IValidateCodeTest {
|
|||
// assertEquals(CODE, outcome.getCode());
|
||||
assertEquals(ERROR, outcome.getSeverity());
|
||||
assertEquals(getCodeSystemError(), outcome.getMessage());
|
||||
assertFalse(outcome.getCodeValidationIssues().isEmpty());
|
||||
assertFalse(outcome.getIssues().isEmpty());
|
||||
verifyIssues(invalidCodeOutcome, outcome);
|
||||
}
|
||||
|
||||
@Test
|
||||
default void validateCode_withCodeSystemErrorAndIssues_returnsCorrectly() {
|
||||
default void validateCode_withCodeSystemErrorWithDiagnosticsWithoutIssues_returnsCorrectly() {
|
||||
createCodeSystemReturnParameters(false, null, ERROR_MESSAGE, null);
|
||||
|
||||
CodeValidationResult outcome = getService()
|
||||
|
@ -223,10 +228,32 @@ public interface IValidateCodeTest {
|
|||
assertNull(outcome.getDisplay());
|
||||
assertEquals(ERROR, outcome.getSeverity());
|
||||
assertEquals(expectedError, outcome.getMessage());
|
||||
assertFalse(outcome.getCodeValidationIssues().isEmpty());
|
||||
assertEquals(1, outcome.getCodeValidationIssues().size());
|
||||
assertEquals(expectedError, outcome.getCodeValidationIssues().get(0).getMessage());
|
||||
assertEquals(ERROR, outcome.getCodeValidationIssues().get(0).getSeverity());
|
||||
assertFalse(outcome.getIssues().isEmpty());
|
||||
assertEquals(1, outcome.getIssues().size());
|
||||
assertEquals(expectedError, outcome.getIssues().get(0).getDiagnostics());
|
||||
assertEquals(ERROR, outcome.getIssues().get(0).getSeverity());
|
||||
}
|
||||
|
||||
@Test
|
||||
default void validateCode_withCodeSystemErrorWithoutDiagnosticsWithIssues_returnsCorrectly() {
|
||||
IBaseOperationOutcome invalidCodeOutcome = getCodeSystemInvalidCodeOutcome();
|
||||
createCodeSystemReturnParameters(false, null, null, invalidCodeOutcome);
|
||||
|
||||
CodeValidationResult outcome = getService()
|
||||
.validateCode(null, null, CODE_SYSTEM, CODE, null, null);
|
||||
|
||||
String expectedError = getCodeSystemError();
|
||||
assertNotNull(outcome);
|
||||
assertEquals(CODE_SYSTEM, outcome.getCodeSystemName());
|
||||
assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion());
|
||||
// assertEquals(CODE, outcome.getCode());
|
||||
assertNull(outcome.getDisplay());
|
||||
assertEquals(ERROR, outcome.getSeverity());
|
||||
assertNull(outcome.getMessage());
|
||||
assertFalse(outcome.getIssues().isEmpty());
|
||||
assertEquals(1, outcome.getIssues().size());
|
||||
assertNull(outcome.getIssues().get(0).getDiagnostics());
|
||||
assertEquals(ERROR, outcome.getIssues().get(0).getSeverity());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -242,10 +269,7 @@ public interface IValidateCodeTest {
|
|||
assertNull(outcome.getDisplay());
|
||||
assertNull(outcome.getSeverity());
|
||||
assertNull(outcome.getMessage());
|
||||
assertTrue(outcome.getCodeValidationIssues().isEmpty());
|
||||
|
||||
assertEquals(CODE, getValueSetProvider().getCode());
|
||||
assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet());
|
||||
assertTrue(outcome.getIssues().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -261,11 +285,7 @@ public interface IValidateCodeTest {
|
|||
assertEquals(DISPLAY, outcome.getDisplay());
|
||||
assertNull(outcome.getSeverity());
|
||||
assertNull(outcome.getMessage());
|
||||
assertTrue(outcome.getCodeValidationIssues().isEmpty());
|
||||
|
||||
assertEquals(CODE, getValueSetProvider().getCode());
|
||||
assertEquals(DISPLAY, getValueSetProvider().getDisplay());
|
||||
assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet());
|
||||
assertTrue(outcome.getIssues().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -283,13 +303,9 @@ public interface IValidateCodeTest {
|
|||
assertEquals(DISPLAY, outcome.getDisplay());
|
||||
assertEquals(ERROR, outcome.getSeverity());
|
||||
assertEquals(expectedError, outcome.getMessage());
|
||||
assertEquals(1, outcome.getCodeValidationIssues().size());
|
||||
assertEquals(expectedError, outcome.getCodeValidationIssues().get(0).getMessage());
|
||||
assertEquals(ERROR, outcome.getCodeValidationIssues().get(0).getSeverity());
|
||||
|
||||
assertEquals(CODE, getValueSetProvider().getCode());
|
||||
assertEquals(DISPLAY, getValueSetProvider().getDisplay());
|
||||
assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet());
|
||||
assertEquals(1, outcome.getIssues().size());
|
||||
assertEquals(expectedError, outcome.getIssues().get(0).getDiagnostics());
|
||||
assertEquals(ERROR, outcome.getIssues().get(0).getSeverity());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -306,24 +322,28 @@ public interface IValidateCodeTest {
|
|||
assertEquals(DISPLAY, outcome.getDisplay());
|
||||
assertEquals(ERROR, outcome.getSeverity());
|
||||
assertEquals(getValueSetError(), outcome.getMessage());
|
||||
assertFalse(outcome.getCodeValidationIssues().isEmpty());
|
||||
assertFalse(outcome.getIssues().isEmpty());
|
||||
verifyIssues(invalidCodeOutcome, outcome);
|
||||
|
||||
assertEquals(CODE, getValueSetProvider().getCode());
|
||||
assertEquals(DISPLAY, getValueSetProvider().getDisplay());
|
||||
assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet());
|
||||
}
|
||||
|
||||
default void verifyIssues(IBaseOperationOutcome theOperationOutcome, CodeValidationResult theResult) {
|
||||
List<IValidationSupport.CodeValidationIssue> issues = getCodeValidationIssues(theOperationOutcome);
|
||||
assertEquals(issues.size(), theResult.getCodeValidationIssues().size());
|
||||
assertEquals(issues.size(), theResult.getIssues().size());
|
||||
for (int i = 0; i < issues.size(); i++) {
|
||||
IValidationSupport.CodeValidationIssue expectedIssue = issues.get(i);
|
||||
IValidationSupport.CodeValidationIssue actualIssue = theResult.getCodeValidationIssues().get(i);
|
||||
assertEquals(expectedIssue.getCode(), actualIssue.getCode());
|
||||
IValidationSupport.CodeValidationIssue actualIssue = theResult.getIssues().get(i);
|
||||
assertEquals(expectedIssue.getType().getCode(), actualIssue.getType().getCode());
|
||||
assertEquals(expectedIssue.getSeverity(), actualIssue.getSeverity());
|
||||
assertEquals(expectedIssue.getCoding(), actualIssue.getCoding());
|
||||
assertEquals(expectedIssue.getMessage(), actualIssue.getMessage());
|
||||
assertEquals(expectedIssue.getDetails().getText(), actualIssue.getDetails().getText());
|
||||
assertEquals(expectedIssue.getDetails().getCodings().size(), actualIssue.getDetails().getCodings().size());
|
||||
for (int index = 0; index < expectedIssue.getDetails().getCodings().size(); index++) {
|
||||
IValidationSupport.CodeValidationIssueCoding expectedCoding = expectedIssue.getDetails().getCodings().get(index);
|
||||
IValidationSupport.CodeValidationIssueCoding actualCoding = actualIssue.getDetails().getCodings().get(index);
|
||||
assertEquals(expectedCoding.getSystem(), actualCoding.getSystem());
|
||||
assertEquals(expectedCoding.getCode(), actualCoding.getCode());
|
||||
}
|
||||
assertEquals(expectedIssue.getDetails().getText(), actualIssue.getDetails().getText());
|
||||
assertEquals(expectedIssue.getDiagnostics(), actualIssue.getDiagnostics());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
package org.hl7.fhir.common.hapi.validation;
|
||||
|
||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||
|
||||
public interface IValidationProviders {
|
||||
String CODE_SYSTEM = "http://code.system/url";
|
||||
String CODE_SYSTEM_VERSION = "1.0.0";
|
||||
String CODE_SYSTEM_NAME = "Test Code System";
|
||||
String CODE = "CODE";
|
||||
String VALUE_SET_URL = "http://value.set/url";
|
||||
String DISPLAY = "Explanation for code TestCode.";
|
||||
String LANGUAGE = "en";
|
||||
String ERROR_MESSAGE = "This is an error message";
|
||||
|
||||
interface IMyCodeSystemProvider extends IResourceProvider {
|
||||
String getCode();
|
||||
String getSystem();
|
||||
String getDisplay();
|
||||
void setException(Exception theException);
|
||||
void setReturnParams(IBaseParameters theParameters);
|
||||
}
|
||||
|
||||
interface IMyLookupCodeProvider extends IResourceProvider {
|
||||
String getCode();
|
||||
String getSystem();
|
||||
void setLookupCodeResult(IValidationSupport.LookupCodeResult theLookupCodeResult);
|
||||
}
|
||||
|
||||
interface IMyValueSetProvider extends IResourceProvider {
|
||||
String getCode();
|
||||
String getSystem();
|
||||
String getDisplay();
|
||||
String getValueSet();
|
||||
void setException(Exception theException);
|
||||
void setReturnParams(IBaseParameters theParameters);
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@ import ca.uhn.fhir.context.support.ValidationSupportContext;
|
|||
import ca.uhn.fhir.fhirpath.BaseValidationTestWithInlineMocks;
|
||||
import ca.uhn.fhir.i18n.HapiLocalizer;
|
||||
import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
|
||||
|
||||
import org.hl7.fhir.r5.model.Resource;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
|
||||
|
@ -16,17 +15,18 @@ import org.hl7.fhir.utilities.validation.ValidationOptions;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.withSettings;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class VersionSpecificWorkerContextWrapperTest extends BaseValidationTestWithInlineMocks {
|
||||
|
||||
final byte[] EXPECTED_BINARY_CONTENT_1 = "dummyBinaryContent1".getBytes();
|
||||
|
@ -80,7 +80,7 @@ public class VersionSpecificWorkerContextWrapperTest extends BaseValidationTestW
|
|||
}
|
||||
|
||||
@Test
|
||||
public void validateCode_normally_resolvesCodeSystemFromValueSet() {
|
||||
public void validateCode_codeInValueSet_resolvesCodeSystemFromValueSet() {
|
||||
// setup
|
||||
IValidationSupport validationSupport = mockValidationSupport();
|
||||
ValidationSupportContext mockContext = mockValidationSupportContext(validationSupport);
|
||||
|
@ -90,8 +90,7 @@ public class VersionSpecificWorkerContextWrapperTest extends BaseValidationTestW
|
|||
ValueSet valueSet = new ValueSet();
|
||||
valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0");
|
||||
valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2");
|
||||
when(validationSupport.fetchResource(eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(valueSet);
|
||||
when(validationSupport.validateCodeInValueSet(any(), any(), any(), any(), any(), any())).thenReturn(new IValidationSupport.CodeValidationResult());
|
||||
when(validationSupport.validateCodeInValueSet(any(), any(), any(), any(), any(), any())).thenReturn(mock(IValidationSupport.CodeValidationResult.class));
|
||||
|
||||
// execute
|
||||
wrapper.validateCode(new ValidationOptions(), "code0", valueSet);
|
||||
|
@ -101,6 +100,26 @@ public class VersionSpecificWorkerContextWrapperTest extends BaseValidationTestW
|
|||
verify(validationSupport, times(1)).validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateCode_codeNotInValueSet_doesNotResolveSystem() {
|
||||
// setup
|
||||
IValidationSupport validationSupport = mockValidationSupport();
|
||||
ValidationSupportContext mockContext = mockValidationSupportContext(validationSupport);
|
||||
VersionCanonicalizer versionCanonicalizer = new VersionCanonicalizer(FhirContext.forR5Cached());
|
||||
VersionSpecificWorkerContextWrapper wrapper = new VersionSpecificWorkerContextWrapper(mockContext, versionCanonicalizer);
|
||||
|
||||
ValueSet valueSet = new ValueSet();
|
||||
valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0");
|
||||
valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2");
|
||||
|
||||
// execute
|
||||
wrapper.validateCode(new ValidationOptions(), "code1", valueSet);
|
||||
|
||||
// verify
|
||||
verify(validationSupport, times(1)).validateCodeInValueSet(any(), any(), eq(null), eq("code1"), any(), any());
|
||||
verify(validationSupport, never()).validateCode(any(), any(), any(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isPrimitive_primitive() {
|
||||
// setup
|
||||
|
|
|
@ -4,7 +4,6 @@ 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.fhirpath.BaseValidationTestWithInlineMocks;
|
||||
import ca.uhn.fhir.model.dstu2.composite.PeriodDt;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Parameters;
|
||||
|
@ -28,10 +27,7 @@ import org.hl7.fhir.dstu2.model.Observation.ObservationStatus;
|
|||
import org.hl7.fhir.dstu2.model.QuestionnaireResponse;
|
||||
import org.hl7.fhir.dstu2.model.QuestionnaireResponse.QuestionnaireResponseStatus;
|
||||
import org.hl7.fhir.dstu2.model.StringType;
|
||||
import org.hl7.fhir.dstu3.model.CodeSystem;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -41,9 +37,7 @@ import org.mockito.stubbing.Answer;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -100,7 +94,7 @@ public class FhirInstanceValidatorDstu2Test extends BaseValidationTestWithInline
|
|||
if (myValidConcepts.contains(system + "___" + code)) {
|
||||
retVal = new IValidationSupport.CodeValidationResult().setCode(code);
|
||||
} else if (myValidSystems.contains(system)) {
|
||||
return new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage("Unknown code");
|
||||
return new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage("Unknown code");
|
||||
} else {
|
||||
retVal = null;
|
||||
}
|
||||
|
|
|
@ -58,7 +58,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
|
|||
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
|
||||
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
|
||||
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
|
@ -229,10 +228,10 @@ public class FhirInstanceValidatorDstu3Test extends BaseValidationTestWithInline
|
|||
retVal = new IValidationSupport.CodeValidationResult().setCode(code);
|
||||
} else if (myValidSystems.contains(system)) {
|
||||
final String message = "Unknown code (for '" + system + "#" + code + "')";
|
||||
retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE)));
|
||||
retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message).setIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE)));
|
||||
} else if (myValidSystemsNotReturningIssues.contains(system)) {
|
||||
final String message = "Unknown code (for '" + system + "#" + code + "')";
|
||||
retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message);
|
||||
retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message);
|
||||
} else if (myCodeSystems.containsKey(system)) {
|
||||
CodeSystem cs = myCodeSystems.get(system);
|
||||
Optional<ConceptDefinitionComponent> found = cs.getConcept().stream().filter(t -> t.getCode().equals(code)).findFirst();
|
||||
|
|
|
@ -1,159 +0,0 @@
|
|||
package org.hl7.fhir.dstu3.hapi.validation;
|
||||
|
||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||
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.api.server.RequestDetails;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
|
||||
import org.hl7.fhir.dstu3.model.BooleanType;
|
||||
import org.hl7.fhir.dstu3.model.CodeSystem;
|
||||
import org.hl7.fhir.dstu3.model.CodeType;
|
||||
import org.hl7.fhir.dstu3.model.Coding;
|
||||
import org.hl7.fhir.dstu3.model.IdType;
|
||||
import org.hl7.fhir.dstu3.model.Parameters;
|
||||
import org.hl7.fhir.dstu3.model.StringType;
|
||||
import org.hl7.fhir.dstu3.model.UriType;
|
||||
import org.hl7.fhir.dstu3.model.ValueSet;
|
||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IValidateCodeProvidersDstu3 {
|
||||
@SuppressWarnings("unused")
|
||||
class MyCodeSystemProviderDstu3 implements IValidationProviders.IMyCodeSystemProvider {
|
||||
private UriType mySystemUrl;
|
||||
private CodeType myCode;
|
||||
private StringType myDisplay;
|
||||
private Exception myException;
|
||||
private Parameters myReturnParams;
|
||||
|
||||
@Operation(name = "validate-code", idempotent = true, returnParameters = {
|
||||
@OperationParam(name = "result", type = org.hl7.fhir.dstu3.model.BooleanType.class, min = 1),
|
||||
@OperationParam(name = "message", type = org.hl7.fhir.dstu3.model.StringType.class),
|
||||
@OperationParam(name = "display", type = org.hl7.fhir.dstu3.model.StringType.class)
|
||||
})
|
||||
public org.hl7.fhir.dstu3.model.Parameters validateCode(
|
||||
HttpServletRequest theServletRequest,
|
||||
@IdParam(optional = true) org.hl7.fhir.dstu3.model.IdType theId,
|
||||
@OperationParam(name = "url", min = 0, max = 1) org.hl7.fhir.dstu3.model.UriType theCodeSystemUrl,
|
||||
@OperationParam(name = "code", min = 0, max = 1) org.hl7.fhir.dstu3.model.CodeType theCode,
|
||||
@OperationParam(name = "display", min = 0, max = 1) org.hl7.fhir.dstu3.model.StringType theDisplay
|
||||
) throws Exception {
|
||||
mySystemUrl = theCodeSystemUrl;
|
||||
myCode = theCode;
|
||||
myDisplay = theDisplay;
|
||||
if (myException != null) {
|
||||
throw myException;
|
||||
}
|
||||
return myReturnParams;
|
||||
}
|
||||
|
||||
@Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= {
|
||||
@OperationParam(name = "name", type = org.hl7.fhir.dstu3.model.StringType.class, min = 1),
|
||||
@OperationParam(name = "version", type = org.hl7.fhir.dstu3.model.StringType.class),
|
||||
@OperationParam(name = "display", type = org.hl7.fhir.dstu3.model.StringType.class, min = 1),
|
||||
@OperationParam(name = "abstract", type = org.hl7.fhir.dstu3.model.BooleanType.class, min = 1),
|
||||
@OperationParam(name = "property", type = org.hl7.fhir.dstu3.model.StringType.class, min = 0, max = OperationParam.MAX_UNLIMITED)
|
||||
})
|
||||
public IBaseParameters lookup(
|
||||
HttpServletRequest theServletRequest,
|
||||
@OperationParam(name = "code", max = 1) org.hl7.fhir.dstu3.model.CodeType theCode,
|
||||
@OperationParam(name = "system",max = 1) org.hl7.fhir.dstu3.model.UriType theSystem,
|
||||
@OperationParam(name = "coding", max = 1) Coding theCoding,
|
||||
@OperationParam(name = "version", max = 1) org.hl7.fhir.dstu3.model.StringType theVersion,
|
||||
@OperationParam(name = "displayLanguage", max = 1) org.hl7.fhir.dstu3.model.CodeType theDisplayLanguage,
|
||||
@OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List<org.hl7.fhir.dstu3.model.CodeType> thePropertyNames,
|
||||
RequestDetails theRequestDetails
|
||||
) {
|
||||
myCode = theCode;
|
||||
return myReturnParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return CodeSystem.class;
|
||||
}
|
||||
|
||||
public void setException(Exception theException) {
|
||||
myException = theException;
|
||||
}
|
||||
@Override
|
||||
public void setReturnParams(IBaseParameters theParameters) {
|
||||
myReturnParams = (Parameters) theParameters;
|
||||
}
|
||||
@Override
|
||||
public String getCode() {
|
||||
return myCode != null ? myCode.getValueAsString() : null;
|
||||
}
|
||||
@Override
|
||||
public String getSystem() {
|
||||
return mySystemUrl != null ? mySystemUrl.getValueAsString() : null;
|
||||
}
|
||||
public String getDisplay() {
|
||||
return myDisplay != null ? myDisplay.getValue() : null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
class MyValueSetProviderDstu3 implements IValidationProviders.IMyValueSetProvider {
|
||||
private Exception myException;
|
||||
private Parameters myReturnParams;
|
||||
private UriType mySystemUrl;
|
||||
private UriType myValueSetUrl;
|
||||
private CodeType myCode;
|
||||
private StringType myDisplay;
|
||||
|
||||
@Operation(name = "validate-code", idempotent = true, returnParameters = {
|
||||
@OperationParam(name = "result", type = BooleanType.class, min = 1),
|
||||
@OperationParam(name = "message", type = org.hl7.fhir.dstu3.model.StringType.class),
|
||||
@OperationParam(name = "display", type = org.hl7.fhir.dstu3.model.StringType.class)
|
||||
})
|
||||
public Parameters validateCode(
|
||||
HttpServletRequest theServletRequest,
|
||||
@IdParam(optional = true) IdType theId,
|
||||
@OperationParam(name = "url", min = 0, max = 1) org.hl7.fhir.dstu3.model.UriType theValueSetUrl,
|
||||
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
|
||||
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
|
||||
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
|
||||
@OperationParam(name = "valueSet") org.hl7.fhir.dstu3.model.ValueSet theValueSet
|
||||
) throws Exception {
|
||||
mySystemUrl = theSystem;
|
||||
myValueSetUrl = theValueSetUrl;
|
||||
myCode = theCode;
|
||||
myDisplay = theDisplay;
|
||||
if (myException != null) {
|
||||
throw myException;
|
||||
}
|
||||
return myReturnParams;
|
||||
}
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return ValueSet.class;
|
||||
}
|
||||
public void setException(Exception theException) {
|
||||
myException = theException;
|
||||
}
|
||||
@Override
|
||||
public void setReturnParams(IBaseParameters theParameters) {
|
||||
myReturnParams = (Parameters) theParameters;
|
||||
}
|
||||
@Override
|
||||
public String getCode() {
|
||||
return myCode != null ? myCode.getValueAsString() : null;
|
||||
}
|
||||
@Override
|
||||
public String getSystem() {
|
||||
return mySystemUrl != null ? mySystemUrl.getValueAsString() : null;
|
||||
}
|
||||
@Override
|
||||
public String getValueSet() {
|
||||
return myValueSetUrl != null ? myValueSetUrl.getValueAsString() : null;
|
||||
}
|
||||
public String getDisplay() {
|
||||
return myDisplay != null ? myDisplay.getValue() : null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,7 +41,6 @@ import org.hl7.fhir.dstu3.model.Type;
|
|||
import org.hl7.fhir.dstu3.model.UriType;
|
||||
import org.hl7.fhir.dstu3.model.ValueSet;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
@ -56,6 +55,8 @@ import java.util.Date;
|
|||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity.ERROR;
|
||||
import static ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity.WARNING;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType.BOOLEAN;
|
||||
import static org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType.CHOICE;
|
||||
|
@ -224,7 +225,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
|
|||
when(myValSupport.validateCodeInValueSet(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(ValueSet.class)))
|
||||
.thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0"));
|
||||
when(myValSupport.validateCodeInValueSet(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(ValueSet.class)))
|
||||
.thenReturn(new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage("Unknown code"));
|
||||
.thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(ERROR).setMessage("Unknown code"));
|
||||
|
||||
CodeSystem codeSystem = new CodeSystem();
|
||||
codeSystem.setContent(CodeSystemContentMode.COMPLETE);
|
||||
|
@ -246,7 +247,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
|
|||
when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class)))
|
||||
.thenReturn(new IValidationSupport.CodeValidationResult().setCode(CODE_ICC_SCHOOLTYPE_PT));
|
||||
when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class)))
|
||||
.thenReturn(new IValidationSupport.CodeValidationResult().setSeverityCode("warning").setMessage("Unknown code: http://codesystems.com/system / code1"));
|
||||
.thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(WARNING).setMessage("Unknown code: http://codesystems.com/system / code1"));
|
||||
|
||||
|
||||
QuestionnaireResponse qa;
|
||||
|
@ -1034,7 +1035,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
|
|||
when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class)))
|
||||
.thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0"));
|
||||
when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class)))
|
||||
.thenReturn(new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage("Unknown code"));
|
||||
.thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(ERROR).setMessage("Unknown code"));
|
||||
|
||||
CodeSystem codeSystem = new CodeSystem();
|
||||
codeSystem.setContent(CodeSystemContentMode.COMPLETE);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue