Skip to content

Commit

Permalink
Merge branch 'vsm_operations' into aphl-300-valueset-interceptor
Browse files Browse the repository at this point in the history
  • Loading branch information
TahaAttari authored Mar 22, 2024
2 parents a03a5bf + f6af4a4 commit 8990d2a
Show file tree
Hide file tree
Showing 20 changed files with 3,208 additions and 2,391 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.opencds.cqf.ruler.provider;

import org.opencds.cqf.ruler.api.OperationProvider;
import org.opencds.cqf.ruler.behavior.DaoRegistryUser;
import org.springframework.beans.factory.annotation.Autowired;

import ca.uhn.fhir.cr.repo.HapiFhirRepository;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.RestfulServer;

public class HapiFhirRepositoryProvider implements OperationProvider, DaoRegistryUser {
@Autowired
private DaoRegistry myDaoRegistry;

@Autowired
RestfulServer myRestfulServer;

public DaoRegistry getDaoRegistry() {
return myDaoRegistry;
}
public HapiFhirRepository getRepository(RequestDetails theRequestDetails) {
return new HapiFhirRepository(myDaoRegistry, theRequestDetails, myRestfulServer);
}
}
142 changes: 69 additions & 73 deletions ecr/src/test/resources/ersd-bundle-example.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,10 @@ public KnowledgeArtifactProcessor knowledgeArtifactProcessor() {
return new KnowledgeArtifactProcessor();
}

@Bean
@Conditional(OnR4Condition.class)
public TerminologyServerClient terminologyServerClient() {
return new TerminologyServerClient(crRulerProperties().getVsacUsername(), crRulerProperties().getVsacApiKey());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,17 @@ public String getRckmsSynonymsUrl() {
return this.rckmsSynonymsUrl;
}

private String vsacUsername;

public String getVsacUsername() { return vsacUsername; }

public void setVsacUsername(String vsacUsername) { this.vsacUsername = vsacUsername; }

private String vsacApiKey;

public String getVsacApiKey() { return vsacApiKey; }

public void setVsacApiKey(String vsacApiKey) { this.vsacApiKey = vsacApiKey; }


}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package org.opencds.cqf.ruler.cr;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;

import org.cqframework.fhir.api.FhirDal;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.NpmPackageValidationSupport;
Expand All @@ -23,18 +24,22 @@
import org.hl7.fhir.r4.model.MetadataResource;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.ValueSet;
import org.opencds.cqf.cql.evaluator.fhir.util.Canonicals;
import org.opencds.cqf.ruler.cr.r4.ArtifactAssessment;
import org.opencds.cqf.ruler.cr.r4.CRMIReleaseExperimentalBehavior.CRMIReleaseExperimentalBehaviorCodes;
import org.opencds.cqf.ruler.cr.r4.CRMIReleaseVersionBehavior.CRMIReleaseVersionBehaviorCodes;
import org.opencds.cqf.ruler.provider.DaoRegistryOperationProvider;
import org.opencds.cqf.ruler.cr.r4.helper.ResourceClassMapHelper;
import org.opencds.cqf.ruler.provider.HapiFhirRepositoryProvider;
import org.springframework.beans.factory.annotation.Autowired;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.cr.repo.HapiFhirRepository;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.validation.ValidatorResourceFetcher;
import ca.uhn.fhir.model.api.annotation.Description;
Expand All @@ -48,10 +53,7 @@
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.validation.FhirValidator;

