From 7bda10ebc2b8d8946adcf8ec941b7c58ae7503ed Mon Sep 17 00:00:00 2001 From: Dmytro Rud Date: Mon, 1 Jul 2024 07:48:31 +0200 Subject: [PATCH] Changes required for XDS MU / RMU --- .../java/ch/bfh/ti/i4mi/mag/MagConstants.java | 37 ++ .../mag/mhd/iti65/Iti65RequestConverter.java | 376 +++++++++--------- .../Iti67FromIti57ResponseConverter.java | 31 +- .../mag/mhd/iti67/Iti67RequestConverter.java | 3 +- .../iti67}/Iti67RequestUpdateConverter.java | 78 ++-- .../mag/mhd/iti67/Iti67ResponseConverter.java | 55 ++- .../i4mi/mag/mhd/iti67/Iti67RouteBuilder.java | 3 +- .../mag/pmir/PatientReferenceCreator.java | 3 + .../mag/mhd/Iti57RequestTranslatorTest.java | 51 +++ .../java/ch/bfh/ti/i4mi/mag/mhd/MhdTest.java | 81 ++++ .../ti/i4mi/mag/mhd/MhdTestRouteBuilder.java | 32 ++ src/test/resources/application-test.yml | 12 +- src/test/resources/update-request-1.json | 175 ++++++++ 13 files changed, 689 insertions(+), 248 deletions(-) create mode 100644 src/main/java/ch/bfh/ti/i4mi/mag/MagConstants.java rename src/main/java/{org/openehealth/ipf/commons/ihe/fhir/iti67_v401 => ch/bfh/ti/i4mi/mag/mhd/iti67}/Iti67RequestUpdateConverter.java (59%) create mode 100644 src/test/java/ch/bfh/ti/i4mi/mag/mhd/Iti57RequestTranslatorTest.java create mode 100644 src/test/java/ch/bfh/ti/i4mi/mag/mhd/MhdTest.java create mode 100644 src/test/java/ch/bfh/ti/i4mi/mag/mhd/MhdTestRouteBuilder.java create mode 100644 src/test/resources/update-request-1.json diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/MagConstants.java b/src/main/java/ch/bfh/ti/i4mi/mag/MagConstants.java new file mode 100644 index 00000000..82b94341 --- /dev/null +++ b/src/main/java/ch/bfh/ti/i4mi/mag/MagConstants.java @@ -0,0 +1,37 @@ +package ch.bfh.ti.i4mi.mag; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public class MagConstants { + + @UtilityClass + public static class FhirExtensionUrls { + private static final String MHD_PREFIX = "https://profiles.ihe.net/ITI/MHD/StructureDefinition/"; + private static final String CH_PREFIX = "http://fhir.ch/ig/ch-epr-mhealth/StructureDefinition/"; + + // standard MHD extensions + public static final String REPOSITORY_UNIQUE_ID = MHD_PREFIX + "ihe-repositoryUniqueId"; + public static final String DOCUMENT_ENTRY_VERSION = MHD_PREFIX + "ihe-version"; + public static final String DOCUMENT_AVAILABILITY = MHD_PREFIX + "ihe-documentAvailability"; + + // Swiss EPR specific extensions + public static final String CH_AUTHOR_ROLE = CH_PREFIX + "ch-ext-author-authorrole"; + public static final String CH_DELETION_STATUS = CH_PREFIX + "ch-ext-deletionstatus"; + } + + @UtilityClass + public static class XdsExtraMetadataSlotNames { + // Swiss EPR specific extra XDS metadata + public static final String CH_ORIGINAL_PROVIDER_ROLE = "urn:e-health-suisse:2020:originalProviderRole"; + public static final String CH_DELETION_STATUS = "urn:e-health-suisse:2019:deletionStatus"; + } + + // TODO: Move further string literals here + @UtilityClass + public static class FhirCodingSystemIds { + public static final String RFC_3986 = "urn:ietf:rfc:3986"; + public static final String MHD_DOCUMENT_ID_TYPE = "https://profiles.ihe.net/ITI/MHD/CodeSystem/DocumentIdentifierTypes"; + } + +} diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti65/Iti65RequestConverter.java b/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti65/Iti65RequestConverter.java index 9de4af2c..5ed105ca 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti65/Iti65RequestConverter.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti65/Iti65RequestConverter.java @@ -19,17 +19,15 @@ import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Base64; -import java.util.Collections; -import java.util.Formatter; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import javax.activation.DataHandler; +import ch.bfh.ti.i4mi.mag.MagConstants; +import lombok.Setter; import org.apache.camel.Body; import org.apache.commons.codec.binary.Hex; +import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.r4.model.Address; import org.hl7.fhir.r4.model.Annotation; import org.hl7.fhir.r4.model.Attachment; @@ -117,41 +115,43 @@ public class Iti65RequestConverter extends BaseRequestConverter { private SchemeMapper schemeMapper; - + @Autowired + @Setter private Config config; - + @Autowired public void setSchemeMapper(SchemeMapper schemeMapper) { this.schemeMapper = schemeMapper; } - + @Autowired + @Setter private PatientReferenceCreator patientRefCreator; - + /** * convert ITI-65 to ITI-41 request * @param requestBundle * @return */ public ProvideAndRegisterDocumentSet convert(@Body Bundle requestBundle) { - + SubmissionSet submissionSet = new SubmissionSet(); - + ProvideAndRegisterDocumentSetBuilder builder = new ProvideAndRegisterDocumentSetBuilder(true, submissionSet); - + // create mapping fullUrl -> resource for each resource in bundle Map resources = new HashMap(); - - ListResource manifestNeu = null; - + + ListResource manifestNeu = null; + for (Bundle.BundleEntryComponent requestEntry : requestBundle.getEntry()) { Resource resource = requestEntry.getResource(); /*if (resource instanceof DocumentManifest) { manifest = (DocumentManifest) resource; } else*/ if (resource instanceof DocumentReference) { resources.put(requestEntry.getFullUrl(), resource); - + } else if (resource instanceof ListResource) { manifestNeu = (ListResource) resource; //resources.put(requestEntry.getFullUrl(), resource); @@ -159,7 +159,7 @@ public ProvideAndRegisterDocumentSet convert(@Body Bundle requestBundle) { resources.put(requestEntry.getFullUrl(), resource); } else { throw new IllegalArgumentException(resource + " is not allowed here"); - } + } } /*if (manifest != null) { @@ -167,7 +167,7 @@ public ProvideAndRegisterDocumentSet convert(@Body Bundle requestBundle) { } else {*/ processDocumentManifest(manifestNeu, submissionSet); //} - + // set limited metadata for (CanonicalType profile : requestBundle.getMeta().getProfile()) { if ("http://ihe.net/fhir/StructureDefinition/IHE_MHD_Provide_Comprehensive_DocumentBundle".equals(profile.getValue())) { @@ -182,9 +182,9 @@ public ProvideAndRegisterDocumentSet convert(@Body Bundle requestBundle) { submissionSet.setLimitedMetadata(false); } else if ("https://profiles.ihe.net/ITI/MHD/StructureDefinition/IHE.MHD.Minimal.ProvideBundle".equals(profile.getValue())) { submissionSet.setLimitedMetadata(true); - } + } } - + // process all resources referenced in DocumentManifest.content for (ListEntryComponent listEntry : manifestNeu.getEntry()) { Reference content = listEntry.getItem(); @@ -192,8 +192,9 @@ public ProvideAndRegisterDocumentSet convert(@Body Bundle requestBundle) { Resource resource = resources.get(refTarget); if (resource instanceof DocumentReference) { DocumentReference documentReference = (DocumentReference) resource; - Document doc = new Document(); - DocumentEntry entry = new DocumentEntry(); + Document doc = new Document(); + DocumentEntry entry = new DocumentEntry(); + entry.setExtraMetadata(new HashMap<>()); processDocumentReference(documentReference, entry); doc.setDocumentEntry(entry); @@ -208,18 +209,18 @@ public ProvideAndRegisterDocumentSet convert(@Body Bundle requestBundle) { DocumentRelationshipType code = relatesTo.getCode(); Association association = new Association(); switch(code) { - case REPLACES:association.setAssociationType(AssociationType.REPLACE);break; - case TRANSFORMS:association.setAssociationType(AssociationType.TRANSFORM);break; - case SIGNS:association.setAssociationType(AssociationType.SIGNS);break; + case REPLACES:association.setAssociationType(AssociationType.REPLACE);break; + case TRANSFORMS:association.setAssociationType(AssociationType.TRANSFORM);break; + case SIGNS:association.setAssociationType(AssociationType.SIGNS);break; case APPENDS:association.setAssociationType(AssociationType.APPEND);break; default: } association.setSourceUuid(entry.getEntryUuid()); association.setTargetUuid(transformUriFromReference(target)); - + builder.withAssociation(association); } - + // get binary content from attachment.data or from referenced Binary resource Attachment attachment = documentReference.getContentFirstRep().getAttachment(); if (attachment.hasData()) { @@ -228,27 +229,27 @@ public ProvideAndRegisterDocumentSet convert(@Body Bundle requestBundle) { entry.setSize((long) decoded.length); entry.setHash(SHAsum(decoded)); } else if (attachment.hasUrl()) { - String contentURL = attachment.getUrl(); + String contentURL = attachment.getUrl(); Resource binaryContent = resources.get(contentURL); if (binaryContent instanceof Binary) { - String contentType = attachment.getContentType(); - Binary binary = (Binary) binaryContent; + String contentType = attachment.getContentType(); + Binary binary = (Binary) binaryContent; if (binary.hasContentType() && !binary.getContentType().equals(contentType)) throw new InvalidRequestException("ContentType in Binary and in DocumentReference must match"); doc.setDataHandler(new DataHandler(new ByteArrayDataSource(binary.getData(),contentType))); - byte[] decoded = binary.getData(); + byte[] decoded = binary.getData(); entry.setSize((long) decoded.length); - entry.setHash(SHAsum(decoded)); + entry.setHash(SHAsum(decoded)); Identifier masterIdentifier = documentReference.getMasterIdentifier(); - binary.setUserData("masterIdentifier", noPrefix(masterIdentifier.getValue())); + binary.setUserData("masterIdentifier", noPrefix(masterIdentifier.getValue())); } } builder.withDocument(doc); } } - + return builder.build(); } - + /** * wrap string in localized string * @param string @@ -259,7 +260,7 @@ public LocalizedString localizedString(String string) { // FIX FOR CARA return new LocalizedString(string,"en","UTF-8"); } - + /** * FHIR CodeableConcept -> XDS Code * @param cc @@ -269,9 +270,10 @@ public Code transformCodeableConcept(CodeableConcept cc) { if (cc == null) return null; if (!cc.hasCoding()) return null; Coding coding = cc.getCodingFirstRep(); - return new Code(coding.getCode(), localizedString(coding.getDisplay()), schemeMapper.getScheme(coding.getSystem())); + String displayName = StringUtils.isEmpty(coding.getDisplay()) ? coding.getCode() : coding.getDisplay(); + return new Code(coding.getCode(), localizedString(displayName), schemeMapper.getScheme(coding.getSystem())); } - + /** * FHIR CodeableConcept list -> XDS code list * @param ccs @@ -280,7 +282,7 @@ public Code transformCodeableConcept(CodeableConcept cc) { public void transformCodeableConcepts(List ccs, List target) { if (ccs == null || ccs.isEmpty()) return; for (CodeableConcept cc : ccs) { - Code code = transformCodeableConcept(cc); + Code code = transformCodeableConcept(cc); if (code!=null) target.add(code); } } @@ -301,37 +303,37 @@ public Timestamp removeSeparatorsExceptForTimezone(String fhirDate) { } return Timestamp.fromHL7(dateString); } - + /** * FHIR DateType -> XDS Timestamp * @param date * @return */ public Timestamp timestampFromDate(DateType date) { - if (date == null) return null; + if (date == null) return null; return removeSeparatorsExceptForTimezone(date.asStringValue()); } - + /** * FHIR DateTimeType -> XDS Timestamp * @param date * @return */ public Timestamp timestampFromDate(DateTimeType date) { - if (date == null) return null; + if (date == null) return null; return removeSeparatorsExceptForTimezone(date.asStringValue()); } - + /** * FHIR InstantType -> XDS Timestamp * @param date * @return */ public Timestamp timestampFromDate(InstantType date) { - if (date == null) return null; + if (date == null) return null; return removeSeparatorsExceptForTimezone(date.asStringValue()); } - + /** * FHIR Coding -> XDS Code * @param coding @@ -339,9 +341,10 @@ public Timestamp timestampFromDate(InstantType date) { */ public Code transform(Coding coding) { if (coding==null) return null; - return new Code(coding.getCode(), localizedString(coding.getDisplay()), schemeMapper.getScheme(coding.getSystem())); + String displayName = StringUtils.isEmpty(coding.getDisplay()) ? coding.getCode() : coding.getDisplay(); + return new Code(coding.getCode(), localizedString(displayName), schemeMapper.getScheme(coding.getSystem())); } - + /** * FHIR CodeableConcept -> XDS Code * @param cc @@ -352,7 +355,7 @@ public Code transform(CodeableConcept cc) { Coding coding = cc.getCodingFirstRep(); return transform(coding); } - + /** * FHIR CodeableConcept list -> XDS code * @param ccs @@ -362,7 +365,7 @@ public Code transform(List ccs) { if (ccs==null || ccs.isEmpty()) return null; return transform(ccs.get(0)); } - + /** * FHIR CodeableConcept -> XDS Identifiable * @param cc @@ -370,10 +373,10 @@ public Code transform(List ccs) { */ public Identifiable transformToIdentifiable(CodeableConcept cc) { Code code = transform(cc); - String system = code.getSchemeName(); + String system = code.getSchemeName(); return new Identifiable(code.getCode(), new AssigningAuthority(system)); } - + /** * FHIR Address -> XDS Address * @param address @@ -381,23 +384,23 @@ public Identifiable transformToIdentifiable(CodeableConcept cc) { */ public org.openehealth.ipf.commons.ihe.xds.core.metadata.Address transform(Address address) { org.openehealth.ipf.commons.ihe.xds.core.metadata.Address targetAddress = new org.openehealth.ipf.commons.ihe.xds.core.metadata.Address(); - + targetAddress.setCity(address.getCity()); targetAddress.setCountry(address.getCountry()); targetAddress.setCountyParishCode(address.getDistrict()); targetAddress.setStateOrProvince(address.getState()); targetAddress.setZipOrPostalCode(address.getPostalCode()); - String streetAddress = null; + String streetAddress = null; for (StringType street : address.getLine()) { if (streetAddress == null) streetAddress = street.getValue(); else streetAddress += "\n"+street.getValue(); } targetAddress.setStreetAddress(streetAddress); - - + + return targetAddress; } - + /** * remove "urn:oid:" prefix from code system * @param system @@ -410,12 +413,12 @@ public String noPrefix(String system) { } return system; } - + public String noBaseUrl(String in) { if (in == null) return null; - return new IdType(in).getIdPart(); + return new IdType(in).getIdPart(); } - + /** * FHIR Identifier -> XDS Identifiable * @param identifier @@ -428,7 +431,7 @@ public Identifiable transform(Identifier identifier) { } return new Identifiable(identifier.getValue()); } - + /** * FHIR Reference -> XDS Identifiable * Only for References to Patients or Encounters @@ -439,9 +442,9 @@ public Identifiable transform(Identifier identifier) { */ public Identifiable transformReferenceToIdentifiable(Reference reference, DomainResource container) { if (reference.hasReference()) { - String targetRef = reference.getReference(); - List resources = container.getContained(); - for (Resource resource : resources) { + String targetRef = reference.getReference(); + List resources = container.getContained(); + for (Resource resource : resources) { if (targetRef.equals(resource.getId())) { if (resource instanceof Patient) { return transform(((Patient) resource).getIdentifierFirstRep()); @@ -450,10 +453,10 @@ public Identifiable transformReferenceToIdentifiable(Reference reference, Domai } } } - + Identifiable result = patientRefCreator.resolvePatientReference(reference.getReference()); if (result != null) return result; - + MultiValueMap vals = UriComponentsBuilder.fromUriString(targetRef).build().getQueryParams(); if (vals.containsKey("identifier")) { String ids = vals.getFirst("identifier"); @@ -474,7 +477,7 @@ public Identifiable transformReferenceToIdentifiable(Reference reference, Domai OperationOutcome.IssueType.INVALID, null, null, "Patient url must be in the form "+config.getUriExternalPatientEndpoint()+"/Patient/... but was "+ref - ); + ); } String patientId = ref.substring(ref.indexOf("/Patient/")+9); @@ -495,15 +498,15 @@ public Identifiable transformReferenceToIdentifiable(Reference reference, Domai OperationOutcome.IssueType.INVALID, null, null, "Patient not found or or no identifier for affinity domain "+ref+" "+config.getOidMpiPid() - ); + ); } - } else if (reference.hasIdentifier()) { + } else if (reference.hasIdentifier()) { return transform(reference.getIdentifier()); - } + } throw new InvalidRequestException("Cannot resolve reference to "+(reference.getReference()!=null ? reference.getReference().toString(): "")); } - + /** * FHIR Reference to Patient -> XDS PatientInfo * @param ref @@ -513,13 +516,13 @@ public Identifiable transformReferenceToIdentifiable(Reference reference, Domai public PatientInfo transformReferenceToPatientInfo(Reference ref, DomainResource container) { if (ref == null) return null; if (!ref.hasReference()) return null; - List resources = container.getContained(); - for (Resource resource : resources) { + List resources = container.getContained(); + for (Resource resource : resources) { String targetRef = ref.getReference(); - + if (targetRef.equals(resource.getId())) { Patient patient = ((Patient) resource); - + PatientInfo patientInfo = new PatientInfo(); patientInfo.setDateOfBirth(timestampFromDate(patient.getBirthDateElement())); Enumerations.AdministrativeGender gender = patient.getGender(); @@ -531,25 +534,25 @@ public PatientInfo transformReferenceToPatientInfo(Reference ref, DomainResource default: patientInfo.setGender("U");break; } } - - for (HumanName name : patient.getName()) { - patientInfo.getNames().add(transform(name)); + + for (HumanName name : patient.getName()) { + patientInfo.getNames().add(transform(name)); } - - for (Address address : patient.getAddress()) { + + for (Address address : patient.getAddress()) { patientInfo.getAddresses().add(transform(address)); } - + for (Identifier id : patient.getIdentifier()) { patientInfo.getIds().add(transform(id)); } - + return patientInfo; } } return null; } - + /** * FHIR Reference -> URI String * @param ref @@ -563,21 +566,21 @@ private String transformUriFromReference(Reference ref) { if (!result.startsWith("urn:")) result = "urn:uuid:" + result; return result; } - - + + /** * ITI-65: process ListResource resource from Bundle * @param manifest * @param submissionSet */ private void processDocumentManifest(ListResource manifest, SubmissionSet submissionSet) { - + for (Identifier id : manifest.getIdentifier()) { if (id.getUse() == null || id.getUse().equals(Identifier.IdentifierUse.OFFICIAL)) { - + } else if (id.getUse().equals(Identifier.IdentifierUse.USUAL)) { - String uniqueId = noPrefix(id.getValue()); - submissionSet.setUniqueId(uniqueId); + String uniqueId = noPrefix(id.getValue()); + submissionSet.setUniqueId(uniqueId); } } if (submissionSet.getUniqueId() == null) throw FhirUtils.invalidRequest( @@ -586,7 +589,7 @@ private void processDocumentManifest(ListResource manifest, SubmissionSet submi null, null, "List.identifier with use usual missing" ); - + submissionSet.assignEntryUuid(); manifest.setId(submissionSet.getEntryUuid()); @@ -595,23 +598,23 @@ private void processDocumentManifest(ListResource manifest, SubmissionSet submi if (designationType != null && designationType.getValue() instanceof CodeableConcept) { submissionSet.setContentTypeCode(transformCodeableConcept((CodeableConcept) designationType.getValue())); } - + DateTimeType created = manifest.getDateElement(); - + if (created == null || !created.hasValue()) throw FhirUtils.invalidRequest( OperationOutcome.IssueSeverity.ERROR, OperationOutcome.IssueType.INVALID, null, null, "List.date missing" - ); + ); submissionSet.setSubmissionTime(timestampFromDate(created)); - + // subject SubmissionSet.patientId Reference ref = manifest.getSubject(); submissionSet.setPatientId(transformReferenceToIdentifiable(ref, manifest)); - + // Author - Extension authorRoleExt = manifest.getExtensionByUrl("http://fhir.ch/ig/ch-epr-mhealth/StructureDefinition/ch-ext-author-authorrole"); + Extension authorRoleExt = manifest.getExtensionByUrl(MagConstants.FhirExtensionUrls.CH_AUTHOR_ROLE); if (manifest.hasSource() || (authorRoleExt!=null)) { Identifiable identifiable = null; Reference author = manifest.getSource(); @@ -629,9 +632,9 @@ private void processDocumentManifest(ListResource manifest, SubmissionSet submi if (recipients.isEmpty()) recipients = manifest.getExtensionsByUrl("http://profiles.ihe.net/ITI/MHD/StructureDefinition/ihe-intendedRecipient"); for (Extension recipientExt : recipients) { - Reference recipientRef = (Reference) recipientExt.getValue(); + Reference recipientRef = (Reference) recipientExt.getValue(); Resource res = findResource(recipientRef, manifest.getContained()); - + if (res instanceof Practitioner) { Recipient recipient = new Recipient(); recipient.setPerson(transform((Practitioner) res)); @@ -657,8 +660,8 @@ private void processDocumentManifest(ListResource manifest, SubmissionSet submi Recipient recipient = new Recipient(); recipient.setPerson(transform((RelatedPerson) res)); recipient.setTelecom(transform(((RelatedPerson) res).getTelecomFirstRep())); - } - + } + } Extension source = getExtensionByUrl(manifest, "https://profiles.ihe.net/ITI/MHD/StructureDefinition/ihe-sourceId"); @@ -666,43 +669,52 @@ private void processDocumentManifest(ListResource manifest, SubmissionSet submi if (source != null && source.getValue() instanceof Identifier) { submissionSet.setSourceId(noPrefix(((Identifier) source.getValue()).getValue())); } - - String title = manifest.getTitle(); - if (title!=null) submissionSet.setTitle(localizedString(title)); - + + String title = manifest.getTitle(); + if (title!=null) submissionSet.setTitle(localizedString(title)); + Annotation note = manifest.getNoteFirstRep(); - if (note != null && note.hasText()) { + if (note != null && note.hasText()) { submissionSet.setComments(localizedString(note.getText())); } - + } - + /** * ITI-65: process DocumentReference resource from Bundle * @param reference * @param entry */ public void processDocumentReference(DocumentReference reference, DocumentEntry entry) { - + //if (reference.getIdElement()!=null) { // entry.setEntryUuid(reference.getIdElement().getIdPart()); //} else { - entry.assignEntryUuid(); - reference.setId(entry.getEntryUuid()); + entry.assignEntryUuid(); //} - Identifier masterIdentifier = reference.getMasterIdentifier(); - if (masterIdentifier == null || !masterIdentifier.hasValue() || masterIdentifier.getValue() == null || masterIdentifier.getValue().length() == 0) throw FhirUtils.invalidRequest( + + reference.getIdentifier().stream() + .filter(identifier -> identifier.hasType() && identifier.getType().hasCoding(MagConstants.FhirCodingSystemIds.MHD_DOCUMENT_ID_TYPE, "logicalId")) + .findAny() + .map(Identifier::getValue) + .ifPresent(entry::setLogicalUuid); + + String logicalId = (entry.getLogicalUuid() != null) ? entry.getLogicalUuid() : entry.getEntryUuid(); + reference.setId(logicalId); + + Identifier masterIdentifier = reference.getMasterIdentifier(); + if (masterIdentifier == null || !masterIdentifier.hasValue() || masterIdentifier.getValue() == null || masterIdentifier.getValue().isEmpty()) throw FhirUtils.invalidRequest( OperationOutcome.IssueSeverity.ERROR, OperationOutcome.IssueType.INVALID, null, null, "DocumentReference.masterIdentifier missing" ); entry.setUniqueId(noPrefix(masterIdentifier.getValue())); - + // limitedMetadata -> meta.profile canonical [0..*] // No action - + // availabilityStatus -> status code {DocumentReferenceStatus} [1..1] // approved -> status=current // deprecated -> status=superseded @@ -713,19 +725,19 @@ public void processDocumentReference(DocumentReference reference, DocumentEntry case SUPERSEDED:entry.setAvailabilityStatus(AvailabilityStatus.DEPRECATED);break; default: throw new InvalidRequestException("Unknown document status"); } - + // contentTypeCode -> type CodeableConcept [0..1] CodeableConcept type = reference.getType(); entry.setTypeCode(transform(type)); - + // classCode -> category CodeableConcept [0..*] List category = reference.getCategory(); entry.setClassCode(transform(category)); - + // patientId -> subject Reference(Patient| Practitioner| Group| Device) [0..1], Reference subject = reference.getSubject(); entry.setPatientId(transformReferenceToIdentifiable(subject, reference)); - + // creationTime -> date instant [0..1] entry.setCreationTime(timestampFromDate(reference.getDateElement())); @@ -736,43 +748,43 @@ public void processDocumentReference(DocumentReference reference, DocumentEntry for (Reference authorRef : reference.getAuthor()) { entry.getAuthors().add(transformAuthor(authorRef, reference.getContained(),null)); } - + // legalAuthenticator -> authenticator Note 1 - + if (reference.hasAuthenticator()) { Reference authenticatorRef = reference.getAuthenticator(); Resource authenticator = findResource(authenticatorRef, reference.getContained()); if (authenticator instanceof Practitioner) { - entry.setLegalAuthenticator(transform((Practitioner) authenticator)); + entry.setLegalAuthenticator(transform((Practitioner) authenticator)); } else if (authenticator instanceof PractitionerRole) { Practitioner practitioner = (Practitioner) findResource(((PractitionerRole) authenticator).getPractitioner(), reference.getContained()); if (practitioner != null) entry.setLegalAuthenticator(transform(practitioner)); - } else throw new InvalidRequestException("No authenticator of type Organization supported."); + } else throw new InvalidRequestException("No authenticator of type Organization supported."); } - + // comments -> description string [0..1] String comments = reference.getDescription(); if (comments != null) entry.setComments(localizedString(comments)); - + // confidentialityCode -> securityLabel CodeableConcept [0..*] Note: This // is NOT the DocumentReference.meta, as that holds the meta tags for the // DocumentReference itself. List securityLabels = reference.getSecurityLabel(); - transformCodeableConcepts(securityLabels, entry.getConfidentialityCodes()); - + transformCodeableConcepts(securityLabels, entry.getConfidentialityCodes()); + // mimeType -> content.attachment.contentType [1..1] code [0..1] - DocumentReferenceContentComponent content = reference.getContentFirstRep(); + DocumentReferenceContentComponent content = reference.getContentFirstRep(); if (content==null) throw new InvalidRequestException("Missing content field in DocumentReference"); Attachment attachment = content.getAttachment(); if (attachment==null) throw new InvalidRequestException("Missing attachment field in DocumentReference"); entry.setMimeType(attachment.getContentType()); - + // languageCode -> content.attachment.language code [0..1] entry.setLanguageCode(attachment.getLanguage()); // size -> content.attachment.size integer [0..1] The size is calculated if (attachment.hasSize()) entry.setSize((long) attachment.getSize()); - + // on the data prior to base64 encoding, if the data is base64 encoded. // hash -> content.attachment.hash string [0..1] byte[] hash = attachment.getHash(); @@ -780,20 +792,20 @@ public void processDocumentReference(DocumentReference reference, DocumentEntry // title -> content.attachment.title string [0..1] String title = attachment.getTitle(); - if (title!=null) entry.setTitle(localizedString(title)); + if (title!=null) entry.setTitle(localizedString(title)); // creationTime -> content.attachment.creation dateTime [0..1] if (attachment.hasCreation()) { if (entry.getCreationTime() == null) entry.setCreationTime(timestampFromDate(attachment.getCreationElement())); else if (!timestampFromDate(attachment.getCreationElement()).equals(entry.getCreationTime())) throw new InvalidRequestException("DocumentReference.date does not match attachment.creation element"); - } + } // formatCode -> content.format Coding [0..1] Coding coding = content.getFormat(); - entry.setFormatCode(transform(coding)); + entry.setFormatCode(transform(coding)); DocumentReferenceContextComponent context = reference.getContext(); - + // referenceIdList -> context.encounter Reference(Encounter) [0..*] When // referenceIdList contains an encounter, and a FHIR Encounter is available, it // may be referenced. @@ -804,13 +816,13 @@ public void processDocumentReference(DocumentReference reference, DocumentEntry Identifiable refId = transformReferenceToIdentifiable(ref, reference); if (refId != null) { ReferenceId referenceId = new ReferenceId(); - referenceId.setAssigningAuthority(new CXiAssigningAuthority(null, refId.getAssigningAuthority().getUniversalId(), refId.getAssigningAuthority().getUniversalIdType())); + referenceId.setAssigningAuthority(new CXiAssigningAuthority(null, refId.getAssigningAuthority().getUniversalId(), refId.getAssigningAuthority().getUniversalIdType())); referenceId.setId(refId.getId()); - entry.getReferenceIdList().add(referenceId); + entry.getReferenceIdList().add(referenceId); } } - - + + // Currently not mapped /*for (Reference encounterRef : context.getEncounter()) { ReferenceId referenceId = new ReferenceId(); @@ -821,44 +833,44 @@ public void processDocumentReference(DocumentReference reference, DocumentEntry //referenceId.setAssigningAuthority(new CXiAid.getAssigningAuthority().getUniversalId()); entry.getReferenceIdList().add(referenceId ); }*/ - + // eventCodeList -> context.event CodeableConcept [0..*] List events = context.getEvent(); transformCodeableConcepts(events, entry.getEventCodeList()); - + // serviceStartTime serviceStopTime -> context.period Period [0..1] Period period = context.getPeriod(); if (period != null) { entry.setServiceStartTime(timestampFromDate(period.getStartElement())); entry.setServiceStopTime(timestampFromDate(period.getEndElement())); } - + // healthcareFacilityTypeCode -> context.facilityType CodeableConcept // [0..1] entry.setHealthcareFacilityTypeCode(transformCodeableConcept(context.getFacilityType())); - + // practiceSettingCode -> context.practiceSetting CodeableConcept [0..1] entry.setPracticeSettingCode(transformCodeableConcept(context.getPracticeSetting())); - - Extension originalRole = reference.getExtensionByUrl("http://fhir.ch/ig/ch-epr-mhealth/StructureDefinition/ch-ext-author-authorrole"); + + Extension originalRole = reference.getExtensionByUrl(MagConstants.FhirExtensionUrls.CH_AUTHOR_ROLE); if (originalRole != null) { if (originalRole.getValue() instanceof Coding) { Coding value = (Coding) originalRole.getValue(); String system = noPrefix(value.getSystem()); String code = value.getCode(); - entry.setExtraMetadata(Collections.singletonMap("urn:e-health-suisse:2020:originalProviderRole", Collections.singletonList(code+"^^^&"+system+"&ISO"))); + entry.getExtraMetadata().put(MagConstants.XdsExtraMetadataSlotNames.CH_ORIGINAL_PROVIDER_ROLE, Collections.singletonList(code+"^^^&"+system+"&ISO")); } } - Extension deletionStatus = reference.getExtensionByUrl("http://fhir.ch/ig/ch-epr-mhealth/StructureDefinition/ch-ext-deletionstatus"); + Extension deletionStatus = reference.getExtensionByUrl(MagConstants.FhirExtensionUrls.CH_DELETION_STATUS); if (deletionStatus != null) { if (deletionStatus.getValue() instanceof Coding) { Coding value = (Coding) deletionStatus.getValue(); String code = value.getCode(); - entry.setExtraMetadata(Collections.singletonMap("urn:e-health-suisse:2019:deletionStatus", Collections.singletonList("urn:e-health-suisse:2019:deletionStatus:"+code))); + entry.getExtraMetadata().put(MagConstants.XdsExtraMetadataSlotNames.CH_DELETION_STATUS, Collections.singletonList(code)); } } - + // sourcePatientId and sourcePatientInfo -> context.sourcePatientInfo // Reference(Patient) [0..1] Contained Patient Resource with // Patient.identifier.use element set to ‘usual’. @@ -866,9 +878,9 @@ public void processDocumentReference(DocumentReference reference, DocumentEntry entry.setSourcePatientId(transformReferenceToIdentifiable(context.getSourcePatientInfo(), reference)); entry.setSourcePatientInfo(transformReferenceToPatientInfo(context.getSourcePatientInfo(), reference)); } - + } - + /** * search a referenced resource from a list of (contained) resources. * @param ref @@ -881,7 +893,7 @@ public Resource findResource(Reference ref, List contained) { } return null; } - + /** * FHIR HumanName -> XDS Name * @param name @@ -895,18 +907,18 @@ public Name transform(HumanName name) { List given = name.getGiven(); if (given != null && !given.isEmpty()) { targetName.setGivenName(given.get(0).getValue()); - if (given.size()>1) { + if (given.size()>1) { StringBuffer restOfName = new StringBuffer(); - for (int part=1;part 1) restOfName.append(" "); - restOfName.append(given.get(part).getValue()); + restOfName.append(given.get(part).getValue()); } targetName.setSecondAndFurtherGivenNames(restOfName.toString()); } } - return targetName; + return targetName; } - + /** * FHIR Practitioner -> XDS Person * @param practitioner @@ -921,7 +933,7 @@ public Person transform(Practitioner practitioner) { } return result; } - + /** * FHIR Patient -> XDS Person * @param patient @@ -933,10 +945,10 @@ public Person transform(Patient patient) { if (patient.hasIdentifier()) { result.setId(transform(patient.getIdentifierFirstRep())); } - if (patient.hasName()) result.setName(transform(patient.getNameFirstRep())); + if (patient.hasName()) result.setName(transform(patient.getNameFirstRep())); return result; } - + /** * FHIR RelatedPerson -> XDS Person * @param related @@ -948,10 +960,10 @@ public Person transform(RelatedPerson related) { if (related.hasIdentifier()) { result.setId(transform(related.getIdentifierFirstRep())); } - if (related.hasName()) result.setName(transform(related.getNameFirstRep())); + if (related.hasName()) result.setName(transform(related.getNameFirstRep())); return result; - } - + } + /** * FHIR ContactPoint -> XDS Telecom * @param contactPoint @@ -960,9 +972,9 @@ public Person transform(RelatedPerson related) { public Telecom transform(ContactPoint contactPoint) { if (contactPoint == null) return null; Telecom result = new Telecom(); - + if (contactPoint.getSystem().equals(ContactPointSystem.EMAIL) || contactPoint.getSystem().equals(ContactPointSystem.URL)) { - result.setEmail(contactPoint.getValue()); + result.setEmail(contactPoint.getValue()); result.setUse("NET"); result.setType("Internet"); } else { @@ -972,21 +984,21 @@ public Telecom transform(ContactPoint contactPoint) { case SMS: case PHONE:result.setType("PH");break; case FAX:result.setType("FX");break; - case PAGER:result.setType("BP");break; + case PAGER:result.setType("BP");break; } - + if (contactPoint.hasUse()) switch (contactPoint.getUse()) { case HOME: result.setUse("PRN");break; case WORK: result.setUse("WPN");break; - case MOBILE: result.setType("CP");break; + case MOBILE: result.setType("CP");break; } - - } - + + } + return result; } - + /** * FHIR Organization -> XDS Organization * @param org @@ -999,10 +1011,10 @@ public org.openehealth.ipf.commons.ihe.xds.core.metadata.Organization transform( if (identifier != null) { result.setIdNumber(identifier.getValue()); result.setAssigningAuthority(new AssigningAuthority(noPrefix(identifier.getSystem()))); - } + } return result; } - + /** * FHIR Reference to Author -> XDS Author * @param author @@ -1014,8 +1026,8 @@ public Author transformAuthor(Reference author, List contained, Identi if (authorRole!=null) { Author result = new Author(); Person person = new Person(); - // CARA PMP - // At least an authorPerson, authorTelecommunication, or authorInstitution sub-attribute must be present + // CARA PMP + // At least an authorPerson, authorTelecommunication, or authorInstitution sub-attribute must be present // Either authorPerson, authorInstitution or authorTelecom shall be specified in the SubmissionSet [IHE ITI Technical Framework Volume 3 (4.2.3.1.4)]. person.setName(transform(new HumanName().setFamily("---"))); result.setAuthorPerson(person); @@ -1045,7 +1057,7 @@ public Author transformAuthor(Reference author, List contained, Identi } result.getAuthorRole().add(authorRole); return result; - } else if (authorObj instanceof PractitionerRole) { + } else if (authorObj instanceof PractitionerRole) { Author result = new Author(); PractitionerRole role = (PractitionerRole) authorObj; Practitioner practitioner = (Practitioner) findResource(role.getPractitioner(), contained); @@ -1057,13 +1069,13 @@ public Author transformAuthor(Reference author, List contained, Identi for (ContactPoint contactPoint : role.getTelecom()) result.getAuthorTelecom().add(transform(contactPoint)); return result; } else throw new InvalidRequestException("Author role not supported."); - + //return null; } - + public String SHAsum(byte[] convertme) { - try { - MessageDigest md = MessageDigest.getInstance("SHA-1"); + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); return Hex.encodeHexString(md.digest(convertme)); } catch (NoSuchAlgorithmException e) { return ""; } } @@ -1081,6 +1093,6 @@ private String base64ToHex(String input) { return Hex.encodeHexString(decoded); } */ - - + + } diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67FromIti57ResponseConverter.java b/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67FromIti57ResponseConverter.java index f849816b..31e463ab 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67FromIti57ResponseConverter.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67FromIti57ResponseConverter.java @@ -16,40 +16,33 @@ package ch.bfh.ti.i4mi.mag.mhd.iti67; -import java.util.Date; -import java.util.Map; - -import org.hl7.fhir.r4.model.Binary; -import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.DocumentReference; -import org.hl7.fhir.r4.model.ListResource; -import org.hl7.fhir.r4.model.OperationOutcome; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ch.bfh.ti.i4mi.mag.Config; +import ch.bfh.ti.i4mi.mag.mhd.BaseResponseConverter; import org.openehealth.ipf.commons.ihe.fhir.translation.ToFhirTranslator; import org.openehealth.ipf.commons.ihe.xds.core.responses.Response; import org.openehealth.ipf.commons.ihe.xds.core.responses.Status; -import ch.bfh.ti.i4mi.mag.Config; -import ch.bfh.ti.i4mi.mag.mhd.BaseResponseConverter; -import ch.bfh.ti.i4mi.mag.mhd.Utils; +import java.util.Map; public class Iti67FromIti57ResponseConverter extends BaseResponseConverter implements ToFhirTranslator { -private Config config; - + private Config config; + public Iti67FromIti57ResponseConverter(final Config config) { this.config = config; } - + /** * convert ITI-57 response to ITI-67 response */ @Override public Object translateToFhir(Response input, Map parameters) { - - if (input.getStatus().equals(Status.SUCCESS)) { - OperationOutcome response = new OperationOutcome(); - return response; - } else { + if (input.getStatus().equals(Status.SUCCESS)) { + MethodOutcome methodOutcome = new MethodOutcome(); + methodOutcome.setResponseStatusCode(200); + return methodOutcome; + } else { processError(input); return null; } diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67RequestConverter.java b/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67RequestConverter.java index eeb6aa00..db986d79 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67RequestConverter.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67RequestConverter.java @@ -70,7 +70,8 @@ public QueryRegistry searchParameterIti67ToFindDocumentsQuery(@Body Iti67SearchP query.setUuids(Collections.singletonList(val.substring("urn:uuid:".length()))); } } else { - query.setUuids(Collections.singletonList(searchParameter.get_id().getValue())); + //query.setUuids(Collections.singletonList(searchParameter.get_id().getValue())); + query.getLogicalUuid().add(searchParameter.get_id().getValue()); } searchQuery = query; } else { diff --git a/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti67_v401/Iti67RequestUpdateConverter.java b/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67RequestUpdateConverter.java similarity index 59% rename from src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti67_v401/Iti67RequestUpdateConverter.java rename to src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67RequestUpdateConverter.java index b0252693..37a86368 100644 --- a/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti67_v401/Iti67RequestUpdateConverter.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67RequestUpdateConverter.java @@ -1,4 +1,4 @@ -package org.openehealth.ipf.commons.ihe.fhir.iti67_v401; +package ch.bfh.ti.i4mi.mag.mhd.iti67; /* * Copyright 2020 the original author or authors. * @@ -16,25 +16,14 @@ */ import java.time.ZonedDateTime; +import java.util.HashMap; import java.util.UUID; +import ch.bfh.ti.i4mi.mag.MagConstants; import org.apache.camel.Body; -import org.hl7.fhir.r4.model.CodeableConcept; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.DocumentReference; -import org.hl7.fhir.r4.model.Extension; -import org.hl7.fhir.r4.model.Identifier; -import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.*; import org.openehealth.ipf.commons.core.URN; -import org.openehealth.ipf.commons.ihe.xds.core.metadata.AssigningAuthority; -import org.openehealth.ipf.commons.ihe.xds.core.metadata.Association; -import org.openehealth.ipf.commons.ihe.xds.core.metadata.AssociationLabel; -import org.openehealth.ipf.commons.ihe.xds.core.metadata.AssociationType; -import org.openehealth.ipf.commons.ihe.xds.core.metadata.DocumentEntry; -import org.openehealth.ipf.commons.ihe.xds.core.metadata.Identifiable; -import org.openehealth.ipf.commons.ihe.xds.core.metadata.SubmissionSet; -import org.openehealth.ipf.commons.ihe.xds.core.metadata.Timestamp; -import org.openehealth.ipf.commons.ihe.xds.core.metadata.XDSMetaClass; +import org.openehealth.ipf.commons.ihe.xds.core.metadata.*; import org.openehealth.ipf.commons.ihe.xds.core.requests.builder.RegisterDocumentSetBuilder; import org.openehealth.ipf.commons.ihe.xds.core.stub.ebrs30.lcm.SubmitObjectsRequest; import org.openehealth.ipf.platform.camel.ihe.xds.core.converters.EbXML30Converters; @@ -44,7 +33,7 @@ /** * ITI-67 DocumentReference Update request converter - * + * * @author oliver egger * */ @@ -53,7 +42,7 @@ public class Iti67RequestUpdateConverter extends Iti65RequestConverter { /** * ITI-67 Response to ITI-57 request converter - * + * * @param searchParameter * @return */ @@ -66,16 +55,21 @@ public SubmitObjectsRequest convertDocumentReferenceToDocumentEntry(@Body Docume getExtensionByUrl(documentReference, "https://profiles.ihe.net/ITI/MHD/StructureDefinition/ihe-sourceId"); if (source != null && source.getValue() instanceof Identifier) { submissionSet.setSourceId(noPrefix(((Identifier) source.getValue()).getValue())); + } else { + // todo: use Config.getDocumentSourceId + submissionSet.setSourceId("2.16.756.5.30.1.190.0.0.12.2.101.32"); } Extension designationType = getExtensionByUrl(documentReference, "https://profiles.ihe.net/ITI/MHD/StructureDefinition/ihe-designationType"); if (designationType != null && designationType.getValue() instanceof CodeableConcept) { submissionSet.setContentTypeCode(transformCodeableConcept((CodeableConcept) designationType.getValue())); + } else { + submissionSet.setContentTypeCode(new Code("71388002", new LocalizedString("Procedure"), "2.16.840.1.113883.6.96")); } - Extension authorRoleExt = - getExtensionByUrl(documentReference, "https://fhir.ch/ig/ch-epr-mhealth/StructureDefinition/ch-ext-author-authorrole"); + Extension authorRoleExt = + getExtensionByUrl(documentReference, MagConstants.FhirExtensionUrls.CH_AUTHOR_ROLE); if (authorRoleExt != null) { Identifiable identifiable = null; if (authorRoleExt != null) { @@ -90,21 +84,39 @@ public SubmitObjectsRequest convertDocumentReferenceToDocumentEntry(@Body Docume RegisterDocumentSetBuilder builder = new RegisterDocumentSetBuilder(true, submissionSet); // TODO should be // true? DocumentEntry entry = new DocumentEntry(); + entry.setExtraMetadata(new HashMap<>()); + processDocumentReference(documentReference, entry); - Extension proprietaryType = documentReference - .getExtensionByUrl("http://post.ch/e-health/windseeker/repositoryUniqueId"); - if (proprietaryType != null && proprietaryType.getValue() instanceof Identifier) { - entry.setRepositoryUniqueId(proprietaryType.getValue().getId()); + Extension repositoryUniqueIdExtension = documentReference + .getExtensionByUrl(MagConstants.FhirExtensionUrls.REPOSITORY_UNIQUE_ID); + if (repositoryUniqueIdExtension != null && repositoryUniqueIdExtension.getValue() instanceof Identifier) { + Identifier identifier = (Identifier) repositoryUniqueIdExtension.getValue(); + entry.setRepositoryUniqueId(noPrefix(identifier.getValue())); } - processDocumentReference(documentReference, entry); - - entry.setLogicalUuid(new URN(UUID.randomUUID()).toString()); + Extension documentAvailabilityExtension = documentReference + .getExtensionByUrl(MagConstants.FhirExtensionUrls.DOCUMENT_AVAILABILITY); + if (documentAvailabilityExtension != null && documentAvailabilityExtension.getValue() instanceof Coding) { + Coding coding = (Coding) documentAvailabilityExtension.getValue(); + if (MagConstants.FhirCodingSystemIds.RFC_3986.equals(coding.getSystem()) && coding.getCode().startsWith("urn:ihe:iti:2010:DocumentAvailability:")) { + entry.setDocumentAvailability(DocumentAvailability.valueOfOpcode(coding.getCode().substring(38))); + } + } submissionSet.setPatientId(entry.getPatientId()); submissionSet.assignEntryUuid(); builder.withDocument(entry); - builder.withAssociation(createHasMemberAssocationWithOriginalPreviousLabel(submissionSet, entry)); + + int version; + Extension versionExtension = documentReference.getExtensionByUrl(MagConstants.FhirExtensionUrls.DOCUMENT_ENTRY_VERSION); + if ((versionExtension != null) && (versionExtension.getValue() instanceof PositiveIntType)) { + PositiveIntType versionElement = (PositiveIntType) versionExtension.getValue(); + version = versionElement.getValue(); + } else { + version = 1; + } + + builder.withAssociation(createHasMemberAssociationWithOriginalPreviousLabel(version, submissionSet, entry)); // Submission contains a DocumentEntry object. // The logicalID attribute is present in the DocumentEntry object and has a UUID @@ -116,15 +128,15 @@ public SubmitObjectsRequest convertDocumentReferenceToDocumentEntry(@Body Docume return EbXML30Converters.convert(builder.build()); } - private Association createHasMemberAssocationWithOriginalPreviousLabel(SubmissionSet submissionSet, - DocumentEntry entry) { - var assoc = createHasMemberAssocation(entry.getEntryUuid(), submissionSet); + private Association createHasMemberAssociationWithOriginalPreviousLabel(int version, SubmissionSet submissionSet, DocumentEntry entry) { + var assoc = createHasMemberAssociation(entry.getEntryUuid(), submissionSet); assoc.setLabel(AssociationLabel.ORIGINAL); - assoc.setPreviousVersion("1"); // FIXME: how do we get that? maybe we could have more updated versions too + assoc.setPreviousVersion(Integer.toString(version)); + assoc.setAssociationPropagation(true); return assoc; } - private Association createHasMemberAssocation(String entryUuid, SubmissionSet submissionSet) { + private Association createHasMemberAssociation(String entryUuid, SubmissionSet submissionSet) { return new Association(AssociationType.HAS_MEMBER, new URN(UUID.randomUUID()).toString(), submissionSet.getEntryUuid(), entryUuid); diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67ResponseConverter.java b/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67ResponseConverter.java index 80e33f7c..16d26ff2 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67ResponseConverter.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67ResponseConverter.java @@ -18,6 +18,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.client.impl.GenericClient; import ch.bfh.ti.i4mi.mag.Config; +import ch.bfh.ti.i4mi.mag.MagConstants; import ch.bfh.ti.i4mi.mag.mhd.BaseQueryResponseConverter; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.r4.model.*; @@ -90,11 +91,6 @@ public List translateToFhir(QueryResponse input, Map meta.profile canonical [0..*] if (documentEntry.isLimitedMetadata()) { @@ -337,7 +333,56 @@ public List translateToFhir(QueryResponse input, Map originalProviderRoles = documentEntry.getExtraMetadata().get(MagConstants.XdsExtraMetadataSlotNames.CH_ORIGINAL_PROVIDER_ROLE); + if (originalProviderRoles != null) { + for (String originalProviderRole : originalProviderRoles) { + Identifiable cx = Hl7v2Based.parse(originalProviderRole, Identifiable.class); + documentReference.addExtension( + MagConstants.FhirExtensionUrls.CH_AUTHOR_ROLE, + new Coding("urn:oid:" + cx.getAssigningAuthority().getUniversalId(), cx.getId(), null)); + } + } + + List deletionStatuses = documentEntry.getExtraMetadata().get(MagConstants.XdsExtraMetadataSlotNames.CH_DELETION_STATUS); + if (deletionStatuses != null) { + for (String deletionStatus : deletionStatuses) { + documentReference.addExtension( + MagConstants.FhirExtensionUrls.CH_DELETION_STATUS, + new Coding("urn:oid:2.16.756.5.30.1.127.3.10.18", deletionStatus, null)); + } + } + } + + if (documentEntry.getRepositoryUniqueId() != null) { + documentReference.addExtension( + MagConstants.FhirExtensionUrls.REPOSITORY_UNIQUE_ID, + new Identifier().setSystem(MagConstants.FhirCodingSystemIds.RFC_3986).setValue("urn:oid:" + documentEntry.getRepositoryUniqueId())); + } + + if (documentEntry.getVersion() != null) { + documentReference.addExtension( + MagConstants.FhirExtensionUrls.DOCUMENT_ENTRY_VERSION, + new PositiveIntType(documentEntry.getVersion().getVersionName())); + } + + if (documentEntry.getAvailabilityStatus() != null) { + documentReference.addExtension( + MagConstants.FhirExtensionUrls.DOCUMENT_AVAILABILITY, + new Coding(MagConstants.FhirCodingSystemIds.RFC_3986, documentEntry.getDocumentAvailability().getFullQualified(), null)); + } + + String logicalId = (documentEntry.getLogicalUuid() != null) ? documentEntry.getLogicalUuid() : documentEntry.getEntryUuid(); + if (logicalId != null) { + documentReference.addIdentifier() + .setValue(asUuid(logicalId)) + .setSystem(MagConstants.FhirCodingSystemIds.RFC_3986) + .setType(new CodeableConcept().addCoding(new Coding(MagConstants.FhirCodingSystemIds.MHD_DOCUMENT_ID_TYPE, "logicalId", "Logical ID"))); + } + + documentReference.setId(noUuidPrefix(logicalId)); } + } } else { processError(input); diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67RouteBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67RouteBuilder.java index 8c6af659..290fadf6 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67RouteBuilder.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/mhd/iti67/Iti67RouteBuilder.java @@ -21,7 +21,6 @@ import org.apache.camel.builder.PredicateBuilder; import org.apache.camel.builder.RouteBuilder; import org.openehealth.ipf.commons.ihe.fhir.Constants; -import org.openehealth.ipf.commons.ihe.fhir.iti67_v401.Iti67RequestUpdateConverter; import org.openehealth.ipf.commons.ihe.xds.core.responses.QueryResponse; import org.openehealth.ipf.commons.ihe.xds.core.responses.Response; import org.openehealth.ipf.commons.ihe.xds.core.stub.ebrs30.rs.RegistryRequestType; @@ -88,7 +87,7 @@ public void configure() throws Exception { .process(translateToFhir(new Iti67ResponseConverter(config) , QueryResponse.class)) .endChoice() .when(PredicateBuilder.and(header("FhirHttpUri").isNotNull(),header("FhirHttpMethod").isEqualTo("GET"))) - .bean(IdRequestConverter.class) + .bean(IdRequestConverter.class) .to(endpoint) .process(translateToFhir(new Iti67ResponseConverter(config) , QueryResponse.class)) .endChoice() diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/pmir/PatientReferenceCreator.java b/src/main/java/ch/bfh/ti/i4mi/mag/pmir/PatientReferenceCreator.java index ed480417..48c2c278 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/pmir/PatientReferenceCreator.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/pmir/PatientReferenceCreator.java @@ -16,6 +16,7 @@ package ch.bfh.ti.i4mi.mag.pmir; +import lombok.Setter; import org.hl7.fhir.r4.model.Reference; import org.openehealth.ipf.commons.ihe.xds.core.metadata.AssigningAuthority; import org.openehealth.ipf.commons.ihe.xds.core.metadata.Identifiable; @@ -36,9 +37,11 @@ public class PatientReferenceCreator { @Autowired + @Setter private Config config; @Autowired + @Setter private SchemeMapper schemeMapper; diff --git a/src/test/java/ch/bfh/ti/i4mi/mag/mhd/Iti57RequestTranslatorTest.java b/src/test/java/ch/bfh/ti/i4mi/mag/mhd/Iti57RequestTranslatorTest.java new file mode 100644 index 00000000..ae627906 --- /dev/null +++ b/src/test/java/ch/bfh/ti/i4mi/mag/mhd/Iti57RequestTranslatorTest.java @@ -0,0 +1,51 @@ +package ch.bfh.ti.i4mi.mag.mhd; + +import ca.uhn.fhir.context.FhirContext; +import ch.bfh.ti.i4mi.mag.Config; +import ch.bfh.ti.i4mi.mag.MagConstants; +import ch.bfh.ti.i4mi.mag.pmir.PatientReferenceCreator; +import org.hl7.fhir.r4.model.DocumentReference; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import ch.bfh.ti.i4mi.mag.mhd.iti67.Iti67RequestUpdateConverter; +import org.openehealth.ipf.commons.ihe.xds.core.stub.ebrs30.lcm.SubmitObjectsRequest; +import org.openehealth.ipf.platform.camel.ihe.xds.core.converters.XdsRenderingUtils; + +public class Iti57RequestTranslatorTest { + + private static Iti67RequestUpdateConverter iti67RequestUpdateConverter; + + @BeforeAll + public static void beforeAll() { + Config config = new Config(); + config.setRepositoryUniqueId("1.1.4567332.1.2"); + config.setOidMpiPid("1.3.6.1.4.1.12559.11.20.1"); + SchemeMapper schemeMapper = new SchemeMapper(); + PatientReferenceCreator patientReferenceCreator = new PatientReferenceCreator(); + patientReferenceCreator.setConfig(config); + patientReferenceCreator.setSchemeMapper(schemeMapper); + iti67RequestUpdateConverter = new Iti67RequestUpdateConverter(); + iti67RequestUpdateConverter.setConfig(config); + iti67RequestUpdateConverter.setSchemeMapper(schemeMapper); + iti67RequestUpdateConverter.setPatientRefCreator(patientReferenceCreator); + } + + @Test + public void test1() { + DocumentReference documentReference = (DocumentReference) FhirContext.forR4().newJsonParser().parseResource(Iti57RequestTranslatorTest.class.getClassLoader().getResourceAsStream("update-request-1.json")); + SubmitObjectsRequest ebXml = iti67RequestUpdateConverter.convertDocumentReferenceToDocumentEntry(documentReference); + String ebXmlString = XdsRenderingUtils.renderEbxml(ebXml); + + Assertions.assertTrue(ebXmlString.contains(MagConstants.XdsExtraMetadataSlotNames.CH_DELETION_STATUS)); + Assertions.assertTrue(ebXmlString.contains(MagConstants.XdsExtraMetadataSlotNames.CH_ORIGINAL_PROVIDER_ROLE)); + Assertions.assertTrue(ebXmlString.contains("repositoryUniqueId")); + Assertions.assertTrue(ebXmlString.contains("documentAvailability")); + Assertions.assertTrue(ebXmlString.contains("version")); + Assertions.assertTrue(ebXmlString.contains("42")); + Assertions.assertTrue(ebXmlString.contains("urn:uuid:18f08725-b77e-4d78-a409-b9f0ab4ef406")); + + Assertions.assertFalse(ebXmlString.contains("urn:oid:")); + } + +} diff --git a/src/test/java/ch/bfh/ti/i4mi/mag/mhd/MhdTest.java b/src/test/java/ch/bfh/ti/i4mi/mag/mhd/MhdTest.java new file mode 100644 index 00000000..c6c5e3fd --- /dev/null +++ b/src/test/java/ch/bfh/ti/i4mi/mag/mhd/MhdTest.java @@ -0,0 +1,81 @@ +package ch.bfh.ti.i4mi.mag.mhd; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; +import ch.bfh.ti.i4mi.mag.MobileAccessGateway; +import lombok.extern.slf4j.Slf4j; +import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; +import org.hl7.fhir.r4.model.DocumentReference; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ActiveProfiles; + +import java.util.Locale; + +/** + * @author Dmytro Rud + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@Import(MobileAccessGateway.class) +@ActiveProfiles("test") +@Slf4j +public class MhdTest { + + @Autowired + protected CamelContext camelContext; + + @Autowired + protected ProducerTemplate producerTemplate; + + @Value("${server.port}") + protected Integer serverPort; + + @BeforeAll + public static void beforeAll() { + Locale.setDefault(Locale.ENGLISH); + +// System.setProperty("javax.net.ssl.keyStore", "src/main/resources/example-server-certificate.p12"); +// System.setProperty("javax.net.ssl.keyStorePassword", "a1b2c3"); +// System.setProperty("javax.net.ssl.trustStore", "src/main/resources/example-server-certificate.p12"); +// System.setProperty("javax.net.ssl.trustStorePassword", "a1b2c3"); + } + + @Test + public void testMetadataUpdate() { + DocumentReference documentReference = (DocumentReference) FhirContext.forR4().newJsonParser().parseResource(MhdTest.class.getClassLoader().getResourceAsStream("update-request-1.json")); + IGenericClient client = FhirContext.forR4().newRestfulGenericClient("http://localhost:" + serverPort + "/fhir"); + MethodOutcome methodOutcome; + + // test success case + documentReference.getContent().get(0).getAttachment().setSize(100); + methodOutcome = client.update() + .resource(documentReference) + .execute(); + assertEquals(200, methodOutcome.getResponseStatusCode()); + + // test failure case + boolean errorCatched = false; + try { + documentReference.getContent().get(0).getAttachment().setSize(101); + methodOutcome = client.update() + .resource(documentReference) + .execute(); + } catch (BaseServerResponseException e) { + assertEquals(400, e.getStatusCode()); + errorCatched = true; + } + assertTrue(errorCatched); + + } + +} diff --git a/src/test/java/ch/bfh/ti/i4mi/mag/mhd/MhdTestRouteBuilder.java b/src/test/java/ch/bfh/ti/i4mi/mag/mhd/MhdTestRouteBuilder.java new file mode 100644 index 00000000..6c71b0ed --- /dev/null +++ b/src/test/java/ch/bfh/ti/i4mi/mag/mhd/MhdTestRouteBuilder.java @@ -0,0 +1,32 @@ +package ch.bfh.ti.i4mi.mag.mhd; + +import org.apache.camel.builder.RouteBuilder; +import org.openehealth.ipf.commons.ihe.xds.core.requests.RegisterDocumentSet; +import org.openehealth.ipf.commons.ihe.xds.core.responses.Response; +import org.openehealth.ipf.commons.ihe.xds.core.responses.Status; +import org.openehealth.ipf.platform.camel.ihe.xds.XdsCamelValidators; +import org.springframework.stereotype.Component; + +/** + * @author Dmytro Rud + */ +@Component +public class MhdTestRouteBuilder extends RouteBuilder { + + @Override + public void configure() throws Exception { + + from("xds-iti57://iti57Endpoint") + .process(XdsCamelValidators.iti57RequestValidator()) + .process(exchange -> { + log.info("Received ITI-57 request"); + RegisterDocumentSet request = exchange.getIn().getMandatoryBody(RegisterDocumentSet.class); + Response response = new Response(); + response.setStatus((request.getDocumentEntries().get(0).getSize() % 2 == 0) ? Status.SUCCESS : Status.FAILURE); + exchange.getMessage().setBody(response); + }) + ; + + } + +} diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index e82052e3..eb6073e0 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -4,7 +4,7 @@ mag: homeCommunityId: urn:oid:1.2.3.4.5 baseurl: http://localhost client-ssl: - enabled: true + enabled: false key-store: path: src/test/resources/keystore.jks password: a1b2c3 @@ -13,15 +13,15 @@ mag: password: a1b2c3 cert-alias: gateway xds: - https: true + https: false iti-18: - url: ehealthsuisse.ihe-europe.net:10443/xdstools7/sim/default__ahdis/reg/sq + url: localhost:9090/services/iti18Endpoint iti-43: - url: ehealthsuisse.ihe-europe.net:10443/xdstools7/sim/default__ahdis/rep/ret + url: localhost:9090/services/iti43Endpoint iti-41: - url: ehealthsuisse.ihe-europe.net:10443/xdstools7/sim/default__ahdis/rep/prb + url: localhost:9090/services/iti41Endpoint iti-57: - url: ehealthsuisse.ihe-europe.net:10443/xdstools7/sim/default__ahdis/rep/prb + url: localhost:9090/services/iti57Endpoint retrieve: url: http://localhost:9090/camel/xdsretrieve repositoryUniqueId: 1.1.4567332.1.2 diff --git a/src/test/resources/update-request-1.json b/src/test/resources/update-request-1.json new file mode 100644 index 00000000..882bd1fb --- /dev/null +++ b/src/test/resources/update-request-1.json @@ -0,0 +1,175 @@ +{ + "resourceType": "DocumentReference", + "id": "18f08725-b77e-4d78-a409-b9f0ab4ef405", + "meta": { + "profile": [ + "https://ihe.net/fhir/StructureDefinition/IHE_MHD_Comprehensive_DocumentManifest" + ] + }, + "contained": [ + { + "resourceType": "Patient", + "id": "1", + "name": [ + { + "family": "some-last-name", + "given": [ + "some-first-name" + ] + } + ] + }, + { + "resourceType": "Patient", + "id": "2", + "identifier": [ + { + "use": "official", + "system": "urn:oid:2.16.756.5.30.1.190.0.0.2.1", + "value": "37b35b8b-b26b-4e7f-9519-df1e68dc411e" + } + ] + } + ], + "extension": [ + { + "url": "http://fhir.ch/ig/ch-epr-mhealth/StructureDefinition/ch-ext-author-authorrole", + "valueCoding": { + "system": "urn:oid:2.16.756.5.30.1.127.3.10.6", + "code": "PAT" + } + }, + { + "url": "https://profiles.ihe.net/ITI/MHD/StructureDefinition/ihe-repositoryUniqueId", + "valueIdentifier": { + "system": "urn:ietf:rfc:3986", + "value": "urn:oid:2.16.756.5.30.1.190.0.0.12.1.101.31" + } + }, + { + "url": "https://profiles.ihe.net/ITI/MHD/StructureDefinition/ihe-version", + "valuePositiveInteger": { + "value": 42 + } + }, + { + "url": "https://profiles.ihe.net/ITI/MHD/StructureDefinition/ihe-documentAvailability", + "valueCoding": { + "system": "urn:ietf:rfc:3986", + "code": "urn:ihe:iti:2010:DocumentAvailability:Online" + } + }, + { + "url": "http://fhir.ch/ig/ch-epr-mhealth/StructureDefinition/ch-ext-deletionstatus", + "valueCoding": { + "system": "http://fhir.ch/ig/ch-epr-mhealth/CodeSysteme/ch-ehealth-codesystem-deletionstatus", + "code": "urn:e-health-suisse:2019:deletionStatus:deletionRequested" + } + } + ], + "masterIdentifier": { + "system": "urn:ietf:rfc:3986", + "value": "urn:oid:2.25.304709611975869688989227577335602046635" + }, + "identifier": [ + { + "use": "official", + "system": "urn:ietf:rfc:3986", + "value": "urn:uuid:18f08725-b77e-4d78-a409-b9f0ab4ef405" + }, + { + "system": "urn:ietf:rfc:3986", + "value": "urn:uuid:18f08725-b77e-4d78-a409-b9f0ab4ef406", + "type": { + "coding": [ + { + "system": "https://profiles.ihe.net/ITI/MHD/CodeSystem/DocumentIdentifierTypes", + "code": "logicalId" + } + ] + } + } + ], + "status": "current", + "type": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "722446000", + "display": "722446000" + } + ] + }, + "category": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "184216000", + "display": "184216000" + } + ] + } + ], + "subject": { + "reference": "http://localhost/fhir/Patient/2.16.756.5.30.1.190.0.0.2.1-37b35b8b-b26b-4e7f-9519-df1e68dc411e" + }, + "date": "2024-05-16T14:53:29+02:00", + "author": [ + { + "reference": "#1" + } + ], + "securityLabel": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "17621005", + "display": "17621005" + } + ] + } + ], + "content": [ + { + "attachment": { + "contentType": "application/pdf", + "language": "it-CH", + "url": "/camel/xdsretrieve?uniqueId=2.25.304709611975869688989227577335602046635&repositoryUniqueId=2.16.756.5.30.1.190.0.0.12.1.101.31", + "size": 17191, + "hash": "QHN3dtgTmhEOU+LRq7cS977nsLs=", + "title": "WithAuthorFromAPI - description -3", + "creation": "2024-05-16T14:53:29+02:00" + }, + "format": { + "system": "http://ihe.net/fhir/ihe.formatcode.fhir/CodeSystem/formatcode", + "code": "urn:ihe:iti:xds:2017:mimeTypeSufficient", + "display": "urn:ihe:iti:xds:2017:mimeTypeSufficient" + } + } + ], + "context": { + "facilityType": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "22232009", + "display": "22232009" + } + ] + }, + "practiceSetting": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "394802001", + "display": "394802001" + } + ] + }, + "sourcePatientInfo": { + "reference": "#2" + } + } +} \ No newline at end of file