Skip to content

Commit

Permalink
Merge pull request #870 from HL7/2024-03-gg-auto-oid-root
Browse files Browse the repository at this point in the history
2024 03 gg auto oid root
  • Loading branch information
grahamegrieve authored Mar 19, 2024
2 parents 8a53336 + b60f730 commit 91db306
Show file tree
Hide file tree
Showing 11 changed files with 1,045 additions and 408 deletions.
9 changes: 9 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
* Loader: Add support for auto-oid-root
* Cross-Version Module: Further development
* Validator: CodeSystem property validation improvements
* Validator: More valueset validation improvements after real world testing
* Validator: Fix processing of NotSelectable filters using in | not-in
* Validator: Fix wrong collection type checking FHIRPath subsetOf parameter type
* Generator: Put jurisdiction in NPM package file
* Renderer: Rendering improvements for xver IG
* Renderer: improved markdown support for [[[type.path]]]
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,8 @@ public void setNoSushi(boolean noSushi) {
private List<String> viewDefinitions = new ArrayList<>();
private int validationLogTime = 0;
private long maxMemory = 0;
private String oidRoot;
private IniFile oidIni;

long last = System.currentTimeMillis();
private List<String> unknownParams = new ArrayList<>();
Expand Down Expand Up @@ -2945,7 +2947,25 @@ else if (p.getValue().equals("show-reference-messages"))
fetcher.setReport(true);
}
break;
default:
case "auto-oid-root":
oidRoot = p.getValue();
if (!Utilities.isValidOID(oidRoot)) {
throw new Error("Invalid oid found in assign-missing-oids-root: "+oidRoot);
}
oidIni = new IniFile(Utilities.path(Utilities.getDirectoryForFile(igName), "oids.ini"));
if (!oidIni.hasSection("Documentation")) {
oidIni.setStringProperty("Documentation", "information1", "This file stores the OID assignments for resources defined in this IG.", null);
oidIni.setStringProperty("Documentation", "information2", "It must be added to git and committed when resources are added or their id is changed", null);
oidIni.setStringProperty("Documentation", "information3", "You should not generally need to edit this file, but if you do:", null);
oidIni.setStringProperty("Documentation", "information4", " (a) you can change the id of a resource (left side) if you change it's actual id in your source, to maintain OID consistency", null);
oidIni.setStringProperty("Documentation", "information5", " (b) you can change the oid of the resource to an OID you assign manually. If you really know what you're doing with OIDs", null);
oidIni.setStringProperty("Documentation", "information6", "There is never a reason to edit anything else", null);
oidIni.save();
}
if (!hasOid(sourceIg.getIdentifier())) {
sourceIg.getIdentifier().add(new Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:oid:"+oidRoot));
}
default:
if (!template.isParameter(pc)) {
unknownParams.add(pc+"="+p.getValue());
}
Expand Down Expand Up @@ -4409,6 +4429,7 @@ private boolean load() throws Exception {
rc.setParser(getTypeLoader(version));
rc.addLink(KnownLinkType.SELF, targetOutput);
rc.setFixedFormat(fixedFormat);
module.defineTypeMap(rc.getTypeMap());
if (publishedIg.hasJurisdiction()) {
Locale locale = null;
try {
Expand Down Expand Up @@ -6266,6 +6287,11 @@ private void load(String type) throws Exception {
if (new AdjunctFileLoader(binaryPaths, cql).replaceAttachments2(f, r)) {
altered = true;
}
if (oidRoot != null && !hasOid(bc.getIdentifier())) {
String oid = getOid(r.fhirType(), bc.getIdBase());
bc.getIdentifier().add(new Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:oid:"+oid));
altered = true;
}
if (altered) {
if (Utilities.existsInList(r.fhirType(), "GraphDefinition")) {
f.getErrors().add(new ValidationMessage(Source.Publisher, IssueType.PROCESSING, bc.fhirType()+".where(url = '"+bc.getUrl()+"')",
Expand Down Expand Up @@ -6315,6 +6341,75 @@ private void load(String type) throws Exception {
}
}

private String getOid(String type, String id) {
String ot = oidNodeForType(type);
String oid = oidIni.getStringProperty(type, id);
if (oid != null) {
return oid;
}
Integer keyR = oidIni.getIntegerProperty("Key", type);
int key = keyR == null ? 0 : keyR.intValue();
key++;
oid = oidRoot+"."+ot+"."+key;
oidIni.setIntegerProperty("Key", type, key, null);
oidIni.setStringProperty(type, id, oid, null);
oidIni.save();
return oid;
}

private String oidNodeForType(String type) {
switch (type) {
case "ActivityDefinition" : return "11";
case "ActorDefinition" : return "12";
case "CapabilityStatement" : return "13";
case "ChargeItemDefinition" : return "14";
case "Citation" : return "15";
case "CodeSystem" : return "16";
case "CompartmentDefinition" : return "17";
case "ConceptMap" : return "18";
case "ConditionDefinition" : return "19";
case "EffectEvidenceSynthesis" : return "20";
case "EventDefinition" : return "21";
case "Evidence" : return "22";
case "EvidenceReport" : return "23";
case "EvidenceVariable" : return "24";
case "ExampleScenario" : return "25";
case "GraphDefinition" : return "26";
case "ImplementationGuide" : return "27";
case "Library" : return "28";
case "Measure" : return "29";
case "MessageDefinition" : return "30";
case "NamingSystem" : return "31";
case "ObservationDefinition" : return "32";
case "OperationDefinition" : return "33";
case "PlanDefinition" : return "34";
case "Questionnaire" : return "35";
case "Requirements" : return "36";
case "ResearchDefinition" : return "37";
case "ResearchElementDefinition" : return "38";
case "RiskEvidenceSynthesis" : return "39";
case "SearchParameter" : return "40";
case "SpecimenDefinition" : return "41";
case "StructureDefinition" : return "42";
case "StructureMap" : return "43";
case "SubscriptionTopic" : return "44";
case "TerminologyCapabilities" : return "45";
case "TestPlan" : return "46";
case "TestScript" : return "47";
case "ValueSet" : return "48";
default: return "10";
}
}

private boolean hasOid(List<Identifier> identifiers) {
for (Identifier id : identifiers) {
if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:")) {
return true;
}
}
return false;
}

private boolean canonicalUrlIsOk(CanonicalResource bc) {
if (bc.getUrl().equals(Utilities.pathURL(igpkp.getCanonical(), bc.fhirType(), bc.getId()))) {
return true;
Expand Down Expand Up @@ -6869,19 +6964,57 @@ private void checkURLsUnique() {
}

private void checkOIDsUnique() {
if (oidRoot != null) {
try {
JsonObject json = org.hl7.fhir.utilities.json.parser.JsonParser.parseObjectFromUrl("https://raw.githubusercontent.com/FHIR/ig-registry/master/oid-assignments.json");
JsonObject assignments = json.getJsonObject("assignments");
String ig = null;
String oid = null;
if (assignments.has(oidRoot)) {
ig = assignments.getJsonObject(oidRoot).asString("id");
}
for (JsonProperty p : assignments.getProperties()) {
if (p.getValue().isJsonObject() && sourceIg.getPackageId().equals(p.getValue().asJsonObject().asString("id"))) {
oid = p.getName();
}
}
if (oid == null && ig == null) {
errors.add(new ValidationMessage(Source.Publisher, IssueType.BUSINESSRULE, "ImplementationGuide", "The assigned auto-oid-root value '"+oidRoot+"' is not registered in https://github.com/FHIR/ig-registry/blob/master/oid-assignments.json so isn't known to be valid", IssueSeverity.WARNING));
} else if (oid != null && !oid.equals(oidRoot)) {
throw new FHIRException("The assigned auto-oid-root value '"+oidRoot+"' does not match the value of '"+oidRoot+"' registered in https://github.com/FHIR/ig-registry/blob/master/oid-assignments.json so cannot proceed");
} else if (ig != null && !ig.equals(sourceIg.getPackageId())) {
throw new FHIRException("The assigned auto-oid-root value '"+oidRoot+"' is already registered to the IG '"+ig+"' in https://github.com/FHIR/ig-registry/blob/master/oid-assignments.json so cannot proceed");
}
} catch (Exception e) {
errors.add(new ValidationMessage(Source.Publisher, IssueType.BUSINESSRULE, "ImplementationGuide", "Unable to check auto-oid-root because "+e.getMessage(), IssueSeverity.INFORMATION));
}
}
String oidHint = " (OIDs are easy to assign - see https://build.fhir.org/ig/FHIR/fhir-tools-ig/CodeSystem-ig-parameters.html#ig-parameters-auto-oid-root)";
Map<String, FetchedResource> oidMap = new HashMap<>();
for (FetchedFile f : fileList) {
for (FetchedResource r : f.getResources()) {
if (r.getResource() != null && r.getResource() instanceof CanonicalResource) {
List<String> oids = loadOids(((CanonicalResource) r.getResource()));
for (String oid : oids) {
if (oidMap.containsKey(oid)) {
FetchedResource rs = oidMap.get(oid);
FetchedFile fs = findFileForResource(rs);
f.getErrors().add(new ValidationMessage(Source.Publisher, IssueType.BUSINESSRULE, "Resource", "The OID '"+oid+"' has already been used by "+rs.getId()+" in "+fs.getName(), IssueSeverity.ERROR));
fs.getErrors().add(new ValidationMessage(Source.Publisher, IssueType.BUSINESSRULE, "Resource", "The OID '"+oid+"' is also used by "+r.getId()+" in "+f.getName(), IssueSeverity.ERROR));
if (oids.isEmpty()) {
if (Utilities.existsInList(r.getResource().fhirType(), "CodeSystem", "ValueSet")) {
if (forHL7orFHIR()) {
f.getErrors().add(new ValidationMessage(Source.Publisher, IssueType.BUSINESSRULE, "Resource", "This resource must have an OID assigned to cater for possible e.g. CDA usage"+oidHint, IssueSeverity.ERROR));
} else {
f.getErrors().add(new ValidationMessage(Source.Publisher, IssueType.BUSINESSRULE, "Resource", "This resource should have an OID assigned to cater for possible e.g. CDA usage"+oidHint, IssueSeverity.WARNING));
}
} else {
oidMap.put(oid, r);
f.getErrors().add(new ValidationMessage(Source.Publisher, IssueType.BUSINESSRULE, "Resource", "This resource could usefully have an OID assigned"+oidHint, IssueSeverity.INFORMATION));
}
} else {
for (String oid : oids) {
if (oidMap.containsKey(oid)) {
FetchedResource rs = oidMap.get(oid);
FetchedFile fs = findFileForResource(rs);
f.getErrors().add(new ValidationMessage(Source.Publisher, IssueType.BUSINESSRULE, "Resource", "The OID '"+oid+"' has already been used by "+rs.getId()+" in "+fs.getName(), IssueSeverity.ERROR));
fs.getErrors().add(new ValidationMessage(Source.Publisher, IssueType.BUSINESSRULE, "Resource", "The OID '"+oid+"' is also used by "+r.getId()+" in "+f.getName(), IssueSeverity.ERROR));
} else {
oidMap.put(oid, r);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import org.hl7.fhir.igtools.publisher.modules.xver.ElementDefinitionLink;
import org.hl7.fhir.igtools.publisher.modules.xver.ElementDefinitionPair;
import org.hl7.fhir.igtools.publisher.modules.xver.SourcedElementDefinition;
import org.hl7.fhir.igtools.publisher.modules.xver.SourcedElementDefinitionSorter;
import org.hl7.fhir.igtools.publisher.modules.xver.StructureDefinitionColumn;
import org.hl7.fhir.igtools.publisher.modules.xver.XVerAnalysisEngine;
import org.hl7.fhir.igtools.publisher.modules.xver.XVerAnalysisEngine.MakeLinkMode;
Expand All @@ -32,10 +31,10 @@
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.ConceptMap;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
Expand All @@ -48,11 +47,11 @@
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.ZipGenerator;
import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
Expand Down Expand Up @@ -102,15 +101,16 @@ public boolean preProcess(String path) {
}

engine.logProgress("Generating extensions");
Utilities.createDirectory(Utilities.path(path, "temp", "xver", "extensions"));
for (StructureDefinition sd : engine.getExtensions()) {
new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(path, "input", "extensions", "StructureDefinition-"+sd.getId()+".json")), sd);
new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(path, "temp", "xver", "extensions", "StructureDefinition-"+sd.getId()+".json")), sd);
genExtensionPage(path, sd);
}
for (ValueSet vs : engine.getNewValueSets().values()) {
new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(path, "input", "extensions", "ValueSet-"+vs.getId()+".json")), vs);
new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(path, "temp", "xver", "extensions", "ValueSet-"+vs.getId()+".json")), vs);
}
for (CodeSystem cs : engine.getNewCodeSystems().values()) {
new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(path, "input", "extensions", "CodeSystem-"+cs.getId()+".json")), cs);
new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(path, "temp", "xver", "extensions", "CodeSystem-"+cs.getId()+".json")), cs);
}
genSummaryPages(path);
genZips(path);
Expand Down Expand Up @@ -161,6 +161,10 @@ private void cleanup(String path) throws IOException {
}

private void genExtensionPage(String path, StructureDefinition sd) throws IOException {
String fn = Utilities.path(path, "temp", "xver-qa", "StructureDefinition-"+sd.getId()+".html");
if (new File(fn).exists()) {
throw new FHIRException("Duplicate id: "+sd.getId());
}
XhtmlNode body = new XhtmlNode(NodeType.Element, "div");
body.h1().tx(sd.getTitle());
var tbl = body.table("grid");
Expand Down Expand Up @@ -200,7 +204,7 @@ private void genExtensionPage(String path, StructureDefinition sd) throws IOExce
body.hr();

body.pre().tx(new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(sd));
TextFile.stringToFile(new XhtmlComposer(false, false).compose(wrapPage(body, sd.getName())), Utilities.path(path, "temp", "xver-qa", "StructureDefinition-"+sd.getId()+".html"));
TextFile.stringToFile(new XhtmlComposer(false, false).compose(wrapPage(body, sd.getName())), fn);
}

private void renderVersionRange(XhtmlNode x, Extension ext) {
Expand Down Expand Up @@ -713,4 +717,10 @@ public boolean prependLinks() {
public String getLinkForUrl(String corePath, String s) {
throw new NotImplementedError();
}

@Override
public void defineTypeMap(Map<String, String> typeMap) {
typeMap.putAll(engine.getTypeMap());

}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.hl7.fhir.igtools.publisher.modules;

import java.util.Map;

// modules are triggered from ig.ini
public interface IPublisherModule {

Expand All @@ -17,4 +19,7 @@ public interface IPublisherModule {
// should be documented clearly in the console output
// all the actions will be taken on the files in the path (which is the root of the IG that contains ig.ini)
public boolean preProcess(String path);

// if the module lnows of type aliases, define them for the rendering system
public void defineTypeMap(Map<String, String> typeMap);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.hl7.fhir.igtools.publisher.modules;

import java.util.Map;

public class NullModule implements IPublisherModule {

public boolean preProcess(String path) {
Expand All @@ -22,4 +24,10 @@ public boolean useRoutine(String name) {
return false;
}

@Override
public void defineTypeMap(Map<String, String> typeMap) {
// TODO Auto-generated method stub

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import java.util.HashSet;
import java.util.Set;

import org.hl7.fhir.igtools.publisher.modules.xver.SourcedElementDefinition.ElementValidState;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.StructureDefinition;
Expand All @@ -13,7 +12,7 @@

public class SourcedElementDefinition {
public enum ElementValidState {
FULL_VALID, CARDINALITY, NEW_TYPES, NEW_TARGETS, NOT_VALID, CODES
FULL_VALID, CARDINALITY, NEW_TYPES, NEW_TARGETS, NOT_VALID, CODES, REMOVED_TYPES, PARENT
}

private StructureDefinition sd;
Expand Down Expand Up @@ -51,7 +50,11 @@ public ElementDefinition getEd() {
}

public String getStatusReason() {
return statusReasons.length() == 0 ? "No Change" : statusReasons.toString();
if (statusReasons.length() == 0) {
return validState == ElementValidState.FULL_VALID ? "Made up reason" : "No Change";
} else {
return statusReasons.toString();
}
}

void addStatusReason(String statusReason) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ValueSet;

Expand Down
Loading

0 comments on commit 91db306

Please sign in to comment.