7.6.0 Mergeback (#6494)

* 6323 resource creation deadlock (#6324)

* make map reads concurrent

* change log

* CUstom version number for guaranteed build determinism

* licenses

* Expand translation cache (#6341)

* Expand translation cache

* Add changelog

* Correction to #6341 (#6342)

* Contained bug (#6402)

* Contained bug

* more tests

* changelog, tests, implementation

* code review

* backwards logic

* Fix Questionnaire doc (#6400)

* fix reindex optimizeStorage=ALL_VERSIONS (#6421)

* fixing broken rename of last step of reindex (#6429)

* Fixes for the translation of parameter issues as part of the output for $validate-code operation. (#6438)

* Move the validation providers to the test utilities package such that they can be reused.

* Update IValidationSupport.CodeValidationIssue structure such that it meets the FHIR specification. Update RemoteTerminologyServiceValidationSupport and VersionSpecificWorkerContextWrapper translation for issues.

* New tests for issue translation. Move test class to a different package so that we can add another test class.

* Some simplification in the issue API to simplify building of issues.

* Fix compilation errors.

* Update providers to allow multiple responses and add  support for the fetchCodeSystem call through method find.

* Setup the first test for resource validation with remote terminology providers.

* Fix NullPointerException

* avoid calling validateCode for CodeSystem where system is null

* Keep old public API methods (and class name) in IValidationSupport and mark them as deprecated to avoid breaking dependencies.

* Revert local change to debug for duplicate errors.

* Add more test cases for resource validation. Throw exception to signal missing test setup to make it obvious.

* Simplify test setup.

* Add some more javadoc

* Add javadoc for the new test class

* Add more tests

* Address code review comments in IValidationSupport.

* Add changelog

* Change Repository search interface from Map to Multimap (#6445)

Change Map to Multimap to support multiple and clauses.

* licenses

* fix interceptor hooks from requestDetails not getting called for STORAGE_PRECHECK_FOR_CACHED_SEARCH (#6436)

* fix interceptor hooks from requestDetails not getting called for STORAGE_PRECHECK_FOR_CACHED_SEARCH

* added unit tests and updated changelog

* added one more test case

* Add Adapter api (#6450)

Lightweight implementation of the adapter pattern.

* Bulk Import job status not changed after activation - failing test, fix, changelog (#6452)

* Update CR to 3.13.1 (#6433)

* Automated Migration Testing (HAPI-FHIR) - updated test migration scripts for 7_4_0 (#6439)

* Rel 7 6 CVE (#6446)

* Bump commons io

* Bump HS and Lucene, and hibernate

* Jetty bump for CVE

* Resolve CVES

* Bump with mismatch lucene

* Bump velocity template engine

* Revert bom bump

* wip

* Version bump

* move changelog, fix cve

* Bump commons-lang

* Replace imports

* wip

* fix HQL break

* remove dead code

* Fix changelog entry

* Bump org.hl7.fhir.core to 6.4.0 (#6454)

* Bump HAPI version + org.hl7.fhir.core to 6.4.0

* Apply spotless

* Correct a bug with duplicate parser IDs being assigned.  (#6456)

* Fix changelog entry

* wip

* compilation problem

* Correct tests

* changelog

* Update hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java

Co-authored-by: volodymyr-korzh <132366313+volodymyr-korzh@users.noreply.github.com>

* Correct a bug with duplicate parser IDs being assigned - test fixes

* Correct a bug with duplicate parser IDs being assigned - spotless

* Correct a bug with duplicate parser IDs being assigned - added test-utilities dependency to fhir-structures poms

* Correct a bug with duplicate parser IDs being assigned - test fixes

* Contained resources without assigned IDs are now assigned GUIDs - address comments

---------

Co-authored-by: volodymyr-korzh <132366313+volodymyr-korzh@users.noreply.github.com>
Co-authored-by: volodymyr <volodymyr.korzh@smilecdr.com>

* licenses

* ValueSet expansion fails if Hibernate Search configured to use Lucene - fixed incompatibility between Hibernate Search and Lucene versions (#6468)

* Change the migrator to avoid table locks when adding an index. (#6489)

* version bump

* Updating version to: 7.6.1 post release.

* Bump to 7.7.7.

* deadsapce

* Profile reference param (#6501)

* updated QueryStack to not throw error with _profile as a ReferenceParam

* update QueryStack

* remove warnings

* cleanup cast

* cleanup test

* add changelog

* cleanup imports

* updates per review feedback

* updates per review feedback

---------

Co-authored-by: taha.attari@smilecdr.com <taha.attari@smilecdr.com>

* move changelog

---------

Co-authored-by: JasonRoberts-smile <85363818+JasonRoberts-smile@users.noreply.github.com>
Co-authored-by: James Agnew <jamesagnew@gmail.com>
Co-authored-by: Brenin Rhodes <brenin@alphora.com>
Co-authored-by: Emre Dincturk <74370953+mrdnctrk@users.noreply.github.com>
Co-authored-by: TipzCM <leif.stawnyczy@gmail.com>
Co-authored-by: Martha Mitran <martha.mitran@smiledigitalhealth.com>
Co-authored-by: Michael Buckley <michaelabuckley@gmail.com>
Co-authored-by: volodymyr-korzh <132366313+volodymyr-korzh@users.noreply.github.com>
Co-authored-by: dotasek <david.otasek@smilecdr.com>
Co-authored-by: volodymyr <volodymyr.korzh@smilecdr.com>
Co-authored-by: markiantorno <markiantorno@gmail.com>
Co-authored-by: Gary Graham <garygraham@smiledigitalhealth.com>
Co-authored-by: Taha <TahaAttari@users.noreply.github.com>
Co-authored-by: taha.attari@smilecdr.com <taha.attari@smilecdr.com>
This commit is contained in:
Tadgh 2024-11-24 12:31:59 -08:00 committed by GitHub
parent d4f1766ca5
commit 86c2c13e0f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
135 changed files with 3801 additions and 1048 deletions

View File

@ -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");
private final String myCode;
// this is intentionally not exposed
CodeValidationIssueCode(String theCode) {
myCode = theCode;
}
enum CodeValidationIssueCoding {
VS_INVALID,
NOT_FOUND,
NOT_IN_VS,
INVALID_CODE,
INVALID_DISPLAY,
OTHER
/**
* 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 myMessage;
private final String myDiagnostics;
private final IssueSeverity mySeverity;
private final CodeValidationIssueCode myCode;
private final CodeValidationIssueCoding myCoding;
private CodeValidationIssueDetails myDetails;
public CodeValidationIssue(
String theMessage,
IssueSeverity mySeverity,
CodeValidationIssueCode theCode,
CodeValidationIssueCoding theCoding) {
this.myMessage = theMessage;
this.mySeverity = mySeverity;
this.myCode = theCode;
this.myCoding = theCoding;
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;
}

View File

@ -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 {

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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());

View File

@ -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
/**

View File

@ -38,7 +38,6 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
import ca.uhn.fhir.model.base.composite.BaseContainedDt;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException;
import com.google.common.collect.Lists;
@ -61,6 +60,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;
@ -70,6 +70,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@ -77,16 +78,28 @@ import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.substring;
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 +1431,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 +1457,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());
@ -1768,8 +1788,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 +1800,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;
@ -1789,16 +1812,7 @@ public class FhirTerser {
IIdType newId = theResource.getIdElement();
if (isBlank(newId.getValue())) {
newId.setValue("#" + myNextContainedId++);
} else {
// Avoid auto-assigned contained IDs colliding with pre-existing ones
String idPart = newId.getValue();
if (substring(idPart, 0, 1).equals("#")) {
idPart = idPart.substring(1);
if (StringUtils.isNumeric(idPart)) {
myNextContainedId = Long.parseLong(idPart) + 1;
}
}
newId.setValue("#" + UUID.randomUUID());
}
getResourceToIdMap().put(theResource, newId);
@ -1862,45 +1876,5 @@ public class FhirTerser {
public boolean hasExistingIdToContainedResource() {
return myExistingIdToContainedResourceMap != null;
}
public void assignIdsToContainedResources() {
if (!getContainedResources().isEmpty()) {
/*
* The idea with the code block below:
*
* We want to preserve any IDs that were user-assigned, so that if it's really
* important to someone that their contained resource have the ID of #FOO
* or #1 we will keep that.
*
* For any contained resources where no ID was assigned by the user, we
* want to manually create an ID but make sure we don't reuse an existing ID.
*/
Set<String> ids = new HashSet<>();
// Gather any user assigned IDs
for (IBaseResource nextResource : getContainedResources()) {
if (getResourceToIdMap().get(nextResource) != null) {
ids.add(getResourceToIdMap().get(nextResource).getValue());
}
}
// Automatically assign IDs to the rest
for (IBaseResource nextResource : getContainedResources()) {
while (getResourceToIdMap().get(nextResource) == null) {
String nextCandidate = "#" + myNextContainedId;
myNextContainedId++;
if (!ids.add(nextCandidate)) {
continue;
}
getResourceToIdMap().put(nextResource, new IdDt(nextCandidate));
}
}
}
}
}
}

View File

@ -170,6 +170,7 @@ public enum VersionEnum {
V7_5_0,
V7_6_0,
V7_6_1,
V7_7_0,
V7_8_0;

View File

@ -0,0 +1,62 @@
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* 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.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);
}
}

View File

@ -0,0 +1,48 @@
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* 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.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);
}
}

View File

@ -0,0 +1,38 @@
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* 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.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);
}
}

View File

@ -0,0 +1,44 @@
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* 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.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();
}

View File

@ -0,0 +1,29 @@
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* 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.util.adapters;
import java.util.Optional;
/**
* Get an adaptor
*/
public interface IAdapterManager {
<T> Optional<T> getAdapter(Object theTarget, Class<T> theAdapter);
}

View File

@ -0,0 +1,39 @@
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* 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%
*/
/**
* 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;

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,4 @@
---
type: change
jira: SMILE-9161
title: "Contained resources which arrive without assigned IDs are now assigned GUIDs, as opposed to monotonically increasing numeric IDs. This avoids a whole class of issues related to processing order and collisions."

View File

@ -0,0 +1,6 @@
---
type: fix
jira: SMILE-9161
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."

View File

@ -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."

View File

@ -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."

View File

@ -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."

View File

@ -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."

View File

@ -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`"

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 6451
jira: SMILE-9089
title: "Previously, activating `BulkDataImport` job would not change jobs status to `RUNNING`,
causing it to be processed multiple times instead of single time. This has been fixed."

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 6467
title: "Fixed an incompatibility between Hibernate Search and Lucene versions that caused ValueSet expansion to fail
when Hibernate Search was configured to use Lucene."

View File

@ -0,0 +1,4 @@
---
type: perf
issue: 6489
title: "Change the migrator to avoid table locks when adding an index. This allows systems to continue running during upgrade."

View File

@ -4,7 +4,7 @@
title: "The version of a few dependencies have been bumped to more recent versions
(dependent HAPI modules listed in brackets):
<ul>
<li>org.hl7.fhir.core (Base): 6.3.18 -&gt; 6.3.25</li>
<li>org.hl7.fhir.core (Base): 6.3.18 -&gt; 6.4.0</li>
<li>Bower/Moment.js (hapi-fhir-testpage-overlay): 2.27.0 -&gt; 2.29.4</li>
<li>htmlunit (Base): 3.9.0 -&gt; 3.11.0</li>
<li>Elasticsearch (Base): 8.11.1 -&gt; 8.14.3</li>

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 6502
backport: 7.6.1
title: "Support ReferenceParam in addition to UriParam for `_profile` in queries using the SearchParameterMap to match the change in the specification from DSTU3 to R4."

View File

@ -49,15 +49,15 @@ 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. |
| 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. |

View File

@ -217,6 +217,12 @@ public class BulkDataImportSvcImpl implements IBulkDataImportSvc, IHasScheduledJ
String biJobId = null;
try {
biJobId = processJob(bulkImportJobEntity);
// set job status to RUNNING so it would not be processed again
myTxTemplate.execute(t -> {
bulkImportJobEntity.setStatus(BulkImportJobStatusEnum.RUNNING);
myJobDao.save(bulkImportJobEntity);
return null;
});
} catch (Exception e) {
ourLog.error("Failure while preparing bulk export extract", e);
myTxTemplate.execute(t -> {
@ -256,6 +262,7 @@ public class BulkDataImportSvcImpl implements IBulkDataImportSvc, IHasScheduledJ
}
@Override
@Transactional
public JobInfo getJobStatus(String theBiJobId) {
BulkImportJobEntity theJob = findJobByBiJobId(theBiJobId);
return new JobInfo()

View File

@ -134,6 +134,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;
@ -1697,9 +1698,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);
}

View File

@ -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) {

View File

@ -150,6 +150,6 @@ public interface IBatch2WorkChunkRepository
@Param("status") WorkChunkStatusEnum theStatus);
@Query(
"SELECT new ca.uhn.fhir.batch2.model.BatchWorkChunkStatusDTO(e.myTargetStepId, e.myStatus, min(e.myStartTime), max(e.myEndTime), avg(e.myEndTime - e.myStartTime), count(*)) FROM Batch2WorkChunkEntity e WHERE e.myInstanceId=:instanceId GROUP BY e.myTargetStepId, e.myStatus")
"SELECT new ca.uhn.fhir.batch2.model.BatchWorkChunkStatusDTO(e.myTargetStepId, e.myStatus, min(e.myStartTime), max(e.myEndTime), avg(cast((e.myEndTime - e.myStartTime) as long)), count(*)) FROM Batch2WorkChunkEntity e WHERE e.myInstanceId=:instanceId GROUP BY e.myTargetStepId, e.myStatus")
List<BatchWorkChunkStatusDTO> fetchWorkChunkStatusForInstance(@Param("instanceId") String theInstanceId);
}

View File

@ -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;
}

View File

@ -210,7 +210,7 @@ public class QueryStack {
CoordsPredicateBuilder coordsBuilder = (CoordsPredicateBuilder) builder;
List<List<IQueryParameterType>> params = theParams.get(theParamName);
if (params.size() > 0 && params.get(0).size() > 0) {
if (!params.isEmpty() && !params.get(0).isEmpty()) {
IQueryParameterType param = params.get(0).get(0);
ParsedLocationParam location = ParsedLocationParam.from(theParams, param);
double latitudeValue = location.getLatitudeValue();
@ -2134,6 +2134,10 @@ public class QueryStack {
if (nextParam.getModifier() == TokenParamModifier.NOT) {
paramInverted = true;
}
} else if (nextOrParam instanceof ReferenceParam) {
ReferenceParam nextParam = (ReferenceParam) nextOrParam;
code = nextParam.getValue();
system = null;
} else {
UriParam nextParam = (UriParam) nextOrParam;
code = nextParam.getValue();
@ -2160,8 +2164,10 @@ public class QueryStack {
}
}
UriParam nextParam = (UriParam) nextParamUncasted;
if (isNotBlank(nextParam.getValue())) {
if (nextParamUncasted instanceof ReferenceParam
&& isNotBlank(((ReferenceParam) nextParamUncasted).getValue())) {
return true;
} else if (nextParamUncasted instanceof UriParam && isNotBlank(((UriParam) nextParamUncasted).getValue())) {
return true;
}
}

View File

@ -1056,7 +1056,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);
@ -2203,7 +2204,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,

View File

@ -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");

View File

@ -881,6 +881,11 @@ public class JpaJobPersistenceImplTest extends BaseJpaR4Test {
public void testFetchInstanceAndWorkChunkStatus() {
// Setup
Date date1 = new Date();
Date date2 = new Date();
List<String> chunkIds = new ArrayList<>();
JobInstance instance = createInstance();
String instanceId = mySvc.storeNewInstance(instance);

View File

@ -17,6 +17,7 @@ import ca.uhn.fhir.jpa.bulk.imprt.api.IBulkDataImportSvc;
import ca.uhn.fhir.jpa.bulk.imprt.model.ActivateJobResult;
import ca.uhn.fhir.jpa.bulk.imprt.model.BulkImportJobFileJson;
import ca.uhn.fhir.jpa.bulk.imprt.model.BulkImportJobJson;
import ca.uhn.fhir.jpa.bulk.imprt.model.BulkImportJobStatusEnum;
import ca.uhn.fhir.jpa.bulk.imprt.model.JobFileRowProcessingModeEnum;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.subscription.channel.api.ChannelConsumerSettings;
@ -166,6 +167,8 @@ public class BulkDataImportR4Test extends BaseJpaR4Test implements ITestDataBuil
ActivateJobResult activateJobOutcome = mySvc.activateNextReadyJob();
assertTrue(activateJobOutcome.isActivated);
// validate that job changed status from READY to RUNNING
assertEquals(BulkImportJobStatusEnum.RUNNING, mySvc.getJobStatus(jobId).getStatus());
JobInstance instance = myBatch2JobHelper.awaitJobCompletion(activateJobOutcome.jobId, 60);
assertNotNull(instance);
@ -196,6 +199,8 @@ public class BulkDataImportR4Test extends BaseJpaR4Test implements ITestDataBuil
ActivateJobResult activateJobOutcome = mySvc.activateNextReadyJob();
assertTrue(activateJobOutcome.isActivated);
// validate that job changed status from READY to RUNNING
assertEquals(BulkImportJobStatusEnum.RUNNING, mySvc.getJobStatus(jobId).getStatus());
JobInstance instance = myBatch2JobHelper.awaitJobCompletion(activateJobOutcome.jobId);
assertNotNull(instance);

View File

@ -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

View File

@ -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;

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.test.utilities.UuidUtils;
import ca.uhn.fhir.util.BundleBuilder;
import ca.uhn.fhir.util.ClasspathUtil;
import org.apache.commons.lang3.StringUtils;
@ -81,6 +82,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import static ca.uhn.fhir.test.utilities.UuidUtils.HASH_UUID_PATTERN;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -738,7 +740,8 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
String encoded = myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(p);
ourLog.info("Input: {}", encoded);
assertThat(encoded).contains("#1");
String organizationUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(organizationUuid);
IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless();
@ -746,10 +749,12 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
encoded = myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(p);
ourLog.info("Output: {}", encoded);
assertThat(encoded).contains("#1");
String organizationUuidParsed = UuidUtils.findFirstUUID(encoded);
assertNotNull(organizationUuidParsed);
assertEquals(organizationUuid, organizationUuidParsed);
Organization org = (Organization) p.getManagingOrganization().getResource();
assertEquals("#1", org.getId());
assertEquals("#" + organizationUuid, org.getId());
assertThat(org.getMeta().getTag()).hasSize(1);
}

View File

@ -2722,7 +2722,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();

View File

@ -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);

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.dao.r4;
import static ca.uhn.fhir.test.utilities.UuidUtils.HASH_UUID_PATTERN;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;
@ -3219,8 +3220,8 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
String id = outcome.getEntry().get(0).getResponse().getLocation();
patient = myPatientDao.read(new IdType(id));
assertEquals("#1", patient.getManagingOrganization().getReference());
assertEquals("#1", patient.getContained().get(0).getId());
assertThat(patient.getManagingOrganization().getReference()).containsPattern(HASH_UUID_PATTERN);
assertEquals(patient.getManagingOrganization().getReference(), patient.getContained().get(0).getId());
}
@Nonnull

View File

@ -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();

View File

@ -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

View File

@ -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;
}
}
@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;
// 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);
}
private void setupCodeSystemValidateCode(String theUrl, String theCode, IBaseParameters theResponseParams) {
CodeSystem codeSystem = myCodeSystemProvider.addTerminologyResource(theUrl);
myCodeSystemProvider.addTerminologyResponse(OPERATION_VALIDATE_CODE, codeSystem.getUrl(), theCode, theResponseParams);
}
}

View File

@ -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();
}
}

View File

@ -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
}
]
}
}

View File

@ -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"
}
}
]
}
}
]
}

View File

@ -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"
}
]
}

View File

@ -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"
}
}
]
}
}
]
}

View File

@ -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"
}
}
]
}
}
]
}

View File

@ -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'"
}
]
}

View File

@ -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"
}
}
]
}
}
]
}

View File

@ -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'"
}
]
}

View File

@ -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"
}
]
}

View File

@ -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'"
}
]
}

View File

@ -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"
}
]
}

View File

@ -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
}
]
}
}

View File

@ -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
}
]
}
}

View File

@ -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"
}
}
]
}
}
]
}

View File

@ -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"
}
}
]
}
}
]
}

View File

@ -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'"
}
]
}

View File

@ -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"
}
]
}

View File

@ -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"
}
}
]
}
}
]
}

View File

@ -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"
}
]
}

View File

@ -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'"
}
]
}

View File

@ -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'"
}
]
}

View File

@ -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"
}
}
]
}
}
]
}

View File

@ -51,3 +51,33 @@ INSERT INTO TRM_CONCEPT_DESIG (
54,
150
);
INSERT INTO HFJ_RES_LINK (
PID,
PARTITION_DATE,
PARTITION_ID,
SRC_PATH,
SRC_RESOURCE_ID,
SOURCE_RESOURCE_TYPE,
TARGET_RESOURCE_ID,
TARGET_RESOURCE_TYPE,
TARGET_RESOURCE_URL,
TARGET_RESOURCE_VERSION,
SP_UPDATED,
TARGET_RES_PARTITION_ID,
TARGET_RES_PARTITION_DATE
) VALUES (
702,
'2024-11-05',
1,
'Observation.subject.where(resolve() is Patient)',
1656,
'Observation',
1906,
'Patient',
'http://localhost:8000/Patient/123',
1,
'2024-11-01 18:01:12.921',
1,
'2024-11-05'
);

View File

@ -51,3 +51,33 @@ INSERT INTO TRM_CONCEPT_DESIG (
54,
150
);
INSERT INTO HFJ_RES_LINK (
PID,
PARTITION_DATE,
PARTITION_ID,
SRC_PATH,
SRC_RESOURCE_ID,
SOURCE_RESOURCE_TYPE,
TARGET_RESOURCE_ID,
TARGET_RESOURCE_TYPE,
TARGET_RESOURCE_URL,
TARGET_RESOURCE_VERSION,
SP_UPDATED,
TARGET_RES_PARTITION_ID,
TARGET_RES_PARTITION_DATE
) VALUES (
702,
'2024-11-05',
1,
'Observation.subject.where(resolve() is Patient)',
1653,
'Observation',
1906,
'Patient',
'http://localhost:8000/Patient/123',
1,
'2024-11-01 18:01:12.921',
1,
'2024-11-05'
);

View File

@ -51,3 +51,33 @@ INSERT INTO TRM_CONCEPT_DESIG (
54,
150
);
INSERT INTO HFJ_RES_LINK (
PID,
PARTITION_DATE,
PARTITION_ID,
SRC_PATH,
SRC_RESOURCE_ID,
SOURCE_RESOURCE_TYPE,
TARGET_RESOURCE_ID,
TARGET_RESOURCE_TYPE,
TARGET_RESOURCE_URL,
TARGET_RESOURCE_VERSION,
SP_UPDATED,
TARGET_RES_PARTITION_ID,
TARGET_RES_PARTITION_DATE
) VALUES (
702,
SYSDATE,
1,
'Observation.subject.where(resolve() is Patient)',
1653,
'Observation',
1906,
'Patient',
'http://localhost:8000/Patient/123',
1,
SYSDATE,
1,
SYSDATE
);

View File

@ -51,3 +51,33 @@ INSERT INTO TRM_CONCEPT_DESIG (
54,
150
);
INSERT INTO HFJ_RES_LINK (
PID,
PARTITION_DATE,
PARTITION_ID,
SRC_PATH,
SRC_RESOURCE_ID,
SOURCE_RESOURCE_TYPE,
TARGET_RESOURCE_ID,
TARGET_RESOURCE_TYPE,
TARGET_RESOURCE_URL,
TARGET_RESOURCE_VERSION,
SP_UPDATED,
TARGET_RES_PARTITION_ID,
TARGET_RES_PARTITION_DATE
) VALUES (
702,
'2024-11-05',
1,
'Observation.subject.where(resolve() is Patient)',
1653,
'Observation',
1906,
'Patient',
'http://localhost:8000/Patient/123',
1,
'2024-11-01 18:01:12.921',
1,
'2024-11-05'
);

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -397,7 +397,7 @@ public class Builder {
private final String myVersion;
private final boolean myUnique;
private String[] myIncludeColumns;
private boolean myOnline;
private boolean myOnline = true;
public BuilderAddIndexUnique(String theVersion, boolean theUnique) {
myVersion = theVersion;

View File

@ -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();
}

View File

@ -26,6 +26,7 @@ public class BatchWorkChunkStatusDTO {
public final WorkChunkStatusEnum status;
public final Date start;
public final Date stop;
public final Double avg;
public final Long totalChunks;

View File

@ -21,15 +21,20 @@ package ca.uhn.fhir.cr.config.dstu3;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.cr.config.CrProcessorConfig;
import ca.uhn.fhir.cr.config.ProviderLoader;
import ca.uhn.fhir.cr.config.ProviderSelector;
import ca.uhn.fhir.rest.server.RestfulServer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import java.util.Arrays;
import java.util.Map;
@Configuration
@Import(CrProcessorConfig.class)
public class EvaluateOperationConfig {
@Bean
ca.uhn.fhir.cr.dstu3.library.LibraryEvaluateProvider dstu3LibraryEvaluateProvider() {

View File

@ -21,15 +21,20 @@ package ca.uhn.fhir.cr.config.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.cr.config.CrProcessorConfig;
import ca.uhn.fhir.cr.config.ProviderLoader;
import ca.uhn.fhir.cr.config.ProviderSelector;
import ca.uhn.fhir.rest.server.RestfulServer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import java.util.Arrays;
import java.util.Map;
@Configuration
@Import(CrProcessorConfig.class)
public class EvaluateOperationConfig {
@Bean
ca.uhn.fhir.cr.r4.library.LibraryEvaluateProvider r4LibraryEvaluateProvider() {

View File

@ -25,6 +25,7 @@ import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -54,6 +55,25 @@ public class HapiFhirRepositoryR4Test extends BaseCrR4TestServer {
assertTrue(crudTest(repository));
}
@Test
void _profileCanBeReferenceParam() {
// as per https://www.hl7.org/fhir/r4/search.html#all _profile is a reference param
var repository = new HapiFhirRepository(myDaoRegistry, setupRequestDetails(), myRestfulServer);
var profileToFind = "http://www.a-test-profile.com";
var encounterWithProfile = new Encounter();
encounterWithProfile.getMeta().addProfile(profileToFind);
repository.create(encounterWithProfile);
repository.create(new Encounter());
Map<String, List<IQueryParameterType>> map = new HashMap<>();
map.put("_profile", Collections.singletonList(new ReferenceParam(profileToFind)));
assertDoesNotThrow(() -> {
var returnBundle = repository.search(Bundle.class, Encounter.class, map);
assertTrue(returnBundle.hasEntry());
assertEquals(1,returnBundle.getEntry().size());
assertEquals(profileToFind, returnBundle.getEntryFirstRep().getResource().getMeta().getProfile().get(0).getValue());
});
}
Boolean crudTest(HapiFhirRepository theRepository) {

View File

@ -11,6 +11,7 @@ import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
import ca.uhn.fhir.parser.PatientWithExtendedContactDstu3.CustomContactComponent;
import ca.uhn.fhir.parser.XmlParserDstu2_1Test.TestPatientFor327;
import ca.uhn.fhir.test.utilities.UuidUtils;
import ca.uhn.fhir.util.ClasspathUtil;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Sets;
@ -407,6 +408,8 @@ public class JsonParserDstu2_1Test {
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
String conditionUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(conditionUuid);
//@formatter:off
assertThat(encoded).containsSubsequence(
@ -415,14 +418,14 @@ public class JsonParserDstu2_1Test {
"\"contained\": [",
"{",
"\"resourceType\": \"Condition\",",
"\"id\": \"1\"",
"\"id\": \"" + conditionUuid + "\"",
"}",
"],",
"\"extension\": [",
"{",
"\"url\": \"test\",",
"\"valueReference\": {",
"\"reference\": \"#1\"",
"\"reference\": \"#" + conditionUuid + "\"",
"}",
"}",
"],",
@ -632,19 +635,21 @@ public class JsonParserDstu2_1Test {
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
String conditionUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(conditionUuid);
//@formatter:off
assertThat(encoded).containsSubsequence(
"\"resourceType\": \"Patient\"",
"\"contained\": [",
"\"resourceType\": \"Condition\"",
"\"id\": \"1\"",
"\"id\": \"" + conditionUuid + "\"",
"\"bodySite\": [",
"\"text\": \"BODY SITE\"",
"\"extension\": [",
"\"url\": \"testCondition\",",
"\"valueReference\": {",
"\"reference\": \"#1\"",
"\"reference\": \"#" + conditionUuid + "\"",
"\"birthDate\": \"2016-04-14\"",
"}"
);

View File

@ -13,6 +13,7 @@ import ca.uhn.fhir.parser.FooMessageHeaderWithExplicitField.FooMessageSourceComp
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
import ca.uhn.fhir.parser.PatientWithCustomCompositeExtension.FooParentExtension;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.test.utilities.UuidUtils;
import ca.uhn.fhir.util.ClasspathUtil;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Sets;
@ -378,8 +379,11 @@ public class XmlParserDstu2_1Test {
String encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
String organizationUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(organizationUuid);
assertThat(encoded).contains("<contained>");
assertThat(encoded).contains("<reference value=\"#1\"/>");
assertThat(encoded).contains("<reference value=\"#" + organizationUuid + "\"/>");
// Create a bundle with just the patient resource
Bundle b = new Bundle();
@ -388,35 +392,35 @@ public class XmlParserDstu2_1Test {
// Encode the bundle
encoded = xmlParser.encodeResourceToString(b);
ourLog.info(encoded);
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<id value=\"1\"/>", "</contained>"));
assertThat(encoded).contains("<reference value=\"#1\"/>");
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<id value=\"" + organizationUuid + "\"/>", "</contained>"));
assertThat(encoded).contains("<reference value=\"#" + organizationUuid + "\"/>");
assertThat(encoded).containsSubsequence(Arrays.asList("<entry>", "</entry>"));
assertThat(encoded).doesNotContainPattern("(?s)<entry>.*</entry>.*<entry>");
// Re-parse the bundle
patient = (Patient) xmlParser.parseResource(xmlParser.encodeResourceToString(patient));
assertEquals("#1", patient.getManagingOrganization().getReference());
assertEquals("#" + organizationUuid, patient.getManagingOrganization().getReference());
assertNotNull(patient.getManagingOrganization().getResource());
org = (Organization) patient.getManagingOrganization().getResource();
assertEquals("#1", org.getIdElement().getValue());
assertEquals("#" + organizationUuid, org.getIdElement().getValue());
assertEquals("Contained Test Organization", org.getName());
// And re-encode a second time
encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<Organization ", "<id value=\"1\"/>", "</Organization", "</contained>", "<reference value=\"#1\"/>"));
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<Organization ", "<id value=\"" + organizationUuid + "\"/>", "</Organization", "</contained>", "<reference value=\"#" + organizationUuid + "\"/>"));
assertThat(encoded).doesNotContainPattern("(?s)<contained>.*<Org.*<contained>");
assertThat(encoded).contains("<reference value=\"#1\"/>");
assertThat(encoded).contains("<reference value=\"#" + organizationUuid + "\"/>");
// And re-encode once more, with the references cleared
patient.getContained().clear();
patient.getManagingOrganization().setReference(null);
encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<Organization ", "<id value=\"1\"/>", "</Organization", "</contained>", "<reference value=\"#1\"/>"));
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<Organization ", "<id value=\"" + organizationUuid + "\"/>", "</Organization", "</contained>", "<reference value=\"#" + organizationUuid + "\"/>"));
assertThat(encoded).doesNotContainPattern("(?s)<contained>.*<Org.*<contained>");
assertThat(encoded).contains("<reference value=\"#1\"/>");
assertThat(encoded).contains("<reference value=\"#" + organizationUuid + "\"/>");
// And re-encode once more, with the references cleared and a manually set local ID
patient.getContained().clear();
@ -447,6 +451,8 @@ public class XmlParserDstu2_1Test {
String output = parser.encodeResourceToString(dr);
ourLog.info(output);
String observationUuid = UuidUtils.findFirstUUID(output);
assertNotNull(observationUuid);
//@formatter:off
assertThat(output).containsSubsequence(
@ -456,7 +462,7 @@ public class XmlParserDstu2_1Test {
"</meta>",
"<contained>",
"<Observation xmlns=\"http://hl7.org/fhir\">",
"<id value=\"1\"/>",
"<id value=\"" + observationUuid + "\"/>",
"<meta>",
"<profile value=\"http://custom_Observation\"/>",
"</meta>",
@ -465,7 +471,7 @@ public class XmlParserDstu2_1Test {
"</contained>",
"<status value=\"final\"/>",
"<result>",
"<reference value=\"#1\"/>",
"<reference value=\"#" + observationUuid + "\"/>",
"</result>",
"</DiagnosticReport>");
//@formatter:on
@ -477,7 +483,7 @@ public class XmlParserDstu2_1Test {
dr = (CustomDiagnosticReport) parser.parseResource(output);
assertEquals(DiagnosticReportStatus.FINAL, dr.getStatus());
assertEquals("#1", dr.getResult().get(0).getReference());
assertEquals("#" + observationUuid, dr.getResult().get(0).getReference());
obs = (CustomObservation) dr.getResult().get(0).getResource();
assertEquals(ObservationStatus.FINAL, obs.getStatus());
@ -500,19 +506,21 @@ public class XmlParserDstu2_1Test {
String output = parser.encodeResourceToString(dr);
ourLog.info(output);
String observationUuid = UuidUtils.findFirstUUID(output);
assertNotNull(observationUuid);
//@formatter:off
assertThat(output).containsSubsequence(
"<DiagnosticReport xmlns=\"http://hl7.org/fhir\">",
"<contained>",
"<Observation xmlns=\"http://hl7.org/fhir\">",
"<id value=\"1\"/>",
"<id value=\"" + observationUuid + "\"/>",
"<status value=\"final\"/>",
"</Observation>",
"</contained>",
"<status value=\"final\"/>",
"<result>",
"<reference value=\"#1\"/>",
"<reference value=\"#" + observationUuid + "\"/>",
"</result>",
"</DiagnosticReport>");
//@formatter:on
@ -524,7 +532,7 @@ public class XmlParserDstu2_1Test {
dr = (DiagnosticReport) parser.parseResource(output);
assertEquals(DiagnosticReportStatus.FINAL, dr.getStatus());
assertEquals("#1", dr.getResult().get(0).getReference());
assertEquals("#" + observationUuid, dr.getResult().get(0).getReference());
obs = (Observation) dr.getResult().get(0).getResource();
assertEquals(ObservationStatus.FINAL, obs.getStatus());
@ -832,18 +840,20 @@ public class XmlParserDstu2_1Test {
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
String conditionUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(conditionUuid);
//@formatter:off
assertThat(encoded).containsSubsequence(
"<Patient xmlns=\"http://hl7.org/fhir\">",
"<contained>",
"<Condition xmlns=\"http://hl7.org/fhir\">",
"<id value=\"1\"/>",
"<id value=\"" + conditionUuid + "\"/>",
"</Condition>",
"</contained>",
"<extension url=\"test\">",
"<valueReference>",
"<reference value=\"#1\"/>",
"<reference value=\"#" + conditionUuid + "\"/>",
"</valueReference>",
"</extension>",
"<birthDate value=\"2016-04-05\"/>",
@ -911,10 +921,12 @@ public class XmlParserDstu2_1Test {
IParser p = ourCtx.newXmlParser().setPrettyPrint(true);
String encoded = p.encodeResourceToString(medicationPrescript);
ourLog.info(encoded);
String medicationUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(medicationUuid);
//@formatter:on
assertThat(encoded).containsSubsequence("<MedicationOrder xmlns=\"http://hl7.org/fhir\">", "<contained>", "<Medication xmlns=\"http://hl7.org/fhir\">", "<id value=\"1\"/>", "<code>", "<coding>",
"<system value=\"urn:sys\"/>", "<code value=\"code1\"/>", "</coding>", "</code>", "</Medication>", "</contained>", "<medicationReference>", "<reference value=\"#1\"/>",
assertThat(encoded).containsSubsequence("<MedicationOrder xmlns=\"http://hl7.org/fhir\">", "<contained>", "<Medication xmlns=\"http://hl7.org/fhir\">", "<id value=\"" + medicationUuid + "\"/>", "<code>", "<coding>",
"<system value=\"urn:sys\"/>", "<code value=\"code1\"/>", "</coding>", "</code>", "</Medication>", "</contained>", "<medicationReference>", "<reference value=\"#" + medicationUuid + "\"/>",
"<display value=\"MedRef\"/>", "</medicationReference>", "</MedicationOrder>");
//@formatter:off
}
@ -1185,13 +1197,15 @@ public class XmlParserDstu2_1Test {
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
String conditionUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(conditionUuid);
//@formatter:off
assertThat(encoded).containsSubsequence(
"<Patient xmlns=\"http://hl7.org/fhir\">",
"<contained>",
"<Condition xmlns=\"http://hl7.org/fhir\">",
"<id value=\"1\"/>",
"<id value=\"" + conditionUuid + "\"/>",
"<bodySite>",
"<text value=\"BODY SITE\"/>",
"</bodySite>",
@ -1199,7 +1213,7 @@ public class XmlParserDstu2_1Test {
"</contained>",
"<extension url=\"testCondition\">",
"<valueReference>",
"<reference value=\"#1\"/>",
"<reference value=\"#" + conditionUuid + "\"/>",
"</valueReference>",
"</extension>",
"<birthDate value=\"2016-04-14\"/>",

View File

@ -16,6 +16,7 @@ import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.CustomResource364Dstu2.CustomResource364CustomDate;
import ca.uhn.fhir.test.utilities.UuidUtils;
import ca.uhn.fhir.util.ElementUtil;
import ca.uhn.fhir.util.TestUtil;
import org.junit.jupiter.api.AfterAll;
@ -55,19 +56,22 @@ public class CustomTypeDstu2Test {
String string = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(mo);
ourLog.info(string);
String medicationUuid = UuidUtils.findFirstUUID(string);
assertNotNull(medicationUuid);
//@formatter:on
assertThat(string).containsSubsequence(
"<MedicationOrder xmlns=\"http://hl7.org/fhir\">",
" <contained>",
" <Medication xmlns=\"http://hl7.org/fhir\">",
" <id value=\"1\"/>",
" <id value=\"" + medicationUuid + "\"/>",
" <code>",
" <text value=\"MED TEXT\"/>",
" </code>",
" </Medication>",
" </contained>",
" <medication>",
" <reference value=\"#1\"/>",
" <reference value=\"#" + medicationUuid + "\"/>",
" </medication>",
"</MedicationOrder>");
//@formatter:on
@ -76,7 +80,7 @@ public class CustomTypeDstu2Test {
medication = (Medication) mo.getMedication().getResource();
assertNotNull(medication);
assertEquals("#1", medication.getId().getValue());
assertEquals("#" + medicationUuid, medication.getId().getValue());
assertEquals("MED TEXT", medication.getCode().getText());
}

View File

@ -50,6 +50,7 @@ import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
import ca.uhn.fhir.parser.testprofile.CommunicationProfile;
import ca.uhn.fhir.parser.testprofile.PatientProfile;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.test.utilities.UuidUtils;
import ca.uhn.fhir.util.TestUtil;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
@ -1705,14 +1706,16 @@ public class JsonParserDstu2Test {
String enc = parser.encodeResourceToString(o);
ourLog.info(enc);
String patientUuid = UuidUtils.findFirstUUID(enc);
assertNotNull(patientUuid);
//@formatter:off
assertThat(enc).containsSubsequence(
"\"resourceType\": \"Observation\"",
"\"contained\": [",
"\"resourceType\": \"Patient\",",
"\"id\": \"1\"",
"\"reference\": \"#1\""
"\"id\": \"" + patientUuid + "\"",
"\"reference\": \"#" + patientUuid + "\""
);
//@formatter:on

View File

@ -62,6 +62,7 @@ import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.test.utilities.UuidUtils;
import ca.uhn.fhir.util.ClasspathUtil;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Sets;
@ -512,8 +513,10 @@ public class XmlParserDstu2Test {
String encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
String organizationUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(organizationUuid);
assertThat(encoded).contains("<contained>");
assertThat(encoded).contains("<reference value=\"#1\"/>");
assertThat(encoded).contains("<reference value=\"#" + organizationUuid + "\"/>");
// Create a bundle with just the patient resource
ca.uhn.fhir.model.dstu2.resource.Bundle b = new ca.uhn.fhir.model.dstu2.resource.Bundle();
@ -522,35 +525,37 @@ public class XmlParserDstu2Test {
// Encode the bundle
encoded = xmlParser.encodeResourceToString(b);
ourLog.info(encoded);
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<id value=\"1\"/>", "</contained>"));
assertThat(encoded).contains("<reference value=\"#1\"/>");
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<id value=\"" + organizationUuid + "\"/>", "</contained>"));
assertThat(encoded).contains("<reference value=\"#" + organizationUuid + "\"/>");
assertThat(encoded).containsSubsequence(Arrays.asList("<entry>", "</entry>"));
assertThat(encoded).doesNotContainPattern("(?s)<entry>.*</entry>.*<entry>");
// Re-parse the bundle
patient = (Patient) xmlParser.parseResource(xmlParser.encodeResourceToString(patient));
assertEquals("#1", patient.getManagingOrganization().getReference().getValue());
assertEquals("#" + organizationUuid, patient.getManagingOrganization().getReference().getValue());
assertNotNull(patient.getManagingOrganization().getResource());
org = (Organization) patient.getManagingOrganization().getResource();
assertEquals("#1", org.getId().getValue());
assertEquals("#" + organizationUuid, org.getId().getValue());
assertEquals("Contained Test Organization", org.getName());
// And re-encode a second time
encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<Organization ", "<id value=\"1\"/>", "</Organization", "</contained>", "<reference value=\"#1\"/>"));
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<Organization ", "<id value=\""
+ organizationUuid + "\"/>", "</Organization", "</contained>", "<reference value=\"#" + organizationUuid + "\"/>"));
assertThat(encoded).doesNotContainPattern("(?s)<contained>.*<Org.*<contained>");
assertThat(encoded).contains("<reference value=\"#1\"/>");
assertThat(encoded).contains("<reference value=\"#" + organizationUuid + "\"/>");
// And re-encode once more, with the references cleared
patient.getContained().getContainedResources().clear();
patient.getManagingOrganization().setReference((String) null);
encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<Organization ", "<id value=\"1\"/>", "</Organization", "</contained>", "<reference value=\"#1\"/>"));
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<Organization ", "<id value=\"" + organizationUuid +
"\"/>", "</Organization", "</contained>", "<reference value=\"#" + organizationUuid + "\"/>"));
assertThat(encoded).doesNotContainPattern("(?s)<contained>.*<Org.*<contained>");
assertThat(encoded).contains("<reference value=\"#1\"/>");
assertThat(encoded).contains("<reference value=\"#" + organizationUuid + "\"/>");
// And re-encode once more, with the references cleared and a manually set local ID
patient.getContained().getContainedResources().clear();
@ -581,6 +586,8 @@ public class XmlParserDstu2Test {
String output = parser.encodeResourceToString(dr);
ourLog.info(output);
String observationUuid = UuidUtils.findFirstUUID(output);
assertNotNull(observationUuid);
//@formatter:off
assertThat(output).containsSubsequence(
@ -590,7 +597,7 @@ public class XmlParserDstu2Test {
"</meta>",
"<contained>",
"<Observation xmlns=\"http://hl7.org/fhir\">",
"<id value=\"1\"/>",
"<id value=\"" + observationUuid + "\"/>",
"<meta>",
"<profile value=\"http://custom_Observation\"/>",
"</meta>",
@ -599,7 +606,7 @@ public class XmlParserDstu2Test {
"</contained>",
"<status value=\"final\"/>",
"<result>",
"<reference value=\"#1\"/>",
"<reference value=\"#" + observationUuid + "\"/>",
"</result>",
"</DiagnosticReport>");
//@formatter:on
@ -611,7 +618,7 @@ public class XmlParserDstu2Test {
dr = (CustomDiagnosticReportDstu2) parser.parseResource(output);
assertEquals(DiagnosticReportStatusEnum.FINAL, dr.getStatusElement().getValueAsEnum());
assertEquals("#1", dr.getResult().get(0).getReference().getValueAsString());
assertEquals("#" + observationUuid, dr.getResult().get(0).getReference().getValueAsString());
obs = (CustomObservationDstu2) dr.getResult().get(0).getResource();
assertEquals(ObservationStatusEnum.FINAL, obs.getStatusElement().getValueAsEnum());
@ -665,19 +672,21 @@ public class XmlParserDstu2Test {
String output = parser.encodeResourceToString(dr);
ourLog.info(output);
String observationUuid = UuidUtils.findFirstUUID(output);
assertNotNull(observationUuid);
//@formatter:off
assertThat(output).containsSubsequence(
"<DiagnosticReport xmlns=\"http://hl7.org/fhir\">",
"<contained>",
"<Observation xmlns=\"http://hl7.org/fhir\">",
"<id value=\"1\"/>",
"<id value=\"" + observationUuid + "\"/>",
"<status value=\"final\"/>",
"</Observation>",
"</contained>",
"<status value=\"final\"/>",
"<result>",
"<reference value=\"#1\"/>",
"<reference value=\"#" + observationUuid + "\"/>",
"</result>",
"</DiagnosticReport>");
//@formatter:on
@ -689,7 +698,7 @@ public class XmlParserDstu2Test {
dr = (DiagnosticReport) parser.parseResource(output);
assertEquals(DiagnosticReportStatusEnum.FINAL, dr.getStatusElement().getValueAsEnum());
assertEquals("#1", dr.getResult().get(0).getReference().getValueAsString());
assertEquals("#" + observationUuid, dr.getResult().get(0).getReference().getValueAsString());
obs = (Observation) dr.getResult().get(0).getResource();
assertEquals(ObservationStatusEnum.FINAL, obs.getStatusElement().getValueAsEnum());
@ -1305,10 +1314,12 @@ public class XmlParserDstu2Test {
IParser p = ourCtx.newXmlParser().setPrettyPrint(true);
String encoded = p.encodeResourceToString(medicationPrescript);
ourLog.info(encoded);
String medicationUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(medicationUuid);
//@formatter:on
assertThat(encoded).containsSubsequence("<MedicationOrder xmlns=\"http://hl7.org/fhir\">", "<contained>", "<Medication xmlns=\"http://hl7.org/fhir\">", "<id value=\"1\"/>", "<code>", "<coding>",
"<system value=\"urn:sys\"/>", "<code value=\"code1\"/>", "</coding>", "</code>", "</Medication>", "</contained>", "<medicationReference>", "<reference value=\"#1\"/>",
assertThat(encoded).containsSubsequence("<MedicationOrder xmlns=\"http://hl7.org/fhir\">", "<contained>", "<Medication xmlns=\"http://hl7.org/fhir\">", "<id value=\"" + medicationUuid + "\"/>", "<code>", "<coding>",
"<system value=\"urn:sys\"/>", "<code value=\"code1\"/>", "</coding>", "</code>", "</Medication>", "</contained>", "<medicationReference>", "<reference value=\"#" + medicationUuid + "\"/>",
"<display value=\"MedRef\"/>", "</medicationReference>", "</MedicationOrder>");
//@formatter:off
}
@ -1561,13 +1572,15 @@ public class XmlParserDstu2Test {
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
String conditionUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(conditionUuid);
//@formatter:off
assertThat(encoded).containsSubsequence(
"<Patient xmlns=\"http://hl7.org/fhir\">",
"<contained>",
"<Condition xmlns=\"http://hl7.org/fhir\">",
"<id value=\"1\"/>",
"<id value=\"" + conditionUuid + "\"/>",
"<bodySite>",
"<text value=\"BODY SITE\"/>",
"</bodySite>",
@ -1575,7 +1588,7 @@ public class XmlParserDstu2Test {
"</contained>",
"<extension url=\"testCondition\">",
"<valueReference>",
"<reference value=\"#1\"/>",
"<reference value=\"#" + conditionUuid + "\"/>",
"</valueReference>",
"</extension>",
"<birthDate value=\"2016-04-17\"/>",
@ -2535,15 +2548,17 @@ public class XmlParserDstu2Test {
String enc = parser.encodeResourceToString(o);
ourLog.info(enc);
String patientUuid = UuidUtils.findFirstUUID(enc);
assertNotNull(patientUuid);
//@formatter:off
assertThat(enc).containsSubsequence(
"<Observation xmlns=\"http://hl7.org/fhir\">",
"<contained>",
"<Patient xmlns=\"http://hl7.org/fhir\">",
"<id value=\"1\"/>",
"<id value=\"" + patientUuid + "\"/>",
"</contained>",
"<reference value=\"#1\"/>"
"<reference value=\"#" + patientUuid + "\"/>"
);
//@formatter:on

View File

@ -13,6 +13,7 @@ import ca.uhn.fhir.parser.PatientWithExtendedContactDstu3.CustomContactComponent
import ca.uhn.fhir.parser.XmlParserDstu3Test.TestPatientFor327;
import ca.uhn.fhir.parser.json.BaseJsonLikeValue.ScalarType;
import ca.uhn.fhir.parser.json.BaseJsonLikeValue.ValueType;
import ca.uhn.fhir.test.utilities.UuidUtils;
import ca.uhn.fhir.util.ClasspathUtil;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.FhirValidator;
@ -648,6 +649,8 @@ public class JsonParserDstu3Test {
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
String conditionUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(conditionUuid);
//@formatter:off
assertThat(encoded).contains(
@ -656,14 +659,14 @@ public class JsonParserDstu3Test {
"\"contained\": [",
"{",
"\"resourceType\": \"Condition\",",
"\"id\": \"1\"",
"\"id\": \"" + conditionUuid + "\"",
"}",
"],",
"\"extension\": [",
"{",
"\"url\": \"test\",",
"\"valueReference\": {",
"\"reference\": \"#1\"",
"\"reference\": \"#" + conditionUuid + "\"",
"}",
"}",
"],",
@ -920,19 +923,21 @@ public class JsonParserDstu3Test {
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
String conditionUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(conditionUuid);
//@formatter:off
assertThat(encoded).contains(
"\"resourceType\": \"Patient\"",
"\"contained\": [",
"\"resourceType\": \"Condition\"",
"\"id\": \"1\"",
"\"id\": \"" + conditionUuid + "\"",
"\"bodySite\": [",
"\"text\": \"BODY SITE\"",
"\"extension\": [",
"\"url\": \"testCondition\",",
"\"valueReference\": {",
"\"reference\": \"#1\"",
"\"reference\": \"#" + conditionUuid + "\"",
"\"birthDate\": \"2016-04-14\"",
"}"
);

View File

@ -13,6 +13,7 @@ import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.parser.FooMessageHeaderWithExplicitField.FooMessageSourceComponent;
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
import ca.uhn.fhir.parser.PatientWithCustomCompositeExtension.FooParentExtension;
import ca.uhn.fhir.test.utilities.UuidUtils;
import ca.uhn.fhir.util.ClasspathUtil;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Sets;
@ -560,8 +561,11 @@ public class XmlParserDstu3Test {
String encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
String organizationUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(organizationUuid);
assertThat(encoded).contains("<contained>");
assertThat(encoded).contains("<reference value=\"#1\"/>");
assertThat(encoded).contains("<reference value=\"#" + organizationUuid + "\"/>");
// Create a bundle with just the patient resource
Bundle b = new Bundle();
@ -570,35 +574,35 @@ public class XmlParserDstu3Test {
// Encode the bundle
encoded = xmlParser.encodeResourceToString(b);
ourLog.info(encoded);
assertThat(encoded).contains(Arrays.asList("<contained>", "<id value=\"1\"/>", "</contained>"));
assertThat(encoded).contains("<reference value=\"#1\"/>");
assertThat(encoded).contains(Arrays.asList("<contained>", "<id value=\"" + organizationUuid + "\"/>", "</contained>"));
assertThat(encoded).contains("<reference value=\"#" + organizationUuid + "\"/>");
assertThat(encoded).contains(Arrays.asList("<entry>", "</entry>"));
assertThat(encoded).doesNotContainPattern("(?s)<entry>.*</entry>.*<entry>");
// Re-parse the bundle
patient = (Patient) xmlParser.parseResource(xmlParser.encodeResourceToString(patient));
assertEquals("#1", patient.getManagingOrganization().getReference());
assertEquals("#" + organizationUuid, patient.getManagingOrganization().getReference());
assertNotNull(patient.getManagingOrganization().getResource());
org = (Organization) patient.getManagingOrganization().getResource();
assertEquals("#1", org.getIdElement().getValue());
assertEquals("#" + organizationUuid, org.getIdElement().getValue());
assertEquals("Contained Test Organization", org.getName());
// And re-encode a second time
encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded).contains(Arrays.asList("<contained>", "<Organization ", "<id value=\"1\"/>", "</Organization", "</contained>", "<reference value=\"#1\"/>"));
assertThat(encoded).contains(Arrays.asList("<contained>", "<Organization ", "<id value=\"" + organizationUuid + "\"/>", "</Organization", "</contained>", "<reference value=\"#" + organizationUuid + "\"/>"));
assertThat(encoded).doesNotContainPattern("(?s)<contained>.*<Org.*<contained>");
assertThat(encoded).contains("<reference value=\"#1\"/>");
assertThat(encoded).contains("<reference value=\"#" + organizationUuid + "\"/>");
// And re-encode once more, with the references cleared
patient.getContained().clear();
patient.getManagingOrganization().setReference(null);
encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded).contains(Arrays.asList("<contained>", "<Organization ", "<id value=\"1\"/>", "</Organization", "</contained>", "<reference value=\"#1\"/>"));
assertThat(encoded).contains(Arrays.asList("<contained>", "<Organization ", "<id value=\"" + organizationUuid + "\"/>", "</Organization", "</contained>", "<reference value=\"#" + organizationUuid + "\"/>"));
assertThat(encoded).doesNotContainPattern("(?s)<contained>.*<Org.*<contained>");
assertThat(encoded).contains("<reference value=\"#1\"/>");
assertThat(encoded).contains("<reference value=\"#" + organizationUuid + "\"/>");
// And re-encode once more, with the references cleared and a manually set local ID
patient.getContained().clear();
@ -629,6 +633,8 @@ public class XmlParserDstu3Test {
String output = parser.encodeResourceToString(dr);
ourLog.info(output);
String observationUuid = UuidUtils.findFirstUUID(output);
assertNotNull(observationUuid);
assertThat(output).contains(
"<DiagnosticReport xmlns=\"http://hl7.org/fhir\">",
@ -637,7 +643,7 @@ public class XmlParserDstu3Test {
"</meta>",
"<contained>",
"<Observation xmlns=\"http://hl7.org/fhir\">",
"<id value=\"1\"/>",
"<id value=\"" + observationUuid + "\"/>",
"<meta>",
"<profile value=\"http://custom_Observation\"/>",
"</meta>",
@ -646,7 +652,7 @@ public class XmlParserDstu3Test {
"</contained>",
"<status value=\"final\"/>",
"<result>",
"<reference value=\"#1\"/>",
"<reference value=\"#" + observationUuid + "\"/>",
"</result>",
"</DiagnosticReport>");
@ -657,7 +663,7 @@ public class XmlParserDstu3Test {
dr = (CustomDiagnosticReport) parser.parseResource(output);
assertEquals(DiagnosticReportStatus.FINAL, dr.getStatus());
assertEquals("#1", dr.getResult().get(0).getReference());
assertEquals("#" + observationUuid, dr.getResult().get(0).getReference());
obs = (CustomObservation) dr.getResult().get(0).getResource();
assertEquals(ObservationStatus.FINAL, obs.getStatus());
@ -680,18 +686,20 @@ public class XmlParserDstu3Test {
String output = parser.encodeResourceToString(dr);
ourLog.info(output);
String observationUuid = UuidUtils.findFirstUUID(output);
assertNotNull(observationUuid);
assertThat(output).contains(
"<DiagnosticReport xmlns=\"http://hl7.org/fhir\">",
"<contained>",
"<Observation xmlns=\"http://hl7.org/fhir\">",
"<id value=\"1\"/>",
"<id value=\"" + observationUuid + "\"/>",
"<status value=\"final\"/>",
"</Observation>",
"</contained>",
"<status value=\"final\"/>",
"<result>",
"<reference value=\"#1\"/>",
"<reference value=\"#" + observationUuid + "\"/>",
"</result>",
"</DiagnosticReport>");
@ -702,7 +710,7 @@ public class XmlParserDstu3Test {
dr = (DiagnosticReport) parser.parseResource(output);
assertEquals(DiagnosticReportStatus.FINAL, dr.getStatus());
assertEquals("#1", dr.getResult().get(0).getReference());
assertEquals("#" + observationUuid, dr.getResult().get(0).getReference());
obs = (Observation) dr.getResult().get(0).getResource();
assertEquals(ObservationStatus.FINAL, obs.getStatus());
@ -1282,17 +1290,19 @@ public class XmlParserDstu3Test {
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
String conditionUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(conditionUuid);
assertThat(encoded).contains(
"<Patient xmlns=\"http://hl7.org/fhir\">",
"<contained>",
"<Condition xmlns=\"http://hl7.org/fhir\">",
"<id value=\"1\"/>",
"<id value=\"" + conditionUuid + "\"/>",
"</Condition>",
"</contained>",
"<extension url=\"test\">",
"<valueReference>",
"<reference value=\"#1\"/>",
"<reference value=\"#" + conditionUuid + "\"/>",
"</valueReference>",
"</extension>",
"<birthDate value=\"2016-04-05\"/>",
@ -1359,10 +1369,12 @@ public class XmlParserDstu3Test {
IParser p = ourCtx.newXmlParser().setPrettyPrint(true);
String encoded = p.encodeResourceToString(medicationPrescript);
ourLog.info(encoded);
String medicationUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(medicationUuid);
assertThat(encoded).contains
("<MedicationRequest xmlns=\"http://hl7.org/fhir\">", "<contained>", "<Medication xmlns=\"http://hl7.org/fhir\">", "<id value=\"1\"/>", "<code>", "<coding>",
"<system value=\"urn:sys\"/>", "<code value=\"code1\"/>", "</coding>", "</code>", "</Medication>", "</contained>", "<medicationReference>", "<reference value=\"#1\"/>",
("<MedicationRequest xmlns=\"http://hl7.org/fhir\">", "<contained>", "<Medication xmlns=\"http://hl7.org/fhir\">", "<id value=\"" + medicationUuid + "\"/>", "<code>", "<coding>",
"<system value=\"urn:sys\"/>", "<code value=\"code1\"/>", "</coding>", "</code>", "</Medication>", "</contained>", "<medicationReference>", "<reference value=\"#" + medicationUuid + "\"/>",
"<display value=\"MedRef\"/>", "</medicationReference>", "</MedicationRequest>");
}
@ -1726,12 +1738,14 @@ public class XmlParserDstu3Test {
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
String conditionUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(conditionUuid);
assertThat(encoded).contains(
"<Patient xmlns=\"http://hl7.org/fhir\">",
"<contained>",
"<Condition xmlns=\"http://hl7.org/fhir\">",
"<id value=\"1\"/>",
"<id value=\"" + conditionUuid + "\"/>",
"<bodySite>",
"<text value=\"BODY SITE\"/>",
"</bodySite>",
@ -1739,7 +1753,7 @@ public class XmlParserDstu3Test {
"</contained>",
"<extension url=\"testCondition\">",
"<valueReference>",
"<reference value=\"#1\"/>",
"<reference value=\"#" + conditionUuid + "\"/>",
"</valueReference>",
"</extension>",
"<birthDate value=\"2016-04-14\"/>",

View File

@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.test.utilities.UuidUtils;
import ca.uhn.fhir.util.ClasspathUtil;
import ca.uhn.fhir.util.TestUtil;
import net.sf.json.JSON;
@ -58,6 +59,7 @@ import java.io.StringReader;
import java.util.Arrays;
import java.util.List;
import static ca.uhn.fhir.test.utilities.UuidUtils.UUID_PATTERN;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@ -408,7 +410,10 @@ public class JsonParserHl7OrgDstu2Test {
String encoded = jsonParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded).containsSubsequence(Arrays.asList("\"contained\": [", "\"id\": \"1\"", "\"identifier\"", "\"reference\": \"#1\""));
String organizationUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(organizationUuid);
assertThat(encoded).containsSubsequence(Arrays.asList("\"contained\": [", "\"id\": \"" + organizationUuid + "\"", "\"identifier\"", "\"reference\": \"#" + organizationUuid + "\""));
// Create a bundle with just the patient resource
Bundle b = new Bundle();
@ -417,21 +422,21 @@ public class JsonParserHl7OrgDstu2Test {
// Encode the bundle
encoded = jsonParser.encodeResourceToString(b);
ourLog.info(encoded);
assertThat(encoded).containsSubsequence(Arrays.asList("\"contained\": [", "\"id\": \"1\"", "\"identifier\"", "\"reference\": \"#1\""));
assertThat(encoded).containsSubsequence(Arrays.asList("\"contained\": [", "\"id\": \"" + organizationUuid + "\"", "\"identifier\"", "\"reference\": \"#" + organizationUuid + "\""));
// Re-parse the bundle
patient = (Patient) jsonParser.parseResource(jsonParser.encodeResourceToString(patient));
assertEquals("#1", patient.getManagingOrganization().getReference());
assertEquals("#" + organizationUuid, patient.getManagingOrganization().getReference());
assertNotNull(patient.getManagingOrganization().getResource());
org = (Organization) patient.getManagingOrganization().getResource();
assertEquals("#1", org.getIdElement().getValue());
assertEquals("#" + organizationUuid, org.getIdElement().getValue());
assertEquals("Contained Test Organization", org.getName());
// And re-encode a second time
encoded = jsonParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded).containsSubsequence(Arrays.asList("\"contained\": [", "\"id\": \"1\"", "\"identifier\"", "\"reference\": \"#1\""));
assertThat(encoded).containsSubsequence(Arrays.asList("\"contained\": [", "\"id\": \"" + organizationUuid + "\"", "\"identifier\"", "\"reference\": \"#" + organizationUuid + "\""));
assertThat(encoded).doesNotContainPattern("(?s)\"contained\":.*\\[.*\"contained\":");
// And re-encode once more, with the references cleared
@ -439,7 +444,7 @@ public class JsonParserHl7OrgDstu2Test {
patient.getManagingOrganization().setReference(null);
encoded = jsonParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded).containsSubsequence(Arrays.asList("\"contained\": [", "\"id\": \"1\"", "\"identifier\"", "\"reference\": \"#1\""));
assertThat(encoded).containsSubsequence(Arrays.asList("\"contained\": [", "\"id\": \"" + organizationUuid + "\"", "\"identifier\"", "\"reference\": \"#" + organizationUuid + "\""));
assertThat(encoded).doesNotContainPattern("(?s).*\"contained\":.*\\[.*\"contained\":");
// And re-encode once more, with the references cleared and a manually set local ID
@ -472,13 +477,16 @@ public class JsonParserHl7OrgDstu2Test {
// Encode the buntdle
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(b);
ourLog.info(encoded);
assertThat(encoded).containsSubsequence(Arrays.asList("\"contained\"", "resourceType\": \"Organization", "id\": \"1\""));
assertThat(encoded).contains("reference\": \"#1\"");
String organizationUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(organizationUuid);
assertThat(encoded).containsSubsequence(Arrays.asList("\"contained\"", "resourceType\": \"Organization", "id\": \"" + organizationUuid + "\""));
assertThat(encoded).contains("reference\": \"#" + organizationUuid + "\"");
encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded).containsSubsequence(Arrays.asList("\"contained\"", "resourceType\": \"Organization", "id\": \"1\""));
assertThat(encoded).contains("reference\": \"#1\"");
assertThat(encoded).containsSubsequence(Arrays.asList("\"contained\"", "resourceType\": \"Organization", "id\": \"" + organizationUuid + "\""));
assertThat(encoded).contains("reference\": \"#" + organizationUuid + "\"");
}
@Test
@ -696,7 +704,7 @@ public class JsonParserHl7OrgDstu2Test {
String enc = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(list);
ourLog.info(enc);
assertThat(enc).contains("\"id\": \"1\"");
assertThat(enc).containsPattern("\"id\": \"" + UUID_PATTERN);
List_ parsed = ourCtx.newJsonParser().parseResource(List_.class,enc);
assertEquals(Patient.class, parsed.getEntry().get(0).getItem().getResource().getClass());

View File

@ -9,6 +9,7 @@ import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.parser.JsonParserHl7OrgDstu2Test.MyPatientWithOneDeclaredAddressExtension;
import ca.uhn.fhir.parser.JsonParserHl7OrgDstu2Test.MyPatientWithOneDeclaredExtension;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.test.utilities.UuidUtils;
import net.sf.json.JSON;
import net.sf.json.JSONSerializer;
import org.apache.commons.io.IOUtils;
@ -216,8 +217,11 @@ public class XmlParserHl7OrgDstu2Test {
String encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
String organizationUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(organizationUuid);
assertThat(encoded).contains("<contained>");
assertThat(encoded).contains("<reference value=\"#1\"/>");
assertThat(encoded).contains("<reference value=\"#" + organizationUuid + "\"/>");
// Create a bundle with just the patient resource
Bundle b = new Bundle();
@ -226,37 +230,37 @@ public class XmlParserHl7OrgDstu2Test {
// Encode the bundle
encoded = xmlParser.encodeResourceToString(b);
ourLog.info(encoded);
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<id value=\"1\"/>", "</contained>"));
assertThat(encoded).contains("<reference value=\"#1\"/>");
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<id value=\"" + organizationUuid + "\"/>", "</contained>"));
assertThat(encoded).contains("<reference value=\"#" + organizationUuid + "\"/>");
assertThat(encoded).containsSubsequence(Arrays.asList("<entry>", "</entry>"));
assertThat(encoded).doesNotContainPattern("(?s)<entry>.*</entry>.*<entry>");
// Re-parse the bundle
patient = (Patient) xmlParser.parseResource(xmlParser.encodeResourceToString(patient));
assertEquals("#1", patient.getManagingOrganization().getReferenceElement().getValue());
assertEquals("#" + organizationUuid, patient.getManagingOrganization().getReferenceElement().getValue());
assertNotNull(patient.getManagingOrganization().getResource());
org = (Organization) patient.getManagingOrganization().getResource();
assertEquals("#1", org.getIdElement().getValue());
assertEquals("#" + organizationUuid, org.getIdElement().getValue());
assertEquals("Contained Test Organization", org.getName());
// And re-encode a second time
encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<Organization ", "<id value=\"1\"/>",
"</Organization", "</contained>", "<reference value=\"#1\"/>"));
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<Organization ", "<id value=\"" + organizationUuid + "\"/>",
"</Organization", "</contained>", "<reference value=\"#" + organizationUuid + "\"/>"));
assertThat(encoded).doesNotContainPattern("(?s)<contained>.*<Org.*<contained>");
assertThat(encoded).contains("<reference value=\"#1\"/>");
assertThat(encoded).contains("<reference value=\"#" + organizationUuid + "\"/>");
// And re-encode once more, with the references cleared
patient.getContained().clear();
patient.getManagingOrganization().setReference(null);
encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<Organization ", "<id value=\"1\"/>",
"</Organization", "</contained>", "<reference value=\"#1\"/>"));
assertThat(encoded).containsSubsequence(Arrays.asList("<contained>", "<Organization ", "<id value=\"" + organizationUuid + "\"/>",
"</Organization", "</contained>", "<reference value=\"#" + organizationUuid + "\"/>"));
assertThat(encoded).doesNotContainPattern("(?s)<contained>.*<Org.*<contained>");
assertThat(encoded).contains("<reference value=\"#1\"/>");
assertThat(encoded).contains("<reference value=\"#" + organizationUuid + "\"/>");
// And re-encode once more, with the references cleared and a manually set
// local ID
@ -969,13 +973,15 @@ public class XmlParserHl7OrgDstu2Test {
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
String conditionUuid = UuidUtils.findFirstUUID(encoded);
assertNotNull(conditionUuid);
//@formatter:off
assertThat(encoded).containsSubsequence(
"<Patient xmlns=\"http://hl7.org/fhir\">",
"<contained>",
"<Condition xmlns=\"http://hl7.org/fhir\">",
"<id value=\"1\"/>",
"<id value=\"" + conditionUuid + "\"/>",
"<bodySite>",
"<text value=\"BODY SITE\"/>",
"</bodySite>",
@ -983,7 +989,7 @@ public class XmlParserHl7OrgDstu2Test {
"</contained>",
"<extension url=\"testCondition\">",
"<valueReference>",
"<reference value=\"#1\"/>",
"<reference value=\"#" + conditionUuid + "\"/>",
"</valueReference>",
"</extension>",
"<birthDate value=\"2016-04-14\"/>",

View File

@ -7,6 +7,7 @@ import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.test.BaseTest;
import ca.uhn.fhir.util.BundleBuilder;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Sets;
@ -23,10 +24,12 @@ 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;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Medication;
import org.hl7.fhir.r4.model.MedicationDispense;
@ -43,6 +46,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,7 +59,10 @@ 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.sql.Ref;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
@ -261,6 +268,36 @@ 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\":\""+ specimen.getId().replaceFirst("#", "") +"\"}]");
//Then: Obs should contain one local contained specimen, and one local contained pract
assertThat(encoded).contains("\"resource\":{\"resourceType\":\"Observation\",\"contained\":[{\"resourceType\":\"Specimen\",\"id\":\""+ specimen.getId().replaceFirst("#", "") +"\"},{\"resourceType\":\"Practitioner\",\"id\":\"" + practitioner.getId().replaceAll("#","") + "\"}]");
assertThat(encoded).contains("\"performer\":[{\"reference\":\""+practitioner.getId()+"\"}],\"specimen\":{\"reference\":\""+specimen.getId()+"\"}");
//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);
}
@ -279,8 +316,9 @@ public class JsonParserR4Test extends BaseTest {
ourCtx.getParserOptions().setAutoContainReferenceTargetsWithNoId(true);
encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(md);
assertEquals("{\"resourceType\":\"MedicationDispense\",\"contained\":[{\"resourceType\":\"Medication\",\"id\":\"1\",\"code\":{\"text\":\"MED\"}}],\"identifier\":[{\"value\":\"DISPENSE\"}],\"medicationReference\":{\"reference\":\"#1\"}}", encoded);
String guidWithHash = med.getId();
String withoutHash = guidWithHash.replace("#", "");
assertThat(encoded).contains("{\"resourceType\":\"MedicationDispense\",\"contained\":[{\"resourceType\":\"Medication\",\"id\":\"" + withoutHash + "\",\"code\":{\"text\":\"MED\"}}],\"identifier\":[{\"value\":\"DISPENSE\"}],\"medicationReference\":{\"reference\":\"" + guidWithHash +"\"}}"); //Note we dont check exact ID since its a GUID
}
@Test
@ -571,7 +609,7 @@ public class JsonParserR4Test extends BaseTest {
obs = ourCtx.newJsonParser().parseResource(Observation.class, encoded);
assertEquals("#1", obs.getContained().get(0).getId());
assertEquals("#2", obs.getContained().get(1).getId());
assertEquals(enc.getId(), obs.getContained().get(1).getId());
pt = (Patient) obs.getSubject().getResource();
assertEquals("FAM", pt.getNameFirstRep().getFamily());
@ -600,7 +638,7 @@ public class JsonParserR4Test extends BaseTest {
obs = ourCtx.newJsonParser().parseResource(Observation.class, encoded);
assertEquals("#1", obs.getContained().get(0).getId());
assertEquals("#2", obs.getContained().get(1).getId());
assertEquals(pt.getId(), obs.getContained().get(1).getId());
pt = (Patient) obs.getSubject().getResource();
assertEquals("FAM", pt.getNameFirstRep().getFamily());
@ -620,13 +658,14 @@ public class JsonParserR4Test extends BaseTest {
ourLog.info(encoded);
mr = ourCtx.newJsonParser().parseResource(MedicationRequest.class, encoded);
mr.setMedication(new Reference(new Medication().setStatus(Medication.MedicationStatus.ACTIVE)));
Medication med = new Medication().setStatus(Medication.MedicationStatus.ACTIVE);
mr.setMedication(new Reference(med));
encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(mr);
ourLog.info(encoded);
mr = ourCtx.newJsonParser().parseResource(MedicationRequest.class, encoded);
assertEquals("#1", mr.getContained().get(0).getId());
assertEquals("#2", mr.getContained().get(1).getId());
assertEquals(pract.getId(), mr.getContained().get(0).getId());
assertEquals(med.getId(), mr.getContained().get(1).getId());
}

View File

@ -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;
@ -62,6 +67,7 @@ import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import static ca.uhn.fhir.test.utilities.UuidUtils.HASH_UUID_PATTERN;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@ -188,12 +194,12 @@ public class FhirTerserR4Test {
FhirTerser.ContainedResources contained = myCtx.newTerser().containResources(mr, FhirTerser.OptionsEnum.MODIFY_RESOURCE, FhirTerser.OptionsEnum.STORE_AND_REUSE_RESULTS);
assertEquals("#1", mr.getContained().get(0).getId());
assertEquals("#2", mr.getContained().get(1).getId());
assertThat(mr.getContained().get(0).getId()).containsPattern(HASH_UUID_PATTERN);
assertThat(mr.getContained().get(1).getId()).containsPattern(HASH_UUID_PATTERN);
assertEquals(ResourceType.Medication, mr.getContained().get(0).getResourceType());
assertEquals(ResourceType.Practitioner, mr.getContained().get(1).getResourceType());
assertEquals("#1", mr.getMedicationReference().getReference());
assertEquals("#2", mr.getRequester().getReference());
assertEquals(mr.getContained().get(0).getId(), mr.getMedicationReference().getReference());
assertEquals(mr.getContained().get(1).getId(), mr.getRequester().getReference());
FhirTerser.ContainedResources secondPass = myCtx.newTerser().containResources(mr, FhirTerser.OptionsEnum.MODIFY_RESOURCE, FhirTerser.OptionsEnum.STORE_AND_REUSE_RESULTS);
assertThat(secondPass).isSameAs(contained);
@ -212,12 +218,12 @@ public class FhirTerserR4Test {
myCtx.newTerser().containResources(medAdmin, FhirTerser.OptionsEnum.MODIFY_RESOURCE, FhirTerser.OptionsEnum.STORE_AND_REUSE_RESULTS);
assertEquals("#1", medAdmin.getContained().get(0).getId());
assertEquals("#2", medAdmin.getContained().get(1).getId());
assertThat(medAdmin.getContained().get(0).getId()).containsPattern(HASH_UUID_PATTERN);
assertThat(medAdmin.getContained().get(1).getId()).containsPattern(HASH_UUID_PATTERN);
assertEquals(ResourceType.Medication, medAdmin.getContained().get(0).getResourceType());
assertEquals(ResourceType.Substance, medAdmin.getContained().get(1).getResourceType());
assertEquals("#1", medAdmin.getMedicationReference().getReference());
assertEquals("#2", ((Medication) (medAdmin.getContained().get(0))).getIngredientFirstRep().getItemReference().getReference());
assertEquals(medAdmin.getContained().get(0).getId(), medAdmin.getMedicationReference().getReference());
assertEquals(medAdmin.getContained().get(1).getId(), ((Medication) (medAdmin.getContained().get(0))).getIngredientFirstRep().getItemReference().getReference());
}
@ -1545,23 +1551,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());
}
/**

View File

@ -0,0 +1,47 @@
/*-
* #%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;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class UuidUtils {
private UuidUtils() {
}
public static final String UUID_PATTERN = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}";
public static final String HASH_UUID_PATTERN = "#" + UUID_PATTERN;
private static final Pattern COMPILED_UUID_PATTERN = Pattern.compile(UUID_PATTERN);
/**
* Extracts first UUID from String.
* Returns null if no UUID present in the String.
*/
public static String findFirstUUID(String input) {
Matcher matcher = COMPILED_UUID_PATTERN.matcher(input);
if (matcher.find()) {
return matcher.group();
}
return null;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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;
String url = theSystem != null ? theSystem.getValue() : null;
String code = theCode != null ? theCode.getValue() : null;
return getTerminologyResponse("$lookup", url, code);
}
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;
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;
String url = theValueSetUrl != null ? theValueSetUrl.getValue() : null;
String code = theCode != null ? theCode.getValue() : null;
return getTerminologyResponse("$validate-code", url, code);
}
return myReturnParams;
}
@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;
}
}
}

View File

@ -0,0 +1,29 @@
package ca.uhn.fhir.test.utilities;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
public class UuidUtilsTest {
@Test
void testFindsUuid() {
String xml = "<id value=#cdb6dfa1-74b7-4ea9-88e0-d3afaef8c016/>";
String uuid = UuidUtils.findFirstUUID(xml);
assertEquals("cdb6dfa1-74b7-4ea9-88e0-d3afaef8c016", uuid);
}
@Test
void testFindsFirstUuid() {
String xml = "<id value=#cdb6dfa1-74b7-4ea9-88e0-d3afaef8c016/><id value=#da8a08e3-ddf5-4a62-baae-3e8c3ea04687/>";
String uuid = UuidUtils.findFirstUUID(xml);
assertEquals("cdb6dfa1-74b7-4ea9-88e0-d3afaef8c016", uuid);
}
@Test
void testNoUuidReturnsNull() {
String xml = "<id value=x />";
assertNull(UuidUtils.findFirstUUID(xml));
}
}

View File

@ -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,

Some files were not shown because too many files have changed in this diff Show More