diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java index 71d561c1db3d..fdbc4ca31d77 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java @@ -440,74 +440,259 @@ default String getName() { return "Unknown " + getFhirContext().getVersion().getVersion() + " Validation Support"; } + /** + * Defines codes in system http://hl7.org/fhir/issue-severity. + */ + /* 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 http://hl7.org/fhir/issue-severity. + * @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 http://hl7.org/fhir/issue-type. + * 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; + } + + /** + * Retrieve the corresponding code from system http://hl7.org/fhir/issue-type. + * @return the code + */ + public String getCode() { + return myCode; + } } - enum CodeValidationIssueCoding { - VS_INVALID, - NOT_FOUND, - NOT_IN_VS, + /** + * 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 myCodings; + + public CodeValidationIssueDetails(String theText) { + myText = theText; + } + + // intentionally not exposed + void addCoding(CodeValidationIssueCoding theCoding) { + getCodings().add(theCoding); + } - INVALID_CODE, - INVALID_DISPLAY, - OTHER + 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 getCodings() { + if (myCodings == null) { + myCodings = new ArrayList<>(); + } + return myCodings; + } } - class CodeValidationIssue { + /** + * Defines codes that can be part of the details of an issue. + * There are some constants available (pre-defined) for codes for system http://hl7.org/fhir/tools/CodeSystem/tx-issue-type. + * 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; + } + } - private final String myMessage; + /** + * This is a hapi-fhir internal version agnostic object holding information about a validation issue. + * An alternative (which requires significant refactoring) would be to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult instead. + */ + class CodeValidationIssue { + private final String myDiagnostics; private final IssueSeverity mySeverity; private final CodeValidationIssueCode myCode; - private 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 String getType() { } } + /** + * 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 @@ class CodeValidationResult { private String myDisplay; private String mySourceDetails; - private List myCodeValidationIssues; + private List myIssues; public CodeValidationResult() { super(); @@ -771,23 +960,48 @@ public CodeValidationResult setSeverity(IssueSeverity theSeverity) { return this; } + /** + * @deprecated Please use method {@link #getIssues()} instead. + */ + @Deprecated(since = "7.4.6") public List 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 theCodeValidationIssues) { - myCodeValidationIssues = new ArrayList<>(theCodeValidationIssues); - return this; + 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 List getIssues() { + if (myIssues == null) { + myIssues = new ArrayList<>(); + } + return myIssues; + } + + public CodeValidationResult setIssues(List theIssues) { + myIssues = new ArrayList<>(theIssues); + return this; + } + + public CodeValidationResult addIssue(CodeValidationIssue theCodeValidationIssue) { + getIssues().add(theCodeValidationIssue); + return this; + } + public boolean isOk() { return isNotBlank(myCode); } @@ -811,17 +1025,19 @@ public LookupCodeResult asLookupCodeResult(String theSearchedForSystem, String t 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 IBaseParameters toParameters(FhirContext theContext) { 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; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java index f0559373e0a9..ce9323338406 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java @@ -105,7 +105,6 @@ public abstract class BaseParser implements IParser { private static final Set notEncodeForContainedResource = new HashSet<>(Arrays.asList("security", "versionId", "lastUpdated")); - private FhirTerser.ContainedResources myContainedResources; private boolean myEncodeElementsAppliesToChildResourcesOnly; private final FhirContext myContext; private Collection myDontEncodeElements; @@ -183,12 +182,15 @@ protected Iterable compositeChildIterator( } 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 String encodeResourceToString(IBaseResource theResource) throws DataForma @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 void encodeToWriter(IBase theElement, Writer theWriter) throws DataFormat } 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 @@ protected String getCompositeElementId(IBase theElement) { return elementId; } - FhirTerser.ContainedResources getContainedResources() { - return myContainedResources; - } - @Override public Set getDontStripVersionsFromReferencesAtPaths() { return myDontStripVersionsFromReferencesAtPaths; @@ -539,10 +539,11 @@ public boolean getSuppressNarratives() { 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 @@ protected List preProcessValues( */ 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 @@ protected boolean shouldEncodeResource(String theName, EncodeContext theEncodeCo 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 @@ protected void containResourcesInReferences(IBaseResource theResource) { } } - myContainedResources = getContext().newTerser().containResources(theResource); + theContext.setContainedResources(getContext().newTerser().containResources(theResource)); } static class ChildNameAndDef { @@ -1034,8 +1036,12 @@ class EncodeContext extends EncodeContextPath { private final List myEncodeElementPaths; private final Set myEncodeElementsAppliesToResourceTypes; private final List myDontEncodeElementPaths; + private FhirTerser.ContainedResources myContainedResources; - public EncodeContext(BaseParser theParser, ParserOptions theParserOptions) { + public EncodeContext( + BaseParser theParser, + ParserOptions theParserOptions, + FhirTerser.ContainedResources theContainedResources) { Collection encodeElements = theParser.myEncodeElements; Collection dontEncodeElements = theParser.myDontEncodeElements; if (isSummaryMode()) { @@ -1058,6 +1064,8 @@ public EncodeContext(BaseParser theParser, ParserOptions theParserOptions) { dontEncodeElements.stream().map(EncodeContextPath::new).collect(Collectors.toList()); } + myContainedResources = theContainedResources; + myEncodeElementsAppliesToResourceTypes = ParserUtil.determineApplicableResourceTypesForTerserPaths(myEncodeElementPaths); } @@ -1065,6 +1073,14 @@ public EncodeContext(BaseParser theParser, ParserOptions theParserOptions) { private Map> getCompositeChildrenCache() { return myCompositeChildrenCache; } + + public FhirTerser.ContainedResources getContainedResources() { + return myContainedResources; + } + + public void setContainedResources(FhirTerser.ContainedResources theContainedResources) { + myContainedResources = theContainedResources; + } } protected class CompositeChildElement { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java index 0b25304ee5fd..5731742ac1fe 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java @@ -54,6 +54,7 @@ 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 String toString() { } case CONTAINED_RESOURCE_LIST: case CONTAINED_RESOURCES: { - List containedResources = getContainedResources().getContainedResources(); + List 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 @@ private void encodeCompositeElementChildrenToStreamWriter( 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 void encodeResourceToJsonLikeWriter(IBaseResource theResource, BaseJsonLi + 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 @@ private void encodeResourceToJsonStreamWriter( } if (!theContainedResource) { - containResourcesInReferences(theResource); + containResourcesInReferences(theResource, theEncodeContext); } RuntimeResourceDefinition resDef = getContext().getResourceDefinition(theResource); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java index b75ee9c897f8..18572824fee0 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java @@ -191,7 +191,7 @@ private Resource encodeResourceToRDFStreamWriter( } if (!containedResource) { - containResourcesInReferences(resource); + containResourcesInReferences(resource, encodeContext); } if (!(resource instanceof IAnyResource)) { @@ -354,7 +354,7 @@ private Model encodeChildElementToStreamWriter( try { if (element == null || element.isEmpty()) { - if (!isChildContained(childDef, includedResource)) { + if (!isChildContained(childDef, includedResource, theEncodeContext)) { return rdfModel; } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java index 7a5aaa021bfc..71b83de8d8ec 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java @@ -295,7 +295,7 @@ private void encodeChildElementToStreamWriter( 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 @@ private void encodeChildElementToStreamWriter( * 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 @@ private void encodeResourceToXmlStreamWriter( } if (!theContainedResource) { - containResourcesInReferences(theResource); + containResourcesInReferences(theResource, theEncodeContext); } theEventWriter.writeStartElement(resDef.getName()); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/repository/Repository.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/repository/Repository.java index e859a9ae569c..c272656f47a2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/repository/Repository.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/repository/Repository.java @@ -28,6 +28,8 @@ 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 @@ MethodOutcome delete( // Querying starts here + /** + * Searches this repository + * + * @see FHIR search + * + * @param a Bundle type + * @param 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 search( + Class bundleType, Class resourceType, Multimap> searchParameters) { + return this.search(bundleType, resourceType, searchParameters, Collections.emptyMap()); + } + /** * Searches this repository * @@ -264,9 +283,32 @@ default B search( B search( Class bundleType, Class resourceType, - Map> searchParameters, + Multimap> searchParameters, Map headers); + /** + * Searches this repository + * + * @see FHIR search + * + * @param a Bundle type + * @param 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 search( + Class bundleType, + Class resourceType, + Map> searchParameters, + Map headers) { + ArrayListMultimap> multimap = ArrayListMultimap.create(); + searchParameters.forEach(multimap::put); + return this.search(bundleType, resourceType, multimap, headers); + } + // Paging starts here /** diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java index 6eab1ce9ed37..eebb07b63c3b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java @@ -38,7 +38,6 @@ 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.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.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 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 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 boolean acceptUndeclaredExtension( private void containResourcesForEncoding( ContainedResources theContained, IBaseResource theResource, boolean theModifyResource) { List 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 @@ private void containResourcesForEncoding( 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 @@ private interface ICompartmentOwnerVisitor { } public static class ContainedResources { - private long myNextContainedId = 1; - private List myResourceList; private IdentityHashMap myResourceToIdMap; private Map myExistingIdToContainedResourceMap; @@ -1782,6 +1800,11 @@ public Map getExistingIdToContainedResource() { } 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 IIdType addContained(IBaseResource theResource) { 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 boolean isEmpty() { 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 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)); - } - } - } - } } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java index 8d4867594cf0..0f89ed04d071 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java @@ -170,6 +170,7 @@ public enum VersionEnum { V7_5_0, V7_6_0, + V7_6_1, V7_7_0, V7_8_0; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/AdapterManager.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/AdapterManager.java new file mode 100644 index 000000000000..93a7b054a39f --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/AdapterManager.java @@ -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 myAdapterFactories = new HashSet<>(); + + /** + * Hidden to force shared use of the public INSTANCE. + */ + AdapterManager() {} + + public @Nonnull Optional getAdapter(Object theObject, Class 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); + } +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/AdapterUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/AdapterUtils.java new file mode 100644 index 000000000000..369b7ab324e1 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/AdapterUtils.java @@ -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 Optional adapt(Object theObject, Class 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); + } +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdaptable.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdaptable.java new file mode 100644 index 000000000000..4310c294fbaf --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdaptable.java @@ -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 @Nonnull Optional getAdapter(@Nonnull Class theTargetType) { + return AdapterUtils.adapt(this, theTargetType); + } +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdapterFactory.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdapterFactory.java new file mode 100644 index 000000000000..f4aa88edde56 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdapterFactory.java @@ -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. + */ + Optional getAdapter(Object theObject, Class theAdapterType); + + /** + * @return the collection of adapter target types handled by this factory. + */ + Collection> getAdapters(); +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdapterManager.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdapterManager.java new file mode 100644 index 000000000000..d199267f9277 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdapterManager.java @@ -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 { + Optional getAdapter(Object theTarget, Class theAdapter); +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/package-info.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/package-info.java new file mode 100644 index 000000000000..c053c1b0248d --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/package-info.java @@ -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. + *

+ * 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. + *

+ *

+ * For implementors, you can support adaptation via two mechanisms: + *

    + *
  • by implementing {@link ca.uhn.fhir.util.adapters.IAdaptable} directly on a class to provide supported adapters, + *
  • 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}.
  • + *
+ * The AdapterUtils.adapt() supports both of these. + *

+ * Inspired by the Eclipse runtime. + */ +package ca.uhn.fhir.util.adapters; diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/adapters/AdapterManagerTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/adapters/AdapterManagerTest.java new file mode 100644 index 000000000000..ee621533587c --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/adapters/AdapterManagerTest.java @@ -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 Optional getAdapter(Object theObject, Class 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> getAdapters() { + return List.of(Integer.class); + } + } +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/adapters/AdapterUtilsTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/adapters/AdapterUtilsTest.java new file mode 100644 index 000000000000..c7dfef8661bb --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/adapters/AdapterUtilsTest.java @@ -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 Optional getAdapter(@Nonnull Class 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 Optional getAdapter(Object theObject, Class theAdapterType) { + if (theObject instanceof ManagerAdaptableClass && theAdapterType == InterfaceA.class) { + T adapter = theAdapterType.cast(buildInterfaceAWrapper(theObject)); + return Optional.of(adapter); + } + return Optional.empty(); + } + + @Override + public Collection> getAdapters() { + return Set.of(InterfaceA.class); + } + } +} diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6403-json-parser-bugs-again.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6403-json-parser-bugs-again.yaml new file mode 100644 index 000000000000..8ef5947d66e8 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6403-json-parser-bugs-again.yaml @@ -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." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6403-json-parser-bugs.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6403-json-parser-bugs.yaml new file mode 100644 index 000000000000..1c76ceaa3a8b --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6403-json-parser-bugs.yaml @@ -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." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6419-fix-reindex-optimize-storage-all-versions-posgtres.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6419-fix-reindex-optimize-storage-all-versions-posgtres.yaml new file mode 100644 index 000000000000..4d0100526ad8 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6419-fix-reindex-optimize-storage-all-versions-posgtres.yaml @@ -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." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6420-fix-reindex-optimize-storage-all-versions-for-a-single-resource.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6420-fix-reindex-optimize-storage-all-versions-for-a-single-resource.yaml new file mode 100644 index 000000000000..ee409dc43ee1 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6420-fix-reindex-optimize-storage-all-versions-for-a-single-resource.yaml @@ -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." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6422-fixes-remote-terminology-issues.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6422-fixes-remote-terminology-issues.yaml new file mode 100644 index 000000000000..9b6dc6320ce9 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6422-fixes-remote-terminology-issues.yaml @@ -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." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6440-fix-hooks-not-called-for-precheck-for-cached-search.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6440-fix-hooks-not-called-for-precheck-for-cached-search.yaml new file mode 100644 index 000000000000..86f16018012d --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6440-fix-hooks-not-called-for-precheck-for-cached-search.yaml @@ -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." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6445-repository-api-multimap.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6445-repository-api-multimap.yaml new file mode 100644 index 000000000000..841287f278d4 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6445-repository-api-multimap.yaml @@ -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`" diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6451-bulk-import-job-status-not-changing-after-activation.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6451-bulk-import-job-status-not-changing-after-activation.yaml new file mode 100644 index 000000000000..ce62822d3707 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6451-bulk-import-job-status-not-changing-after-activation.yaml @@ -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." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6467-hibernate-search-lucene-version-incompatibility.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6467-hibernate-search-lucene-version-incompatibility.yaml new file mode 100644 index 000000000000..f5c95876cd11 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6467-hibernate-search-lucene-version-incompatibility.yaml @@ -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." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6489-change-index-add-concurrency-default.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6489-change-index-add-concurrency-default.yaml new file mode 100644 index 000000000000..86a4c3d7a59f --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6489-change-index-add-concurrency-default.yaml @@ -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." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/changes.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/changes.yaml index c900db52e430..ac97e01fc904 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/changes.yaml +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/changes.yaml @@ -4,7 +4,7 @@ title: "The version of a few dependencies have been bumped to more recent versions (dependent HAPI modules listed in brackets):
    -
  • org.hl7.fhir.core (Base): 6.3.18 -> 6.3.25
  • +
  • org.hl7.fhir.core (Base): 6.3.18 -> 6.4.0
  • Bower/Moment.js (hapi-fhir-testpage-overlay): 2.27.0 -> 2.29.4
  • htmlunit (Base): 3.9.0 -> 3.11.0
  • Elasticsearch (Base): 8.11.1 -> 8.14.3
  • diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6502-profile-can-be-reference-param.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6502-profile-can-be-reference-param.yaml new file mode 100644 index 000000000000..a0a8d5d1d429 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6502-profile-can-be-reference-param.yaml @@ -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." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/clinical_reasoning/questionnaires.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/clinical_reasoning/questionnaires.md index 8b2426a7d65d..d409d2545824 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/clinical_reasoning/questionnaires.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/clinical_reasoning/questionnaires.md @@ -48,24 +48,24 @@ Additional parameters have been added to support CQL evaluation. The following parameters are supported for the `Questionnaire/$populate` operation: -| Parameter | Type | Description | -|---------------------|---------------|-------------| -| questionnaire | Questionnaire | The Questionnaire to populate. Used when the operation is invoked at the 'type' level. | -| canonical | canonical | The canonical identifier for the Questionnaire (optionally version-specific). | -| url | uri | Canonical URL of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters. | -| version | string | Version of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters. | -| subject | Reference | The resource that is to be the QuestionnaireResponse.subject. The QuestionnaireResponse instance will reference the provided subject. | -| context | | Resources containing information to be used to help populate the QuestionnaireResponse. | -| context.name | string | The name of the launchContext or root Questionnaire variable the passed content should be used as for population purposes. The name SHALL correspond to a launchContext or variable delared at the root of the Questionnaire. | -| context.reference | Reference | The actual resource (or resources) to use as the value of the launchContext or variable. | -| local | boolean | Whether the server should use what resources and other knowledge it has about the referenced subject when pre-populating answers to questions. | -| launchContext | Extension | The [Questionnaire Launch Context](https://hl7.org/fhir/uv/sdc/StructureDefinition-sdc-questionnaire-launchContext.html) extension containing Resources that provide context for form processing logic (pre-population) when creating/displaying/editing a QuestionnaireResponse. | -| parameters | Parameters | Any input parameters defined in libraries referenced by the Questionnaire. | -| useServerData | boolean | Whether to use data from the server performing the evaluation. | -| data | Bundle | Data to be made available during CQL evaluation. | -| dataEndpoint | Endpoint | An endpoint to use to access data referenced by retrieve operations in libraries referenced by the Questionnaire. | -| contentEndpoint | Endpoint | An endpoint to use to access content (i.e. libraries) referenced by the Questionnaire. | -| terminologyEndpoint | Endpoint | An endpoint to use to access terminology (i.e. valuesets, codesystems, and membership testing) referenced by the Questionnaire. | +| Parameter | Type | Description | +|---------------------|--------------------|-------------| +| questionnaire | Questionnaire | The Questionnaire to populate. Used when the operation is invoked at the 'type' level. | +| canonical | canonical | The canonical identifier for the Questionnaire (optionally version-specific). | +| url | uri | Canonical URL of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters. | +| version | string | Version of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters. | +| subject | Reference | The resource that is to be the QuestionnaireResponse.subject. The QuestionnaireResponse instance will reference the provided subject. | +| context | | Resources containing information to be used to help populate the QuestionnaireResponse. | +| context.name | string | The name of the launchContext or root Questionnaire variable the passed content should be used as for population purposes. The name SHALL correspond to a launchContext or variable declared at the root of the Questionnaire. | +| context.content | Reference/Resource | The actual resource (or reference) to use as the value of the launchContext or variable. | +| local | boolean | Whether the server should use what resources and other knowledge it has about the referenced subject when pre-populating answers to questions. | +| launchContext | Extension | The [Questionnaire Launch Context](https://hl7.org/fhir/uv/sdc/StructureDefinition-sdc-questionnaire-launchContext.html) extension containing Resources that provide context for form processing logic (pre-population) when creating/displaying/editing a QuestionnaireResponse. | +| parameters | Parameters | Any input parameters defined in libraries referenced by the Questionnaire. | +| useServerData | boolean | Whether to use data from the server performing the evaluation. | +| data | Bundle | Data to be made available during CQL evaluation. | +| dataEndpoint | Endpoint | An endpoint to use to access data referenced by retrieve operations in libraries referenced by the Questionnaire. | +| contentEndpoint | Endpoint | An endpoint to use to access content (i.e. libraries) referenced by the Questionnaire. | +| terminologyEndpoint | Endpoint | An endpoint to use to access terminology (i.e. valuesets, codesystems, and membership testing) referenced by the Questionnaire. | ## Extract diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/imprt/svc/BulkDataImportSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/imprt/svc/BulkDataImportSvcImpl.java index 42eba8257550..bf72dbf645ce 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/imprt/svc/BulkDataImportSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/imprt/svc/BulkDataImportSvcImpl.java @@ -217,6 +217,12 @@ private ActivateJobResult doActivateNextReadyJob() { 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 BulkImportJobJson fetchJob(String theBiJobId) { } @Override + @Transactional public JobInfo getJobStatus(String theBiJobId) { BulkImportJobEntity theJob = findJobByBiJobId(theBiJobId); return new JobInfo() diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index c7fc08cae7cb..95f8cdbe480e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -134,6 +134,7 @@ 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 @@ private void reindexOptimizeStorage( 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 historyEntities = myResourceHistoryTableDao.findForResourceIdAndReturnEntitiesAndFetchProvenance( - PageRequest.of(page, pageSize), entity.getId(), historyEntity.getVersion()); + pageRequest, entity.getId(), historyEntity.getVersion()); + for (ResourceHistoryTable next : historyEntities) { reindexOptimizeStorageHistoryEntity(entity, next); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java index fde57c39836f..d21dff545d32 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java @@ -205,7 +205,7 @@ public

    void preFetchResources( * * However, for realistic average workloads, this should reduce the number of round trips. */ - if (idChunk.size() >= 2) { + if (!idChunk.isEmpty()) { List entityChunk = prefetchResourceTableHistoryAndProvenance(idChunk); if (thePreFetchIndexes) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IBatch2WorkChunkRepository.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IBatch2WorkChunkRepository.java index e9611614e45b..2b6462072957 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IBatch2WorkChunkRepository.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IBatch2WorkChunkRepository.java @@ -150,6 +150,6 @@ List fetchAllChunkIdsForStepWithStatus( @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 fetchWorkChunkStatusForInstance(@Param("instanceId") String theInstanceId); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java index 477999b3d3eb..f169fba81d84 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java @@ -605,12 +605,12 @@ private PersistedJpaBundleProvider findCachedQuery( .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; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java index 5eadee42cb1f..e7d688624d22 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java @@ -210,7 +210,7 @@ public void addSortOnCoordsNear(String theParamName, boolean theAscending, Searc CoordsPredicateBuilder coordsBuilder = (CoordsPredicateBuilder) builder; List> 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 @@ private boolean populateTokens( 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 @@ private boolean checkHaveTags(List theParams, Str } } - 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; } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java index 8cd06745910c..972255d93762 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java @@ -1056,7 +1056,8 @@ private void expandValueSetHandleIncludeOrExclude( 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 @@ private CodeValidationResult createFailureCodeValidationResult( .setSeverity(IssueSeverity.ERROR) .setCodeSystemVersion(theCodeSystemVersion) .setMessage(theMessage) - .addCodeValidationIssue(new CodeValidationIssue( + .addIssue(new CodeValidationIssue( theMessage, IssueSeverity.ERROR, CodeValidationIssueCode.CODE_INVALID, diff --git a/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/helper/BaseMdmHelper.java b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/helper/BaseMdmHelper.java index 33a288ee811a..ee0e08ef7eae 100644 --- a/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/helper/BaseMdmHelper.java +++ b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/helper/BaseMdmHelper.java @@ -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 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 void beforeEach(ExtensionContext context) throws Exception { //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"); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/batch2/JpaJobPersistenceImplTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/batch2/JpaJobPersistenceImplTest.java index d7f85325a7f6..ab890c96853e 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/batch2/JpaJobPersistenceImplTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/batch2/JpaJobPersistenceImplTest.java @@ -881,6 +881,11 @@ public void testPrestorageInterceptor_whenModifyingJobInstance_modifiedJobInstan public void testFetchInstanceAndWorkChunkStatus() { // Setup + Date date1 = new Date(); + Date date2 = new Date(); + + + List chunkIds = new ArrayList<>(); JobInstance instance = createInstance(); String instanceId = mySvc.storeNewInstance(instance); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/bulk/imprt/svc/BulkDataImportR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/bulk/imprt/svc/BulkDataImportR4Test.java index dec666c0c157..7c3a6e67c46e 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/bulk/imprt/svc/BulkDataImportR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/bulk/imprt/svc/BulkDataImportR4Test.java @@ -17,6 +17,7 @@ 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 void testFlow_TransactionRows() { 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 void testFlow_WithTenantNamesInInput() { 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); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseComboParamsR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseComboParamsR4Test.java index f73670fbddc8..2eb8a60aa2e8 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseComboParamsR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseComboParamsR4Test.java @@ -62,6 +62,9 @@ public void before() throws Exception { 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 diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ContainedTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ContainedTest.java index 636ffd02b401..6b10598174b5 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ContainedTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ContainedTest.java @@ -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.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; diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java index c09eed9cb620..c8b4bdbe41bb 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java @@ -28,6 +28,7 @@ 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.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 void testTagsInContainedResourcesPreserved() { 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 void testTagsInContainedResourcesPreserved() { 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); } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java index 635e847d5dcb..aa5fd85af49f 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java @@ -2722,7 +2722,7 @@ public void testTransactionWithCreateClientAssignedIdAndReference() { 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(); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4VersionedReferenceTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4VersionedReferenceTest.java index 4ad723edfa30..f835c300c199 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4VersionedReferenceTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4VersionedReferenceTest.java @@ -428,7 +428,7 @@ public void testInsertVersionedReferenceAtPath_InTransaction_TargetUpdate() { 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 void testInsertVersionedReferenceAtPath_InTransaction_TargetUpdateConditi 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); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java index 88a8def13c86..f36a093e147c 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java @@ -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 void testTransactionWithContainedResource() { 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 diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index 1ddb1cca9d32..8f3e849719f1 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -263,6 +263,8 @@ public void before() throws Exception { myStorageSettings.setSearchPreFetchThresholds(new JpaStorageSettings().getSearchPreFetchThresholds()); } + + @Test public void testParameterWithNoValueThrowsError_InvalidChainOnCustomSearch() throws IOException { SearchParameter searchParameter = new SearchParameter(); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/reindex/ReindexTaskTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/reindex/ReindexTaskTest.java index c04a40f1ac7f..290dfcdf9b4b 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/reindex/ReindexTaskTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/reindex/ReindexTaskTest.java @@ -180,6 +180,59 @@ public void testOptimizeStorage_AllVersions() { } + @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 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 diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ValidateCodeOperationWithRemoteTerminologyR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/validation/ValidateCodeWithRemoteTerminologyR4Test.java similarity index 59% rename from hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ValidateCodeOperationWithRemoteTerminologyR4Test.java rename to hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/validation/ValidateCodeWithRemoteTerminologyR4Test.java index 4da204217f05..3787c611eb75 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ValidateCodeOperationWithRemoteTerminologyR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/validation/ValidateCodeWithRemoteTerminologyR4Test.java @@ -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.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.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 myCodeSystemProvider; + private IValidationProviders.MyValidationProvider myValueSetProvider; @Autowired @Qualifier(JpaConfig.JPA_VALIDATION_SUPPORT_CHAIN) @@ -76,8 +66,8 @@ public void before() throws Exception { 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 void validateCodeOperationOnCodeSystem_byCodingAndUrlWhereSystemIsDiffere @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 void validateCodeOperationOnCodeSystem_byCodingAndUrl_usingBuiltInCodeSys .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 void validateCodeOperationOnCodeSystem_byCodingAndUrl_usingBuiltInCodeSys @Test public void validateCodeOperationOnCodeSystem_byCodingAndUrlWhereCodeSystemIsUnknown_returnsFalse() { - myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>(); + myCodeSystemProvider.setShouldThrowExceptionForResourceNotFound(false); Parameters respParam = myClient .operation() @@ -166,21 +156,21 @@ public void validateCodeOperationOnValueSet_byCodingAndUrlWhereSystemIsDifferent @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 void validateCodeOperationOnValueSet_byUrlAndSystem_usingBuiltInCodeSyste @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 void validateCodeOperationOnValueSet_byUrlSystemAndCode() { @Test public void validateCodeOperationOnValueSet_byCodingAndUrlWhereValueSetIsUnknown_returnsFalse() { - myValueSetProvider.myReturnValueSets = new ArrayList<>(); + myValueSetProvider.setShouldThrowExceptionForResourceNotFound(false); Parameters respParam = myClient .operation() @@ -238,70 +227,18 @@ public void validateCodeOperationOnValueSet_byCodingAndUrlWhereValueSetIsUnknown " - Unknown or unusable ValueSet[" + UNKNOWN_VALUE_SYSTEM_URI + "]"); } - @SuppressWarnings("unused") - private static class MyCodeSystemProvider implements IResourceProvider { - private List myReturnCodeSystems; - private Parameters myReturnParams; - - @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 find(@RequiredParam(name = "url") UriParam theUrlParam) { - assert myReturnCodeSystems != null; - return myReturnCodeSystems; - } + 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); - @Override - public Class getResourceType() { - return CodeSystem.class; - } + // we currently do this because VersionSpecificWorkerContextWrapper has logic to infer the system when missing + // based on the ValueSet by calling ValidationSupportUtils#extractCodeSystemForCode. + valueSet.getCompose().addInclude().setSystem(theSystem); } - @SuppressWarnings("unused") - private static class MyValueSetProvider implements IResourceProvider { - private Parameters myReturnParams; - private List 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 find(@RequiredParam(name = "url") UriParam theUrlParam) { - assert myReturnValueSets != null; - return myReturnValueSets; - } - - @Override - public Class getResourceType() { - return ValueSet.class; - } - + private void setupCodeSystemValidateCode(String theUrl, String theCode, IBaseParameters theResponseParams) { + CodeSystem codeSystem = myCodeSystemProvider.addTerminologyResource(theUrl); + myCodeSystemProvider.addTerminologyResponse(OPERATION_VALIDATE_CODE, codeSystem.getUrl(), theCode, theResponseParams); } } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/validation/ValidateWithRemoteTerminologyTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/validation/ValidateWithRemoteTerminologyTest.java new file mode 100644 index 000000000000..79a656db39c7 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/validation/ValidateWithRemoteTerminologyTest.java @@ -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. OntoServer. + * 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 myCodeSystemProvider; + private IValidationProviders.MyValidationProvider 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 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 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 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 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 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(); + } +} diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/profile-encounter-custom.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/profile-encounter-custom.json new file mode 100644 index 000000000000..a553a61a1c20 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/profile-encounter-custom.json @@ -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 + } + ] + } +} diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-encounter-status.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-encounter-status.json new file mode 100644 index 000000000000..2399dc870ec8 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-encounter-status.json @@ -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" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-v2-0203.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-v2-0203.json new file mode 100644 index 000000000000..10747c14ee38 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-v2-0203.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-v3-ActCode.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-v3-ActCode.json new file mode 100644 index 000000000000..b692847e0fbb --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-v3-ActCode.json @@ -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" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-encounter-status.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-encounter-status.json new file mode 100644 index 000000000000..2399dc870ec8 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-encounter-status.json @@ -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" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-identifier-type.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-identifier-type.json new file mode 100644 index 000000000000..b0767dc2f188 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-identifier-type.json @@ -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'" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-v3-ActEncounterCode.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-v3-ActEncounterCode.json new file mode 100644 index 000000000000..083dbffae43d --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-v3-ActEncounterCode.json @@ -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" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-CodeSystem-ICD9CM.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-CodeSystem-ICD9CM.json new file mode 100644 index 000000000000..831ac6660fa6 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-CodeSystem-ICD9CM.json @@ -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'" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-CodeSystem-observation-status.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-CodeSystem-observation-status.json new file mode 100644 index 000000000000..7914321876c3 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-CodeSystem-observation-status.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-ValueSet-codes.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-ValueSet-codes.json new file mode 100644 index 000000000000..4571362033f0 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-ValueSet-codes.json @@ -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'" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-ValueSet-observation-status.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-ValueSet-observation-status.json new file mode 100644 index 000000000000..7914321876c3 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-ValueSet-observation-status.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/profile-procedure-slicing.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/profile-procedure-slicing.json new file mode 100644 index 000000000000..8bc05c70cf00 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/profile-procedure-slicing.json @@ -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 + } + ] + } +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/profile-procedure.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/profile-procedure.json new file mode 100644 index 000000000000..5315694dece4 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/profile-procedure.json @@ -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 + } + ] + } +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-absent-or-unknown.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-absent-or-unknown.json new file mode 100644 index 000000000000..4d7b20f0881d --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-absent-or-unknown.json @@ -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" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-event-status.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-event-status.json new file mode 100644 index 000000000000..620624a991e9 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-event-status.json @@ -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" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-snomed-invalid.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-snomed-invalid.json new file mode 100644 index 000000000000..f6a86048d6e8 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-snomed-invalid.json @@ -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'" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-snomed-valid.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-snomed-valid.json new file mode 100644 index 000000000000..a602bfda9f00 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-snomed-valid.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-absent-or-unknown-procedure.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-absent-or-unknown-procedure.json new file mode 100644 index 000000000000..aaee02a00233 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-absent-or-unknown-procedure.json @@ -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" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-event-status.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-event-status.json new file mode 100644 index 000000000000..aaad08b83e8e --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-event-status.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-invalid-slice.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-invalid-slice.json new file mode 100644 index 000000000000..4dcb4791944e --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-invalid-slice.json @@ -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'" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-invalid.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-invalid.json new file mode 100644 index 000000000000..fac3785fe2d2 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-invalid.json @@ -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'" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-valid.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-valid.json new file mode 100644 index 000000000000..4554379edadf --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-valid.json @@ -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" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/resources/migration/releases/V7_4_0/data/H2_EMBEDDED.sql b/hapi-fhir-jpaserver-test-utilities/src/main/resources/migration/releases/V7_4_0/data/H2_EMBEDDED.sql index fd2eb6c3c988..095d79d0fb33 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/main/resources/migration/releases/V7_4_0/data/H2_EMBEDDED.sql +++ b/hapi-fhir-jpaserver-test-utilities/src/main/resources/migration/releases/V7_4_0/data/H2_EMBEDDED.sql @@ -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' +); diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/resources/migration/releases/V7_4_0/data/MSSQL_2012.sql b/hapi-fhir-jpaserver-test-utilities/src/main/resources/migration/releases/V7_4_0/data/MSSQL_2012.sql index fd2eb6c3c988..82df69243933 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/main/resources/migration/releases/V7_4_0/data/MSSQL_2012.sql +++ b/hapi-fhir-jpaserver-test-utilities/src/main/resources/migration/releases/V7_4_0/data/MSSQL_2012.sql @@ -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' +); diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/resources/migration/releases/V7_4_0/data/ORACLE_12C.sql b/hapi-fhir-jpaserver-test-utilities/src/main/resources/migration/releases/V7_4_0/data/ORACLE_12C.sql index 442d66619194..ecb31b3d5878 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/main/resources/migration/releases/V7_4_0/data/ORACLE_12C.sql +++ b/hapi-fhir-jpaserver-test-utilities/src/main/resources/migration/releases/V7_4_0/data/ORACLE_12C.sql @@ -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 +); diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/resources/migration/releases/V7_4_0/data/POSTGRES_9_4.sql b/hapi-fhir-jpaserver-test-utilities/src/main/resources/migration/releases/V7_4_0/data/POSTGRES_9_4.sql index fd2eb6c3c988..82df69243933 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/main/resources/migration/releases/V7_4_0/data/POSTGRES_9_4.sql +++ b/hapi-fhir-jpaserver-test-utilities/src/main/resources/migration/releases/V7_4_0/data/POSTGRES_9_4.sql @@ -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' +); diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/util/CompositeInterceptorBroadcaster.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/util/CompositeInterceptorBroadcaster.java index 39d75793f28d..6670ab15a4c1 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/util/CompositeInterceptorBroadcaster.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/util/CompositeInterceptorBroadcaster.java @@ -81,7 +81,7 @@ public boolean callHooks(Pointcut thePointcut, HookParams theParams) { } if (theRequestDetails != null && theRequestDetails.getInterceptorBroadcaster() != null && retVal) { IInterceptorBroadcaster interceptorBroadcaster = theRequestDetails.getInterceptorBroadcaster(); - interceptorBroadcaster.callHooks(thePointcut, theParams); + retVal = interceptorBroadcaster.callHooks(thePointcut, theParams); } return retVal; } diff --git a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/util/CompositeInterceptorBroadcasterTest.java b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/util/CompositeInterceptorBroadcasterTest.java new file mode 100644 index 000000000000..2a670d158113 --- /dev/null +++ b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/util/CompositeInterceptorBroadcasterTest.java @@ -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); + } +} diff --git a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java index 88269f41756e..c3489d3f6312 100644 --- a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java +++ b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java @@ -397,7 +397,7 @@ public class BuilderAddIndexUnique { 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; diff --git a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/reindex/v1/ReindexV1Config.java b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/reindex/v1/ReindexV1Config.java index fe8f736aef52..4dd956389370 100644 --- a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/reindex/v1/ReindexV1Config.java +++ b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/reindex/v1/ReindexV1Config.java @@ -92,7 +92,7 @@ public JobDefinition reindexJobDefinitionV1() { "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(); } diff --git a/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/model/BatchWorkChunkStatusDTO.java b/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/model/BatchWorkChunkStatusDTO.java index 4f00448aa764..6c1918f28eea 100644 --- a/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/model/BatchWorkChunkStatusDTO.java +++ b/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/model/BatchWorkChunkStatusDTO.java @@ -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; diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/EvaluateOperationConfig.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/EvaluateOperationConfig.java index 22867e527edd..6598ab08d9ca 100644 --- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/EvaluateOperationConfig.java +++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/dstu3/EvaluateOperationConfig.java @@ -21,15 +21,20 @@ 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() { diff --git a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/EvaluateOperationConfig.java b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/EvaluateOperationConfig.java index c0d0ae48faca..8cf8ecf76533 100644 --- a/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/EvaluateOperationConfig.java +++ b/hapi-fhir-storage-cr/src/main/java/ca/uhn/fhir/cr/config/r4/EvaluateOperationConfig.java @@ -21,15 +21,20 @@ 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() { diff --git a/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/HapiFhirRepositoryR4Test.java b/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/HapiFhirRepositoryR4Test.java index 76996239ee5a..144822283712 100644 --- a/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/HapiFhirRepositoryR4Test.java +++ b/hapi-fhir-storage-cr/src/test/java/ca/uhn/fhir/cr/r4/HapiFhirRepositoryR4Test.java @@ -25,6 +25,7 @@ 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 @@ void repositoryTests(){ 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> 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) { diff --git a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2_1Test.java b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2_1Test.java index e03d8d6c187f..18c74d8617b8 100644 --- a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2_1Test.java +++ b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2_1Test.java @@ -11,6 +11,7 @@ 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 void testEncodeContainedResource() { 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 void testEncodeContainedResource() { "\"contained\": [", "{", "\"resourceType\": \"Condition\",", - "\"id\": \"1\"", + "\"id\": \"" + conditionUuid + "\"", "}", "],", "\"extension\": [", "{", "\"url\": \"test\",", "\"valueReference\": {", - "\"reference\": \"#1\"", + "\"reference\": \"#" + conditionUuid + "\"", "}", "}", "],", @@ -632,19 +635,21 @@ public void testEncodeExtensionWithContainedResource() { 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\"", "}" ); diff --git a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2_1Test.java b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2_1Test.java index 7b3fbd7a4598..4c6fea901d86 100644 --- a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2_1Test.java +++ b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2_1Test.java @@ -13,6 +13,7 @@ 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 void testEncodeAndParseContained() { String encoded = xmlParser.encodeResourceToString(patient); ourLog.info(encoded); + String organizationUuid = UuidUtils.findFirstUUID(encoded); + assertNotNull(organizationUuid); + assertThat(encoded).contains(""); - assertThat(encoded).contains(""); + assertThat(encoded).contains(""); // Create a bundle with just the patient resource Bundle b = new Bundle(); @@ -388,35 +392,35 @@ public void testEncodeAndParseContained() { // Encode the bundle encoded = xmlParser.encodeResourceToString(b); ourLog.info(encoded); - assertThat(encoded).containsSubsequence(Arrays.asList("", "", "")); - assertThat(encoded).contains(""); + assertThat(encoded).containsSubsequence(Arrays.asList("", "", "")); + assertThat(encoded).contains(""); assertThat(encoded).containsSubsequence(Arrays.asList("", "")); assertThat(encoded).doesNotContainPattern("(?s).*.*"); // 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("", "", "", "")); + assertThat(encoded).containsSubsequence(Arrays.asList("", "", "", "")); assertThat(encoded).doesNotContainPattern("(?s).*"); - assertThat(encoded).contains(""); + assertThat(encoded).contains(""); // 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("", "", "", "")); + assertThat(encoded).containsSubsequence(Arrays.asList("", "", "", "")); assertThat(encoded).doesNotContainPattern("(?s).*"); - assertThat(encoded).contains(""); + assertThat(encoded).contains(""); // And re-encode once more, with the references cleared and a manually set local ID patient.getContained().clear(); @@ -447,6 +451,8 @@ public void testEncodeAndParseContainedCustomTypes() { 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 void testEncodeAndParseContainedCustomTypes() { "", "", "", - "", + "", "", "", "", @@ -465,7 +471,7 @@ public void testEncodeAndParseContainedCustomTypes() { "", "", "", - "", + "", "", ""); //@formatter:on @@ -477,7 +483,7 @@ public void testEncodeAndParseContainedCustomTypes() { 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 void testEncodeAndParseContainedNonCustomTypes() { String output = parser.encodeResourceToString(dr); ourLog.info(output); + String observationUuid = UuidUtils.findFirstUUID(output); + assertNotNull(observationUuid); //@formatter:off assertThat(output).containsSubsequence( "", "", "", - "", + "", "", "", "", "", "", - "", + "", "", ""); //@formatter:on @@ -524,7 +532,7 @@ public void testEncodeAndParseContainedNonCustomTypes() { 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 void testEncodeContainedResource() { String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient); ourLog.info(encoded); + String conditionUuid = UuidUtils.findFirstUUID(encoded); + assertNotNull(conditionUuid); //@formatter:off assertThat(encoded).containsSubsequence( "", "", "", - "", + "", "", "", "", "", - "", + "", "", "", "", @@ -911,10 +921,12 @@ public void testEncodeContainedResourcesAutomatic() { 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("", "", "", "", "", "", - "", "", "", "", "", "", "", "", + assertThat(encoded).containsSubsequence("", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", ""); //@formatter:off } @@ -1185,13 +1197,15 @@ public void testEncodeExtensionWithContainedResource() { String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient); ourLog.info(encoded); + String conditionUuid = UuidUtils.findFirstUUID(encoded); + assertNotNull(conditionUuid); //@formatter:off assertThat(encoded).containsSubsequence( "", "", "", - "", + "", "", "", "", @@ -1199,7 +1213,7 @@ public void testEncodeExtensionWithContainedResource() { "", "", "", - "", + "", "", "", "", diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/CustomTypeDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/CustomTypeDstu2Test.java index 5862838b5b10..e3f230440f1b 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/CustomTypeDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/CustomTypeDstu2Test.java @@ -16,6 +16,7 @@ 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; @@ -54,20 +55,23 @@ public void testConstrainedFieldContainedResource() { String string = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(mo); ourLog.info(string); - + + String medicationUuid = UuidUtils.findFirstUUID(string); + assertNotNull(medicationUuid); + //@formatter:on assertThat(string).containsSubsequence( "", " ", - " ", - " ", + " ", + " ", " ", " ", " ", " ", " ", " ", - " ", + " ", " ", ""); //@formatter:on @@ -76,7 +80,7 @@ public void testConstrainedFieldContainedResource() { medication = (Medication) mo.getMedication().getResource(); assertNotNull(medication); - assertEquals("#1", medication.getId().getValue()); + assertEquals("#" + medicationUuid, medication.getId().getValue()); assertEquals("MED TEXT", medication.getCode().getText()); } diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java index 85fa64620f51..ef7737273957 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java @@ -50,6 +50,7 @@ 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 void testParseContained() { 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 diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java index 9d48d2fc8301..b5586fd64455 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java @@ -62,6 +62,7 @@ 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 void testEncodeAndParseContained() { String encoded = xmlParser.encodeResourceToString(patient); ourLog.info(encoded); + String organizationUuid = UuidUtils.findFirstUUID(encoded); + assertNotNull(organizationUuid); assertThat(encoded).contains(""); - assertThat(encoded).contains(""); + assertThat(encoded).contains(""); // 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 void testEncodeAndParseContained() { // Encode the bundle encoded = xmlParser.encodeResourceToString(b); ourLog.info(encoded); - assertThat(encoded).containsSubsequence(Arrays.asList("", "", "")); - assertThat(encoded).contains(""); + assertThat(encoded).containsSubsequence(Arrays.asList("", "", "")); + assertThat(encoded).contains(""); assertThat(encoded).containsSubsequence(Arrays.asList("", "")); assertThat(encoded).doesNotContainPattern("(?s).*.*"); // 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("", "", "", "")); + assertThat(encoded).containsSubsequence(Arrays.asList("", "", "", "")); assertThat(encoded).doesNotContainPattern("(?s).*"); - assertThat(encoded).contains(""); + assertThat(encoded).contains(""); // 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("", "", "", "")); + assertThat(encoded).containsSubsequence(Arrays.asList("", "", "", "")); assertThat(encoded).doesNotContainPattern("(?s).*"); - assertThat(encoded).contains(""); + assertThat(encoded).contains(""); // And re-encode once more, with the references cleared and a manually set local ID patient.getContained().getContainedResources().clear(); @@ -581,6 +586,8 @@ public void testEncodeAndParseContainedCustomTypes() { 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 void testEncodeAndParseContainedCustomTypes() { "", "", "", - "", + "", "", "", "", @@ -599,7 +606,7 @@ public void testEncodeAndParseContainedCustomTypes() { "", "", "", - "", + "", "", ""); //@formatter:on @@ -611,7 +618,7 @@ public void testEncodeAndParseContainedCustomTypes() { 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 void testEncodeAndParseContainedNonCustomTypes() { String output = parser.encodeResourceToString(dr); ourLog.info(output); + String observationUuid = UuidUtils.findFirstUUID(output); + assertNotNull(observationUuid); //@formatter:off assertThat(output).containsSubsequence( "", "", "", - "", + "", "", "", "", "", "", - "", + "", "", ""); //@formatter:on @@ -689,7 +698,7 @@ public void testEncodeAndParseContainedNonCustomTypes() { 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 void testEncodeContainedResourcesAutomatic() { 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("", "", "", "", "", "", - "", "", "", "", "", "", "", "", + assertThat(encoded).containsSubsequence("", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", ""); //@formatter:off } @@ -1561,13 +1572,15 @@ public void testEncodeExtensionWithContainedResource() { String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient); ourLog.info(encoded); + String conditionUuid = UuidUtils.findFirstUUID(encoded); + assertNotNull(conditionUuid); //@formatter:off assertThat(encoded).containsSubsequence( "", "", "", - "", + "", "", "", "", @@ -1575,7 +1588,7 @@ public void testEncodeExtensionWithContainedResource() { "", "", "", - "", + "", "", "", "", @@ -2535,15 +2548,17 @@ public void testParseContained() { String enc = parser.encodeResourceToString(o); ourLog.info(enc); + String patientUuid = UuidUtils.findFirstUUID(enc); + assertNotNull(patientUuid); //@formatter:off assertThat(enc).containsSubsequence( "", "", "", - "", + "", "", - "" + "" ); //@formatter:on diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java index 268a5bbcc7b2..a0de44b0fde5 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java @@ -13,6 +13,7 @@ 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 void testEncodeContainedResource() { 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 void testEncodeContainedResource() { "\"contained\": [", "{", "\"resourceType\": \"Condition\",", - "\"id\": \"1\"", + "\"id\": \"" + conditionUuid + "\"", "}", "],", "\"extension\": [", "{", "\"url\": \"test\",", "\"valueReference\": {", - "\"reference\": \"#1\"", + "\"reference\": \"#" + conditionUuid + "\"", "}", "}", "],", @@ -920,19 +923,21 @@ public void testEncodeExtensionWithContainedResource() { 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\"", "}" ); diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java index 58e1477e361c..2388fa72413d 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java @@ -13,6 +13,7 @@ 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 void testEncodeAndParseContained() { String encoded = xmlParser.encodeResourceToString(patient); ourLog.info(encoded); + String organizationUuid = UuidUtils.findFirstUUID(encoded); + assertNotNull(organizationUuid); + assertThat(encoded).contains(""); - assertThat(encoded).contains(""); + assertThat(encoded).contains(""); // Create a bundle with just the patient resource Bundle b = new Bundle(); @@ -570,35 +574,35 @@ public void testEncodeAndParseContained() { // Encode the bundle encoded = xmlParser.encodeResourceToString(b); ourLog.info(encoded); - assertThat(encoded).contains(Arrays.asList("", "", "")); - assertThat(encoded).contains(""); + assertThat(encoded).contains(Arrays.asList("", "", "")); + assertThat(encoded).contains(""); assertThat(encoded).contains(Arrays.asList("", "")); assertThat(encoded).doesNotContainPattern("(?s).*.*"); // 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("", "", "", "")); + assertThat(encoded).contains(Arrays.asList("", "", "", "")); assertThat(encoded).doesNotContainPattern("(?s).*"); - assertThat(encoded).contains(""); + assertThat(encoded).contains(""); // 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("", "", "", "")); + assertThat(encoded).contains(Arrays.asList("", "", "", "")); assertThat(encoded).doesNotContainPattern("(?s).*"); - assertThat(encoded).contains(""); + assertThat(encoded).contains(""); // And re-encode once more, with the references cleared and a manually set local ID patient.getContained().clear(); @@ -629,6 +633,8 @@ public void testEncodeAndParseContainedCustomTypes() { String output = parser.encodeResourceToString(dr); ourLog.info(output); + String observationUuid = UuidUtils.findFirstUUID(output); + assertNotNull(observationUuid); assertThat(output).contains( "", @@ -637,7 +643,7 @@ public void testEncodeAndParseContainedCustomTypes() { "", "", "", - "", + "", "", "", "", @@ -646,7 +652,7 @@ public void testEncodeAndParseContainedCustomTypes() { "", "", "", - "", + "", "", ""); @@ -657,7 +663,7 @@ public void testEncodeAndParseContainedCustomTypes() { 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 void testEncodeAndParseContainedNonCustomTypes() { String output = parser.encodeResourceToString(dr); ourLog.info(output); + String observationUuid = UuidUtils.findFirstUUID(output); + assertNotNull(observationUuid); assertThat(output).contains( "", "", "", - "", + "", "", "", "", "", "", - "", + "", "", ""); @@ -702,7 +710,7 @@ public void testEncodeAndParseContainedNonCustomTypes() { 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 void testEncodeContainedResource() { String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient); ourLog.info(encoded); + String conditionUuid = UuidUtils.findFirstUUID(encoded); + assertNotNull(conditionUuid); assertThat(encoded).contains( "", "", "", - "", + "", "", "", "", "", - "", + "", "", "", "", @@ -1359,10 +1369,12 @@ public void testEncodeContainedResourcesAutomatic() { IParser p = ourCtx.newXmlParser().setPrettyPrint(true); String encoded = p.encodeResourceToString(medicationPrescript); ourLog.info(encoded); + String medicationUuid = UuidUtils.findFirstUUID(encoded); + assertNotNull(medicationUuid); assertThat(encoded).contains - ("", "", "", "", "", "", - "", "", "", "", "", "", "", "", + ("", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", ""); } @@ -1726,12 +1738,14 @@ public void testEncodeExtensionWithContainedResource() { String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient); ourLog.info(encoded); + String conditionUuid = UuidUtils.findFirstUUID(encoded); + assertNotNull(conditionUuid); assertThat(encoded).contains( "", "", "", - "", + "", "", "", "", @@ -1739,7 +1753,7 @@ public void testEncodeExtensionWithContainedResource() { "", "", "", - "", + "", "", "", "", diff --git a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserHl7OrgDstu2Test.java b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserHl7OrgDstu2Test.java index 6889414f15bf..6a5ead67c1cf 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserHl7OrgDstu2Test.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserHl7OrgDstu2Test.java @@ -4,6 +4,7 @@ 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.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 void testEncodeContained() { 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 void testEncodeContained() { // 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 void testEncodeContained() { 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 void testEncodeContained__() { // 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 void testEncodeIds() { 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()); diff --git a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserHl7OrgDstu2Test.java b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserHl7OrgDstu2Test.java index 4356a997a060..d9582e02ba23 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserHl7OrgDstu2Test.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserHl7OrgDstu2Test.java @@ -9,6 +9,7 @@ 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 void testEncodeAndParseContained() { String encoded = xmlParser.encodeResourceToString(patient); ourLog.info(encoded); + String organizationUuid = UuidUtils.findFirstUUID(encoded); + assertNotNull(organizationUuid); + assertThat(encoded).contains(""); - assertThat(encoded).contains(""); + assertThat(encoded).contains(""); // Create a bundle with just the patient resource Bundle b = new Bundle(); @@ -226,37 +230,37 @@ public void testEncodeAndParseContained() { // Encode the bundle encoded = xmlParser.encodeResourceToString(b); ourLog.info(encoded); - assertThat(encoded).containsSubsequence(Arrays.asList("", "", "")); - assertThat(encoded).contains(""); + assertThat(encoded).containsSubsequence(Arrays.asList("", "", "")); + assertThat(encoded).contains(""); assertThat(encoded).containsSubsequence(Arrays.asList("", "")); assertThat(encoded).doesNotContainPattern("(?s).*.*"); // 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("", "", - "", "")); + assertThat(encoded).containsSubsequence(Arrays.asList("", "", + "", "")); assertThat(encoded).doesNotContainPattern("(?s).*"); - assertThat(encoded).contains(""); + assertThat(encoded).contains(""); // 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("", "", - "", "")); + assertThat(encoded).containsSubsequence(Arrays.asList("", "", + "", "")); assertThat(encoded).doesNotContainPattern("(?s).*"); - assertThat(encoded).contains(""); + assertThat(encoded).contains(""); // And re-encode once more, with the references cleared and a manually set // local ID @@ -969,13 +973,15 @@ public void testEncodeExtensionWithContainedResource() { String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient); ourLog.info(encoded); + String conditionUuid = UuidUtils.findFirstUUID(encoded); + assertNotNull(conditionUuid); //@formatter:off assertThat(encoded).containsSubsequence( "", "", "", - "", + "", "", "", "", @@ -983,7 +989,7 @@ public void testEncodeExtensionWithContainedResource() { "", "", "", - "", + "", "", "", "", diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java index 251004846491..b80251d29108 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java @@ -7,6 +7,7 @@ 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.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.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.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 void testDuplicateContainedResourcesNotOutputtedTwice() { 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 void testContainedResourcesNotAutoContainedWhenConfiguredNotToDoSo() { 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 void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocal 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 void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocal 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 void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocal 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()); } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java index 87a33805db63..0f2e122fd6f9 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java @@ -6,7 +6,10 @@ 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.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.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 void testContainResourcesWithModify() { 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 void testContainedResourcesWithModify_DoubleLink() { 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 @@ private List toStrings(List theStrings) { @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()); } /** diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/UuidUtils.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/UuidUtils.java new file mode 100644 index 000000000000..7cd9e85f2138 --- /dev/null +++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/UuidUtils.java @@ -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; + } + +} diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProviders.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProviders.java new file mode 100644 index 000000000000..d9933708e477 --- /dev/null +++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProviders.java @@ -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); +

    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 implements IMyValidationProvider { + private final Map myExceptionMap = new HashMap<>(); + private boolean myShouldThrowExceptionForResourceNotFound = true; + private final Map myTerminologyResponseMap = new HashMap<>(); + private final Map 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 getParameterType(); + + @Override + public

    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 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); + } +} diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProvidersDstu3.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProvidersDstu3.java new file mode 100644 index 000000000000..11c5244df414 --- /dev/null +++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProvidersDstu3.java @@ -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 { + @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 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 getResourceType() { + return CodeSystem.class; + } + @Override + Class 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 { + @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 getResourceType() { + return ValueSet.class; + } + @Override + Class 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; + } + } +} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/IValidateCodeProvidersR4.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProvidersR4.java similarity index 55% rename from hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/IValidateCodeProvidersR4.java rename to hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProvidersR4.java index fd03a8163ffa..87a3dacb1fc2 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/IValidateCodeProvidersR4.java +++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProvidersR4.java @@ -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 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 { - @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 IBaseParameters lookup( @OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List thePropertyNames, RequestDetails theRequestDetails ) throws Exception { - mySystemUrl = theSystem; - myCode = theCode; - if (myException != null) { - throw myException; - } - return myReturnParams; + String url = theSystem != null ? theSystem.getValue() : null; + String code = theCode != null ? theCode.getValue() : null; + return getTerminologyResponse("$lookup", url, code); } - @Override public Class getResourceType() { return CodeSystem.class; } - public void setException(Exception theException) { - myException = theException; - } - @Override - public void setReturnParams(IBaseParameters theParameters) { - myReturnParams = (Parameters) theParameters; - } @Override - public String getCode() { - return myCode != null ? myCode.getValueAsString() : null; + Class getParameterType() { + return Parameters.class; } + @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 { - @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 Parameters validateCode( @OperationParam(name = "display", min = 0, max = 1) StringType theDisplay, @OperationParam(name = "valueSet") ValueSet theValueSet ) throws Exception { - mySystemUrl = theSystem; - myValueSetUrl = theValueSetUrl; - myCode = theCode; - myDisplay = theDisplay; - if (myException != null) { - throw myException; - } - return myReturnParams; + String url = theValueSetUrl != null ? theValueSetUrl.getValue() : null; + String code = theCode != null ? theCode.getValue() : null; + return getTerminologyResponse("$validate-code", url, code); } - @Override public Class getResourceType() { return ValueSet.class; } - public void setException(Exception theException) { - myException = theException; - } - @Override - public void setReturnParams(IBaseParameters theParameters) { - myReturnParams = (Parameters) theParameters; - } @Override - public String getCode() { - return myCode != null ? myCode.getValueAsString() : null; + Class getParameterType() { + return Parameters.class; } @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; } } } diff --git a/hapi-fhir-test-utilities/src/test/java/ca/uhn/fhir/test/utilities/UuidUtilsTest.java b/hapi-fhir-test-utilities/src/test/java/ca/uhn/fhir/test/utilities/UuidUtilsTest.java new file mode 100644 index 000000000000..c7c26bdf1077 --- /dev/null +++ b/hapi-fhir-test-utilities/src/test/java/ca/uhn/fhir/test/utilities/UuidUtilsTest.java @@ -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 = ""; + String uuid = UuidUtils.findFirstUUID(xml); + assertEquals("cdb6dfa1-74b7-4ea9-88e0-d3afaef8c016", uuid); + } + + @Test + void testFindsFirstUuid() { + String xml = ""; + String uuid = UuidUtils.findFirstUUID(xml); + assertEquals("cdb6dfa1-74b7-4ea9-88e0-d3afaef8c016", uuid); + } + + @Test + void testNoUuidReturnsNull() { + String xml = ""; + assertNull(UuidUtils.findFirstUUID(xml)); + } +} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java index 1b12e20ec62e..7a05e197ae63 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java @@ -190,7 +190,7 @@ protected CodeValidationResult getValidateCodeResultError(final String theMessag return new CodeValidationResult() .setSeverity(IssueSeverity.ERROR) .setMessage(theMessage) - .setCodeValidationIssues(Collections.singletonList(new CodeValidationIssue( + .setIssues(Collections.singletonList(new CodeValidationIssue( theMessage, IssueSeverity.ERROR, CodeValidationIssueCode.INVALID, diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java index 67e553e3d3b7..a617e04c41b7 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java @@ -28,7 +28,6 @@ import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.Enumerations; -import org.hl7.fhir.utilities.validation.ValidationMessage; import java.util.ArrayList; import java.util.Collections; @@ -258,7 +257,7 @@ public CodeValidationResult validateCodeInValueSet( theValidationSupportContext, theValueSet, theCodeSystemUrlAndVersion, theCode); } catch (ExpansionCouldNotBeCompletedInternallyException e) { CodeValidationResult codeValidationResult = new CodeValidationResult(); - codeValidationResult.setSeverityCode("error"); + codeValidationResult.setSeverity(IssueSeverity.ERROR); String msg = "Failed to expand ValueSet '" + vsUrl + "' (in-memory). Could not validate code " + theCodeSystemUrlAndVersion + "#" + theCode; @@ -267,7 +266,7 @@ public CodeValidationResult validateCodeInValueSet( } codeValidationResult.setMessage(msg); - codeValidationResult.addCodeValidationIssue(e.getCodeValidationIssue()); + codeValidationResult.addIssue(e.getCodeValidationIssue()); return codeValidationResult; } @@ -551,18 +550,18 @@ private CodeValidationResult validateCodeInExpandedValueSet( if (valueSetResult != null) { codeValidationResult = valueSetResult; } else { - ValidationMessage.IssueSeverity severity; + IValidationSupport.IssueSeverity severity; String message; CodeValidationIssueCode issueCode = CodeValidationIssueCode.CODE_INVALID; CodeValidationIssueCoding issueCoding = CodeValidationIssueCoding.INVALID_CODE; if ("fragment".equals(codeSystemResourceContentMode)) { - severity = ValidationMessage.IssueSeverity.WARNING; + severity = IValidationSupport.IssueSeverity.WARNING; message = "Unknown code in fragment CodeSystem '" + getFormattedCodeSystemAndCodeForMessage( theCodeSystemUrlAndVersionToValidate, theCodeToValidate) + "'"; } else { - severity = ValidationMessage.IssueSeverity.ERROR; + severity = IValidationSupport.IssueSeverity.ERROR; message = "Unknown code '" + getFormattedCodeSystemAndCodeForMessage( theCodeSystemUrlAndVersionToValidate, theCodeToValidate) @@ -574,10 +573,9 @@ private CodeValidationResult validateCodeInExpandedValueSet( } codeValidationResult = new CodeValidationResult() - .setSeverityCode(severity.toCode()) + .setSeverity(severity) .setMessage(message) - .addCodeValidationIssue(new CodeValidationIssue( - message, getIssueSeverityFromCodeValidationIssue(severity), issueCode, issueCoding)); + .addIssue(new CodeValidationIssue(message, severity, issueCode, issueCoding)); } return codeValidationResult; @@ -589,19 +587,6 @@ private static String getFormattedCodeSystemAndCodeForMessage( + theCodeToValidate; } - private IValidationSupport.IssueSeverity getIssueSeverityFromCodeValidationIssue( - ValidationMessage.IssueSeverity theSeverity) { - switch (theSeverity) { - case ERROR: - return IValidationSupport.IssueSeverity.ERROR; - case WARNING: - return IValidationSupport.IssueSeverity.WARNING; - case INFORMATION: - return IValidationSupport.IssueSeverity.INFORMATION; - } - return null; - } - private CodeValidationResult findCodeInExpansion( String theCodeToValidate, String theDisplayToValidate, @@ -1123,8 +1108,8 @@ private boolean expandValueSetR5IncludeOrExclude( new CodeValidationIssue( theMessage, IssueSeverity.ERROR, - CodeValidationIssueCode.OTHER, - CodeValidationIssueCoding.OTHER)); + CodeValidationIssueCode.INVALID, + CodeValidationIssueCoding.VS_INVALID)); } for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : subExpansion.getExpansion().getContains()) { @@ -1376,7 +1361,7 @@ private static CodeValidationResult createResultForDisplayMismatch( .setCodeSystemVersion(theCodeSystemVersion) .setDisplay(theExpectedDisplay); if (issueSeverity != null) { - codeValidationResult.setCodeValidationIssues(Collections.singletonList(new CodeValidationIssue( + codeValidationResult.setIssues(Collections.singletonList(new CodeValidationIssue( message, theIssueSeverityForCodeDisplayMismatch, CodeValidationIssueCode.INVALID, diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java index 370f8b423dd2..398eacc52a24 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java @@ -28,6 +28,7 @@ import org.hl7.fhir.r4.model.Base; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.OperationOutcome; import org.hl7.fhir.r4.model.Parameters; @@ -631,7 +632,7 @@ private CodeValidationResult createErrorCodeValidationResult( return new CodeValidationResult() .setSeverity(severity) .setMessage(theMessage) - .addCodeValidationIssue(new CodeValidationIssue( + .addIssue(new CodeValidationIssue( theMessage, severity, theIssueCode, CodeValidationIssueCoding.INVALID_CODE)); } @@ -680,13 +681,13 @@ private CodeValidationResult createCodeValidationResult( createCodeValidationIssues( (IBaseOperationOutcome) issuesValue.get(), fhirContext.getVersion().getVersion()) - .ifPresent(i -> i.forEach(result::addCodeValidationIssue)); + .ifPresent(i -> i.forEach(result::addIssue)); } else { // create a validation issue out of the message // this is a workaround to overcome an issue in the FHIR Validator library // where ValueSet bindings are only reading issues but not messages // @see https://github.com/hapifhir/org.hl7.fhir.core/issues/1766 - result.addCodeValidationIssue(createCodeValidationIssue(result.getMessage())); + result.addIssue(createCodeValidationIssue(result.getMessage())); } return result; } @@ -717,23 +718,42 @@ public static Optional> createCodeValidationIssu private static Collection createCodeValidationIssuesR4(OperationOutcome theOperationOutcome) { return theOperationOutcome.getIssue().stream() - .map(issueComponent -> - createCodeValidationIssue(issueComponent.getDetails().getText())) + .map(issueComponent -> { + String diagnostics = issueComponent.getDiagnostics(); + IssueSeverity issueSeverity = + IssueSeverity.fromCode(issueComponent.getSeverity().toCode()); + String issueTypeCode = issueComponent.getCode().toCode(); + CodeableConcept details = issueComponent.getDetails(); + CodeValidationIssue issue = new CodeValidationIssue(diagnostics, issueSeverity, issueTypeCode); + CodeValidationIssueDetails issueDetails = new CodeValidationIssueDetails(details.getText()); + details.getCoding().forEach(coding -> issueDetails.addCoding(coding.getSystem(), coding.getCode())); + issue.setDetails(issueDetails); + return issue; + }) .collect(Collectors.toList()); } private static Collection createCodeValidationIssuesDstu3( org.hl7.fhir.dstu3.model.OperationOutcome theOperationOutcome) { return theOperationOutcome.getIssue().stream() - .map(issueComponent -> - createCodeValidationIssue(issueComponent.getDetails().getText())) + .map(issueComponent -> { + String diagnostics = issueComponent.getDiagnostics(); + IssueSeverity issueSeverity = + IssueSeverity.fromCode(issueComponent.getSeverity().toCode()); + String issueTypeCode = issueComponent.getCode().toCode(); + org.hl7.fhir.dstu3.model.CodeableConcept details = issueComponent.getDetails(); + CodeValidationIssue issue = new CodeValidationIssue(diagnostics, issueSeverity, issueTypeCode); + CodeValidationIssueDetails issueDetails = new CodeValidationIssueDetails(details.getText()); + details.getCoding().forEach(coding -> issueDetails.addCoding(coding.getSystem(), coding.getCode())); + issue.setDetails(issueDetails); + return issue; + }) .collect(Collectors.toList()); } private static CodeValidationIssue createCodeValidationIssue(String theMessage) { return new CodeValidationIssue( theMessage, - // assume issue type is OperationOutcome.IssueType#CODEINVALID as it is the only match IssueSeverity.ERROR, CodeValidationIssueCode.INVALID, CodeValidationIssueCoding.INVALID_CODE); diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java index 1898292c4512..265debed058b 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java @@ -87,7 +87,7 @@ public CodeValidationResult validateCode( result.setSeverity(null); result.setMessage(null); } else { - result.addCodeValidationIssue(new CodeValidationIssue( + result.addIssue(new CodeValidationIssue( theMessage, myNonExistentCodeSystemSeverity, CodeValidationIssueCode.NOT_FOUND, diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportUtils.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportUtils.java index 7321f33d8c8e..7093ab2a7ff1 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportUtils.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportUtils.java @@ -11,6 +11,17 @@ public final class ValidationSupportUtils { private ValidationSupportUtils() {} + /** + * This method extracts a code system that can be (potentially) associated with a code when + * performing validation against a ValueSet. This method was created for internal purposes. + * Please use this method with care because it will only cover some + * use-cases (e.g. standard bindings) while for others it may not return correct results or return null. + * An incorrect result could be considered if the resource declares a code with a system, and you're calling + * this method to check a binding against a ValueSet that has nothing to do with that system. + * @param theValueSet the valueSet + * @param theCode the code + * @return the system which can be associated with the code + */ public static String extractCodeSystemForCode(IBaseResource theValueSet, String theCode) { if (theValueSet instanceof org.hl7.fhir.dstu3.model.ValueSet) { return extractCodeSystemForCodeDSTU3((org.hl7.fhir.dstu3.model.ValueSet) theValueSet, theCode); diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java index f0f3f41e7f9b..393d8ce1dc54 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java @@ -62,6 +62,7 @@ import java.util.Objects; import java.util.Set; +import static ca.uhn.fhir.context.support.IValidationSupport.CodeValidationIssueCoding.INVALID_DISPLAY; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toSet; import static org.apache.commons.lang3.StringUtils.isBlank; @@ -296,7 +297,7 @@ private ValidationResult convertValidationResult( theResult.getCodeSystemVersion(), conceptDefinitionComponent, display, - getIssuesForCodeValidation(theResult.getCodeValidationIssues())); + getIssuesForCodeValidation(theResult.getIssues())); } if (retVal == null) { @@ -307,73 +308,36 @@ private ValidationResult convertValidationResult( } private List getIssuesForCodeValidation( - List codeValidationIssues) { - List issues = new ArrayList<>(); - - for (IValidationSupport.CodeValidationIssue codeValidationIssue : codeValidationIssues) { - - CodeableConcept codeableConcept = new CodeableConcept().setText(codeValidationIssue.getMessage()); - codeableConcept.addCoding( - "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", - getIssueCodingFromCodeValidationIssue(codeValidationIssue), - null); - - OperationOutcome.OperationOutcomeIssueComponent issue = + List theIssues) { + List issueComponents = new ArrayList<>(); + + for (IValidationSupport.CodeValidationIssue issue : theIssues) { + OperationOutcome.IssueSeverity severity = + OperationOutcome.IssueSeverity.fromCode(issue.getSeverity().getCode()); + OperationOutcome.IssueType issueType = + OperationOutcome.IssueType.fromCode(issue.getType().getCode()); + String diagnostics = issue.getDiagnostics(); + + IValidationSupport.CodeValidationIssueDetails details = issue.getDetails(); + CodeableConcept codeableConcept = new CodeableConcept().setText(details.getText()); + details.getCodings().forEach(detailCoding -> codeableConcept + .addCoding() + .setSystem(detailCoding.getSystem()) + .setCode(detailCoding.getCode())); + + OperationOutcome.OperationOutcomeIssueComponent issueComponent = new OperationOutcome.OperationOutcomeIssueComponent() - .setSeverity(getIssueSeverityFromCodeValidationIssue(codeValidationIssue)) - .setCode(getIssueTypeFromCodeValidationIssue(codeValidationIssue)) - .setDetails(codeableConcept); - issue.getDetails().setText(codeValidationIssue.getMessage()); - issue.addExtension() + .setSeverity(severity) + .setCode(issueType) + .setDetails(codeableConcept) + .setDiagnostics(diagnostics); + issueComponent + .addExtension() .setUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id") .setValue(new StringType("Terminology_PassThrough_TX_Message")); - issues.add(issue); - } - return issues; - } - - private String getIssueCodingFromCodeValidationIssue(IValidationSupport.CodeValidationIssue codeValidationIssue) { - switch (codeValidationIssue.getCoding()) { - case VS_INVALID: - return "vs-invalid"; - case NOT_FOUND: - return "not-found"; - case NOT_IN_VS: - return "not-in-vs"; - case INVALID_CODE: - return "invalid-code"; - case INVALID_DISPLAY: - return "invalid-display"; - } - return null; - } - - private OperationOutcome.IssueType getIssueTypeFromCodeValidationIssue( - IValidationSupport.CodeValidationIssue codeValidationIssue) { - switch (codeValidationIssue.getCode()) { - case NOT_FOUND: - return OperationOutcome.IssueType.NOTFOUND; - case CODE_INVALID: - return OperationOutcome.IssueType.CODEINVALID; - case INVALID: - return OperationOutcome.IssueType.INVALID; - } - return null; - } - - private OperationOutcome.IssueSeverity getIssueSeverityFromCodeValidationIssue( - IValidationSupport.CodeValidationIssue codeValidationIssue) { - switch (codeValidationIssue.getSeverity()) { - case FATAL: - return OperationOutcome.IssueSeverity.FATAL; - case ERROR: - return OperationOutcome.IssueSeverity.ERROR; - case WARNING: - return OperationOutcome.IssueSeverity.WARNING; - case INFORMATION: - return OperationOutcome.IssueSeverity.INFORMATION; + issueComponents.add(issueComponent); } - return null; + return issueComponents; } @Override @@ -851,25 +815,22 @@ private IValidationSupport.CodeValidationResult validateCodeInValueSet( .getRootValidationSupport() .validateCodeInValueSet( myValidationSupportContext, theValidationOptions, theSystem, theCode, theDisplay, theValueSet); - if (result != null) { + if (result != null && theSystem != null) { /* We got a value set result, which could be successful, or could contain errors/warnings. The code might also be invalid in the code system, so we will check that as well and add those issues to our result. */ IValidationSupport.CodeValidationResult codeSystemResult = validateCodeInCodeSystem(theValidationOptions, theSystem, theCode, theDisplay); - final boolean valueSetResultContainsInvalidDisplay = result.getCodeValidationIssues().stream() - .anyMatch(codeValidationIssue -> codeValidationIssue.getCoding() - == IValidationSupport.CodeValidationIssueCoding.INVALID_DISPLAY); + final boolean valueSetResultContainsInvalidDisplay = result.getIssues().stream() + .anyMatch(VersionSpecificWorkerContextWrapper::hasInvalidDisplayDetailCode); if (codeSystemResult != null) { - for (IValidationSupport.CodeValidationIssue codeValidationIssue : - codeSystemResult.getCodeValidationIssues()) { + for (IValidationSupport.CodeValidationIssue codeValidationIssue : codeSystemResult.getIssues()) { /* Value set validation should already have checked the display name. If we get INVALID_DISPLAY issues from code system validation, they will only repeat what was already caught. */ - if (codeValidationIssue.getCoding() != IValidationSupport.CodeValidationIssueCoding.INVALID_DISPLAY - || !valueSetResultContainsInvalidDisplay) { - result.addCodeValidationIssue(codeValidationIssue); + if (!hasInvalidDisplayDetailCode(codeValidationIssue) || !valueSetResultContainsInvalidDisplay) { + result.addIssue(codeValidationIssue); } } } @@ -877,6 +838,10 @@ private IValidationSupport.CodeValidationResult validateCodeInValueSet( return result; } + private static boolean hasInvalidDisplayDetailCode(IValidationSupport.CodeValidationIssue theIssue) { + return theIssue.hasIssueDetailCode(INVALID_DISPLAY.getCode()); + } + private IValidationSupport.CodeValidationResult validateCodeInCodeSystem( ConceptValidationOptions theValidationOptions, String theSystem, String theCode, String theDisplay) { return myValidationSupportContext diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ILookupCodeTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ILookupCodeTest.java index bb7eaf1c17ba..eac448ad0ebd 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ILookupCodeTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ILookupCodeTest.java @@ -9,6 +9,7 @@ import ca.uhn.fhir.context.support.IValidationSupport.StringConceptProperty; import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.test.utilities.validation.IValidationProviders; import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.junit.jupiter.api.Test; @@ -21,12 +22,12 @@ import static ca.uhn.fhir.context.support.IValidationSupport.TYPE_STRING; import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM_NAME; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM_VERSION; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.DISPLAY; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.LANGUAGE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_NAME; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_VERSION; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.DISPLAY; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.LANGUAGE; import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.createConceptProperty; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -189,8 +190,6 @@ default void verifyLookupCodeResult(LookupCodeRequest theRequest, LookupCodeResu // verify assertNotNull(outcome); - assertEquals(theRequest.getCode(), getLookupCodeProvider().getCode()); - assertEquals(theRequest.getSystem(), getLookupCodeProvider().getSystem()); assertEquals(theExpectedResult.isFound(), outcome.isFound()); assertEquals(theExpectedResult.getErrorMessage(), outcome.getErrorMessage()); assertEquals(theExpectedResult.getCodeSystemDisplayName(), outcome.getCodeSystemDisplayName()); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyLookupCodeTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyLookupCodeTest.java index 5ba79bd3e6f9..95c4fd7d3b97 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyLookupCodeTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyLookupCodeTest.java @@ -2,6 +2,7 @@ import ca.uhn.fhir.context.support.IValidationSupport.LookupCodeResult; import ca.uhn.fhir.context.support.LookupCodeRequest; +import ca.uhn.fhir.test.utilities.validation.IValidationProviders; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.junit.jupiter.api.Test; diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyValidateCodeTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyValidateCodeTest.java index cb6bb02ac072..21c5d0dc5e74 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyValidateCodeTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyValidateCodeTest.java @@ -1,17 +1,76 @@ package org.hl7.fhir.common.hapi.validation; +import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.support.IValidationSupport; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; +import org.junit.jupiter.api.Test; import java.util.Collection; import java.util.List; import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.createCodeValidationIssues; + public interface IRemoteTerminologyValidateCodeTest extends IValidateCodeTest { default List getCodeValidationIssues(IBaseOperationOutcome theOperationOutcome) { // this method should be removed once support for issues is fully implemented across all validator types Optional> issues = RemoteTerminologyServiceValidationSupport.createCodeValidationIssues(theOperationOutcome, getService().getFhirContext().getVersion().getVersion()); return issues.map(theCodeValidationIssues -> theCodeValidationIssues.stream().toList()).orElseGet(List::of); } + + @Test + default void createCodeValidationIssues_withCodeSystemOutcomeForInvalidCode_returnsAsExpected() { + IBaseOperationOutcome outcome = getCodeSystemInvalidCodeOutcome(); + FhirVersionEnum versionEnum = getService().getFhirContext().getVersion().getVersion(); + Optional> issuesOptional = createCodeValidationIssues(outcome, versionEnum); + assertThat(issuesOptional).isPresent(); + assertThat(issuesOptional.get()).hasSize(1); + IValidationSupport.CodeValidationIssue issue = issuesOptional.get().iterator().next(); + assertThat(issue.getType().getCode()).isEqualTo("code-invalid"); + assertThat(issue.getSeverity().getCode()).isEqualTo("error"); + assertThat(issue.getDetails().getCodings()).hasSize(1); + IValidationSupport.CodeValidationIssueCoding issueCoding = issue.getDetails().getCodings().get(0); + assertThat(issueCoding.getSystem()).isEqualTo("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type"); + assertThat(issueCoding.getCode()).isEqualTo("invalid-code"); + assertThat(issue.getDetails().getText()).isEqualTo("Unknown code 'CODE' in the CodeSystem 'http://code.system/url' version '1.0.0'"); + assertThat(issue.getDiagnostics()).isNull(); + } + + @Test + default void createCodeValidationIssues_withValueSetOutcomeForInvalidCode_returnsAsExpected() { + IBaseOperationOutcome outcome = getValueSetInvalidCodeOutcome(); + FhirVersionEnum versionEnum = getService().getFhirContext().getVersion().getVersion(); + Optional> issuesOptional = createCodeValidationIssues(outcome, versionEnum); + assertThat(issuesOptional).isPresent(); + assertThat(issuesOptional.get()).hasSize(2); + IValidationSupport.CodeValidationIssue issue = issuesOptional.get().iterator().next(); + assertThat(issue.getType().getCode()).isEqualTo("code-invalid"); + assertThat(issue.getSeverity().getCode()).isEqualTo("error"); + assertThat(issue.getDetails().getCodings()).hasSize(1); + IValidationSupport.CodeValidationIssueCoding issueCoding = issue.getDetails().getCodings().get(0); + assertThat(issueCoding.getSystem()).isEqualTo("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type"); + assertThat(issueCoding.getCode()).isEqualTo("not-in-vs"); + assertThat(issue.getDetails().getText()).isEqualTo("The provided code 'http://code.system/url#CODE' was not found in the value set 'http://value.set/url%7C1.0.0'"); + assertThat(issue.getDiagnostics()).isNull(); + } + + @Test + default void createCodeValidationIssues_withValueSetOutcomeWithCustomDetailCode_returnsAsExpected() { + IBaseOperationOutcome outcome = getValueSetCustomDetailCodeOutcome(); + FhirVersionEnum versionEnum = getService().getFhirContext().getVersion().getVersion(); + Optional> issuesOptional = createCodeValidationIssues(outcome, versionEnum); + assertThat(issuesOptional).isPresent(); + assertThat(issuesOptional.get()).hasSize(1); + IValidationSupport.CodeValidationIssue issue = issuesOptional.get().iterator().next(); + assertThat(issue.getType().getCode()).isEqualTo("processing"); + assertThat(issue.getSeverity().getCode()).isEqualTo("information"); + assertThat(issue.getDetails().getCodings()).hasSize(1); + IValidationSupport.CodeValidationIssueCoding issueCoding = issue.getDetails().getCodings().get(0); + assertThat(issueCoding.getSystem()).isEqualTo("http://example.com/custom-issue-type"); + assertThat(issueCoding.getCode()).isEqualTo("valueset-is-draft"); + assertThat(issue.getDetails().getText()).isNull(); + assertThat(issue.getDiagnostics()).isEqualTo("The ValueSet status is marked as draft."); + } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidateCodeTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidateCodeTest.java index 52dbf1177a86..53411b440fe6 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidateCodeTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidateCodeTest.java @@ -4,6 +4,9 @@ import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.test.utilities.validation.IValidationProviders; +import ca.uhn.fhir.util.ClasspathUtil; +import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -16,12 +19,13 @@ import java.util.stream.Stream; import static ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity.ERROR; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM_VERSION; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.DISPLAY; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.ERROR_MESSAGE; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.VALUE_SET_URL; +import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_VALIDATE_CODE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_VERSION; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.DISPLAY; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.ERROR_MESSAGE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.VALUE_SET_URL; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -31,21 +35,35 @@ public interface IValidateCodeTest { - IValidationProviders.IMyCodeSystemProvider getCodeSystemProvider(); - IValidationProviders.IMyValueSetProvider getValueSetProvider(); + IValidationProviders.IMyValidationProvider getCodeSystemProvider(); + IValidationProviders.IMyValidationProvider getValueSetProvider(); IValidationSupport getService(); IBaseParameters createParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource); String getCodeSystemError(); String getValueSetError(); IBaseOperationOutcome getCodeSystemInvalidCodeOutcome(); IBaseOperationOutcome getValueSetInvalidCodeOutcome(); + IBaseOperationOutcome getValueSetCustomDetailCodeOutcome(); + + default IBaseOperationOutcome getCodeSystemInvalidCodeOutcome(Class theResourceClass) { + return getOutcome(theResourceClass, "/terminology/OperationOutcome-CodeSystem-invalid-code.json"); + } + default IBaseOperationOutcome getValueSetInvalidCodeOutcome(Class theResourceClass) { + return getOutcome(theResourceClass, "/terminology/OperationOutcome-ValueSet-invalid-code.json"); + } + default IBaseOperationOutcome getValueSetCustomDetailCodeOutcome(Class theResourceClass) { + return getOutcome(theResourceClass, "/terminology/OperationOutcome-ValueSet-custom-issue-detail.json"); + } + default IBaseOperationOutcome getOutcome(Class theResourceClass, String theFile) { + return ClasspathUtil.loadResource(getService().getFhirContext(), theResourceClass, theFile); + } default void createCodeSystemReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) { - getCodeSystemProvider().setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource)); + getCodeSystemProvider().addTerminologyResponse(OPERATION_VALIDATE_CODE, CODE_SYSTEM, CODE, createParameters(theResult, theDisplay, theMessage, theIssuesResource)); } default void createValueSetReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) { - getValueSetProvider().setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource)); + getValueSetProvider().addTerminologyResponse(OPERATION_VALIDATE_CODE, VALUE_SET_URL, CODE, createParameters(theResult, theDisplay, theMessage, theIssuesResource)); } @Test @@ -91,8 +109,8 @@ default void validateCode_codeSystemAndValueSetUrlAreIncorrect_returnsValidation String theValidationMessage, String theCodeSystem, String theValueSetUrl) { - getCodeSystemProvider().setException(theException); - getValueSetProvider().setException(theException); + getCodeSystemProvider().addException(OPERATION_VALIDATE_CODE, theCodeSystem, CODE, theException); + getValueSetProvider().addException(OPERATION_VALIDATE_CODE, theValueSetUrl, CODE, theException); CodeValidationResult outcome = getService().validateCode(null, null, theCodeSystem, CODE, DISPLAY, theValueSetUrl); verifyErrorResultFromException(outcome, theValidationMessage, theServerMessage); @@ -105,7 +123,7 @@ default void verifyErrorResultFromException(CodeValidationResult outcome, String for (String message : theMessages) { assertTrue(outcome.getMessage().contains(message)); } - assertFalse(outcome.getCodeValidationIssues().isEmpty()); + assertFalse(outcome.getIssues().isEmpty()); } @Test @@ -130,11 +148,7 @@ default void validateCode_withValueSetSuccess_returnsCorrectly() { assertEquals(DISPLAY, outcome.getDisplay()); assertNull(outcome.getSeverity()); assertNull(outcome.getMessage()); - assertTrue(outcome.getCodeValidationIssues().isEmpty()); - - assertEquals(CODE, getValueSetProvider().getCode()); - assertEquals(DISPLAY, getValueSetProvider().getDisplay()); - assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet()); + assertTrue(outcome.getIssues().isEmpty()); } @Test @@ -147,9 +161,7 @@ default void validateCode_withCodeSystemSuccess_returnsCorrectly() { assertEquals(DISPLAY, outcome.getDisplay()); assertNull(outcome.getSeverity()); assertNull(outcome.getMessage()); - assertTrue(outcome.getCodeValidationIssues().isEmpty()); - - assertEquals(CODE, getCodeSystemProvider().getCode()); + assertTrue(outcome.getIssues().isEmpty()); } @Test @@ -165,10 +177,7 @@ default void validateCode_withCodeSystemProvidingMinimalInputs_ReturnsSuccess() assertNull(outcome.getDisplay()); assertNull(outcome.getSeverity()); assertNull(outcome.getMessage()); - assertTrue(outcome.getCodeValidationIssues().isEmpty()); - - assertEquals(CODE, getCodeSystemProvider().getCode()); - assertEquals(CODE_SYSTEM, getCodeSystemProvider().getSystem()); + assertTrue(outcome.getIssues().isEmpty()); } @Test @@ -184,15 +193,11 @@ default void validateCode_withCodeSystemSuccessWithMessageValue_returnsCorrectly assertEquals(DISPLAY, outcome.getDisplay()); assertNull(outcome.getSeverity()); assertNull(outcome.getMessage()); - assertTrue(outcome.getCodeValidationIssues().isEmpty()); - - assertEquals(CODE, getCodeSystemProvider().getCode()); - assertEquals(DISPLAY, getCodeSystemProvider().getDisplay()); - assertEquals(CODE_SYSTEM, getCodeSystemProvider().getSystem()); + assertTrue(outcome.getIssues().isEmpty()); } @Test - default void validateCode_withCodeSystemError_returnsCorrectly() { + default void validateCode_withCodeSystemErrorWithDiagnosticsWithIssues_returnsCorrectly() { IBaseOperationOutcome invalidCodeOutcome = getCodeSystemInvalidCodeOutcome(); createCodeSystemReturnParameters(false, null, ERROR_MESSAGE, invalidCodeOutcome); @@ -204,12 +209,12 @@ default void validateCode_withCodeSystemError_returnsCorrectly() { // assertEquals(CODE, outcome.getCode()); assertEquals(ERROR, outcome.getSeverity()); assertEquals(getCodeSystemError(), outcome.getMessage()); - assertFalse(outcome.getCodeValidationIssues().isEmpty()); + assertFalse(outcome.getIssues().isEmpty()); verifyIssues(invalidCodeOutcome, outcome); } @Test - default void validateCode_withCodeSystemErrorAndIssues_returnsCorrectly() { + default void validateCode_withCodeSystemErrorWithDiagnosticsWithoutIssues_returnsCorrectly() { createCodeSystemReturnParameters(false, null, ERROR_MESSAGE, null); CodeValidationResult outcome = getService() @@ -223,10 +228,32 @@ default void validateCode_withCodeSystemErrorAndIssues_returnsCorrectly() { assertNull(outcome.getDisplay()); assertEquals(ERROR, outcome.getSeverity()); assertEquals(expectedError, outcome.getMessage()); - assertFalse(outcome.getCodeValidationIssues().isEmpty()); - assertEquals(1, outcome.getCodeValidationIssues().size()); - assertEquals(expectedError, outcome.getCodeValidationIssues().get(0).getMessage()); - assertEquals(ERROR, outcome.getCodeValidationIssues().get(0).getSeverity()); + assertFalse(outcome.getIssues().isEmpty()); + assertEquals(1, outcome.getIssues().size()); + assertEquals(expectedError, outcome.getIssues().get(0).getDiagnostics()); + assertEquals(ERROR, outcome.getIssues().get(0).getSeverity()); + } + + @Test + default void validateCode_withCodeSystemErrorWithoutDiagnosticsWithIssues_returnsCorrectly() { + IBaseOperationOutcome invalidCodeOutcome = getCodeSystemInvalidCodeOutcome(); + createCodeSystemReturnParameters(false, null, null, invalidCodeOutcome); + + CodeValidationResult outcome = getService() + .validateCode(null, null, CODE_SYSTEM, CODE, null, null); + + String expectedError = getCodeSystemError(); + assertNotNull(outcome); + assertEquals(CODE_SYSTEM, outcome.getCodeSystemName()); + assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion()); + // assertEquals(CODE, outcome.getCode()); + assertNull(outcome.getDisplay()); + assertEquals(ERROR, outcome.getSeverity()); + assertNull(outcome.getMessage()); + assertFalse(outcome.getIssues().isEmpty()); + assertEquals(1, outcome.getIssues().size()); + assertNull(outcome.getIssues().get(0).getDiagnostics()); + assertEquals(ERROR, outcome.getIssues().get(0).getSeverity()); } @Test @@ -242,10 +269,7 @@ default void validateCode_withValueSetProvidingMinimalInputsSuccess_returnsCorre assertNull(outcome.getDisplay()); assertNull(outcome.getSeverity()); assertNull(outcome.getMessage()); - assertTrue(outcome.getCodeValidationIssues().isEmpty()); - - assertEquals(CODE, getValueSetProvider().getCode()); - assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet()); + assertTrue(outcome.getIssues().isEmpty()); } @Test @@ -261,11 +285,7 @@ default void validateCode_withValueSetSuccessWithMessage_returnsCorrectly() { assertEquals(DISPLAY, outcome.getDisplay()); assertNull(outcome.getSeverity()); assertNull(outcome.getMessage()); - assertTrue(outcome.getCodeValidationIssues().isEmpty()); - - assertEquals(CODE, getValueSetProvider().getCode()); - assertEquals(DISPLAY, getValueSetProvider().getDisplay()); - assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet()); + assertTrue(outcome.getIssues().isEmpty()); } @Test @@ -283,13 +303,9 @@ default void validateCode_withValueSetError_returnsCorrectly() { assertEquals(DISPLAY, outcome.getDisplay()); assertEquals(ERROR, outcome.getSeverity()); assertEquals(expectedError, outcome.getMessage()); - assertEquals(1, outcome.getCodeValidationIssues().size()); - assertEquals(expectedError, outcome.getCodeValidationIssues().get(0).getMessage()); - assertEquals(ERROR, outcome.getCodeValidationIssues().get(0).getSeverity()); - - assertEquals(CODE, getValueSetProvider().getCode()); - assertEquals(DISPLAY, getValueSetProvider().getDisplay()); - assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet()); + assertEquals(1, outcome.getIssues().size()); + assertEquals(expectedError, outcome.getIssues().get(0).getDiagnostics()); + assertEquals(ERROR, outcome.getIssues().get(0).getSeverity()); } @Test @@ -306,24 +322,28 @@ default void validateCode_withValueSetErrorWithIssues_returnsCorrectly() { assertEquals(DISPLAY, outcome.getDisplay()); assertEquals(ERROR, outcome.getSeverity()); assertEquals(getValueSetError(), outcome.getMessage()); - assertFalse(outcome.getCodeValidationIssues().isEmpty()); + assertFalse(outcome.getIssues().isEmpty()); verifyIssues(invalidCodeOutcome, outcome); - - assertEquals(CODE, getValueSetProvider().getCode()); - assertEquals(DISPLAY, getValueSetProvider().getDisplay()); - assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet()); } default void verifyIssues(IBaseOperationOutcome theOperationOutcome, CodeValidationResult theResult) { List issues = getCodeValidationIssues(theOperationOutcome); - assertEquals(issues.size(), theResult.getCodeValidationIssues().size()); + assertEquals(issues.size(), theResult.getIssues().size()); for (int i = 0; i < issues.size(); i++) { IValidationSupport.CodeValidationIssue expectedIssue = issues.get(i); - IValidationSupport.CodeValidationIssue actualIssue = theResult.getCodeValidationIssues().get(i); - assertEquals(expectedIssue.getCode(), actualIssue.getCode()); + IValidationSupport.CodeValidationIssue actualIssue = theResult.getIssues().get(i); + assertEquals(expectedIssue.getType().getCode(), actualIssue.getType().getCode()); assertEquals(expectedIssue.getSeverity(), actualIssue.getSeverity()); - assertEquals(expectedIssue.getCoding(), actualIssue.getCoding()); - assertEquals(expectedIssue.getMessage(), actualIssue.getMessage()); + assertEquals(expectedIssue.getDetails().getText(), actualIssue.getDetails().getText()); + assertEquals(expectedIssue.getDetails().getCodings().size(), actualIssue.getDetails().getCodings().size()); + for (int index = 0; index < expectedIssue.getDetails().getCodings().size(); index++) { + IValidationSupport.CodeValidationIssueCoding expectedCoding = expectedIssue.getDetails().getCodings().get(index); + IValidationSupport.CodeValidationIssueCoding actualCoding = actualIssue.getDetails().getCodings().get(index); + assertEquals(expectedCoding.getSystem(), actualCoding.getSystem()); + assertEquals(expectedCoding.getCode(), actualCoding.getCode()); + } + assertEquals(expectedIssue.getDetails().getText(), actualIssue.getDetails().getText()); + assertEquals(expectedIssue.getDiagnostics(), actualIssue.getDiagnostics()); } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidationProviders.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidationProviders.java deleted file mode 100644 index 1537f8e5c00b..000000000000 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidationProviders.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.hl7.fhir.common.hapi.validation; - -import ca.uhn.fhir.context.support.IValidationSupport; -import ca.uhn.fhir.rest.server.IResourceProvider; -import org.hl7.fhir.instance.model.api.IBaseParameters; - -public interface IValidationProviders { - String CODE_SYSTEM = "http://code.system/url"; - String CODE_SYSTEM_VERSION = "1.0.0"; - String CODE_SYSTEM_NAME = "Test Code System"; - String CODE = "CODE"; - String VALUE_SET_URL = "http://value.set/url"; - String DISPLAY = "Explanation for code TestCode."; - String LANGUAGE = "en"; - String ERROR_MESSAGE = "This is an error message"; - - interface IMyCodeSystemProvider extends IResourceProvider { - String getCode(); - String getSystem(); - String getDisplay(); - void setException(Exception theException); - void setReturnParams(IBaseParameters theParameters); - } - - interface IMyLookupCodeProvider extends IResourceProvider { - String getCode(); - String getSystem(); - void setLookupCodeResult(IValidationSupport.LookupCodeResult theLookupCodeResult); - } - - interface IMyValueSetProvider extends IResourceProvider { - String getCode(); - String getSystem(); - String getDisplay(); - String getValueSet(); - void setException(Exception theException); - void setReturnParams(IBaseParameters theParameters); - } -} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapperTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapperTest.java index 57aae8d96f99..cbc79fadc981 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapperTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapperTest.java @@ -7,7 +7,6 @@ import ca.uhn.fhir.fhirpath.BaseValidationTestWithInlineMocks; import ca.uhn.fhir.i18n.HapiLocalizer; import ca.uhn.hapi.converters.canonical.VersionCanonicalizer; - import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; @@ -16,17 +15,18 @@ import org.junit.jupiter.api.Test; import org.mockito.quality.Strictness; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; -import java.util.List; - public class VersionSpecificWorkerContextWrapperTest extends BaseValidationTestWithInlineMocks { final byte[] EXPECTED_BINARY_CONTENT_1 = "dummyBinaryContent1".getBytes(); @@ -80,7 +80,7 @@ public void cacheResource_normally_executesWithoutException() { } @Test - public void validateCode_normally_resolvesCodeSystemFromValueSet() { + public void validateCode_codeInValueSet_resolvesCodeSystemFromValueSet() { // setup IValidationSupport validationSupport = mockValidationSupport(); ValidationSupportContext mockContext = mockValidationSupportContext(validationSupport); @@ -90,8 +90,7 @@ public void validateCode_normally_resolvesCodeSystemFromValueSet() { ValueSet valueSet = new ValueSet(); valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0"); valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2"); - when(validationSupport.fetchResource(eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(valueSet); - when(validationSupport.validateCodeInValueSet(any(), any(), any(), any(), any(), any())).thenReturn(new IValidationSupport.CodeValidationResult()); + when(validationSupport.validateCodeInValueSet(any(), any(), any(), any(), any(), any())).thenReturn(mock(IValidationSupport.CodeValidationResult.class)); // execute wrapper.validateCode(new ValidationOptions(), "code0", valueSet); @@ -101,6 +100,26 @@ public void validateCode_normally_resolvesCodeSystemFromValueSet() { verify(validationSupport, times(1)).validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), any()); } + @Test + public void validateCode_codeNotInValueSet_doesNotResolveSystem() { + // setup + IValidationSupport validationSupport = mockValidationSupport(); + ValidationSupportContext mockContext = mockValidationSupportContext(validationSupport); + VersionCanonicalizer versionCanonicalizer = new VersionCanonicalizer(FhirContext.forR5Cached()); + VersionSpecificWorkerContextWrapper wrapper = new VersionSpecificWorkerContextWrapper(mockContext, versionCanonicalizer); + + ValueSet valueSet = new ValueSet(); + valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0"); + valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2"); + + // execute + wrapper.validateCode(new ValidationOptions(), "code1", valueSet); + + // verify + verify(validationSupport, times(1)).validateCodeInValueSet(any(), any(), eq(null), eq("code1"), any(), any()); + verify(validationSupport, never()).validateCode(any(), any(), any(), any(), any(), any()); + } + @Test public void isPrimitive_primitive() { // setup diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu2/hapi/validation/FhirInstanceValidatorDstu2Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu2/hapi/validation/FhirInstanceValidatorDstu2Test.java index da73c0be8000..5a53ba0ac3d4 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu2/hapi/validation/FhirInstanceValidatorDstu2Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu2/hapi/validation/FhirInstanceValidatorDstu2Test.java @@ -4,7 +4,6 @@ import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport; -import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.fhirpath.BaseValidationTestWithInlineMocks; import ca.uhn.fhir.model.dstu2.composite.PeriodDt; import ca.uhn.fhir.model.dstu2.resource.Parameters; @@ -28,10 +27,7 @@ import org.hl7.fhir.dstu2.model.QuestionnaireResponse; import org.hl7.fhir.dstu2.model.QuestionnaireResponse.QuestionnaireResponseStatus; import org.hl7.fhir.dstu2.model.StringType; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.utilities.validation.ValidationMessage; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -41,9 +37,7 @@ import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; -import java.util.Optional; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -100,7 +94,7 @@ public IValidationSupport.CodeValidationResult answer(InvocationOnMock theInvoca if (myValidConcepts.contains(system + "___" + code)) { retVal = new IValidationSupport.CodeValidationResult().setCode(code); } else if (myValidSystems.contains(system)) { - return new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage("Unknown code"); + return new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage("Unknown code"); } else { retVal = null; } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java index ad6276f2761a..cfbeca68626a 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java @@ -58,7 +58,6 @@ import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; -import org.hl7.fhir.utilities.validation.ValidationMessage; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -229,10 +228,10 @@ public IValidationSupport.CodeValidationResult answer(InvocationOnMock theInvoca retVal = new IValidationSupport.CodeValidationResult().setCode(code); } else if (myValidSystems.contains(system)) { final String message = "Unknown code (for '" + system + "#" + code + "')"; - retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); + retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message).setIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); } else if (myValidSystemsNotReturningIssues.contains(system)) { final String message = "Unknown code (for '" + system + "#" + code + "')"; - retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message); + retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message); } else if (myCodeSystems.containsKey(system)) { CodeSystem cs = myCodeSystems.get(system); Optional found = cs.getConcept().stream().filter(t -> t.getCode().equals(code)).findFirst(); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/IValidateCodeProvidersDstu3.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/IValidateCodeProvidersDstu3.java deleted file mode 100644 index 0c639e310ee2..000000000000 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/IValidateCodeProvidersDstu3.java +++ /dev/null @@ -1,159 +0,0 @@ -package org.hl7.fhir.dstu3.hapi.validation; - -import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Operation; -import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import jakarta.servlet.http.HttpServletRequest; -import org.hl7.fhir.common.hapi.validation.IValidationProviders; -import org.hl7.fhir.dstu3.model.BooleanType; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.dstu3.model.CodeType; -import org.hl7.fhir.dstu3.model.Coding; -import org.hl7.fhir.dstu3.model.IdType; -import org.hl7.fhir.dstu3.model.Parameters; -import org.hl7.fhir.dstu3.model.StringType; -import org.hl7.fhir.dstu3.model.UriType; -import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.instance.model.api.IBaseParameters; -import org.hl7.fhir.instance.model.api.IBaseResource; - -import java.util.List; - -public interface IValidateCodeProvidersDstu3 { - @SuppressWarnings("unused") - class MyCodeSystemProviderDstu3 implements IValidationProviders.IMyCodeSystemProvider { - private UriType mySystemUrl; - private CodeType myCode; - private StringType myDisplay; - private Exception myException; - private Parameters myReturnParams; - - @Operation(name = "validate-code", idempotent = true, returnParameters = { - @OperationParam(name = "result", type = org.hl7.fhir.dstu3.model.BooleanType.class, min = 1), - @OperationParam(name = "message", type = org.hl7.fhir.dstu3.model.StringType.class), - @OperationParam(name = "display", type = org.hl7.fhir.dstu3.model.StringType.class) - }) - public org.hl7.fhir.dstu3.model.Parameters validateCode( - HttpServletRequest theServletRequest, - @IdParam(optional = true) org.hl7.fhir.dstu3.model.IdType theId, - @OperationParam(name = "url", min = 0, max = 1) org.hl7.fhir.dstu3.model.UriType theCodeSystemUrl, - @OperationParam(name = "code", min = 0, max = 1) org.hl7.fhir.dstu3.model.CodeType theCode, - @OperationParam(name = "display", min = 0, max = 1) org.hl7.fhir.dstu3.model.StringType theDisplay - ) throws Exception { - mySystemUrl = theCodeSystemUrl; - myCode = theCode; - myDisplay = theDisplay; - if (myException != null) { - throw myException; - } - return myReturnParams; - } - - @Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= { - @OperationParam(name = "name", type = org.hl7.fhir.dstu3.model.StringType.class, min = 1), - @OperationParam(name = "version", type = org.hl7.fhir.dstu3.model.StringType.class), - @OperationParam(name = "display", type = org.hl7.fhir.dstu3.model.StringType.class, min = 1), - @OperationParam(name = "abstract", type = org.hl7.fhir.dstu3.model.BooleanType.class, min = 1), - @OperationParam(name = "property", type = org.hl7.fhir.dstu3.model.StringType.class, min = 0, max = OperationParam.MAX_UNLIMITED) - }) - public IBaseParameters lookup( - HttpServletRequest theServletRequest, - @OperationParam(name = "code", max = 1) org.hl7.fhir.dstu3.model.CodeType theCode, - @OperationParam(name = "system",max = 1) org.hl7.fhir.dstu3.model.UriType theSystem, - @OperationParam(name = "coding", max = 1) Coding theCoding, - @OperationParam(name = "version", max = 1) org.hl7.fhir.dstu3.model.StringType theVersion, - @OperationParam(name = "displayLanguage", max = 1) org.hl7.fhir.dstu3.model.CodeType theDisplayLanguage, - @OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List thePropertyNames, - RequestDetails theRequestDetails - ) { - myCode = theCode; - return myReturnParams; - } - - @Override - public Class getResourceType() { - return CodeSystem.class; - } - - public void setException(Exception theException) { - myException = theException; - } - @Override - public void setReturnParams(IBaseParameters theParameters) { - myReturnParams = (Parameters) theParameters; - } - @Override - public String getCode() { - return myCode != null ? myCode.getValueAsString() : null; - } - @Override - public String getSystem() { - return mySystemUrl != null ? mySystemUrl.getValueAsString() : null; - } - public String getDisplay() { - return myDisplay != null ? myDisplay.getValue() : null; - } - } - - @SuppressWarnings("unused") - class MyValueSetProviderDstu3 implements IValidationProviders.IMyValueSetProvider { - private Exception myException; - private Parameters myReturnParams; - private UriType mySystemUrl; - private UriType myValueSetUrl; - private CodeType myCode; - private StringType myDisplay; - - @Operation(name = "validate-code", idempotent = true, returnParameters = { - @OperationParam(name = "result", type = BooleanType.class, min = 1), - @OperationParam(name = "message", type = org.hl7.fhir.dstu3.model.StringType.class), - @OperationParam(name = "display", type = org.hl7.fhir.dstu3.model.StringType.class) - }) - public Parameters validateCode( - HttpServletRequest theServletRequest, - @IdParam(optional = true) IdType theId, - @OperationParam(name = "url", min = 0, max = 1) org.hl7.fhir.dstu3.model.UriType theValueSetUrl, - @OperationParam(name = "code", min = 0, max = 1) CodeType theCode, - @OperationParam(name = "system", min = 0, max = 1) UriType theSystem, - @OperationParam(name = "display", min = 0, max = 1) StringType theDisplay, - @OperationParam(name = "valueSet") org.hl7.fhir.dstu3.model.ValueSet theValueSet - ) throws Exception { - mySystemUrl = theSystem; - myValueSetUrl = theValueSetUrl; - myCode = theCode; - myDisplay = theDisplay; - if (myException != null) { - throw myException; - } - return myReturnParams; - } - @Override - public Class getResourceType() { - return ValueSet.class; - } - public void setException(Exception theException) { - myException = theException; - } - @Override - public void setReturnParams(IBaseParameters theParameters) { - myReturnParams = (Parameters) theParameters; - } - @Override - public String getCode() { - return myCode != null ? myCode.getValueAsString() : null; - } - @Override - public String getSystem() { - return mySystemUrl != null ? mySystemUrl.getValueAsString() : null; - } - @Override - public String getValueSet() { - return myValueSetUrl != null ? myValueSetUrl.getValueAsString() : null; - } - public String getDisplay() { - return myDisplay != null ? myDisplay.getValue() : null; - } - } -} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java index 7804b9df10f2..57591b31e763 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java @@ -41,7 +41,6 @@ import org.hl7.fhir.dstu3.model.UriType; import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.utilities.validation.ValidationMessage; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -56,6 +55,8 @@ import java.util.List; import java.util.stream.Collectors; +import static ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity.ERROR; +import static ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity.WARNING; import static org.assertj.core.api.Assertions.assertThat; import static org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType.BOOLEAN; import static org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType.CHOICE; @@ -224,7 +225,7 @@ public void testCodedAnswer() { when(myValSupport.validateCodeInValueSet(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(ValueSet.class))) .thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0")); when(myValSupport.validateCodeInValueSet(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(ValueSet.class))) - .thenReturn(new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage("Unknown code")); + .thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(ERROR).setMessage("Unknown code")); CodeSystem codeSystem = new CodeSystem(); codeSystem.setContent(CodeSystemContentMode.COMPLETE); @@ -246,7 +247,7 @@ public void testCodedAnswer() { when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class))) .thenReturn(new IValidationSupport.CodeValidationResult().setCode(CODE_ICC_SCHOOLTYPE_PT)); when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class))) - .thenReturn(new IValidationSupport.CodeValidationResult().setSeverityCode("warning").setMessage("Unknown code: http://codesystems.com/system / code1")); + .thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(WARNING).setMessage("Unknown code: http://codesystems.com/system / code1")); QuestionnaireResponse qa; @@ -1034,7 +1035,7 @@ public void testOpenchoiceAnswer() { when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class))) .thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0")); when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class))) - .thenReturn(new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage("Unknown code")); + .thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(ERROR).setMessage("Unknown code")); CodeSystem codeSystem = new CodeSystem(); codeSystem.setContent(CodeSystemContentMode.COMPLETE); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeDstu3Test.java index 6e98c4b31a94..56bc892c3d41 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeDstu3Test.java @@ -12,9 +12,9 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; +import ca.uhn.fhir.test.utilities.validation.IValidationProviders; import jakarta.servlet.http.HttpServletRequest; import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyLookupCodeTest; -import org.hl7.fhir.common.hapi.validation.IValidationProviders; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.dstu3.model.BooleanType; import org.hl7.fhir.dstu3.model.CodeSystem; @@ -164,8 +164,6 @@ void lookupCode_withCodeSystemWithDesignation_returnsCorrectDesignation(final IV @SuppressWarnings("unused") static class MyLookupCodeProviderDstu3 implements IValidationProviders.IMyLookupCodeProvider { - private UriType mySystemUrl; - private CodeType myCode; private LookupCodeResult myLookupCodeResult; @Override @@ -190,8 +188,6 @@ public IBaseParameters lookup( @OperationParam(name= " property", max = OperationParam.MAX_UNLIMITED) List thePropertyNames, RequestDetails theRequestDetails ) { - myCode = theCode; - mySystemUrl = theSystem; if (theSystem == null) { throw new InvalidRequestException(MessageFormat.format(MESSAGE_RESPONSE_INVALID, theCode)); } @@ -205,15 +201,5 @@ public IBaseParameters lookup( public Class getResourceType() { return CodeSystem.class; } - - @Override - public String getCode() { - return myCode != null ? myCode.getValueAsString() : null; - } - - @Override - public String getSystem() { - return mySystemUrl != null ? mySystemUrl.getValueAsString() : null; - } } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeWithResponseFileDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeWithResponseFileDstu3Test.java index 48a99f260d0b..4817542ef4dd 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeWithResponseFileDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeWithResponseFileDstu3Test.java @@ -5,13 +5,10 @@ import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; -import ca.uhn.fhir.util.ClasspathUtil; -import org.hl7.fhir.common.hapi.validation.IValidationProviders; +import ca.uhn.fhir.test.utilities.validation.IValidationProvidersDstu3; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; -import org.hl7.fhir.dstu3.model.Parameters; import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.instance.model.api.IBaseParameters; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -19,13 +16,15 @@ import java.util.List; +import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_LOOKUP; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; public class RemoteTerminologyLookupCodeWithResponseFileDstu3Test { private static final FhirContext ourCtx = FhirContext.forDstu3Cached(); - private IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3 myCodeSystemProvider; + private IValidationProvidersDstu3.MyCodeSystemProviderDstu3 myCodeSystemProvider; @RegisterExtension public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); @@ -36,7 +35,7 @@ public void before() { String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl); mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true)); - myCodeSystemProvider = new IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3(); + myCodeSystemProvider = new IValidationProvidersDstu3.MyCodeSystemProviderDstu3(); ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider); } @@ -47,13 +46,10 @@ public void after() { } @Test void lookupCode_withParametersOutput_convertsCorrectly() { - String paramsAsString = ClasspathUtil.loadResource("/terminology/CodeSystem-lookup-output-with-subproperties.json"); - IBaseResource baseResource = ourCtx.newJsonParser().parseResource(paramsAsString); - assertTrue(baseResource instanceof Parameters); - Parameters resultParameters = (Parameters) baseResource; - myCodeSystemProvider.setReturnParams(resultParameters); + String outputFile ="/terminology/CodeSystem-lookup-output-with-subproperties.json"; + IBaseParameters resultParameters = myCodeSystemProvider.addTerminologyResponse(OPERATION_LOOKUP, CODE_SYSTEM, CODE, ourCtx, outputFile); - LookupCodeRequest request = new LookupCodeRequest(IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, null, List.of("interfaces")); + LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, null, List.of("interfaces")); // test IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, request); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyValidateCodeDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyValidateCodeDstu3Test.java index af4f39f09262..2f573ad3e5fc 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyValidateCodeDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyValidateCodeDstu3Test.java @@ -4,16 +4,18 @@ import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; -import ca.uhn.fhir.util.ClasspathUtil; +import ca.uhn.fhir.test.utilities.validation.IValidationProviders; +import ca.uhn.fhir.test.utilities.validation.IValidationProvidersDstu3; import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyValidateCodeTest; -import org.hl7.fhir.common.hapi.validation.IValidationProviders; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.dstu3.model.BooleanType; +import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.dstu3.model.Parameters; import org.hl7.fhir.dstu3.model.Resource; 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.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseResource; import org.junit.jupiter.api.AfterEach; @@ -22,6 +24,11 @@ import java.util.List; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_VERSION; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.ERROR_MESSAGE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.VALUE_SET_URL; import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM; import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET; @@ -38,8 +45,8 @@ public class RemoteTerminologyValidateCodeDstu3Test implements IRemoteTerminolog private static final FhirContext ourCtx = FhirContext.forDstu3Cached(); @RegisterExtension public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); - private IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3 myCodeSystemProvider; - private IValidateCodeProvidersDstu3.MyValueSetProviderDstu3 myValueSetProvider; + private IValidationProviders.MyValidationProvider myCodeSystemProvider; + private IValidationProviders.MyValidationProvider myValueSetProvider; private RemoteTerminologyServiceValidationSupport mySvc; private String myCodeSystemError, myValueSetError; @@ -48,14 +55,14 @@ public void before() { String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); myCodeSystemError = ourCtx.getLocalizer().getMessage( RemoteTerminologyServiceValidationSupport.class, - ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM, IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, baseUrl, IValidationProviders.ERROR_MESSAGE); + ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM, CODE_SYSTEM, CODE, baseUrl, ERROR_MESSAGE); myValueSetError = ourCtx.getLocalizer().getMessage( RemoteTerminologyServiceValidationSupport.class, - ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET, IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, IValidationProviders.VALUE_SET_URL, baseUrl, IValidationProviders.ERROR_MESSAGE); + ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET, CODE_SYSTEM, CODE, VALUE_SET_URL, baseUrl, ERROR_MESSAGE); mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl); mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true)); - myCodeSystemProvider = new IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3(); - myValueSetProvider = new IValidateCodeProvidersDstu3.MyValueSetProviderDstu3(); + myCodeSystemProvider = new IValidationProvidersDstu3.MyCodeSystemProviderDstu3(); + myValueSetProvider = new IValidationProvidersDstu3.MyValueSetProviderDstu3(); ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider, myValueSetProvider); } @@ -82,45 +89,40 @@ public String getValueSetError() { } @Override - public IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3 getCodeSystemProvider() { + public IValidationProviders.IMyValidationProvider getCodeSystemProvider() { return myCodeSystemProvider; } @Override - public IValidateCodeProvidersDstu3.MyValueSetProviderDstu3 getValueSetProvider() { + public IValidationProviders.IMyValidationProvider getValueSetProvider() { return myValueSetProvider; } @Override public IBaseOperationOutcome getCodeSystemInvalidCodeOutcome() { - return ClasspathUtil.loadResource(getService().getFhirContext(), OperationOutcome.class, "/terminology/OperationOutcome-CodeSystem-invalid-code.json"); + return getCodeSystemInvalidCodeOutcome(OperationOutcome.class); } @Override public IBaseOperationOutcome getValueSetInvalidCodeOutcome() { - return ClasspathUtil.loadResource(getService().getFhirContext(), OperationOutcome.class, "/terminology/OperationOutcome-ValueSet-invalid-code.json"); + return getValueSetInvalidCodeOutcome(OperationOutcome.class); + } + + @Override + public IBaseOperationOutcome getValueSetCustomDetailCodeOutcome() { + return getValueSetCustomDetailCodeOutcome(OperationOutcome.class); } @Override public Parameters createParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) { Parameters parameters = new Parameters(); parameters.addParameter().setName("result").setValue(new BooleanType(theResult)); - parameters.addParameter().setName("code").setValue(new StringType(IValidationProviders.CODE)); - parameters.addParameter().setName("system").setValue(new UriType(IValidationProviders.CODE_SYSTEM)); - parameters.addParameter().setName("version").setValue(new StringType(IValidationProviders.CODE_SYSTEM_VERSION)); + parameters.addParameter().setName("code").setValue(new StringType(CODE)); + parameters.addParameter().setName("system").setValue(new UriType(CODE_SYSTEM)); + parameters.addParameter().setName("version").setValue(new StringType(CODE_SYSTEM_VERSION)); parameters.addParameter().setName("display").setValue(new StringType(theDisplay)); parameters.addParameter().setName("message").setValue(new StringType(theMessage)); parameters.addParameter().setName("issues").setResource((Resource) theIssuesResource); return parameters; } - - @Override - public void createCodeSystemReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) { - myCodeSystemProvider.setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource)); - } - - @Override - public void createValueSetReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) { - myValueSetProvider.setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource)); - } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java index 15bbc3a8bf35..1c2382b6aac1 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java @@ -54,6 +54,7 @@ public void testCrossResourceBoundaries() throws FHIRException { List value; + value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen", Base.class); assertThat(value).hasSize(1); value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen.resolve()", Base.class); @@ -65,6 +66,9 @@ public void testCrossResourceBoundaries() throws FHIRException { assertEquals("2011-01-01", ((DateTimeType) value.get(0)).getValueAsString()); } + + + @Test public void testComponentCode() { String path = "(Observation.component.value.ofType(FHIR.Quantity)) "; diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java index d03c3aa974d8..fb46f7f80089 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java @@ -307,10 +307,10 @@ public IValidationSupport.CodeValidationResult answer(InvocationOnMock theInvoca retVal = new IValidationSupport.CodeValidationResult().setCode(code); } else if (myValidSystems.contains(system)) { final String message = "Unknown code (for '" + system + "#" + code + "')"; - retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); + retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message).setIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); } else if (myValidSystemsNotReturningIssues.contains(system)) { final String message = "Unknown code (for '" + system + "#" + code + "')"; - retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message); + retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message); } else { retVal = myDefaultValidationSupport.validateCode(new ValidationSupportContext(myDefaultValidationSupport), options, system, code, display, valueSetUrl); } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeR4Test.java index d67966df6d46..382f621f547e 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeR4Test.java @@ -11,9 +11,10 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; 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 jakarta.servlet.http.HttpServletRequest; import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyLookupCodeTest; -import org.hl7.fhir.common.hapi.validation.IValidationProviders; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseParameters; @@ -52,7 +53,7 @@ public class RemoteTerminologyLookupCodeR4Test implements IRemoteTerminologyLook @RegisterExtension public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); private final RemoteTerminologyServiceValidationSupport mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx); - private IValidateCodeProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider; + private IValidationProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider; private MyLookupCodeProviderR4 myLookupCodeProviderR4; @BeforeEach @@ -60,7 +61,7 @@ public void before() { String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); mySvc.setBaseUrl(baseUrl); mySvc.addClientInterceptor(new LoggingInterceptor(true)); - myCodeSystemProvider = new IValidateCodeProvidersR4.MyCodeSystemProviderR4(); + myCodeSystemProvider = new IValidationProvidersR4.MyCodeSystemProviderR4(); myLookupCodeProviderR4 = new MyLookupCodeProviderR4(); ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider, myLookupCodeProviderR4); } @@ -166,8 +167,6 @@ void lookupCode_withCodeSystemWithDesignation_returnsCorrectDesignation(final IV @SuppressWarnings("unused") static class MyLookupCodeProviderR4 implements IValidationProviders.IMyLookupCodeProvider { - private UriType mySystemUrl; - private CodeType myCode; private LookupCodeResult myLookupCodeResult; @Override @@ -192,8 +191,6 @@ public IBaseParameters lookup( @OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List thePropertyNames, RequestDetails theRequestDetails ) { - myCode = theCode; - mySystemUrl = theSystem; if (theSystem == null) { throw new InvalidRequestException(MessageFormat.format(MESSAGE_RESPONSE_INVALID, theCode)); } @@ -206,15 +203,5 @@ public IBaseParameters lookup( public Class getResourceType() { return CodeSystem.class; } - - @Override - public String getCode() { - return myCode != null ? myCode.getValueAsString() : null; - } - - @Override - public String getSystem() { - return mySystemUrl != null ? mySystemUrl.getValueAsString() : null; - } } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeWithResponseFileR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeWithResponseFileR4Test.java index 37eba91d0ca1..a0896bd8c4e7 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeWithResponseFileR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeWithResponseFileR4Test.java @@ -5,12 +5,9 @@ import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; -import ca.uhn.fhir.util.ClasspathUtil; -import org.hl7.fhir.common.hapi.validation.IValidationProviders; +import ca.uhn.fhir.test.utilities.validation.IValidationProvidersR4; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.instance.model.api.IBaseParameters; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.StringType; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -19,13 +16,15 @@ import java.util.List; +import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_LOOKUP; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; public class RemoteTerminologyLookupCodeWithResponseFileR4Test { private static final FhirContext ourCtx = FhirContext.forR4Cached(); - private IValidateCodeProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider; + private IValidationProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider; @RegisterExtension public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); @@ -36,7 +35,7 @@ public void before() { String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl); mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true)); - myCodeSystemProvider = new IValidateCodeProvidersR4.MyCodeSystemProviderR4(); + myCodeSystemProvider = new IValidationProvidersR4.MyCodeSystemProviderR4(); ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider); } @@ -48,13 +47,10 @@ public void after() { @Test void lookupCode_withParametersOutput_convertsCorrectly() { - String paramsAsString = ClasspathUtil.loadResource("/terminology/CodeSystem-lookup-output-with-subproperties.json"); - IBaseResource baseResource = ourCtx.newJsonParser().parseResource(paramsAsString); - assertTrue(baseResource instanceof Parameters); - Parameters resultParameters = (Parameters) baseResource; - myCodeSystemProvider.setReturnParams(resultParameters); + String outputFile ="/terminology/CodeSystem-lookup-output-with-subproperties.json"; + IBaseParameters resultParameters = myCodeSystemProvider.addTerminologyResponse(OPERATION_LOOKUP, CODE_SYSTEM, CODE, ourCtx, outputFile); - LookupCodeRequest request = new LookupCodeRequest(IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, null, List.of("interfaces")); + LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, null, List.of("interfaces")); // test IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, request); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyValidateCodeR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyValidateCodeR4Test.java index 08f6c251869d..ffd8045a8a53 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyValidateCodeR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyValidateCodeR4Test.java @@ -2,8 +2,8 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.ConceptValidationOptions; -import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult; +import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.parser.IJsonLikeParser; import ca.uhn.fhir.rest.client.api.IClientInterceptor; import ca.uhn.fhir.rest.client.api.IHttpRequest; @@ -13,11 +13,11 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; -import ca.uhn.fhir.util.ClasspathUtil; +import ca.uhn.fhir.test.utilities.validation.IValidationProviders; +import ca.uhn.fhir.test.utilities.validation.IValidationProvidersR4; import ca.uhn.fhir.util.ParametersUtil; import com.google.common.collect.Lists; import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyValidateCodeTest; -import org.hl7.fhir.common.hapi.validation.IValidationProviders; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -39,6 +39,12 @@ import java.util.List; import java.util.stream.Stream; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_VERSION; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.DISPLAY; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.ERROR_MESSAGE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.VALUE_SET_URL; import static org.assertj.core.api.Assertions.assertThat; import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM; import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET; @@ -61,8 +67,8 @@ public class RemoteTerminologyValidateCodeR4Test implements IRemoteTerminologyVa private static final FhirContext ourCtx = FhirContext.forR4Cached(); @RegisterExtension public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); - private IValidateCodeProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider; - private IValidateCodeProvidersR4.MyValueSetProviderR4 myValueSetProvider; + private IValidationProviders.IMyValidationProvider myCodeSystemProvider; + private IValidationProviders.IMyValidationProvider myValueSetProvider; private RemoteTerminologyServiceValidationSupport mySvc; private String myCodeSystemError, myValueSetError; @@ -71,14 +77,14 @@ public void before() { String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); myCodeSystemError = ourCtx.getLocalizer().getMessage( RemoteTerminologyServiceValidationSupport.class, - ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM, IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, baseUrl, IValidationProviders.ERROR_MESSAGE); + ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM, CODE_SYSTEM, CODE, baseUrl, ERROR_MESSAGE); myValueSetError = ourCtx.getLocalizer().getMessage( RemoteTerminologyServiceValidationSupport.class, - ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET, IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, IValidationProviders.VALUE_SET_URL, baseUrl, IValidationProviders.ERROR_MESSAGE); + ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET, CODE_SYSTEM, CODE, VALUE_SET_URL, baseUrl, ERROR_MESSAGE); mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl); mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true)); - myCodeSystemProvider = new IValidateCodeProvidersR4.MyCodeSystemProviderR4(); - myValueSetProvider = new IValidateCodeProvidersR4.MyValueSetProviderR4(); + myCodeSystemProvider = new IValidationProvidersR4.MyCodeSystemProviderR4(); + myValueSetProvider = new IValidationProvidersR4.MyValueSetProviderR4(); ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider, myValueSetProvider); } @@ -95,12 +101,12 @@ public RemoteTerminologyServiceValidationSupport getService() { } @Override - public IValidationProviders.IMyCodeSystemProvider getCodeSystemProvider() { + public IValidationProviders.IMyValidationProvider getCodeSystemProvider() { return myCodeSystemProvider; } @Override - public IValidationProviders.IMyValueSetProvider getValueSetProvider() { + public IValidationProviders.IMyValidationProvider getValueSetProvider() { return myValueSetProvider; } @@ -116,51 +122,40 @@ public String getValueSetError() { @Override public IBaseOperationOutcome getCodeSystemInvalidCodeOutcome() { - return ClasspathUtil.loadResource(getService().getFhirContext(), OperationOutcome.class, "/terminology/OperationOutcome-CodeSystem-invalid-code.json"); + return getCodeSystemInvalidCodeOutcome(OperationOutcome.class); } @Override public IBaseOperationOutcome getValueSetInvalidCodeOutcome() { - return ClasspathUtil.loadResource(getService().getFhirContext(), OperationOutcome.class, "/terminology/OperationOutcome-ValueSet-invalid-code.json"); + return getValueSetInvalidCodeOutcome(OperationOutcome.class); } @Override - public List getCodeValidationIssues(IBaseOperationOutcome theOperationOutcome) { - return ((OperationOutcome)theOperationOutcome).getIssue().stream() - .map(issueComponent -> new IValidationSupport.CodeValidationIssue( - issueComponent.getDetails().getText(), - IValidationSupport.IssueSeverity.ERROR, - /* assume issue type is OperationOutcome.IssueType#CODEINVALID as it is the only match */ - IValidationSupport.CodeValidationIssueCode.INVALID, - IValidationSupport.CodeValidationIssueCoding.INVALID_CODE)) - .toList(); + public IBaseOperationOutcome getValueSetCustomDetailCodeOutcome() { + return getValueSetCustomDetailCodeOutcome(OperationOutcome.class); } @Test void validateCodeInValueSet_success() { - createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null); + createValueSetReturnParameters(true, DISPLAY, null, null); ValueSet valueSet = new ValueSet(); - valueSet.setUrl(IValidationProviders.VALUE_SET_URL); + valueSet.setUrl(VALUE_SET_URL); - CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, new ConceptValidationOptions(), IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet); + CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, new ConceptValidationOptions(), CODE_SYSTEM, CODE, DISPLAY, valueSet); assertNotNull(outcome); - assertEquals(IValidationProviders.CODE, outcome.getCode()); - assertEquals(IValidationProviders.DISPLAY, outcome.getDisplay()); + assertEquals(CODE, outcome.getCode()); + assertEquals(DISPLAY, outcome.getDisplay()); assertNull(outcome.getSeverity()); assertNull(outcome.getMessage()); - - assertEquals(IValidationProviders.CODE, myValueSetProvider.getCode()); - assertEquals(IValidationProviders.DISPLAY, myValueSetProvider.getDisplay()); - assertEquals(IValidationProviders.VALUE_SET_URL, myValueSetProvider.getValueSet()); } @Override public Parameters createParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) { Parameters parameters = new Parameters() - .addParameter("code", IValidationProviders.CODE) - .addParameter("system", IValidationProviders.CODE_SYSTEM) - .addParameter("version", IValidationProviders.CODE_SYSTEM_VERSION) + .addParameter("code", CODE) + .addParameter("system", CODE_SYSTEM) + .addParameter("version", CODE_SYSTEM_VERSION) .addParameter("display", theDisplay) .addParameter("message", theMessage); if (theResult != null) { @@ -181,16 +176,16 @@ class ExtractCodeSystemFromValueSet { @Test void validateCodeInValueSet_uniqueComposeInclude() { - createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null); + createValueSetReturnParameters(true, DISPLAY, null, null); ValueSet valueSet = new ValueSet(); - valueSet.setUrl(IValidationProviders.VALUE_SET_URL); + valueSet.setUrl(VALUE_SET_URL); String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender"; valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude( Collections.singletonList(new ValueSet.ConceptSetComponent().setSystem(systemUrl)) )); CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, - new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet); + new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet); // validate service doesn't return error message (as when no code system is present) assertNotNull(outcome); @@ -211,16 +206,16 @@ public static Stream getRemoteTerminologyServerExceptions() { @ParameterizedTest @MethodSource(value = "getRemoteTerminologyServerExceptions") void validateCodeInValueSet_systemNotPresent_returnsValidationResultWithError(Exception theException, String theServerMessage) { - myValueSetProvider.setException(theException); - createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null); + getValueSetProvider().addException("$validate-code", VALUE_SET_URL, CODE, theException); + createValueSetReturnParameters(true, DISPLAY, null, null); ValueSet valueSet = new ValueSet(); - valueSet.setUrl(IValidationProviders.VALUE_SET_URL); + valueSet.setUrl(VALUE_SET_URL); valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude( Lists.newArrayList(new ValueSet.ConceptSetComponent(), new ValueSet.ConceptSetComponent()))); CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, - new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet); + new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet); String unknownCodeForValueSetError = "Unknown code \"null#CODE\" for ValueSet with URL \"http://value.set/url\". The Remote Terminology server http://"; verifyErrorResultFromException(outcome, unknownCodeForValueSetError, theServerMessage); @@ -230,11 +225,11 @@ void validateCodeInValueSet_systemNotPresent_returnsValidationResultWithError(Ex @ParameterizedTest @MethodSource(value = "getRemoteTerminologyServerExceptions") void validateCodeInValueSet_systemPresentCodeNotPresent_returnsValidationResultWithError(Exception theException, String theServerMessage) { - myValueSetProvider.setException(theException); - createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null); + getValueSetProvider().addException(JpaConstants.OPERATION_VALIDATE_CODE, VALUE_SET_URL, CODE, theException); + createValueSetReturnParameters(true, DISPLAY, null, null); ValueSet valueSet = new ValueSet(); - valueSet.setUrl(IValidationProviders.VALUE_SET_URL); + valueSet.setUrl(VALUE_SET_URL); String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender"; String systemUrl2 = "http://hl7.org/fhir/ValueSet/other-valueset"; valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude( @@ -243,7 +238,7 @@ void validateCodeInValueSet_systemPresentCodeNotPresent_returnsValidationResultW new ValueSet.ConceptSetComponent().setSystem(systemUrl2)))); CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, - new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet); + new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet); String unknownCodeForValueSetError = "Unknown code \"null#CODE\" for ValueSet with URL \"http://value.set/url\". The Remote Terminology server http://"; verifyErrorResultFromException(outcome, unknownCodeForValueSetError, theServerMessage); @@ -252,10 +247,10 @@ void validateCodeInValueSet_systemPresentCodeNotPresent_returnsValidationResultW @Test void validateCodeInValueSet_systemPresentCodePresentValidatesOKNoVersioned() { - createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null); + createValueSetReturnParameters(true, DISPLAY, null, null); ValueSet valueSet = new ValueSet(); - valueSet.setUrl(IValidationProviders.VALUE_SET_URL); + valueSet.setUrl(VALUE_SET_URL); String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender"; String systemUrl2 = "http://hl7.org/fhir/ValueSet/other-valueset"; valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude( @@ -264,14 +259,14 @@ void validateCodeInValueSet_systemPresentCodePresentValidatesOKNoVersioned() { new ValueSet.ConceptSetComponent().setSystem(systemUrl2).setConcept( Lists.newArrayList( new ValueSet.ConceptReferenceComponent().setCode("not-the-code"), - new ValueSet.ConceptReferenceComponent().setCode(IValidationProviders.CODE) ) + new ValueSet.ConceptReferenceComponent().setCode(CODE) ) )) )); TestClientInterceptor requestInterceptor = new TestClientInterceptor(); mySvc.addClientInterceptor(requestInterceptor); CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, - new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet); + new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet); assertNotNull(outcome); assertEquals(systemUrl2, requestInterceptor.getCapturedSystemParameter()); @@ -280,10 +275,10 @@ void validateCodeInValueSet_systemPresentCodePresentValidatesOKNoVersioned() { @Test void validateCodeInValueSet_systemPresentCodePresentValidatesOKVersioned() { - createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null); + createValueSetReturnParameters(true, DISPLAY, null, null); ValueSet valueSet = new ValueSet(); - valueSet.setUrl(IValidationProviders.VALUE_SET_URL); + valueSet.setUrl(VALUE_SET_URL); String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender"; String systemVersion = "3.0.2"; String systemUrl2 = "http://hl7.org/fhir/ValueSet/other-valueset"; @@ -294,14 +289,14 @@ void validateCodeInValueSet_systemPresentCodePresentValidatesOKVersioned() { new ValueSet.ConceptSetComponent().setSystem(systemUrl2).setVersion(system2Version).setConcept( Lists.newArrayList( new ValueSet.ConceptReferenceComponent().setCode("not-the-code"), - new ValueSet.ConceptReferenceComponent().setCode(IValidationProviders.CODE) ) + new ValueSet.ConceptReferenceComponent().setCode(CODE) ) )) )); TestClientInterceptor requestInterceptor = new TestClientInterceptor(); mySvc.addClientInterceptor(requestInterceptor); CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, - new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet); + new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet); assertNotNull(outcome); assertEquals(systemUrl2 + "|" + system2Version, requestInterceptor.getCapturedSystemParameter()); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4b/validation/FhirInstanceValidatorR4BTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4b/validation/FhirInstanceValidatorR4BTest.java index ca55a41bf4c2..d5035a8048ef 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4b/validation/FhirInstanceValidatorR4BTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4b/validation/FhirInstanceValidatorR4BTest.java @@ -31,6 +31,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4b.conformance.ProfileUtilities; import org.hl7.fhir.r4b.context.IWorkerContext; +import org.hl7.fhir.r4b.fhirpath.FHIRPathEngine; import org.hl7.fhir.r4b.hapi.ctx.HapiWorkerContext; import org.hl7.fhir.r4b.model.AllergyIntolerance; import org.hl7.fhir.r4b.model.Base; @@ -61,7 +62,6 @@ import org.hl7.fhir.r4b.model.ValueSet; import org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.r4b.terminologies.ValueSetExpander; -import org.hl7.fhir.r4b.fhirpath.FHIRPathEngine; import org.hl7.fhir.r5.test.utils.ClassesLoadedFlags; import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; @@ -203,7 +203,7 @@ public IValidationSupport.CodeValidationResult answer(InvocationOnMock theInvoca retVal = new IValidationSupport.CodeValidationResult().setCode(code); } else if (myValidSystems.contains(system)) { final String message = "Unknown code (for '" + system + "#" + code + "')"; - return new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); + return new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message).setIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); } else { retVal = myDefaultValidationSupport.validateCode(new ValidationSupportContext(myDefaultValidationSupport), options, system, code, display, valueSetUrl); } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java index f0ea48686e17..abcb0f947046 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java @@ -48,7 +48,6 @@ import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; -import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; @@ -200,10 +199,10 @@ public IValidationSupport.CodeValidationResult answer(InvocationOnMock theInvoca retVal = new IValidationSupport.CodeValidationResult().setCode(code); } else if (myValidSystems.contains(system)) { String theMessage = "Unknown code (for '" + system + "#" + code + "')"; - retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(theMessage).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(theMessage, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); + retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(theMessage).setIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(theMessage, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); } else if (myValidSystemsNotReturningIssues.contains(system)) { final String message = "Unknown code (for '" + system + "#" + code + "')"; - retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message); + retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message); } else { retVal = myDefaultValidationSupport.validateCode(new ValidationSupportContext(myDefaultValidationSupport), options, system, code, display, valueSetUrl); } diff --git a/hapi-fhir-validation/src/test/resources/terminology/OperationOutcome-ValueSet-custom-issue-detail.json b/hapi-fhir-validation/src/test/resources/terminology/OperationOutcome-ValueSet-custom-issue-detail.json new file mode 100644 index 000000000000..0823a430cf83 --- /dev/null +++ b/hapi-fhir-validation/src/test/resources/terminology/OperationOutcome-ValueSet-custom-issue-detail.json @@ -0,0 +1,22 @@ +{ + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "information", + "code": "processing", + "details": { + "coding": [ + { + "system": "http://example.com/custom-issue-type", + "code": "valueset-is-draft" + } + ] + }, + "diagnostics": "The ValueSet status is marked as draft.", + "location": [ + "Bundle", + "Line[1] Col[2]" + ] + } + ] +} \ No newline at end of file diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/Configuration.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/Configuration.java index 3fd0a49aa5e2..640bde8c9943 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/Configuration.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/Configuration.java @@ -4,7 +4,7 @@ import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.tinder.parser.BaseStructureSpreadsheetParser; -import org.apache.commons.lang.WordUtils; +import org.apache.commons.text.WordUtils; import java.io.File; import java.io.IOException; diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderGenericSingleFileMojo.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderGenericSingleFileMojo.java index e5a2816340fb..08fc8f2d7bbb 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderGenericSingleFileMojo.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderGenericSingleFileMojo.java @@ -7,7 +7,7 @@ import ca.uhn.fhir.tinder.parser.BaseStructureParser; import ca.uhn.fhir.tinder.parser.DatatypeGeneratorUsingSpreadsheet; import ca.uhn.fhir.tinder.parser.TargetType; -import org.apache.commons.lang.WordUtils; +import org.apache.commons.text.WordUtils; import org.apache.maven.model.Resource; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java index 4503b47061f9..4f2fe43f8f6c 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java @@ -6,7 +6,7 @@ import ca.uhn.fhir.tinder.parser.BaseStructureSpreadsheetParser; import ca.uhn.fhir.tinder.parser.ResourceGeneratorUsingModel; import ca.uhn.fhir.util.ClasspathUtil; -import org.apache.commons.lang.WordUtils; +import org.apache.commons.text.WordUtils; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/ant/TinderGeneratorTask.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/ant/TinderGeneratorTask.java index 9819edcc795b..c8304187c85b 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/ant/TinderGeneratorTask.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/ant/TinderGeneratorTask.java @@ -33,7 +33,7 @@ import ca.uhn.fhir.tinder.parser.BaseStructureSpreadsheetParser; import ca.uhn.fhir.tinder.parser.DatatypeGeneratorUsingSpreadsheet; import ca.uhn.fhir.tinder.parser.TargetType; -import org.apache.commons.lang.WordUtils; +import org.apache.commons.text.WordUtils; import org.apache.maven.plugin.MojoFailureException; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/BaseElement.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/BaseElement.java index 8de25b933767..69c30538f573 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/BaseElement.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/BaseElement.java @@ -8,8 +8,8 @@ import java.util.List; import java.util.Map; -import static org.apache.commons.lang.StringUtils.defaultString; -import static org.apache.commons.lang.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.apache.commons.lang3.StringUtils.isNotBlank; public abstract class BaseElement { diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/SearchParameter.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/SearchParameter.java index 7ac33c816932..fe8233c02b97 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/SearchParameter.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/SearchParameter.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.tinder.model; -import org.apache.commons.lang.WordUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.WordUtils; import java.util.ArrayList; import java.util.Collections; diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureParser.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureParser.java index 68b28b0b96bd..9e465ad18cf8 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureParser.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureParser.java @@ -25,9 +25,9 @@ import com.google.common.base.Charsets; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.WordUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.WordUtils; import org.apache.maven.plugin.MojoFailureException; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; @@ -56,8 +56,8 @@ import java.util.TreeMap; import java.util.TreeSet; -import static org.apache.commons.lang.StringUtils.defaultString; -import static org.apache.commons.lang.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.apache.commons.lang3.StringUtils.isNotBlank; public abstract class BaseStructureParser { diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/ResourceGeneratorUsingModel.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/ResourceGeneratorUsingModel.java index 734a35822ab9..0f90505d77bd 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/ResourceGeneratorUsingModel.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/ResourceGeneratorUsingModel.java @@ -6,7 +6,7 @@ import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.tinder.model.Resource; import ca.uhn.fhir.tinder.model.SearchParameter; -import org.apache.commons.lang.WordUtils; +import org.apache.commons.text.WordUtils; import org.apache.maven.plugin.MojoFailureException; import java.io.File; diff --git a/pom.xml b/pom.xml index 31b2d0c455fe..9b19056b43d8 100644 --- a/pom.xml +++ b/pom.xml @@ -999,8 +999,8 @@ 2.12.0 1.10.0 - 2.11.0 - 3.14.0 + 2.17.0 + 3.17.0 1.2 2.23.0 5.8.0 @@ -1013,15 +1013,15 @@ 4.0.4 4.9.0 3.0.3 - 12.0.9 + 12.0.14 3.0.2 5.10.1 0.64.8 9.4.0 - 6.4.1.Final + 6.4.10.Final 1.4.14 - 7.0.0.Final + 7.0.1.Final 9.8.0 2.2 @@ -1046,8 +1046,8 @@ 2.2.22 2.0.13 2.19.0 - 6.1.8 - 2023.1.6 + 6.1.14 + 2024.0.5 4.3.10 3.2.6 2.0.6 @@ -1063,7 +1063,7 @@ 1.0.8 - 3.13.0 + 3.13.1 5.4.1 @@ -1673,7 +1673,7 @@ org.apache.velocity velocity-engine-core - 2.3 + 2.4.1 org.awaitility