diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java index 62029741d..86b245bbc 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java @@ -11,7 +11,9 @@ import org.hl7.fhir.r5.conformance.ElementRedirection; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent; +import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules; import org.hl7.fhir.r5.model.OperationOutcome.IssueType; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent; @@ -193,9 +195,16 @@ public class ProfilePathProcessor { private void debugProcessPathsIteration(ProfilePathProcessorState cursors, String currentBasePath) { if (profileUtilities.isDebug()) { - System.out.println(getDebugIndent() + " - " + currentBasePath + ": base = " + cursors.baseCursor + " (" + profileUtilities.descED(cursors.base.getElement(), cursors.baseCursor) + ") to " + getBaseLimit() + " (" + profileUtilities.descED(cursors.base.getElement(), getBaseLimit()) + "), diff = " + cursors.diffCursor + " (" + profileUtilities.descED(getDifferential().getElement(), cursors.diffCursor) + ") to " + getDiffLimit() + " (" + profileUtilities.descED(getDifferential().getElement(), getDiffLimit()) + ") " + + System.out.println(getDebugIndent() + " - " + currentBasePath + ": "+ + "base = " + cursors.baseCursor + " (" + profileUtilities.descED(cursors.base.getElement(), cursors.baseCursor) + ") to " + getBaseLimit() +" (" + profileUtilities.descED(cursors.base.getElement(), getBaseLimit()) + "), "+ + "diff = " + cursors.diffCursor + " (" + profileUtilities.descED(getDifferential().getElement(), cursors.diffCursor) + ") to " + getDiffLimit() + " (" + profileUtilities.descED(getDifferential().getElement(), getDiffLimit()) + ") " + "(slicingDone = " + getSlicing().isDone() + ") (diffpath= " + (getDifferential().getElement().size() > cursors.diffCursor ? getDifferential().getElement().get(cursors.diffCursor).getPath() : "n/a") + ")"); + String path = cursors.diffCursor >=0 && cursors.diffCursor < getDifferential().getElement().size() ? getDifferential().getElement().get(cursors.diffCursor).present() : null; +// if (path != null && path.contains(":populationBasis")) { +// System.out.println("!"); +// } } + } private void debugProcessPathsEntry(ProfilePathProcessorState cursors) { @@ -294,7 +303,7 @@ public class ProfilePathProcessor { // differential - if the first one in the list has a name, we'll process it. Else we'll treat it as the base definition of the slice. if (!diffMatches.get(0).hasSliceName()) { - profileUtilities.updateFromDefinition(outcome, diffMatches.get(0), getProfileName(), isTrimDifferential(), getUrl(),getSourceStructureDefinition(), getDerived()); + profileUtilities.updateFromDefinition(outcome, diffMatches.get(0), getProfileName(), isTrimDifferential(), getUrl(),getSourceStructureDefinition(), getDerived(), diffPath(diffMatches.get(0))); profileUtilities.removeStatusExtensions(outcome); if (!outcome.hasContentReference() && !outcome.hasType() && outcome.getPath().contains(".")) { throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.NOT_DONE_YET)); @@ -354,6 +363,10 @@ public class ProfilePathProcessor { cursors.diffCursor = newDiffLimit + 1; } + private String diffPath(ElementDefinition ed) { + return "StructureDefinition.differential.element["+differential.getElement().indexOf(ed)+"]"; + } + private String slicingSummary(ElementDefinitionSlicingComponent s) { return s.toString(); } @@ -635,6 +648,11 @@ public class ProfilePathProcessor { res = outcome; profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl()); if (diffMatches.get(0).hasSliceName()) { + template = currentBase.copy(); + template = profileUtilities.updateURLs(getUrl(), getWebUrl(), template); + template.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), template.getPath(), getRedirector(), getContextPathSource())); + + checkToSeeIfSlicingExists(diffMatches.get(0), template); outcome.setSliceName(diffMatches.get(0).getSliceName()); if (!diffMatches.get(0).hasMin() && (diffMatches.size() > 1 || getSlicing().getElementDefinition()== null || getSlicing().getElementDefinition().getSlicing().getRules() != ElementDefinition.SlicingRules.CLOSED) && !currentBase.hasSliceName()) { if (!currentBasePath.endsWith("xtension.value[x]")) { // hack work around for problems with snapshots in official releases @@ -642,7 +660,7 @@ public class ProfilePathProcessor { } } } - profileUtilities.updateFromDefinition(outcome, diffMatches.get(0), getProfileName(), isTrimDifferential(), getUrl(), getSourceStructureDefinition(), getDerived()); + profileUtilities.updateFromDefinition(outcome, diffMatches.get(0), getProfileName(), isTrimDifferential(), getUrl(), getSourceStructureDefinition(), getDerived(), diffPath(diffMatches.get(0))); profileUtilities.removeStatusExtensions(outcome); // if (outcome.getPath().endsWith("[x]") && outcome.getType().size() == 1 && !outcome.getType().get(0).getCode().equals("*") && !diffMatches.get(0).hasSlicing()) // if the base profile allows multiple types, but the profile only allows one, rename it // outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length()-3)+Utilities.capitalize(outcome.getType().get(0).getCode())); @@ -743,6 +761,66 @@ public class ProfilePathProcessor { return res; } + private void checkToSeeIfSlicingExists(ElementDefinition ed, ElementDefinition template) { + List ss = result.getElement(); + int i = ss.size() -1; + ElementDefinition m = null; + + System.out.println("check for "+ed.getPath()); + while (i >= 0) { + ElementDefinition t = ss.get(i); + System.out.println(""+i+": "+t.getPath()); + if (pathsMatch(t.getPath(), ed.getPath())) { + if (t.hasSlicing() || t.hasSliceName() || t.getPath().endsWith("[x]")) { + m = t; + break; + } + } + if (t.getPath().length() < ed.getPath().length()) { + break; + } + i--; + } + if (m == null) { + if (template.getPath().endsWith(".extension")) { + template.getSlicing().setRules(SlicingRules.OPEN); + template.getSlicing().setOrdered(false); + template.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url"); + result.getElement().add(template); + } else { + System.err.println("checkToSeeIfSlicingExists: "+ed.getPath()+":"+ed.getSliceName()+" is not sliced"); + } + m = ed; + } + } + + private boolean pathsMatch(String path1, String path2) { + String[] p1 = path1.split("\\."); + String[] p2 = path2.split("\\."); + if (p1.length != p2.length) { + return false; + } + for (int i = 0; i < p1.length; i++) { + String pp1 = p1[i]; + String pp2 = p2[i]; + if (!pp1.equals(pp2)) { + if (pp1.endsWith("[x]")) { + if (!pp2.startsWith(pp1.substring(0, pp1.length()-3))) { + return false; + } + } else if (pp2.endsWith("[x]")) { + if (!pp1.startsWith(pp2.substring(0, pp2.length()-3))) { + return false; + } + + } else { + return false; + } + } + } + return true; + } + private int indexOfFirstNonChild(StructureDefinitionSnapshotComponent base, ElementDefinition currentBase, int i, int baseLimit) { return baseLimit+1; } @@ -937,7 +1015,7 @@ public class ProfilePathProcessor { profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl()); if (diffMatches.get(0).hasSlicing() || !diffMatches.get(0).hasSliceName()) { profileUtilities.updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing()); - profileUtilities.updateFromDefinition(outcome, diffMatches.get(0), getProfileName(), closed, getUrl(), getSourceStructureDefinition(), getDerived()); // if there's no slice, we don't want to update the unsliced description + profileUtilities.updateFromDefinition(outcome, diffMatches.get(0), getProfileName(), closed, getUrl(), getSourceStructureDefinition(), getDerived(), diffPath(diffMatches.get(0))); // if there's no slice, we don't want to update the unsliced description profileUtilities.removeStatusExtensions(outcome); } else if (!diffMatches.get(0).hasSliceName()) { diffMatches.get(0).setUserData(profileUtilities.UD_GENERATED_IN_SNAPSHOT, outcome); // because of updateFromDefinition isn't called @@ -1075,7 +1153,7 @@ public class ProfilePathProcessor { throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); debugCheck(outcome); getResult().getElement().add(outcome); - profileUtilities.updateFromDefinition(outcome, diffItem, getProfileName(), isTrimDifferential(), getUrl(), getSourceStructureDefinition(), getDerived()); + profileUtilities.updateFromDefinition(outcome, diffItem, getProfileName(), isTrimDifferential(), getUrl(), getSourceStructureDefinition(), getDerived(), diffPath(diffItem)); profileUtilities.removeStatusExtensions(outcome); // --- LM Added this cursors.diffCursor = getDifferential().getElement().indexOf(diffItem) + 1; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java index 8840a6cdb..8501419ae 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java @@ -139,11 +139,13 @@ public class ProfileUtilities extends TranslatingUtilities { public class ElementDefinitionCounter { int countMin = 0; int countMax = 0; + int index = 0; ElementDefinition focus; Set names = new HashSet<>(); - public ElementDefinitionCounter(ElementDefinition ed) { + public ElementDefinitionCounter(ElementDefinition ed, int i) { focus = ed; + index = i; } public int checkMin() { @@ -192,6 +194,11 @@ public class ProfileUtilities extends TranslatingUtilities { public boolean checkMinMax() { return countMin <= countMax; } + + public int getIndex() { + return index; + } + } public enum MappingMergeModeOption { @@ -683,11 +690,12 @@ public class ProfileUtilities extends TranslatingUtilities { checkGroupConstraints(derived); if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { + int i = 0; for (ElementDefinition e : diff.getElement()) { if (!e.hasUserData(UD_GENERATED_IN_SNAPSHOT) && e.getPath().contains(".")) { ElementDefinition existing = getElementInCurrentContext(e.getPath(), derived.getSnapshot().getElement()); if (existing != null) { - updateFromDefinition(existing, e, profileName, false, url, base, derived); + updateFromDefinition(existing, e, profileName, false, url, base, derived, "StructureDefinition.differential.element["+i+"]"); } else { ElementDefinition outcome = updateURLs(url, webUrl, e.copy()); e.setUserData(UD_GENERATED_IN_SNAPSHOT, outcome); @@ -701,6 +709,7 @@ public class ProfileUtilities extends TranslatingUtilities { } } } + i++; } } @@ -723,6 +732,7 @@ public class ProfileUtilities extends TranslatingUtilities { CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); //Check that all differential elements have a corresponding snapshot element int ce = 0; + int i = 0; for (ElementDefinition e : diff.getElement()) { if (!e.hasUserData("diff-source")) throw new Error(context.formatMessage(I18nConstants.UNXPECTED_INTERNAL_CONDITION__NO_SOURCE_ON_DIFF_ELEMENT)); @@ -736,15 +746,16 @@ public class ProfileUtilities extends TranslatingUtilities { b.append(e.hasId() ? "id: "+e.getId() : "path: "+e.getPath()); ce++; if (e.hasId()) { - String msg = "No match found in the generated snapshot: check that the path and definitions are legal in the differential (including order)"; - messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+e.getId(), msg, ValidationMessage.IssueSeverity.ERROR)); + String msg = "No match found for "+e.getId()+" in the generated snapshot: check that the path and definitions are legal in the differential (including order)"; + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, "StructureDefinition.differential.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR)); } } + i++; } if (!Utilities.noString(b.toString())) { String msg = "The profile "+derived.getUrl()+" has "+ce+" "+Utilities.pluralize("element", ce)+" in the differential ("+b.toString()+") that don't have a matching element in the snapshot: check that the path and definitions are legal in the differential (including order)"; if (debug) { - System.out.println("Error in snapshot generation: "+msg); + System.err.println("Error in snapshot generation: "+msg); if (!debug) { System.out.println("Differential: "); for (ElementDefinition ed : derived.getDifferential().getElement()) @@ -789,10 +800,10 @@ public class ProfileUtilities extends TranslatingUtilities { tn = tn.substring(tn.lastIndexOf("/")+1); } Map slices = new HashMap<>(); - int i = 0; + i = 0; for (ElementDefinition ed : derived.getSnapshot().getElement()) { if (ed.hasSlicing()) { - slices.put(ed.getPath(), new ElementDefinitionCounter(ed)); + slices.put(ed.getPath(), new ElementDefinitionCounter(ed, i)); } else { Set toRemove = new HashSet<>(); for (String s : slices.keySet()) { @@ -809,37 +820,43 @@ public class ProfileUtilities extends TranslatingUtilities { slice.getFocus().setMin(count); } else { String msg = "The slice definition for "+slice.getFocus().getId()+" has a minimum of "+slice.getFocus().getMin()+" but the slices add up to a minimum of "+count; - messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+slice.getFocus().getId(), msg, forPublication ? ValidationMessage.IssueSeverity.ERROR : ValidationMessage.IssueSeverity.INFORMATION).setIgnorableError(true)); + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, + "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, forPublication ? ValidationMessage.IssueSeverity.ERROR : ValidationMessage.IssueSeverity.INFORMATION).setIgnorableError(true)); } } count = slice.checkMax(); if (count > -1 && repeats) { String msg = "The slice definition for "+slice.getFocus().getId()+" has a maximum of "+slice.getFocus().getMax()+" but the slices add up to a maximum of "+count+". Check that this is what is intended"; - messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+slice.getFocus().getId(), msg, ValidationMessage.IssueSeverity.INFORMATION)); + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, + "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, ValidationMessage.IssueSeverity.INFORMATION)); } if (!slice.checkMinMax()) { String msg = "The slice definition for "+slice.getFocus().getId()+" has a maximum of "+slice.getFocus().getMax()+" which is less than the minimum of "+slice.getFocus().getMin(); - messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+"#"+slice.getFocus().getId(), msg, ValidationMessage.IssueSeverity.WARNING)); + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, + "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, ValidationMessage.IssueSeverity.WARNING)); } slices.remove(s); } } if (ed.getPath().contains(".") && !ed.getPath().startsWith(tn+".")) { - throw new Error("The element "+ed.getId()+" in the profile '"+derived.getVersionedUrl()+" (["+i+"]) doesn't have the right path (should start with "+tn+"."); + throw new Error("The element "+ed.getId()+" in the profile '"+derived.getVersionedUrl()+" doesn't have the right path (should start with "+tn+"."); } if (ed.hasSliceName() && !slices.containsKey(ed.getPath())) { - String msg = "The element "+ed.getId()+" (["+i+"]) launches straight into slicing without the slicing being set up properly first"; - messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true)); + String msg = "The element "+ed.getId()+" launches straight into slicing without the slicing being set up properly first"; + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, + "StructureDefinition.snapshot.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true)); } if (ed.hasSliceName() && slices.containsKey(ed.getPath())) { if (!slices.get(ed.getPath()).count(ed, ed.getSliceName())) { String msg = "Duplicate slice name "+ed.getSliceName()+" on "+ed.getId()+" (["+i+"])"; - messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true)); + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, + "StructureDefinition.snapshot.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true)); } } i++; } + i = 0; // last, check for wrong profiles or target profiles for (ElementDefinition ed : derived.getSnapshot().getElement()) { for (TypeRefComponent t : ed.getType()) { @@ -852,7 +869,8 @@ public class ProfileUtilities extends TranslatingUtilities { } if (sd == null) { if (messages != null) { - messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), "The type of profile "+u.getValue()+" cannot be checked as the profile is not known", IssueSeverity.WARNING)); + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, + "StructureDefinition.snapshot.element["+i+"]", "The type of profile "+u.getValue()+" cannot be checked as the profile is not known", IssueSeverity.WARNING)); } } else { String wt = t.getWorkingCode(); @@ -878,6 +896,7 @@ public class ProfileUtilities extends TranslatingUtilities { } } } + i++; } } catch (Exception e) { // if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind @@ -1365,7 +1384,7 @@ public class ProfileUtilities extends TranslatingUtilities { return true; } if (tr.getWorkingCode().equals(t.getCode())) { - System.out.println("Type error: use of a simple type \""+t.getCode()+"\" wrongly constraining "+base.getPath()); + System.err.println("Type error: use of a simple type \""+t.getCode()+"\" wrongly constraining "+base.getPath()); return true; } } @@ -1773,7 +1792,7 @@ public class ProfileUtilities extends TranslatingUtilities { } if (sd == null) { if (debug) { - System.out.println("Failed to find referenced profile: " + type.getProfile()); + System.err.println("Failed to find referenced profile: " + type.getProfile()); } } @@ -1781,14 +1800,14 @@ public class ProfileUtilities extends TranslatingUtilities { if (sd == null) sd = context.fetchTypeDefinition(type.getWorkingCode()); if (sd == null) - System.out.println("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM + System.err.println("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM return sd; } protected StructureDefinition getProfileForDataType(String type) { StructureDefinition sd = context.fetchTypeDefinition(type); if (sd == null) - System.out.println("XX: failed to find profle for type: " + type); // debug GJM + System.err.println("XX: failed to find profle for type: " + type); // debug GJM return sd; } @@ -2259,7 +2278,7 @@ public class ProfileUtilities extends TranslatingUtilities { } - protected void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl, StructureDefinition srcSD, StructureDefinition derivedSrc) throws DefinitionException, FHIRException { + protected void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl, StructureDefinition srcSD, StructureDefinition derivedSrc, String path) throws DefinitionException, FHIRException { source.setUserData(UD_GENERATED_IN_SNAPSHOT, dest); // we start with a clone of the base profile ('dest') and we copy from the profile ('source') // over the top for anything the source has @@ -2690,7 +2709,7 @@ public class ProfileUtilities extends TranslatingUtilities { if (!Base.compareDeep(derived.getType(), base.getType(), false)) { if (base.hasType()) { for (TypeRefComponent ts : derived.getType()) { - checkTypeDerivation(purl, derivedSrc, base, derived, ts); + checkTypeDerivation(purl, derivedSrc, base, derived, ts, path); } } base.getType().clear(); @@ -2816,7 +2835,7 @@ public class ProfileUtilities extends TranslatingUtilities { } } - private void checkTypeDerivation(String purl, StructureDefinition srcSD, ElementDefinition base, ElementDefinition derived, TypeRefComponent ts) { + private void checkTypeDerivation(String purl, StructureDefinition srcSD, ElementDefinition base, ElementDefinition derived, TypeRefComponent ts, String path) { boolean ok = false; CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); String t = ts.getWorkingCode(); @@ -2854,7 +2873,7 @@ public class ProfileUtilities extends TranslatingUtilities { StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); if (sd == null) { if (messages != null) { - messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, purl + "#" + derived.getPath(), "Cannot check whether the target profile " + url + " is valid constraint on the base because it is not known", IssueSeverity.WARNING)); + messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, "Cannot check whether the target profile " + url + " on "+derived.getPath()+" is valid constraint on the base because it is not known", IssueSeverity.WARNING)); } url = null; tgtOk = true; // suppress error message