diff --git a/src/main/java/edu/ohsu/cmp/ecp/sds/SupplementalDataStorePartition.java b/src/main/java/edu/ohsu/cmp/ecp/sds/SupplementalDataStorePartition.java index 8c59ea0..a62bee2 100644 --- a/src/main/java/edu/ohsu/cmp/ecp/sds/SupplementalDataStorePartition.java +++ b/src/main/java/edu/ohsu/cmp/ecp/sds/SupplementalDataStorePartition.java @@ -23,8 +23,6 @@ @Component public class SupplementalDataStorePartition { - public static final String HEADER_PARTITION_NAME = "X-Partition-Name"; - @Inject SupplementalDataStoreProperties sdsProperties; @@ -80,7 +78,8 @@ public RequestPartitionId partitionIdFromRequest(RequestDetails theRequestDetail } protected String partitionNameFromRequest(RequestDetails theRequestDetails) { - final String partitionNameHeaderValue = theRequestDetails.getHeader(HEADER_PARTITION_NAME); + final String httpHeader = sdsProperties.getPartition().getHttpHeader(); + final String partitionNameHeaderValue = theRequestDetails.getHeader( httpHeader ); if (StringUtils.isNotBlank(partitionNameHeaderValue)) { /* * later we want to recreate the partition name diff --git a/src/main/java/edu/ohsu/cmp/ecp/sds/dstu3/SupplementalDataStoreLinkageDstu3.java b/src/main/java/edu/ohsu/cmp/ecp/sds/dstu3/SupplementalDataStoreLinkageDstu3.java index a89ca78..d96cc27 100644 --- a/src/main/java/edu/ohsu/cmp/ecp/sds/dstu3/SupplementalDataStoreLinkageDstu3.java +++ b/src/main/java/edu/ohsu/cmp/ecp/sds/dstu3/SupplementalDataStoreLinkageDstu3.java @@ -1,9 +1,12 @@ package edu.ohsu.cmp.ecp.sds.dstu3; +import static java.util.stream.Collectors.toList; + import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; import javax.inject.Inject; @@ -11,10 +14,12 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.dstu3.model.RelatedPerson; +import org.hl7.fhir.dstu3.model.Type; import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.BooleanType; import org.hl7.fhir.dstu3.model.DomainResource; import org.hl7.fhir.dstu3.model.Extension; +import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.Linkage; import org.hl7.fhir.dstu3.model.Linkage.LinkageType; import org.hl7.fhir.dstu3.model.Patient; @@ -27,6 +32,7 @@ import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.starter.annotations.OnDSTU3Condition; +import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.ReferenceParam; import edu.ohsu.cmp.ecp.sds.SupplementalDataStoreProperties; @@ -52,9 +58,114 @@ public class SupplementalDataStoreLinkageDstu3 extends SupplementalDataStoreLink @Inject IFhirResourceDao daoRelatedPersonDstu3; + private static Predicate sameId( IIdType id ) { + return (i) -> { + if ( id.hasVersionIdPart() && i.hasVersionIdPart() && !id.getVersionIdPart().equals(i.getVersionIdPart())) + return false ; + if ( id.hasBaseUrl() && i.hasBaseUrl() && !id.getBaseUrl().equals(i.getBaseUrl())) + return false ; + if ( id.hasResourceType() && i.hasResourceType() && !id.getResourceType().equals(i.getResourceType())) + return false ; + if ( !id.hasIdPart() || !i.hasIdPart() ) + return false ; + return id.getIdPart().equals( i.getIdPart() ) ; + }; + } + + private static Predicate refersTo( IQueryParameterType param ) { + if ( param instanceof ReferenceParam ) { + ReferenceParam refParam = (ReferenceParam)param ; + return refersTo( new IdType( refParam.getResourceType(), refParam.getIdPart() ) ) ; + } else { + return (i) -> false ; + } + } + + private static Predicate refersTo( IIdType ref ) { + Predicate p = sameId( ref ) ; + return i -> i.hasResource() && i.getResource().hasReference() && p.test( referenceFromLinkage( i.getResource() ).getReferenceElement() ); + } + + private static Predicate sourceRefersTo( IQueryParameterType param ) { + if ( param instanceof ReferenceParam ) { + ReferenceParam refParam = (ReferenceParam)param ; + return sourceRefersTo( new IdType( refParam.getResourceType(), refParam.getIdPart() ) ) ; + } else { + return (i) -> false ; + } + } + + private static Predicate sourceRefersTo( IIdType ref ) { + Predicate p1 = refersTo( ref ) ; + return i -> i.getType() == Linkage.LinkageType.SOURCE && p1.test(i) ; + } + + private Predicate linkageItemFilter( List> parameterValue ) { + if ( null == parameterValue ) + return r -> true ; + return r -> { + if ( !(r instanceof Linkage ) ) + return false ; + Linkage linkage = (Linkage)r ; + + return parameterValue.stream().allMatch( v1 -> v1.stream().anyMatch( v -> linkage.getItem().stream().anyMatch( refersTo( v ) ) ) ) ; + } ; + } + + private Predicate linkageSourceFilter( List> parameterValue ) { + if ( null == parameterValue ) + return r -> true ; + return r -> { + if ( !(r instanceof Linkage ) ) + return false ; + Linkage linkage = (Linkage)r ; + + return parameterValue.stream().allMatch( v1 -> v1.stream().anyMatch( v -> linkage.getItem().stream().anyMatch( sourceRefersTo( v ) ) ) ) ; + } ; + } + + private IQueryParameterType convertQueryParameter( IQueryParameterType parameter ) { + if ( parameter instanceof ReferenceParam ) { + ReferenceParam refParam = (ReferenceParam)parameter ; + IdType id = new IdType( refParam.getValue() ).toUnqualifiedVersionless() ; + return new ReferenceParam( id ) ; + } else { + return parameter ; + } + } + + private List convertQueryParameters( List parameters ) { + return parameters.stream().map( this::convertQueryParameter ).collect( toList() ) ; + } + + private List> convertQueryParametersList( List> parameters ) { + return parameters.stream().map( this::convertQueryParameters ).collect( toList() ) ; + } + @Override protected List searchLinkageResources( SearchParameterMap linkageSearchParamMap, RequestDetails theRequestDetails ) { - return daoLinkageDstu3.search(linkageSearchParamMap, theRequestDetails).getAllResources(); + /* + * server is returning LINKAGE resources while searching on SOURCE that match the id but are not SOURCE + */ + /* + * item and source parameters must be relative (i.e. no baseUrl) in order to find these LINKAGE resources + */ + + SearchParameterMap replacementSearchParameterMap = linkageSearchParamMap.clone() ; + List> itemQueryParameter = replacementSearchParameterMap.remove("item"); + List> sourceQueryParameter = replacementSearchParameterMap.remove("source"); + if ( null != itemQueryParameter ) { + replacementSearchParameterMap.put( "item", convertQueryParametersList( itemQueryParameter ) ); + } + if ( null != sourceQueryParameter ) { + replacementSearchParameterMap.put( "source", convertQueryParametersList( sourceQueryParameter ) ); + } + return daoLinkageDstu3.search(replacementSearchParameterMap, theRequestDetails).getAllResources() + .stream() + .filter( linkageItemFilter( itemQueryParameter ) ) + .filter( linkageSourceFilter( sourceQueryParameter ) ) + .collect( java.util.stream.Collectors.toList() ) + ; } @Override @@ -64,8 +175,9 @@ protected List filterLinkageResourcesHavingAlternateItem( List patientsFromLinkageResources(List linka return linkedPatients; } + private static Reference referenceFromLinkage( Reference ref ) { + if ( !ref.hasExtension(EXTENSION_URL_SDS_PARTITION_NAME) ) + return ref ; + if ( !ref.hasReferenceElement() ) + return ref ; + Type extValue = ref.getExtensionByUrl(EXTENSION_URL_SDS_PARTITION_NAME).getValue() ; + if ( !(extValue instanceof StringType) ) + return ref ; + IIdType id = new IdType( ((StringType)extValue).getValue(), ref.getReferenceElement().getResourceType(), ref.getReferenceElement().getIdPart(), ref.getReferenceElement().getVersionIdPart() ) ; + return new Reference( id ) ; + } + + private static Reference referenceForLinkage( IIdType id ) { + if ( id.hasBaseUrl() ) { + Reference ref = new Reference( id.toUnqualifiedVersionless() ) ; + ref.addExtension(EXTENSION_URL_SDS_PARTITION_NAME, new StringType( id.getBaseUrl() ) ); + return ref ; + } else { + return new Reference(id) ; + } + } + @Override protected void createLinkage( IIdType sourcePatientId, IIdType alternatePatientId, RequestDetails theRequestDetails ) { Linkage linkage = new Linkage(); - linkage.addItem().setType(LinkageType.SOURCE).setResource(new Reference(sourcePatientId)); - linkage.addItem().setType(LinkageType.ALTERNATE).setResource(new Reference(alternatePatientId)); - daoLinkageDstu3.create(linkage, theRequestDetails); + linkage.addItem().setType(LinkageType.SOURCE).setResource(referenceForLinkage(sourcePatientId)); + linkage.addItem().setType(LinkageType.ALTERNATE).setResource(referenceForLinkage(alternatePatientId)); + DaoMethodOutcome outcome = daoLinkageDstu3.create(linkage, theRequestDetails); + if ( Boolean.TRUE != outcome.getCreated() ) { + throw new RuntimeException( "failed to create linkage between " + sourcePatientId + " and " + alternatePatientId ) ; + } } @Override @@ -104,6 +241,7 @@ protected Set alternatePatientsFromLinkageResources(Li .flatMap(k -> k.getItem().stream()) .filter(i -> i.getType() == LinkageType.ALTERNATE) .map(i -> i.getResource()) + .map( SupplementalDataStoreLinkageDstu3::referenceFromLinkage ) .collect(java.util.stream.Collectors.toList()); return FhirResourceComparison.references().createSet( sourceRefs ) ; } @@ -117,6 +255,7 @@ protected Set sourcePatientsFromLinkageResources(List< .flatMap(k -> k.getItem().stream()) .filter(i -> i.getType() == LinkageType.SOURCE) .map(i -> i.getResource()) + .map( SupplementalDataStoreLinkageDstu3::referenceFromLinkage ) .collect(java.util.stream.Collectors.toList()); return FhirResourceComparison.references().createSet( sourceRefs ) ; } diff --git a/src/main/java/edu/ohsu/cmp/ecp/sds/r4/SupplementalDataStoreLinkageR4.java b/src/main/java/edu/ohsu/cmp/ecp/sds/r4/SupplementalDataStoreLinkageR4.java index b937765..fbb5f1a 100644 --- a/src/main/java/edu/ohsu/cmp/ecp/sds/r4/SupplementalDataStoreLinkageR4.java +++ b/src/main/java/edu/ohsu/cmp/ecp/sds/r4/SupplementalDataStoreLinkageR4.java @@ -1,5 +1,7 @@ package edu.ohsu.cmp.ecp.sds.r4; +import static java.util.stream.Collectors.toList; + import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -31,7 +33,6 @@ import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.starter.annotations.OnR4Condition; import ca.uhn.fhir.model.api.IQueryParameterType; -import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.ReferenceParam; import edu.ohsu.cmp.ecp.sds.SupplementalDataStoreProperties; @@ -70,7 +71,7 @@ private static Predicate sameId( IIdType id ) { return id.getIdPart().equals( i.getIdPart() ) ; }; } - + private static Predicate refersTo( IQueryParameterType param ) { if ( param instanceof ReferenceParam ) { ReferenceParam refParam = (ReferenceParam)param ; @@ -79,7 +80,7 @@ private static Predicate refersTo( IQueryParameter return (i) -> false ; } } - + private static Predicate refersTo( IIdType ref ) { Predicate p = sameId( ref ) ; return i -> i.hasResource() && i.getResource().hasReference() && p.test( referenceFromLinkage( i.getResource() ).getReferenceElement() ); @@ -93,13 +94,12 @@ private static Predicate sourceRefersTo( IQueryPar return (i) -> false ; } } - private static Predicate sourceRefersTo( IIdType ref ) { Predicate p1 = refersTo( ref ) ; return i -> i.getType() == Linkage.LinkageType.SOURCE && p1.test(i) ; } - + private Predicate linkageItemFilter( List> parameterValue ) { if ( null == parameterValue ) return r -> true ; @@ -107,11 +107,11 @@ private Predicate linkageItemFilter( List v1.stream().anyMatch( v -> linkage.getItem().stream().anyMatch( refersTo( v ) ) ) ) ; } ; } - + private Predicate linkageSourceFilter( List> parameterValue ) { if ( null == parameterValue ) return r -> true ; @@ -119,28 +119,53 @@ private Predicate linkageSourceFilter( List v1.stream().anyMatch( v -> linkage.getItem().stream().anyMatch( sourceRefersTo( v ) ) ) ) ; } ; } - + + private IQueryParameterType convertQueryParameter( IQueryParameterType parameter ) { + if ( parameter instanceof ReferenceParam ) { + ReferenceParam refParam = (ReferenceParam)parameter ; + IdType id = new IdType( refParam.getValue() ).toUnqualifiedVersionless() ; + return new ReferenceParam( id ) ; + } else { + return parameter ; + } + } + + private List convertQueryParameters( List parameters ) { + return parameters.stream().map( this::convertQueryParameter ).collect( toList() ) ; + } + + private List> convertQueryParametersList( List> parameters ) { + return parameters.stream().map( this::convertQueryParameters ).collect( toList() ) ; + } + @Override protected List searchLinkageResources( SearchParameterMap linkageSearchParamMap, RequestDetails theRequestDetails ) { /* - * server is failing to find existing LINKAGE resources while searching on ITEM - * - return daoLinkageR4.search(linkageSearchParamMap, theRequestDetails).getAllResources(); + * server is returning LINKAGE resources while searching on SOURCE that match the id but are not SOURCE + */ + /* + * item and source parameters must be relative (i.e. no baseUrl) in order to find these LINKAGE resources */ + SearchParameterMap replacementSearchParameterMap = linkageSearchParamMap.clone() ; - Predicate itemFilter = linkageItemFilter( replacementSearchParameterMap.remove("item") ) ; - Predicate sourceFilter = linkageSourceFilter( replacementSearchParameterMap.remove("source") ) ; + List> itemQueryParameter = replacementSearchParameterMap.remove("item"); + List> sourceQueryParameter = replacementSearchParameterMap.remove("source"); + if ( null != itemQueryParameter ) { + replacementSearchParameterMap.put( "item", convertQueryParametersList( itemQueryParameter ) ); + } + if ( null != sourceQueryParameter ) { + replacementSearchParameterMap.put( "source", convertQueryParametersList( sourceQueryParameter ) ); + } return daoLinkageR4.search(replacementSearchParameterMap, theRequestDetails).getAllResources() .stream() - .filter( itemFilter ) - .filter( sourceFilter ) + .filter( linkageItemFilter( itemQueryParameter ) ) + .filter( linkageSourceFilter( sourceQueryParameter ) ) .collect( java.util.stream.Collectors.toList() ) ; - } @Override diff --git a/src/main/java/edu/ohsu/cmp/ecp/sds/r4b/SupplementalDataStoreLinkageR4B.java b/src/main/java/edu/ohsu/cmp/ecp/sds/r4b/SupplementalDataStoreLinkageR4B.java index 413e120..a578f5d 100644 --- a/src/main/java/edu/ohsu/cmp/ecp/sds/r4b/SupplementalDataStoreLinkageR4B.java +++ b/src/main/java/edu/ohsu/cmp/ecp/sds/r4b/SupplementalDataStoreLinkageR4B.java @@ -1,9 +1,12 @@ package edu.ohsu.cmp.ecp.sds.r4b; +import static java.util.stream.Collectors.toList; + import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; import javax.inject.Inject; @@ -13,8 +16,10 @@ import org.hl7.fhir.r4b.model.RelatedPerson; import org.hl7.fhir.r4b.model.UrlType; import org.hl7.fhir.r4b.model.BooleanType; +import org.hl7.fhir.r4b.model.DataType; import org.hl7.fhir.r4b.model.DomainResource; import org.hl7.fhir.r4b.model.Extension; +import org.hl7.fhir.r4b.model.IdType; import org.hl7.fhir.r4b.model.Linkage; import org.hl7.fhir.r4b.model.Linkage.LinkageType; import org.hl7.fhir.r4b.model.Patient; @@ -27,6 +32,7 @@ import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.starter.annotations.OnR4BCondition; +import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.ReferenceParam; import edu.ohsu.cmp.ecp.sds.SupplementalDataStoreProperties; @@ -52,9 +58,114 @@ public class SupplementalDataStoreLinkageR4B extends SupplementalDataStoreLinkag @Inject IFhirResourceDao daoRelatedPersonR4B; + private static Predicate sameId( IIdType id ) { + return (i) -> { + if ( id.hasVersionIdPart() && i.hasVersionIdPart() && !id.getVersionIdPart().equals(i.getVersionIdPart())) + return false ; + if ( id.hasBaseUrl() && i.hasBaseUrl() && !id.getBaseUrl().equals(i.getBaseUrl())) + return false ; + if ( id.hasResourceType() && i.hasResourceType() && !id.getResourceType().equals(i.getResourceType())) + return false ; + if ( !id.hasIdPart() || !i.hasIdPart() ) + return false ; + return id.getIdPart().equals( i.getIdPart() ) ; + }; + } + + private static Predicate refersTo( IQueryParameterType param ) { + if ( param instanceof ReferenceParam ) { + ReferenceParam refParam = (ReferenceParam)param ; + return refersTo( new IdType( refParam.getResourceType(), refParam.getIdPart() ) ) ; + } else { + return (i) -> false ; + } + } + + private static Predicate refersTo( IIdType ref ) { + Predicate p = sameId( ref ) ; + return i -> i.hasResource() && i.getResource().hasReference() && p.test( referenceFromLinkage( i.getResource() ).getReferenceElement() ); + } + + private static Predicate sourceRefersTo( IQueryParameterType param ) { + if ( param instanceof ReferenceParam ) { + ReferenceParam refParam = (ReferenceParam)param ; + return sourceRefersTo( new IdType( refParam.getResourceType(), refParam.getIdPart() ) ) ; + } else { + return (i) -> false ; + } + } + + private static Predicate sourceRefersTo( IIdType ref ) { + Predicate p1 = refersTo( ref ) ; + return i -> i.getType() == Linkage.LinkageType.SOURCE && p1.test(i) ; + } + + private Predicate linkageItemFilter( List> parameterValue ) { + if ( null == parameterValue ) + return r -> true ; + return r -> { + if ( !(r instanceof Linkage ) ) + return false ; + Linkage linkage = (Linkage)r ; + + return parameterValue.stream().allMatch( v1 -> v1.stream().anyMatch( v -> linkage.getItem().stream().anyMatch( refersTo( v ) ) ) ) ; + } ; + } + + private Predicate linkageSourceFilter( List> parameterValue ) { + if ( null == parameterValue ) + return r -> true ; + return r -> { + if ( !(r instanceof Linkage ) ) + return false ; + Linkage linkage = (Linkage)r ; + + return parameterValue.stream().allMatch( v1 -> v1.stream().anyMatch( v -> linkage.getItem().stream().anyMatch( sourceRefersTo( v ) ) ) ) ; + } ; + } + + private IQueryParameterType convertQueryParameter( IQueryParameterType parameter ) { + if ( parameter instanceof ReferenceParam ) { + ReferenceParam refParam = (ReferenceParam)parameter ; + IdType id = new IdType( refParam.getValue() ).toUnqualifiedVersionless() ; + return new ReferenceParam( id ) ; + } else { + return parameter ; + } + } + + private List convertQueryParameters( List parameters ) { + return parameters.stream().map( this::convertQueryParameter ).collect( toList() ) ; + } + + private List> convertQueryParametersList( List> parameters ) { + return parameters.stream().map( this::convertQueryParameters ).collect( toList() ) ; + } + @Override protected List searchLinkageResources( SearchParameterMap linkageSearchParamMap, RequestDetails theRequestDetails ) { - return daoLinkageR4B.search(linkageSearchParamMap, theRequestDetails).getAllResources(); + /* + * server is returning LINKAGE resources while searching on SOURCE that match the id but are not SOURCE + */ + /* + * item and source parameters must be relative (i.e. no baseUrl) in order to find these LINKAGE resources + */ + + SearchParameterMap replacementSearchParameterMap = linkageSearchParamMap.clone() ; + List> itemQueryParameter = replacementSearchParameterMap.remove("item"); + List> sourceQueryParameter = replacementSearchParameterMap.remove("source"); + if ( null != itemQueryParameter ) { + replacementSearchParameterMap.put( "item", convertQueryParametersList( itemQueryParameter ) ); + } + if ( null != sourceQueryParameter ) { + replacementSearchParameterMap.put( "source", convertQueryParametersList( sourceQueryParameter ) ); + } + return daoLinkageR4B.search(replacementSearchParameterMap, theRequestDetails).getAllResources() + .stream() + .filter( linkageItemFilter( itemQueryParameter ) ) + .filter( linkageSourceFilter( sourceQueryParameter ) ) + .collect( java.util.stream.Collectors.toList() ) + ; } @Override @@ -64,8 +175,9 @@ protected List filterLinkageResourcesHavingAlternateItem( List patientsFromLinkageResources(List linka return linkedPatients; } + private static Reference referenceFromLinkage( Reference ref ) { + if ( !ref.hasExtension(EXTENSION_URL_SDS_PARTITION_NAME) ) + return ref ; + if ( !ref.hasReferenceElement() ) + return ref ; + DataType extValue = ref.getExtensionByUrl(EXTENSION_URL_SDS_PARTITION_NAME).getValue() ; + if ( !(extValue instanceof UrlType) ) + return ref ; + IIdType id = new IdType( ((UrlType)extValue).getValue(), ref.getReferenceElement().getResourceType(), ref.getReferenceElement().getIdPart(), ref.getReferenceElement().getVersionIdPart() ) ; + return new Reference( id ) ; + } + + private static Reference referenceForLinkage( IIdType id ) { + if ( id.hasBaseUrl() ) { + Reference ref = new Reference( id.toUnqualifiedVersionless() ) ; + ref.addExtension(EXTENSION_URL_SDS_PARTITION_NAME, new UrlType( id.getBaseUrl() ) ); + return ref ; + } else { + return new Reference(id) ; + } + } + @Override protected void createLinkage( IIdType sourcePatientId, IIdType alternatePatientId, RequestDetails theRequestDetails ) { Linkage linkage = new Linkage(); - linkage.addItem().setType(LinkageType.SOURCE).setResource(new Reference(sourcePatientId)); - linkage.addItem().setType(LinkageType.ALTERNATE).setResource(new Reference(alternatePatientId)); - daoLinkageR4B.create(linkage, theRequestDetails); + linkage.addItem().setType(LinkageType.SOURCE).setResource(referenceForLinkage(sourcePatientId)); + linkage.addItem().setType(LinkageType.ALTERNATE).setResource(referenceForLinkage(alternatePatientId)); + DaoMethodOutcome outcome = daoLinkageR4B.create(linkage, theRequestDetails); + if ( Boolean.TRUE != outcome.getCreated() ) { + throw new RuntimeException( "failed to create linkage between " + sourcePatientId + " and " + alternatePatientId ) ; + } } @Override @@ -104,6 +241,7 @@ protected Set alternatePatientsFromLinkageResources(Li .flatMap(k -> k.getItem().stream()) .filter(i -> i.getType() == LinkageType.ALTERNATE) .map(i -> i.getResource()) + .map( SupplementalDataStoreLinkageR4B::referenceFromLinkage ) .collect(java.util.stream.Collectors.toList()); return FhirResourceComparison.references().createSet( sourceRefs ) ; } @@ -117,6 +255,7 @@ protected Set sourcePatientsFromLinkageResources(List< .flatMap(k -> k.getItem().stream()) .filter(i -> i.getType() == LinkageType.SOURCE) .map(i -> i.getResource()) + .map( SupplementalDataStoreLinkageR4B::referenceFromLinkage ) .collect(java.util.stream.Collectors.toList()); return FhirResourceComparison.references().createSet( sourceRefs ) ; } diff --git a/src/main/java/edu/ohsu/cmp/ecp/sds/r5/SupplementalDataStoreLinkageR5.java b/src/main/java/edu/ohsu/cmp/ecp/sds/r5/SupplementalDataStoreLinkageR5.java index 512e238..bc8ee48 100644 --- a/src/main/java/edu/ohsu/cmp/ecp/sds/r5/SupplementalDataStoreLinkageR5.java +++ b/src/main/java/edu/ohsu/cmp/ecp/sds/r5/SupplementalDataStoreLinkageR5.java @@ -1,9 +1,12 @@ package edu.ohsu.cmp.ecp.sds.r5; +import static java.util.stream.Collectors.toList; + import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; import javax.inject.Inject; @@ -13,8 +16,10 @@ import org.hl7.fhir.r5.model.RelatedPerson; import org.hl7.fhir.r5.model.UrlType; import org.hl7.fhir.r5.model.BooleanType; +import org.hl7.fhir.r5.model.DataType; import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Extension; +import org.hl7.fhir.r5.model.IdType; import org.hl7.fhir.r5.model.Linkage; import org.hl7.fhir.r5.model.Linkage.LinkageType; import org.hl7.fhir.r5.model.Patient; @@ -27,6 +32,7 @@ import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.starter.annotations.OnR5Condition; +import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.ReferenceParam; import edu.ohsu.cmp.ecp.sds.SupplementalDataStoreProperties; @@ -52,9 +58,114 @@ public class SupplementalDataStoreLinkageR5 extends SupplementalDataStoreLinkage @Inject IFhirResourceDao daoRelatedPersonR5; + private static Predicate sameId( IIdType id ) { + return (i) -> { + if ( id.hasVersionIdPart() && i.hasVersionIdPart() && !id.getVersionIdPart().equals(i.getVersionIdPart())) + return false ; + if ( id.hasBaseUrl() && i.hasBaseUrl() && !id.getBaseUrl().equals(i.getBaseUrl())) + return false ; + if ( id.hasResourceType() && i.hasResourceType() && !id.getResourceType().equals(i.getResourceType())) + return false ; + if ( !id.hasIdPart() || !i.hasIdPart() ) + return false ; + return id.getIdPart().equals( i.getIdPart() ) ; + }; + } + + private static Predicate refersTo( IQueryParameterType param ) { + if ( param instanceof ReferenceParam ) { + ReferenceParam refParam = (ReferenceParam)param ; + return refersTo( new IdType( refParam.getResourceType(), refParam.getIdPart() ) ) ; + } else { + return (i) -> false ; + } + } + + private static Predicate refersTo( IIdType ref ) { + Predicate p = sameId( ref ) ; + return i -> i.hasResource() && i.getResource().hasReference() && p.test( referenceFromLinkage( i.getResource() ).getReferenceElement() ); + } + + private static Predicate sourceRefersTo( IQueryParameterType param ) { + if ( param instanceof ReferenceParam ) { + ReferenceParam refParam = (ReferenceParam)param ; + return sourceRefersTo( new IdType( refParam.getResourceType(), refParam.getIdPart() ) ) ; + } else { + return (i) -> false ; + } + } + + private static Predicate sourceRefersTo( IIdType ref ) { + Predicate p1 = refersTo( ref ) ; + return i -> i.getType() == Linkage.LinkageType.SOURCE && p1.test(i) ; + } + + private Predicate linkageItemFilter( List> parameterValue ) { + if ( null == parameterValue ) + return r -> true ; + return r -> { + if ( !(r instanceof Linkage ) ) + return false ; + Linkage linkage = (Linkage)r ; + + return parameterValue.stream().allMatch( v1 -> v1.stream().anyMatch( v -> linkage.getItem().stream().anyMatch( refersTo( v ) ) ) ) ; + } ; + } + + private Predicate linkageSourceFilter( List> parameterValue ) { + if ( null == parameterValue ) + return r -> true ; + return r -> { + if ( !(r instanceof Linkage ) ) + return false ; + Linkage linkage = (Linkage)r ; + + return parameterValue.stream().allMatch( v1 -> v1.stream().anyMatch( v -> linkage.getItem().stream().anyMatch( sourceRefersTo( v ) ) ) ) ; + } ; + } + + private IQueryParameterType convertQueryParameter( IQueryParameterType parameter ) { + if ( parameter instanceof ReferenceParam ) { + ReferenceParam refParam = (ReferenceParam)parameter ; + IdType id = new IdType( refParam.getValue() ).toUnqualifiedVersionless() ; + return new ReferenceParam( id ) ; + } else { + return parameter ; + } + } + + private List convertQueryParameters( List parameters ) { + return parameters.stream().map( this::convertQueryParameter ).collect( toList() ) ; + } + + private List> convertQueryParametersList( List> parameters ) { + return parameters.stream().map( this::convertQueryParameters ).collect( toList() ) ; + } + @Override protected List searchLinkageResources( SearchParameterMap linkageSearchParamMap, RequestDetails theRequestDetails ) { - return daoLinkageR5.search(linkageSearchParamMap, theRequestDetails).getAllResources(); + /* + * server is returning LINKAGE resources while searching on SOURCE that match the id but are not SOURCE + */ + /* + * item and source parameters must be relative (i.e. no baseUrl) in order to find these LINKAGE resources + */ + + SearchParameterMap replacementSearchParameterMap = linkageSearchParamMap.clone() ; + List> itemQueryParameter = replacementSearchParameterMap.remove("item"); + List> sourceQueryParameter = replacementSearchParameterMap.remove("source"); + if ( null != itemQueryParameter ) { + replacementSearchParameterMap.put( "item", convertQueryParametersList( itemQueryParameter ) ); + } + if ( null != sourceQueryParameter ) { + replacementSearchParameterMap.put( "source", convertQueryParametersList( sourceQueryParameter ) ); + } + return daoLinkageR5.search(replacementSearchParameterMap, theRequestDetails).getAllResources() + .stream() + .filter( linkageItemFilter( itemQueryParameter ) ) + .filter( linkageSourceFilter( sourceQueryParameter ) ) + .collect( java.util.stream.Collectors.toList() ) + ; } @Override @@ -64,8 +175,9 @@ protected List filterLinkageResourcesHavingAlternateItem( List patientsFromLinkageResources(List linka return linkedPatients; } + private static Reference referenceFromLinkage( Reference ref ) { + if ( !ref.hasExtension(EXTENSION_URL_SDS_PARTITION_NAME) ) + return ref ; + if ( !ref.hasReferenceElement() ) + return ref ; + DataType extValue = ref.getExtensionByUrl(EXTENSION_URL_SDS_PARTITION_NAME).getValue() ; + if ( !(extValue instanceof UrlType) ) + return ref ; + IIdType id = new IdType( ((UrlType)extValue).getValue(), ref.getReferenceElement().getResourceType(), ref.getReferenceElement().getIdPart(), ref.getReferenceElement().getVersionIdPart() ) ; + return new Reference( id ) ; + } + + private static Reference referenceForLinkage( IIdType id ) { + if ( id.hasBaseUrl() ) { + Reference ref = new Reference( id.toUnqualifiedVersionless() ) ; + ref.addExtension(EXTENSION_URL_SDS_PARTITION_NAME, new UrlType( id.getBaseUrl() ) ); + return ref ; + } else { + return new Reference(id) ; + } + } + @Override protected void createLinkage( IIdType sourcePatientId, IIdType alternatePatientId, RequestDetails theRequestDetails ) { Linkage linkage = new Linkage(); - linkage.addItem().setType(LinkageType.SOURCE).setResource(new Reference(sourcePatientId)); - linkage.addItem().setType(LinkageType.ALTERNATE).setResource(new Reference(alternatePatientId)); - daoLinkageR5.create(linkage, theRequestDetails); + linkage.addItem().setType(LinkageType.SOURCE).setResource(referenceForLinkage(sourcePatientId)); + linkage.addItem().setType(LinkageType.ALTERNATE).setResource(referenceForLinkage(alternatePatientId)); + DaoMethodOutcome outcome = daoLinkageR5.create(linkage, theRequestDetails); + if ( Boolean.TRUE != outcome.getCreated() ) { + throw new RuntimeException( "failed to create linkage between " + sourcePatientId + " and " + alternatePatientId ) ; + } } @Override @@ -104,6 +241,7 @@ protected Set alternatePatientsFromLinkageResources(Li .flatMap(k -> k.getItem().stream()) .filter(i -> i.getType() == LinkageType.ALTERNATE) .map(i -> i.getResource()) + .map( SupplementalDataStoreLinkageR5::referenceFromLinkage ) .collect(java.util.stream.Collectors.toList()); return FhirResourceComparison.references().createSet( sourceRefs ) ; } @@ -117,6 +255,7 @@ protected Set sourcePatientsFromLinkageResources(List< .flatMap(k -> k.getItem().stream()) .filter(i -> i.getType() == LinkageType.SOURCE) .map(i -> i.getResource()) + .map( SupplementalDataStoreLinkageR5::referenceFromLinkage ) .collect(java.util.stream.Collectors.toList()); return FhirResourceComparison.references().createSet( sourceRefs ) ; } diff --git a/src/test/java/edu/ohsu/cmp/ecp/sds/BaseSuppplementalDataStoreTest.java b/src/test/java/edu/ohsu/cmp/ecp/sds/BaseSuppplementalDataStoreTest.java index 7fc0970..c4db806 100644 --- a/src/test/java/edu/ohsu/cmp/ecp/sds/BaseSuppplementalDataStoreTest.java +++ b/src/test/java/edu/ohsu/cmp/ecp/sds/BaseSuppplementalDataStoreTest.java @@ -51,6 +51,9 @@ public abstract class BaseSuppplementalDataStoreTest { @LocalServerPort private int port; + @Autowired + private SupplementalDataStoreProperties sdsProperties ; + @Autowired private ModelConfig myModelConfig; @@ -101,7 +104,7 @@ public void resetTestSpecificIdComponents(TestInfo testInfo) { protected IGenericClient clientTargetingPartition( String partitionName ) { IGenericClient client = client() ; - client.registerInterceptor( new PartitionNameHeaderClientInterceptor( partitionName ) ); + client.registerInterceptor( new PartitionNameHeaderClientInterceptor( sdsProperties.getPartition().getHttpHeader(), partitionName ) ); return client ; } @@ -114,7 +117,7 @@ protected IGenericClient authenticatingClient( String token ) { protected IGenericClient authenticatingClientTargetingPartition( String token, String partitionName ) { IGenericClient client = client() ; - client.registerInterceptor( new PartitionNameHeaderClientInterceptor( partitionName ) ); + client.registerInterceptor( new PartitionNameHeaderClientInterceptor( sdsProperties.getPartition().getHttpHeader(), partitionName ) ); client.registerInterceptor( new BearerTokenAuthInterceptor( token ) ); return client ; } diff --git a/src/test/java/edu/ohsu/cmp/ecp/sds/PartitionNameHeaderClientInterceptor.java b/src/test/java/edu/ohsu/cmp/ecp/sds/PartitionNameHeaderClientInterceptor.java index 809b142..3da3136 100644 --- a/src/test/java/edu/ohsu/cmp/ecp/sds/PartitionNameHeaderClientInterceptor.java +++ b/src/test/java/edu/ohsu/cmp/ecp/sds/PartitionNameHeaderClientInterceptor.java @@ -8,15 +8,17 @@ class PartitionNameHeaderClientInterceptor implements IClientInterceptor { + private final String httpHeader; private final String partitionName; - public PartitionNameHeaderClientInterceptor(String partitionName) { + public PartitionNameHeaderClientInterceptor(String httpHeader, String partitionName) { + this.httpHeader = httpHeader ; this.partitionName = partitionName; } @Override public void interceptRequest(IHttpRequest theRequest) { - theRequest.addHeader( SupplementalDataStorePartition.HEADER_PARTITION_NAME, partitionName ); + theRequest.addHeader( httpHeader, partitionName ); } @Override diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml index d0e1848..9bbbf79 100644 --- a/src/test/resources/application.yaml +++ b/src/test/resources/application.yaml @@ -2,6 +2,7 @@ sds: require-base-url: false partition: local-name: SDS-LOCAL + http-header: X-Partition-Name multiple-linked-local-patients: WARN spring: main: