Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aphl 633 require non experimental release #744

Merged
merged 15 commits into from
Oct 13, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,14 @@
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.UsageContext;
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.ArtifactAssessment.ArtifactAssessmentContentInformationType;
import org.opencds.cqf.ruler.cr.r4.CRMIReleaseExperimentalBehavior.CRMIReleaseExperimentalBehaviorCodes;
import org.opencds.cqf.ruler.cr.r4.CRMIReleaseVersionBehavior.CRMIReleaseVersionBehaviorCodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Configurable;

import ca.uhn.fhir.model.api.IQueryParameterType;
Expand All @@ -61,6 +66,7 @@
@Configurable
// TODO: This belongs in the Evaluator. Only included in Ruler at dev time for shorter cycle.
public class KnowledgeArtifactProcessor {
private Logger myLog = LoggerFactory.getLogger(KnowledgeArtifactProcessor.class);
public static final String CPG_INFERENCEEXPRESSION = "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-inferenceExpression";
public static final String CPG_ASSERTIONEXPRESSION = "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-assertionExpression";
public static final String CPG_FEATUREEXPRESSION = "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-featureExpression";
Expand Down Expand Up @@ -411,23 +417,23 @@ private void processReferencedResourceForDraft(FhirDal fhirDal, Bundle reference
}
}