public class RepositoryService extends DaoRegistryOperationProvider {

@Autowired
private JpaFhirDalFactory fhirDalFactory;
public class RepositoryService extends HapiFhirRepositoryProvider {

@Autowired
private KnowledgeArtifactProcessor artifactProcessor;
Expand Down Expand Up @@ -88,8 +90,8 @@ public Bundle approveOperation(
@OperationParam(name = "artifactCommentTarget") CanonicalType artifactCommentTarget,
@OperationParam(name = "artifactCommentReference") CanonicalType artifactCommentReference,
@OperationParam(name = "artifactCommentUser") Reference artifactCommentUser) throws UnprocessableEntityException {
FhirDal fhirDal = this.fhirDalFactory.create(requestDetails);
MetadataResource resource = (MetadataResource) fhirDal.read(theId);
HapiFhirRepository hapiFhirRepository = this.getRepository(requestDetails);
MetadataResource resource = (MetadataResource)hapiFhirRepository.read(ResourceClassMapHelper.getClass(theId.getResourceType()), theId);
if (resource == null) {
throw new ResourceNotFoundException(theId);
}
Expand Down Expand Up @@ -147,8 +149,8 @@ public Bundle approveOperation(
@Description(shortDefinition = "$draft", value = "Create a new draft version of the reference artifact")
public Bundle draftOperation(RequestDetails requestDetails, @IdParam IdType theId, @OperationParam(name = "version") String version)
throws FHIRException {
FhirDal fhirDal = this.fhirDalFactory.create(requestDetails);
return transaction(this.artifactProcessor.createDraftBundle(theId, fhirDal, version));
HapiFhirRepository hapiFhirRepository = this.getRepository(requestDetails);
return transaction(this.artifactProcessor.createDraftBundle(theId, hapiFhirRepository, version));
}
/**
* Sets the status of an existing artifact to Active if it has status Draft.
Expand Down Expand Up @@ -179,15 +181,15 @@ public Bundle releaseOperation(
} catch (FHIRException e) {
throw new UnprocessableEntityException(e.getMessage());
}
FhirDal fhirDal = this.fhirDalFactory.create(requestDetails);
HapiFhirRepository hapiFhirRepository = this.getRepository(requestDetails);
return transaction(this.artifactProcessor.createReleaseBundle(
theId,
releaseLabel,
version,
versionBehaviorCode,
latestFromTxServer != null && latestFromTxServer.getValue(),
experimentalBehaviorCode,
fhirDal));
hapiFhirRepository));
}

@Operation(name = "$package", idempotent = true, global = true, type = MetadataResource.class)
Expand All @@ -197,30 +199,36 @@ public Bundle packageOperation(
@IdParam IdType theId,
// TODO: $package - should capability be CodeType?
@OperationParam(name = "capability") List<String> capability,
@OperationParam(name = "canonicalVersion") List<CanonicalType> canonicalVersion,
@OperationParam(name = "checkCanonicalVersion") List<CanonicalType> checkCanonicalVersion,
@OperationParam(name = "forceCanonicalVersion") List<CanonicalType> forceCanonicalVersion,
@OperationParam(name = "artifactVersion") List<CanonicalType> artifactVersion,
@OperationParam(name = "checkArtifactVersion") List<CanonicalType> checkArtifactVersion,
@OperationParam(name = "forceArtifactVersion") List<CanonicalType> forceArtifactVersion,
// TODO: $package - should include be CodeType?
@OperationParam(name = "include") List<String> include,
@OperationParam(name = "manifest") CanonicalType manifest,
@OperationParam(name = "offset", typeName = "Integer") IPrimitiveType<Integer> offset,
@OperationParam(name = "count", typeName = "Integer") IPrimitiveType<Integer> count,
@OperationParam(name = "packageOnly", typeName = "Boolean") IPrimitiveType<Boolean> packageOnly,
@OperationParam(name = "contentEndpoint") Endpoint contentEndpoint,
@OperationParam(name = "artifactEndpointConfiguration") ParametersParameterComponent artifactEndpointConfiguration,
@OperationParam(name = "terminologyEndpoint") Endpoint terminologyEndpoint
) throws FHIRException {
FhirDal fhirDal = this.fhirDalFactory.create(requestDetails);
HapiFhirRepository hapiFhirRepository = this.getRepository(requestDetails);
List<ParametersParameterComponent> artifactEndpointParts = Optional.ofNullable(artifactEndpointConfiguration).map(config -> config.getPart()).orElse(new ArrayList<ParametersParameterComponent>());
String artifactRoute = artifactEndpointParts.stream().filter(part -> part.getName().equals("artifactRoute")).map(part -> ((UriType)part.getValue()).getValue()).findAny().orElse(null);
String endpointUri = artifactEndpointParts.stream().filter(part -> part.getName().equals("endpointUri")).map(part -> ((UriType)part.getValue()).getValue()).findAny().orElse(null);
Endpoint artifactEndpoint = artifactEndpointParts.stream().filter(part -> part.getName().equals("endpoint")).map(part -> (Endpoint)part.getResource()).findAny().orElse(null);
return this.artifactProcessor.createPackageBundle(
theId,
fhirDal,
hapiFhirRepository,
capability,
include,
canonicalVersion,
checkCanonicalVersion,
forceCanonicalVersion,
artifactVersion,
checkArtifactVersion,
forceArtifactVersion,
count != null ? count.getValue() : null,
offset != null ? offset.getValue() : null,
contentEndpoint,
artifactRoute,
endpointUri,
artifactEndpoint,
terminologyEndpoint,
packageOnly != null ? packageOnly.getValue() : null
);
Expand All @@ -231,8 +239,8 @@ public Bundle packageOperation(
public IBaseResource reviseOperation(RequestDetails requestDetails, @OperationParam(name = "resource") IBaseResource resource)
throws FHIRException {

FhirDal fhirDal = fhirDalFactory.create(requestDetails);
return (IBaseResource)this.artifactProcessor.revise(fhirDal, (MetadataResource) resource);
HapiFhirRepository hapiFhirRepository = this.getRepository(requestDetails);
return (IBaseResource)this.artifactProcessor.revise(hapiFhirRepository, (MetadataResource) resource);
}

@Operation(name = "$validate", idempotent = true, global = true, type = MetadataResource.class)
Expand Down Expand Up @@ -286,20 +294,22 @@ public Parameters crmiArtifactDiff(RequestDetails requestDetails,
@OperationParam(name = "compareExecutable", typeName = "Boolean") IPrimitiveType<Boolean> compareExecutable,
@OperationParam(name = "compareComputable", typeName = "Boolean") IPrimitiveType<Boolean> compareComputable
) throws UnprocessableEntityException, ResourceNotFoundException{
FhirDal fhirDal = fhirDalFactory.create(requestDetails);
IBaseResource theSourceResource = fhirDal.read(new IdType(source));
HapiFhirRepository hapiFhirRepository = this.getRepository(requestDetails);
IdType sourceId = new IdType(source);
IBaseResource theSourceResource = hapiFhirRepository.read(ResourceClassMapHelper.getClass(sourceId.getResourceType()), sourceId);
if (theSourceResource == null || !(theSourceResource instanceof MetadataResource)) {
throw new UnprocessableEntityException("Source resource must exist and be a Knowledge Artifact type.");
}
IBaseResource theTargetResource = fhirDal.read(new IdType(target));
IdType targetId = new IdType(target);
IBaseResource theTargetResource = hapiFhirRepository.read(ResourceClassMapHelper.getClass(targetId.getResourceType()), targetId);
if (theTargetResource == null || !(theTargetResource instanceof MetadataResource)) {
throw new UnprocessableEntityException("Target resource must exist and be a Knowledge Artifact type.");
}
if (theSourceResource.getClass() != theTargetResource.getClass()) {
throw new UnprocessableEntityException("Source and target resources must be of the same type.");
}
IFhirResourceDaoValueSet<ValueSet> dao = (IFhirResourceDaoValueSet<ValueSet>)this.getDaoRegistry().getResourceDao(ValueSet.class);
return this.artifactProcessor.artifactDiff((MetadataResource)theSourceResource,(MetadataResource)theTargetResource,this.getFhirContext(),fhirDal,compareComputable == null ? false : compareComputable.getValue(), compareExecutable == null ? false : compareExecutable.getValue(),dao);
return this.artifactProcessor.artifactDiff((MetadataResource)theSourceResource, (MetadataResource)theTargetResource, this.getFhirContext(), hapiFhirRepository, compareComputable == null ? false : compareComputable.getValue(), compareExecutable == null ? false : compareExecutable.getValue(), dao);
}
private BundleEntryComponent createEntry(IBaseResource theResource) {
return new Bundle.BundleEntryComponent()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.opencds.cqf.ruler.cr;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.AdditionalRequestHeadersInterceptor;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.ValueSet;
import org.jetbrains.annotations.NotNull;
import org.opencds.cqf.cql.evaluator.fhir.util.Canonicals;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class TerminologyServerClient {

private final FhirContext ctx;

private String username;

private String getUsername() {
return username;
}

private void setUsername(String username) {
this.username = username;
}

private String apiKey;

private String getApiKey() {
return apiKey;
}

private void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public TerminologyServerClient() {
ctx = FhirContext.forR4();
}

public TerminologyServerClient(String username, String apiKey) {
this();
setUsername(username);
setApiKey(apiKey);
}

public ValueSet expand(ValueSet valueSet, String authoritativeSource, Parameters expansionParameters) {
IGenericClient fhirClient = ctx.newRestfulGenericClient(getAuthoritativeSourceBase(authoritativeSource));
fhirClient.registerInterceptor(getAuthInterceptor(getUsername(), getApiKey()));

// Invoke by Value Set ID
return fhirClient
.operation()
.onInstance(valueSet.getId())
.named("$expand")
.withParameters(expansionParameters)
.returnResourceType(ValueSet.class)
.execute();
}

private AdditionalRequestHeadersInterceptor getAuthInterceptor(String username, String apiKey) {
String authString = StringUtils.join("Basic ", Base64.getEncoder()
.encodeToString(StringUtils.join(username, ":", apiKey).getBytes(StandardCharsets.UTF_8)));
AdditionalRequestHeadersInterceptor authInterceptor = new AdditionalRequestHeadersInterceptor();
authInterceptor.addHeaderValue("Authorization", authString);
return authInterceptor;
}

// Strips resource and id from the authoritative source URL, these are not needed as the client constructs the URL.
// Converts http URLs to https
private String getAuthoritativeSourceBase(String authoritativeSource) {
authoritativeSource = authoritativeSource.substring(0, authoritativeSource.indexOf(Canonicals.getResourceType(authoritativeSource)));
if (authoritativeSource.startsWith("http://")) {
authoritativeSource = authoritativeSource.replaceFirst("http://", "https://");
}
return authoritativeSource;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.opencds.cqf.ruler.cr.r4.helper;

import java.util.HashMap;
import java.util.Map;

import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.ActivityDefinition;
import org.hl7.fhir.r4.model.CapabilityStatement;
import org.hl7.fhir.r4.model.ChargeItemDefinition;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CompartmentDefinition;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.EffectEvidenceSynthesis;
import org.hl7.fhir.r4.model.EventDefinition;
import org.hl7.fhir.r4.model.Evidence;
import org.hl7.fhir.r4.model.EvidenceVariable;
import org.hl7.fhir.r4.model.ExampleScenario;
import org.hl7.fhir.r4.model.GraphDefinition;
import org.hl7.fhir.r4.model.ImplementationGuide;
import org.hl7.fhir.r4.model.Library;
import org.hl7.fhir.r4.model.Measure;
import org.hl7.fhir.r4.model.MessageDefinition;
import org.hl7.fhir.r4.model.NamingSystem;
import org.hl7.fhir.r4.model.OperationDefinition;
import org.hl7.fhir.r4.model.PlanDefinition;
import org.hl7.fhir.r4.model.Questionnaire;
import org.hl7.fhir.r4.model.ResearchDefinition;
import org.hl7.fhir.r4.model.ResearchElementDefinition;
import org.hl7.fhir.r4.model.ResourceType;
import org.hl7.fhir.r4.model.RiskEvidenceSynthesis;
import org.hl7.fhir.r4.model.SearchParameter;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.StructureMap;
import org.hl7.fhir.r4.model.TerminologyCapabilities;
import org.hl7.fhir.r4.model.TestScript;
import org.hl7.fhir.r4.model.ValueSet;

import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;

public class ResourceClassMapHelper {
public static final Map<ResourceType, Class<? extends IBaseResource>> resourceTypeToClass = new HashMap<ResourceType, Class<? extends IBaseResource>>() {{
put(ResourceType.ActivityDefinition, ActivityDefinition.class);
put(ResourceType.CapabilityStatement, CapabilityStatement.class);
put(ResourceType.ChargeItemDefinition, ChargeItemDefinition.class);
put(ResourceType.CompartmentDefinition, CompartmentDefinition.class);
put(ResourceType.ConceptMap, ConceptMap.class);
put(ResourceType.EffectEvidenceSynthesis, EffectEvidenceSynthesis.class);
put(ResourceType.EventDefinition, EventDefinition.class);
put(ResourceType.Evidence, Evidence.class);
put(ResourceType.EvidenceVariable, EvidenceVariable.class);
put(ResourceType.ExampleScenario, ExampleScenario.class);
put(ResourceType.GraphDefinition, GraphDefinition.class);
put(ResourceType.ImplementationGuide, ImplementationGuide.class);
put(ResourceType.Library, Library.class);
put(ResourceType.Measure, Measure.class);
put(ResourceType.MessageDefinition, MessageDefinition.class);
put(ResourceType.NamingSystem, NamingSystem.class);
put(ResourceType.OperationDefinition, OperationDefinition.class);
put(ResourceType.PlanDefinition, PlanDefinition.class);
put(ResourceType.Questionnaire, Questionnaire.class);
put(ResourceType.ResearchDefinition, ResearchDefinition.class);
put(ResourceType.ResearchElementDefinition, ResearchElementDefinition.class);
put(ResourceType.RiskEvidenceSynthesis, RiskEvidenceSynthesis.class);
put(ResourceType.SearchParameter, SearchParameter.class);
put(ResourceType.StructureDefinition, StructureDefinition.class);
put(ResourceType.StructureMap, StructureMap.class);
put(ResourceType.TerminologyCapabilities, TerminologyCapabilities.class);
put(ResourceType.TestScript, TestScript.class);
put(ResourceType.ValueSet, ValueSet.class);
put(ResourceType.CodeSystem, CodeSystem.class);
}};

public static Class<? extends IBaseResource> getClass(String resourceType) throws UnprocessableEntityException {
ResourceType type = null;
try {
type = ResourceType.fromCode(resourceType);
} catch (FHIRException e) {
throw new UnprocessableEntityException("Could not find resource type : " + resourceType);
}
Class<? extends IBaseResource> retval = resourceTypeToClass.get(type);
if (retval == null) {
throw new UnprocessableEntityException(resourceType + " is not a valid KnowledgeArtifact resource type");
}
return retval;
}
}
Loading

0 comments on commit 8990d2a

Please sign in to comment.