private Optional<String> getReleaseVersion(String version, CodeType versionBehavior, String existingVersion) throws UnprocessableEntityException {
private Optional<String> getReleaseVersion(String version, CRMIReleaseVersionBehaviorCodes versionBehavior, String existingVersion) throws UnprocessableEntityException {
Optional<String> releaseVersion = Optional.ofNullable(null);
// If no version exists use the version argument provided
if (existingVersion == null || existingVersion.isEmpty() || existingVersion.isBlank()) {
return Optional.ofNullable(version);
}
String replaceDraftInExisting = existingVersion.replace("-draft","");

if (versionBehavior.getCode().equals("default")) {
if (CRMIReleaseVersionBehaviorCodes.DEFAULT == versionBehavior) {
if(replaceDraftInExisting != null && !replaceDraftInExisting.isEmpty()){
releaseVersion = Optional.of(replaceDraftInExisting);
} else {
releaseVersion = Optional.ofNullable(version);
}
} else if (versionBehavior.getCode().equals("force")) {
} else if (CRMIReleaseVersionBehaviorCodes.FORCE == versionBehavior) {
releaseVersion = Optional.ofNullable(version);
} else if (versionBehavior.getCode().equals("check")) {
} else if (CRMIReleaseVersionBehaviorCodes.CHECK == versionBehavior) {
if (!replaceDraftInExisting.equals(version)) {
throw new UnprocessableEntityException(String.format("versionBehavior specified is 'check' and the version provided ('%s') does not match the version currently specified on the root artifact ('%s').",version,existingVersion));
}
Expand All @@ -454,12 +460,12 @@ private Optional<String> getReleaseVersion(String version, CodeType versionBehav
* Links and references between Bundle resources are updated to point to
* the new versions.
*/
public Bundle createReleaseBundle(IdType idType, String version, CodeType versionBehavior, String releaseLabel, boolean latestFromTxServer, FhirDal fhirDal) throws UnprocessableEntityException, ResourceNotFoundException, PreconditionFailedException {
public Bundle createReleaseBundle(IdType idType, String releaseLabel, String version, CRMIReleaseVersionBehaviorCodes versionBehavior, boolean latestFromTxServer, CRMIReleaseExperimentalBehaviorCodes experimentalBehavior, FhirDal fhirDal) throws UnprocessableEntityException, ResourceNotFoundException, PreconditionFailedException {
// TODO: This check is to avoid partial releases and should be removed once the argument is supported.
if (latestFromTxServer) {
throw new NotImplementedOperationException("Support for 'latestFromTxServer' is not yet implemented.");
}
checkReleaseVersion(version,versionBehavior);
checkReleaseVersion(version, versionBehavior);
MetadataResource rootArtifact = (MetadataResource) fhirDal.read(idType);
KnowledgeArtifactAdapter<MetadataResource> rootArtifactAdapter = new KnowledgeArtifactAdapter<>(rootArtifact);
Date currentApprovalDate = rootArtifactAdapter.getApprovalDate();
Expand All @@ -470,7 +476,11 @@ public Bundle createReleaseBundle(IdType idType, String version, CodeType versio
String releaseVersion = getReleaseVersion(version, versionBehavior, existingVersion)
.orElseThrow(() -> new UnprocessableEntityException("Could not resolve a version for the root artifact."));
Period rootEffectivePeriod = rootArtifactAdapter.getEffectivePeriod();
List<MetadataResource> releasedResources = internalRelease(rootArtifactAdapter, releaseVersion, rootEffectivePeriod, versionBehavior, latestFromTxServer, fhirDal);
// if the root artifact is experimental then we don't need to check for experimental children
if (rootArtifact.getExperimental()) {
experimentalBehavior = CRMIReleaseExperimentalBehaviorCodes.NONE;
}
List<MetadataResource> releasedResources = internalRelease(rootArtifactAdapter, releaseVersion, rootEffectivePeriod, versionBehavior, latestFromTxServer, experimentalBehavior, fhirDal);
if (releaseLabel != null) {
Extension releaseLabelExtension = rootArtifact.getExtensionByUrl(releaseLabel);
if (releaseLabelExtension == null) {
Expand Down Expand Up @@ -575,21 +585,18 @@ public Bundle createReleaseBundle(IdType idType, String version, CodeType versio
});
return transactionBundle;
}
private void checkReleaseVersion(String version,CodeType versionBehavior) throws UnprocessableEntityException {
if (versionBehavior == null || versionBehavior.getCode() == null || versionBehavior.getCode().isEmpty()) {
private void checkReleaseVersion(String version,CRMIReleaseVersionBehaviorCodes versionBehavior) throws UnprocessableEntityException {
if (CRMIReleaseVersionBehaviorCodes.NULL == versionBehavior) {
throw new UnprocessableEntityException("'versionBehavior' must be provided as an argument to the $release operation. Valid values are 'default', 'check', 'force'.");
}

if (!versionBehavior.getCode().equals("default") && !versionBehavior.getCode().equals("check") && !versionBehavior.getCode().equals("force")) {
throw new UnprocessableEntityException(String.format("'%s' is not a valid versionBehavior. Valid values are 'default', 'check', 'force'", versionBehavior.getCode()));
}
checkVersionValidSemver(version);
}
private void checkReleasePreconditions(MetadataResource artifact, Date approvalDate) throws PreconditionFailedException {
if (artifact == null) {
throw new ResourceNotFoundException("Resource not found.");
}
if(!artifact.getStatus().equals(Enumerations.PublicationStatus.DRAFT)){

if (Enumerations.PublicationStatus.DRAFT != artifact.getStatus()) {
throw new PreconditionFailedException(String.format("Resource with ID: '%s' does not have a status of 'draft'.", artifact.getIdElement().getIdPart()));
}
if (approvalDate == null) {
Expand All @@ -601,7 +608,7 @@ private void checkReleasePreconditions(MetadataResource artifact, Date approvalD
}
}
private List<MetadataResource> internalRelease(KnowledgeArtifactAdapter<MetadataResource> artifactAdapter, String version, Period rootEffectivePeriod,
CodeType versionBehavior, boolean latestFromTxServer, FhirDal fhirDal) throws NotImplementedOperationException, ResourceNotFoundException {
CRMIReleaseVersionBehaviorCodes versionBehavior, boolean latestFromTxServer, CRMIReleaseExperimentalBehaviorCodes experimentalBehavior, FhirDal fhirDal) throws NotImplementedOperationException, ResourceNotFoundException {
List<MetadataResource> resourcesToUpdate = new ArrayList<MetadataResource>();

// Step 1: Update the Date and the version
Expand Down Expand Up @@ -643,7 +650,10 @@ private List<MetadataResource> internalRelease(KnowledgeArtifactAdapter<Metadata
artifactAdapter.resource.getUrl()))
);
KnowledgeArtifactAdapter<MetadataResource> searchResultAdapter = new KnowledgeArtifactAdapter<>(referencedResource);
resourcesToUpdate.addAll(internalRelease(searchResultAdapter, version, rootEffectivePeriod, versionBehavior, latestFromTxServer, fhirDal));
if (CRMIReleaseExperimentalBehaviorCodes.NULL != experimentalBehavior && CRMIReleaseExperimentalBehaviorCodes.NONE != experimentalBehavior) {
checkNonExperimental(referencedResource, experimentalBehavior, fhirDal);
}
resourcesToUpdate.addAll(internalRelease(searchResultAdapter, version, rootEffectivePeriod, versionBehavior, latestFromTxServer, experimentalBehavior, fhirDal));
}
}
}
Expand Down Expand Up @@ -694,6 +704,29 @@ private Optional<MetadataResource> checkIfReferenceInList(RelatedArtifact artifa
}
return updatedReference;
}
private void checkNonExperimental(MetadataResource resource, CRMIReleaseExperimentalBehaviorCodes experimentalBehavior, FhirDal fhirDal) throws UnprocessableEntityException {
String nonExperimentalError = String.format("Root artifact is not Experimental, but references an Experimental resource with URL '%s'.",
resource.getUrl());
if (CRMIReleaseExperimentalBehaviorCodes.WARN == experimentalBehavior && resource.getExperimental()) {
myLog.warn(nonExperimentalError);
} else if (CRMIReleaseExperimentalBehaviorCodes.ERROR == experimentalBehavior && resource.getExperimental()) {
throw new UnprocessableEntityException(nonExperimentalError);
}
// for ValueSets need to check recursively if any chldren are experimental
// since we don't own these
if (resource.getResourceType().equals(ResourceType.ValueSet)) {
ValueSet valueSet = (ValueSet) resource;
List<CanonicalType> valueSets = valueSet
.getCompose()
.getInclude()
.stream().flatMap(include -> include.getValueSet().stream())
.collect(Collectors.toList());
for (CanonicalType value: valueSets) {
KnowledgeArtifactAdapter.findLatestVersion(searchResourceByUrl(value.getValueAsString(), fhirDal))
.ifPresent(childVs -> checkNonExperimental(childVs, experimentalBehavior, fhirDal));
}
}
}
/* $package */
public Bundle createPackageBundle(IdType id, FhirDal fhirDal, List<String> capability, List<String> include, List<CanonicalType> canonicalVersion, List<CanonicalType> checkCanonicalVersion, List<CanonicalType> forceCanonicalVersion, Integer count, Integer offset, Endpoint contentEndpoint, Endpoint terminologyEndpoint, Boolean packageOnly) throws NotImplementedOperationException, UnprocessableEntityException {
if (contentEndpoint != null || terminologyEndpoint != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import org.hl7.fhir.r4.model.Resource;
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.springframework.beans.factory.annotation.Autowired;

Expand Down Expand Up @@ -149,12 +151,27 @@ public Bundle releaseOperation(
@IdParam IdType theId,
@OperationParam(name = "version") String version,
@OperationParam(name = "versionBehavior") CodeType versionBehavior,
@OperationParam(name = "releaseLabel") String releaseLabel,
@OperationParam(name = "latestFromTxServer", typeName = "Boolean") IPrimitiveType<Boolean> latestFromTxServer)
@OperationParam(name = "latestFromTxServer", typeName = "Boolean") IPrimitiveType<Boolean> latestFromTxServer,
@OperationParam(name = "requireNonExperimental") CodeType requireNonExpermimental,
@OperationParam(name = "releaseLabel") String releaseLabel)
throws FHIRException {

CRMIReleaseVersionBehaviorCodes versionBehaviorCode;
CRMIReleaseExperimentalBehaviorCodes experimentalBehaviorCode;
try {
versionBehaviorCode = versionBehavior == null ? CRMIReleaseVersionBehaviorCodes.NULL : CRMIReleaseVersionBehaviorCodes.fromCode(versionBehavior.getCode());
experimentalBehaviorCode = requireNonExpermimental == null ? CRMIReleaseExperimentalBehaviorCodes.NULL : CRMIReleaseExperimentalBehaviorCodes.fromCode(requireNonExpermimental.getCode());
} catch (FHIRException e) {
throw new UnprocessableEntityException(e.getMessage());
}
FhirDal fhirDal = this.fhirDalFactory.create(requestDetails);
return transaction(this.artifactProcessor.createReleaseBundle(theId, version, versionBehavior, releaseLabel, latestFromTxServer != null && latestFromTxServer.getValue(), fhirDal));
return transaction(this.artifactProcessor.createReleaseBundle(
theId,
releaseLabel,
version,
versionBehaviorCode,
latestFromTxServer != null && latestFromTxServer.getValue(),
experimentalBehaviorCode,
fhirDal));
}

@Operation(name = "$crmi.package", idempotent = true, global = true, type = MetadataResource.class)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package org.opencds.cqf.ruler.cr.r4;

import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4.model.Base;
import org.hl7.fhir.r4.model.Configuration;
import org.hl7.fhir.r4.model.EnumFactory;
import org.hl7.fhir.r4.model.Enumeration;
import org.hl7.fhir.r4.model.PrimitiveType;

public class CRMIReleaseExperimentalBehavior {
public enum CRMIReleaseExperimentalBehaviorCodes {
/**
* The repository should throw an error if a specification which is not Experimental references Experimental components.
*/
ERROR,
/**
* The repository should warn if a specification which is not Experimental references Experimental components.
*/
WARN,
/**
* The repository does not need to consider the state of Experimental.
*/
NONE,
/**
* added to help the parsers with the generic types
*/
NULL;

public static CRMIReleaseExperimentalBehaviorCodes fromCode(String codeString) throws FHIRException {
if (codeString == null || "".equals(codeString))
return null;
if ("error".equals(codeString))
return ERROR;
if ("warn".equals(codeString))
return WARN;
if ("none".equals(codeString))
return NONE;
if (Configuration.isAcceptInvalidEnums())
return null;
else
throw new FHIRException("Unknown CRMIReleaseExperimentalBehaviorCode '" + codeString + "'");
}

public String toCode() {
switch (this) {
case ERROR:
return "error";
case WARN:
return "warn";
case NONE:
return "none";
case NULL:
return null;
default:
return "?";
}
}

public String getSystem() {
switch (this) {
case ERROR:
return "http://hl7.org/fhir/uv/crmi/CodeSystem/crmi-release-experimental-behavior-codes";
case WARN:
return "http://hl7.org/fhir/uv/crmi/CodeSystem/crmi-release-experimental-behavior-codes";
case NONE:
return "http://hl7.org/fhir/uv/crmi/CodeSystem/crmi-release-experimental-behavior-codes";
case NULL:
return null;
default:
return "?";
}
}

public String getDefinition() {
switch (this) {
case ERROR:
return "The repository should throw an error if a specification which is not Experimental references Experimental components.";
case WARN:
return "The repository should warn if a specification which is not Experimental references Experimental components.";
case NONE:
return "The repository does not need to consider the state of Experimental.";
case NULL:
return null;
default:
return "?";
}
}

public String getDisplay() {
switch (this) {
case ERROR:
return "Error";
case WARN:
return "Warn";
case NONE:
return "None";
case NULL:
return null;
default:
return "?";
}
}

}

public static class CRMIReleaseExperimentalBehaviorCodesEnumFactory implements EnumFactory<CRMIReleaseExperimentalBehaviorCodes> {
public CRMIReleaseExperimentalBehaviorCodes fromCode(String codeString) throws IllegalArgumentException {
if (codeString == null || "".equals(codeString))
if (codeString == null || "".equals(codeString))
return null;
if ("error".equals(codeString))
return CRMIReleaseExperimentalBehaviorCodes.ERROR;
if ("warn".equals(codeString))
return CRMIReleaseExperimentalBehaviorCodes.WARN;
if ("none".equals(codeString))
return CRMIReleaseExperimentalBehaviorCodes.NONE;
throw new IllegalArgumentException("Unknown CRMIReleaseExperimentalBehaviorCodes code '" + codeString + "'");
}

public Enumeration<CRMIReleaseExperimentalBehaviorCodes> fromType(Base code) throws FHIRException {
if (code == null)
return null;
if (code.isEmpty())
return new Enumeration<CRMIReleaseExperimentalBehaviorCodes>(this);
String codeString = ((PrimitiveType) code).asStringValue();
if (codeString == null || "".equals(codeString))
return null;
if ("error".equals(codeString))
return new Enumeration<CRMIReleaseExperimentalBehaviorCodes>(this, CRMIReleaseExperimentalBehaviorCodes.ERROR);
if ("warn".equals(codeString))
return new Enumeration<CRMIReleaseExperimentalBehaviorCodes>(this, CRMIReleaseExperimentalBehaviorCodes.WARN);
if ("none".equals(codeString))
return new Enumeration<CRMIReleaseExperimentalBehaviorCodes>(this, CRMIReleaseExperimentalBehaviorCodes.NONE);
throw new FHIRException("Unknown CRMIReleaseExperimentalBehaviorCodes code '" + codeString + "'");
}

public String toCode(CRMIReleaseExperimentalBehaviorCodes code) {
if (code == CRMIReleaseExperimentalBehaviorCodes.ERROR)
return "error";
if (code == CRMIReleaseExperimentalBehaviorCodes.WARN)
return "warn";
if (code == CRMIReleaseExperimentalBehaviorCodes.NONE)
return "none";
return "?";
}

public String toSystem(CRMIReleaseExperimentalBehaviorCodes code) {
return code.getSystem();
}
}


}
Loading