Search in sources :

Example 6 with StructureDefinitionSnapshotComponent

use of org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionSnapshotComponent in project org.hl7.fhir.core by hapifhir.

the class ProfileUtilities method generateSnapshot.

/**
 * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
 *
 * @param base - the base structure on which the differential will be applied
 * @param differential - the differential to apply to the base
 * @param url - where the base has relative urls for profile references, these need to be converted to absolutes by prepending this URL
 * @param trimDifferential - if this is true, then the snap short generator will remove any material in the element definitions that is not different to the base
 * @return
 * @throws FHIRException
 * @throws DefinitionException
 * @throws Exception
 */
public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String profileName) throws DefinitionException, FHIRException {
    if (base == null)
        throw new DefinitionException("no base profile provided");
    if (derived == null)
        throw new DefinitionException("no derived structure provided");
    if (snapshotStack.contains(derived.getUrl()))
        throw new DefinitionException("Circular snapshot references detected; cannot generate snapshot (stack = " + snapshotStack.toString() + ")");
    snapshotStack.add(derived.getUrl());
    // System.out.println("Generate Snapshot for "+derived.getUrl());
    derived.setSnapshot(new StructureDefinitionSnapshotComponent());
    // so we have two lists - the base list, and the differential list
    // the differential list is only allowed to include things that are in the base list, but
    // is allowed to include them multiple times - thereby slicing them
    // our approach is to walk through the base list, and see whether the differential
    // says anything about them.
    int baseCursor = 0;
    // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths
    int diffCursor = 0;
    // we actually delegate the work to a subroutine so we can re-enter it with a different cursors
    processPaths(derived.getSnapshot(), base.getSnapshot(), derived.getDifferential(), baseCursor, diffCursor, base.getSnapshot().getElement().size() - 1, derived.getDifferential().getElement().size() - 1, url, derived.getId(), null, false, base.getUrl(), null, false);
}
Also used : StructureDefinitionSnapshotComponent(org.hl7.fhir.dstu2016may.model.StructureDefinition.StructureDefinitionSnapshotComponent) DefinitionException(org.hl7.fhir.exceptions.DefinitionException)

Example 7 with StructureDefinitionSnapshotComponent

use of org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionSnapshotComponent in project org.hl7.fhir.core by hapifhir.

the class ProfileUtilities method processPaths.

/**
 * @param trimDifferential
 * @throws DefinitionException, FHIRException
 * @throws Exception
 */
private void processPaths(StructureDefinitionSnapshotComponent result, StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, int diffCursor, int baseLimit, int diffLimit, String url, String profileName, String contextPath, boolean trimDifferential, String contextName, String resultPathBase, boolean slicingDone) throws DefinitionException, FHIRException {
    // just repeat processing entries until we run out of our allowed scope (1st entry, the allowed scope is all the entries)
    while (baseCursor <= baseLimit) {
        // get the current focus of the base, and decide what to do
        ElementDefinition currentBase = base.getElement().get(baseCursor);
        String cpath = fixedPath(contextPath, currentBase.getPath());
        // get a list of matching elements in scope
        List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName);
        // in the simple case, source is not sliced.
        if (!currentBase.hasSlicing()) {
            if (diffMatches.isEmpty()) {
                // the differential doesn't say anything about this item
                // so we just copy it in
                ElementDefinition outcome = updateURLs(url, currentBase.copy());
                outcome.setPath(fixedPath(contextPath, outcome.getPath()));
                updateFromBase(outcome, currentBase);
                markDerived(outcome);
                if (resultPathBase == null)
                    resultPathBase = outcome.getPath();
                else if (!outcome.getPath().startsWith(resultPathBase))
                    throw new DefinitionException("Adding wrong path");
                result.getElement().add(outcome);
                baseCursor++;
            } else if (diffMatches.size() == 1 && (slicingDone || (!diffMatches.get(0).hasSlicing() && !(isExtension(diffMatches.get(0)) && !diffMatches.get(0).hasName())))) {
                // one matching element in the differential
                ElementDefinition template = null;
                if (diffMatches.get(0).hasType() && diffMatches.get(0).getType().size() == 1 && diffMatches.get(0).getType().get(0).hasProfile() && !diffMatches.get(0).getType().get(0).getCode().equals("Reference")) {
                    String p = diffMatches.get(0).getType().get(0).getProfile().get(0).asStringValue();
                    StructureDefinition sd = context.fetchResource(StructureDefinition.class, p);
                    if (sd != null) {
                        if (!sd.hasSnapshot()) {
                            StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
                            if (sdb == null)
                                throw new DefinitionException("no base for " + sd.getBaseDefinition());
                            generateSnapshot(sdb, sd, sd.getUrl(), sd.getName());
                        }
                        template = sd.getSnapshot().getElement().get(0).copy().setPath(currentBase.getPath());
                        // temporary work around
                        if (!diffMatches.get(0).getType().get(0).getCode().equals("Extension")) {
                            template.setMin(currentBase.getMin());
                            template.setMax(currentBase.getMax());
                        }
                    }
                }
                if (template == null)
                    template = currentBase.copy();
                else
                    // some of what's in currentBase overrides template
                    template = overWriteWithCurrent(template, currentBase);
                ElementDefinition outcome = updateURLs(url, template);
                outcome.setPath(fixedPath(contextPath, outcome.getPath()));
                updateFromBase(outcome, currentBase);
                if (diffMatches.get(0).hasName())
                    outcome.setName(diffMatches.get(0).getName());
                outcome.setSlicing(null);
                updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url);
                if (// if the base profile allows multiple types, but the profile only allows one, rename it
                outcome.getPath().endsWith("[x]") && outcome.getType().size() == 1 && !outcome.getType().get(0).getCode().equals("*"))
                    outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length() - 3) + Utilities.capitalize(outcome.getType().get(0).getCode()));
                if (resultPathBase == null)
                    resultPathBase = outcome.getPath();
                else if (!outcome.getPath().startsWith(resultPathBase))
                    throw new DefinitionException("Adding wrong path");
                result.getElement().add(outcome);
                baseCursor++;
                diffCursor = differential.getElement().indexOf(diffMatches.get(0)) + 1;
                if (differential.getElement().size() > diffCursor && outcome.getPath().contains(".") && isDataType(outcome.getType())) {
                    // don't want to do this for the root, since that's base, and we're already processing it
                    if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + ".")) {
                        if (outcome.getType().size() > 1)
                            throw new DefinitionException(diffMatches.get(0).getPath() + " has children (" + differential.getElement().get(diffCursor).getPath() + ") and multiple types (" + typeCode(outcome.getType()) + ") in profile " + profileName);
                        StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
                        if (dt == null)
                            throw new DefinitionException(diffMatches.get(0).getPath() + " has children (" + differential.getElement().get(diffCursor).getPath() + ") for type " + typeCode(outcome.getType()) + " in profile " + profileName + ", but can't find type");
                        contextName = dt.getUrl();
                        int start = diffCursor;
                        while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + ".")) diffCursor++;
                        processPaths(result, dt.getSnapshot(), differential, 1, /* starting again on the data type, but skip the root */
                        start - 1, dt.getSnapshot().getElement().size() - 1, diffCursor - 1, url, profileName + pathTail(diffMatches, 0), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false);
                    }
                }
            } else {
                // ok, the differential slices the item. Let's check our pre-conditions to ensure that this is correct
                if (!unbounded(currentBase) && !isSlicedToOneOnly(diffMatches.get(0)))
                    // (but you might do that in order to split up constraints by type)
                    throw new DefinitionException("Attempt to a slice an element that does not repeat: " + currentBase.getPath() + "/" + currentBase.getName() + " from " + contextName);
                if (// well, the diff has set up a slice, but hasn't defined it. this is an error
                !diffMatches.get(0).hasSlicing() && !isExtension(currentBase))
                    throw new DefinitionException("differential does not have a slice: " + currentBase.getPath());
                // well, if it passed those preconditions then we slice the dest.
                // we're just going to accept the differential slicing at face value
                ElementDefinition outcome = updateURLs(url, currentBase.copy());
                outcome.setPath(fixedPath(contextPath, outcome.getPath()));
                updateFromBase(outcome, currentBase);
                if (!diffMatches.get(0).hasSlicing())
                    outcome.setSlicing(makeExtensionSlicing());
                else
                    outcome.setSlicing(diffMatches.get(0).getSlicing().copy());
                if (!outcome.getPath().startsWith(resultPathBase))
                    throw new DefinitionException("Adding wrong path");
                result.getElement().add(outcome);
                // 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.
                int start = 0;
                if (!diffMatches.get(0).hasName()) {
                    updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url);
                    if (!outcome.hasType()) {
                        throw new DefinitionException("not done yet");
                    }
                    start = 1;
                } else
                    checkExtensionDoco(outcome);
                // now, for each entry in the diff matches, we're going to process the base item
                // our processing scope for base is all the children of the current path
                int nbl = findEndOfElement(base, baseCursor);
                int ndc = diffCursor;
                int ndl = diffCursor;
                for (int i = start; i < diffMatches.size(); i++) {
                    // our processing scope for the differential is the item in the list, and all the items before the next one in the list
                    ndc = differential.getElement().indexOf(diffMatches.get(i));
                    ndl = findEndOfElement(differential, ndc);
                    // now we process the base scope repeatedly for each instance of the item in the differential list
                    processPaths(result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName + pathTail(diffMatches, i), contextPath, trimDifferential, contextName, resultPathBase, true);
                }
                // ok, done with that - next in the base list
                baseCursor = nbl + 1;
                diffCursor = ndl + 1;
            }
        } else {
            // the item is already sliced in the base profile.
            // here's the rules
            // 1. irrespective of whether the slicing is ordered or not, the definition order must be maintained
            // 2. slice element names have to match.
            // 3. new slices must be introduced at the end
            // corallory: you can't re-slice existing slices. is that ok?
            // we're going to need this:
            String path = currentBase.getPath();
            ElementDefinition original = currentBase;
            if (diffMatches.isEmpty()) {
                // copy across the currentbase, and all of it's children and siblings
                while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path)) {
                    ElementDefinition outcome = updateURLs(url, base.getElement().get(baseCursor).copy());
                    if (!outcome.getPath().startsWith(resultPathBase))
                        throw new DefinitionException("Adding wrong path: " + outcome.getPath() + " vs " + resultPathBase);
                    // so we just copy it in
                    result.getElement().add(outcome);
                    baseCursor++;
                }
            } else {
                // first - check that the slicing is ok
                boolean closed = currentBase.getSlicing().getRules() == SlicingRules.CLOSED;
                int diffpos = 0;
                boolean isExtension = cpath.endsWith(".extension") || cpath.endsWith(".modifierExtension");
                if (diffMatches.get(0).hasSlicing()) {
                    // it might be null if the differential doesn't want to say anything about slicing
                    if (!isExtension)
                        // if there's a slice on the first, we'll ignore any content it has
                        diffpos++;
                    ElementDefinitionSlicingComponent dSlice = diffMatches.get(0).getSlicing();
                    ElementDefinitionSlicingComponent bSlice = currentBase.getSlicing();
                    if (!orderMatches(dSlice.getOrderedElement(), bSlice.getOrderedElement()))
                        throw new DefinitionException("Slicing rules on differential (" + summariseSlicing(dSlice) + ") do not match those on base (" + summariseSlicing(bSlice) + ") - order @ " + path + " (" + contextName + ")");
                    if (!discriiminatorMatches(dSlice.getDiscriminator(), bSlice.getDiscriminator()))
                        throw new DefinitionException("Slicing rules on differential (" + summariseSlicing(dSlice) + ") do not match those on base (" + summariseSlicing(bSlice) + ") - disciminator @ " + path + " (" + contextName + ")");
                    if (!ruleMatches(dSlice.getRules(), bSlice.getRules()))
                        throw new DefinitionException("Slicing rules on differential (" + summariseSlicing(dSlice) + ") do not match those on base (" + summariseSlicing(bSlice) + ") - rule @ " + path + " (" + contextName + ")");
                }
                ElementDefinition outcome = updateURLs(url, currentBase.copy());
                outcome.setPath(fixedPath(contextPath, outcome.getPath()));
                updateFromBase(outcome, currentBase);
                if (diffMatches.get(0).hasSlicing() && !isExtension) {
                    updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing());
                    // if there's no slice, we don't want to update the unsliced description
                    updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url);
                }
                if (diffMatches.get(0).hasSlicing() && !diffMatches.get(0).hasName())
                    diffpos++;
                result.getElement().add(outcome);
                // now, we have two lists, base and diff. we're going to work through base, looking for matches in diff.
                List<ElementDefinition> baseMatches = getSiblings(base.getElement(), currentBase);
                for (ElementDefinition baseItem : baseMatches) {
                    baseCursor = base.getElement().indexOf(baseItem);
                    outcome = updateURLs(url, baseItem.copy());
                    updateFromBase(outcome, currentBase);
                    outcome.setPath(fixedPath(contextPath, outcome.getPath()));
                    outcome.setSlicing(null);
                    if (!outcome.getPath().startsWith(resultPathBase))
                        throw new DefinitionException("Adding wrong path");
                    if (diffpos < diffMatches.size() && diffMatches.get(diffpos).getName().equals(outcome.getName())) {
                        // if there's a diff, we update the outcome with diff
                        // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, closed, url);
                        // then process any children
                        int nbl = findEndOfElement(base, baseCursor);
                        int ndc = differential.getElement().indexOf(diffMatches.get(diffpos));
                        int ndl = findEndOfElement(differential, ndc);
                        // now we process the base scope repeatedly for each instance of the item in the differential list
                        processPaths(result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName + pathTail(diffMatches, diffpos), contextPath, closed, contextName, resultPathBase, true);
                        // ok, done with that - now set the cursors for if this is the end
                        baseCursor = nbl + 1;
                        diffCursor = ndl + 1;
                        diffpos++;
                    } else {
                        result.getElement().add(outcome);
                        baseCursor++;
                        // just copy any children on the base
                        while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path) && !base.getElement().get(baseCursor).getPath().equals(path)) {
                            outcome = updateURLs(url, currentBase.copy());
                            outcome.setPath(fixedPath(contextPath, outcome.getPath()));
                            if (!outcome.getPath().startsWith(resultPathBase))
                                throw new DefinitionException("Adding wrong path");
                            result.getElement().add(outcome);
                            baseCursor++;
                        }
                    }
                }
                // finally, we process any remaining entries in diff, which are new (and which are only allowed if the base wasn't closed
                if (closed && diffpos < diffMatches.size())
                    throw new DefinitionException("The base snapshot marks a slicing as closed, but the differential tries to extend it in " + profileName + " at " + path + " (" + cpath + ")");
                while (diffpos < diffMatches.size()) {
                    ElementDefinition diffItem = diffMatches.get(diffpos);
                    for (ElementDefinition baseItem : baseMatches) if (baseItem.getName().equals(diffItem.getName()))
                        throw new DefinitionException("Named items are out of order in the slice");
                    outcome = updateURLs(url, original.copy());
                    outcome.setPath(fixedPath(contextPath, outcome.getPath()));
                    updateFromBase(outcome, currentBase);
                    outcome.setSlicing(null);
                    if (!outcome.getPath().startsWith(resultPathBase))
                        throw new DefinitionException("Adding wrong path");
                    result.getElement().add(outcome);
                    updateFromDefinition(outcome, diffItem, profileName, trimDifferential, url);
                    diffpos++;
                }
            }
        }
    }
}
Also used : StructureDefinition(org.hl7.fhir.dstu2016may.model.StructureDefinition) ElementDefinitionSlicingComponent(org.hl7.fhir.dstu2016may.model.ElementDefinition.ElementDefinitionSlicingComponent) ElementDefinition(org.hl7.fhir.dstu2016may.model.ElementDefinition) DefinitionException(org.hl7.fhir.exceptions.DefinitionException)

Example 8 with StructureDefinitionSnapshotComponent

use of org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionSnapshotComponent in project org.hl7.fhir.core by hapifhir.

the class ProfileUtilities method generateSnapshot.

/**
 * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
 *
 * @param base - the base structure on which the differential will be applied
 * @param differential - the differential to apply to the base
 * @param url - where the base has relative urls for profile references, these need to be converted to absolutes by prepending this URL (e.g. the canonical URL)
 * @param webUrl - where the base has relative urls in markdown, these need to be converted to absolutes by prepending this URL (this is not the same as the canonical URL)
 * @param trimDifferential - if this is true, then the snap short generator will remove any material in the element definitions that is not different to the base
 * @return
 * @throws FHIRException
 * @throws DefinitionException
 * @throws Exception
 */
public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String webUrl, String profileName) throws DefinitionException, FHIRException {
    if (base == null) {
        throw new DefinitionException(context.formatMessage(I18nConstants.NO_BASE_PROFILE_PROVIDED));
    }
    if (derived == null) {
        throw new DefinitionException(context.formatMessage(I18nConstants.NO_DERIVED_STRUCTURE_PROVIDED));
    }
    checkNotGenerating(base, "Base for generating a snapshot for the profile " + derived.getUrl());
    checkNotGenerating(derived, "Focus for generating a snapshot");
    if (!base.hasType()) {
        throw new DefinitionException(context.formatMessage(I18nConstants.BASE_PROFILE__HAS_NO_TYPE, base.getUrl()));
    }
    if (!derived.hasType()) {
        throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_TYPE, derived.getUrl()));
    }
    if (!derived.hasDerivation()) {
        throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_DERIVATION_VALUE_AND_SO_CANT_BE_PROCESSED, derived.getUrl()));
    }
    if (!base.getType().equals(derived.getType()) && derived.getDerivation() == TypeDerivationRule.CONSTRAINT) {
        throw new DefinitionException(context.formatMessage(I18nConstants.BASE__DERIVED_PROFILES_HAVE_DIFFERENT_TYPES____VS___, base.getUrl(), base.getType(), derived.getUrl(), derived.getType()));
    }
    if (snapshotStack.contains(derived.getUrl())) {
        throw new DefinitionException(context.formatMessage(I18nConstants.CIRCULAR_SNAPSHOT_REFERENCES_DETECTED_CANNOT_GENERATE_SNAPSHOT_STACK__, snapshotStack.toString()));
    }
    derived.setUserData("profileutils.snapshot.generating", true);
    snapshotStack.add(derived.getUrl());
    try {
        if (!Utilities.noString(webUrl) && !webUrl.endsWith("/"))
            webUrl = webUrl + '/';
        if (defWebRoot == null)
            defWebRoot = webUrl;
        derived.setSnapshot(new StructureDefinitionSnapshotComponent());
        try {
            checkDifferential(derived.getDifferential().getElement(), typeName(derived.getType()), derived.getUrl());
            checkDifferentialBaseType(derived);
            // so we have two lists - the base list, and the differential list
            // the differential list is only allowed to include things that are in the base list, but
            // is allowed to include them multiple times - thereby slicing them
            // our approach is to walk through the base list, and see whether the differential
            // says anything about them.
            int baseCursor = 0;
            // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths
            int diffCursor = 0;
            for (ElementDefinition e : derived.getDifferential().getElement()) e.clearUserData(GENERATED_IN_SNAPSHOT);
            // we actually delegate the work to a subroutine so we can re-enter it with a different cursors
            // we make a copy here because we're sometimes going to hack the differential while processing it. Have to migrate user data back afterwards
            StructureDefinitionDifferentialComponent diff = cloneDiff(derived.getDifferential());
            StructureDefinitionSnapshotComponent baseSnapshot = base.getSnapshot();
            if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
                String derivedType = derived.getType();
                if (StructureDefinitionKind.LOGICAL.equals(derived.getKind()) && derived.getType().contains("/")) {
                    derivedType = derivedType.substring(derivedType.lastIndexOf("/") + 1);
                }
                baseSnapshot = cloneSnapshot(baseSnapshot, base.getType(), derivedType);
            }
            // if (derived.getId().equals("2.16.840.1.113883.10.20.22.2.1.1")) {
            // debug = true;
            // }
            processPaths("", derived.getSnapshot(), baseSnapshot, diff, baseCursor, diffCursor, baseSnapshot.getElement().size() - 1, derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size() - 1 : -1, url, webUrl, derived.present(), null, null, false, base.getUrl(), null, false, null, null, new ArrayList<ElementRedirection>(), base);
            checkGroupConstraints(derived);
            if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
                for (ElementDefinition e : diff.getElement()) {
                    if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) {
                        ElementDefinition outcome = updateURLs(url, webUrl, e.copy());
                        e.setUserData(GENERATED_IN_SNAPSHOT, outcome);
                        derived.getSnapshot().addElement(outcome);
                    }
                }
            }
            if (derived.getKind() != StructureDefinitionKind.LOGICAL && !derived.getSnapshot().getElementFirstRep().getType().isEmpty())
                throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_SNAPSHOT_ELEMENT_FOR__IN__FROM_, derived.getSnapshot().getElementFirstRep().getPath(), derived.getUrl(), base.getUrl()));
            updateMaps(base, derived);
            setIds(derived, false);
            if (debug) {
                System.out.println("Differential: ");
                for (ElementDefinition ed : derived.getDifferential().getElement()) System.out.println("  " + ed.getId() + " : " + typeSummaryWithProfile(ed) + "[" + ed.getMin() + ".." + ed.getMax() + "]" + sliceSummary(ed) + "  " + constraintSummary(ed));
                System.out.println("Snapshot: ");
                for (ElementDefinition ed : derived.getSnapshot().getElement()) System.out.println("  " + ed.getId() + " : " + typeSummaryWithProfile(ed) + "[" + ed.getMin() + ".." + ed.getMax() + "]" + sliceSummary(ed) + "  " + constraintSummary(ed));
            }
            CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
            // Check that all differential elements have a corresponding snapshot element
            int ce = 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));
                else {
                    if (e.hasUserData(DERIVATION_EQUALS))
                        ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_EQUALS, e.getUserData(DERIVATION_EQUALS));
                    if (e.hasUserData(DERIVATION_POINTER))
                        ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_POINTER, e.getUserData(DERIVATION_POINTER));
                }
                if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) {
                    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));
                    }
                }
            }
            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)";
                System.out.println("Error in snapshot generation: " + msg);
                if (!debug) {
                    System.out.println("Differential: ");
                    for (ElementDefinition ed : derived.getDifferential().getElement()) System.out.println("  " + ed.getId() + " = " + ed.getPath() + " : " + typeSummaryWithProfile(ed) + "[" + ed.getMin() + ".." + ed.getMax() + "]" + sliceSummary(ed) + "  " + constraintSummary(ed));
                    System.out.println("Snapshot: ");
                    for (ElementDefinition ed : derived.getSnapshot().getElement()) System.out.println("  " + ed.getId() + " = " + ed.getPath() + " : " + typeSummaryWithProfile(ed) + "[" + ed.getMin() + ".." + ed.getMax() + "]" + sliceSummary(ed) + "  " + constraintSummary(ed));
                }
                if (exception)
                    throw new DefinitionException(msg);
                else
                    messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, msg, ValidationMessage.IssueSeverity.ERROR));
            }
            // hack around a problem in R4 definitions (somewhere?)
            for (ElementDefinition ed : derived.getSnapshot().getElement()) {
                for (ElementDefinitionMappingComponent mm : ed.getMapping()) {
                    if (mm.hasMap()) {
                        mm.setMap(mm.getMap().trim());
                    }
                }
                for (ElementDefinitionConstraintComponent s : ed.getConstraint()) {
                    if (s.hasSource()) {
                        String ref = s.getSource();
                        if (!Utilities.isAbsoluteUrl(ref)) {
                            if (ref.contains(".")) {
                                s.setSource("http://hl7.org/fhir/StructureDefinition/" + ref.substring(0, ref.indexOf(".")) + "#" + ref);
                            } else {
                                s.setSource("http://hl7.org/fhir/StructureDefinition/" + ref);
                            }
                        }
                    }
                }
            }
            if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
                for (ElementDefinition ed : derived.getSnapshot().getElement()) {
                    if (!ed.hasBase()) {
                        ed.getBase().setPath(ed.getPath()).setMin(ed.getMin()).setMax(ed.getMax());
                    }
                }
            }
            // last, check for wrong profiles or target profiles
            for (ElementDefinition ed : derived.getSnapshot().getElement()) {
                for (TypeRefComponent t : ed.getType()) {
                    for (UriType u : t.getProfile()) {
                        StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue());
                        if (sd == null) {
                            if (xver != null && xver.matchingUrl(u.getValue()) && xver.status(u.getValue()) == XVerExtensionStatus.Valid) {
                                sd = xver.makeDefinition(u.getValue());
                            }
                        }
                        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));
                            }
                        } else {
                            String wt = t.getWorkingCode();
                            if (ed.getPath().equals("Bundle.entry.response.outcome")) {
                                wt = "OperationOutcome";
                            }
                            if (!sd.getType().equals(wt)) {
                                boolean ok = isCompatibleType(wt, sd);
                                if (!ok) {
                                    String smsg = "The profile " + u.getValue() + " has type " + sd.getType() + " which is not consistent with the stated type " + wt;
                                    if (exception)
                                        throw new DefinitionException(smsg);
                                    else
                                        messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url + "#" + ed.getId(), smsg, IssueSeverity.ERROR));
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            // if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind
            derived.setSnapshot(null);
            derived.clearUserData("profileutils.snapshot.generating");
            throw e;
        }
    } finally {
        derived.clearUserData("profileutils.snapshot.generating");
        snapshotStack.remove(derived.getUrl());
    }
}
Also used : ValidationMessage(org.hl7.fhir.utilities.validation.ValidationMessage) FHIRFormatError(org.hl7.fhir.exceptions.FHIRFormatError) CommaSeparatedStringBuilder(org.hl7.fhir.utilities.CommaSeparatedStringBuilder) ElementDefinitionConstraintComponent(org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionConstraintComponent) DefinitionException(org.hl7.fhir.exceptions.DefinitionException) IOException(java.io.IOException) FHIRException(org.hl7.fhir.exceptions.FHIRException) FileNotFoundException(java.io.FileNotFoundException) UriType(org.hl7.fhir.r4b.model.UriType) StructureDefinition(org.hl7.fhir.r4b.model.StructureDefinition) TypeRefComponent(org.hl7.fhir.r4b.model.ElementDefinition.TypeRefComponent) StructureDefinitionSnapshotComponent(org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionSnapshotComponent) StructureDefinitionDifferentialComponent(org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionDifferentialComponent) DefinitionException(org.hl7.fhir.exceptions.DefinitionException) ElementDefinition(org.hl7.fhir.r4b.model.ElementDefinition) ElementDefinitionMappingComponent(org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionMappingComponent)

Example 9 with StructureDefinitionSnapshotComponent

use of org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionSnapshotComponent in project org.hl7.fhir.core by hapifhir.

the class ProfileUtilities method generateSnapshot.

/**
 * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
 *
 * @param base - the base structure on which the differential will be applied
 * @param differential - the differential to apply to the base
 * @param url - where the base has relative urls for profile references, these need to be converted to absolutes by prepending this URL (e.g. the canonical URL)
 * @param webUrl - where the base has relative urls in markdown, these need to be converted to absolutes by prepending this URL (this is not the same as the canonical URL)
 * @param trimDifferential - if this is true, then the snap short generator will remove any material in the element definitions that is not different to the base
 * @return
 * @throws FHIRException
 * @throws DefinitionException
 * @throws Exception
 */
public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String webUrl, String profileName) throws DefinitionException, FHIRException {
    if (base == null) {
        throw new DefinitionException(context.formatMessage(I18nConstants.NO_BASE_PROFILE_PROVIDED));
    }
    if (derived == null) {
        throw new DefinitionException(context.formatMessage(I18nConstants.NO_DERIVED_STRUCTURE_PROVIDED));
    }
    checkNotGenerating(base, "Base for generating a snapshot for the profile " + derived.getUrl());
    checkNotGenerating(derived, "Focus for generating a snapshot");
    if (!base.hasType()) {
        throw new DefinitionException(context.formatMessage(I18nConstants.BASE_PROFILE__HAS_NO_TYPE, base.getUrl()));
    }
    if (!derived.hasType()) {
        throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_TYPE, derived.getUrl()));
    }
    if (!derived.hasDerivation()) {
        throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_DERIVATION_VALUE_AND_SO_CANT_BE_PROCESSED, derived.getUrl()));
    }
    if (!base.getType().equals(derived.getType()) && derived.getDerivation() == TypeDerivationRule.CONSTRAINT) {
        throw new DefinitionException(context.formatMessage(I18nConstants.BASE__DERIVED_PROFILES_HAVE_DIFFERENT_TYPES____VS___, base.getUrl(), base.getType(), derived.getUrl(), derived.getType()));
    }
    if (snapshotStack.contains(derived.getUrl())) {
        throw new DefinitionException(context.formatMessage(I18nConstants.CIRCULAR_SNAPSHOT_REFERENCES_DETECTED_CANNOT_GENERATE_SNAPSHOT_STACK__, snapshotStack.toString()));
    }
    derived.setUserData("profileutils.snapshot.generating", true);
    snapshotStack.add(derived.getUrl());
    try {
        if (!Utilities.noString(webUrl) && !webUrl.endsWith("/"))
            webUrl = webUrl + '/';
        if (defWebRoot == null)
            defWebRoot = webUrl;
        derived.setSnapshot(new StructureDefinitionSnapshotComponent());
        try {
            checkDifferential(derived.getDifferential().getElement(), typeName(derived.getType()), derived.getUrl());
            checkDifferentialBaseType(derived);
            // so we have two lists - the base list, and the differential list
            // the differential list is only allowed to include things that are in the base list, but
            // is allowed to include them multiple times - thereby slicing them
            // our approach is to walk through the base list, and see whether the differential
            // says anything about them.
            int baseCursor = 0;
            // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths
            int diffCursor = 0;
            for (ElementDefinition e : derived.getDifferential().getElement()) e.clearUserData(GENERATED_IN_SNAPSHOT);
            // we actually delegate the work to a subroutine so we can re-enter it with a different cursors
            // we make a copy here because we're sometimes going to hack the differential while processing it. Have to migrate user data back afterwards
            StructureDefinitionDifferentialComponent diff = cloneDiff(derived.getDifferential());
            StructureDefinitionSnapshotComponent baseSnapshot = base.getSnapshot();
            if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
                String derivedType = derived.getType();
                if (StructureDefinitionKind.LOGICAL.equals(derived.getKind()) && derived.getType().contains("/")) {
                    derivedType = derivedType.substring(derivedType.lastIndexOf("/") + 1);
                }
                baseSnapshot = cloneSnapshot(baseSnapshot, base.getType(), derivedType);
            }
            // if (derived.getId().equals("2.16.840.1.113883.10.20.22.2.1.1")) {
            // debug = true;
            // }
            processPaths("", derived.getSnapshot(), baseSnapshot, diff, baseCursor, diffCursor, baseSnapshot.getElement().size() - 1, derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size() - 1 : -1, url, webUrl, derived.present(), null, null, false, base.getUrl(), null, false, null, null, new ArrayList<ElementRedirection>(), base);
            checkGroupConstraints(derived);
            if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
                for (ElementDefinition e : diff.getElement()) {
                    if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) {
                        ElementDefinition outcome = updateURLs(url, webUrl, e.copy());
                        e.setUserData(GENERATED_IN_SNAPSHOT, outcome);
                        derived.getSnapshot().addElement(outcome);
                    }
                }
            }
            if (derived.getKind() != StructureDefinitionKind.LOGICAL && !derived.getSnapshot().getElementFirstRep().getType().isEmpty())
                throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_SNAPSHOT_ELEMENT_FOR__IN__FROM_, derived.getSnapshot().getElementFirstRep().getPath(), derived.getUrl(), base.getUrl()));
            updateMaps(base, derived);
            setIds(derived, false);
            if (debug) {
                System.out.println("Differential: ");
                for (ElementDefinition ed : derived.getDifferential().getElement()) System.out.println("  " + ed.getId() + " : " + typeSummaryWithProfile(ed) + "[" + ed.getMin() + ".." + ed.getMax() + "]" + sliceSummary(ed) + "  " + constraintSummary(ed));
                System.out.println("Snapshot: ");
                for (ElementDefinition ed : derived.getSnapshot().getElement()) System.out.println("  " + ed.getId() + " : " + typeSummaryWithProfile(ed) + "[" + ed.getMin() + ".." + ed.getMax() + "]" + sliceSummary(ed) + "  " + constraintSummary(ed));
            }
            CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
            // Check that all differential elements have a corresponding snapshot element
            int ce = 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));
                else {
                    if (e.hasUserData(DERIVATION_EQUALS))
                        ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_EQUALS, e.getUserData(DERIVATION_EQUALS));
                    if (e.hasUserData(DERIVATION_POINTER))
                        ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_POINTER, e.getUserData(DERIVATION_POINTER));
                }
                if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) {
                    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));
                    }
                }
            }
            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)";
                System.out.println("Error in snapshot generation: " + msg);
                if (!debug) {
                    System.out.println("Differential: ");
                    for (ElementDefinition ed : derived.getDifferential().getElement()) System.out.println("  " + ed.getId() + " = " + ed.getPath() + " : " + typeSummaryWithProfile(ed) + "[" + ed.getMin() + ".." + ed.getMax() + "]" + sliceSummary(ed) + "  " + constraintSummary(ed));
                    System.out.println("Snapshot: ");
                    for (ElementDefinition ed : derived.getSnapshot().getElement()) System.out.println("  " + ed.getId() + " = " + ed.getPath() + " : " + typeSummaryWithProfile(ed) + "[" + ed.getMin() + ".." + ed.getMax() + "]" + sliceSummary(ed) + "  " + constraintSummary(ed));
                }
                if (exception)
                    throw new DefinitionException(msg);
                else
                    messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, msg, ValidationMessage.IssueSeverity.ERROR));
            }
            // hack around a problem in R4 definitions (somewhere?)
            for (ElementDefinition ed : derived.getSnapshot().getElement()) {
                for (ElementDefinitionMappingComponent mm : ed.getMapping()) {
                    if (mm.hasMap()) {
                        mm.setMap(mm.getMap().trim());
                    }
                }
                for (ElementDefinitionConstraintComponent s : ed.getConstraint()) {
                    if (s.hasSource()) {
                        String ref = s.getSource();
                        if (!Utilities.isAbsoluteUrl(ref)) {
                            if (ref.contains(".")) {
                                s.setSource("http://hl7.org/fhir/StructureDefinition/" + ref.substring(0, ref.indexOf(".")) + "#" + ref);
                            } else {
                                s.setSource("http://hl7.org/fhir/StructureDefinition/" + ref);
                            }
                        }
                    }
                }
            }
            if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
                for (ElementDefinition ed : derived.getSnapshot().getElement()) {
                    if (!ed.hasBase()) {
                        ed.getBase().setPath(ed.getPath()).setMin(ed.getMin()).setMax(ed.getMax());
                    }
                }
            }
            // last, check for wrong profiles or target profiles
            for (ElementDefinition ed : derived.getSnapshot().getElement()) {
                for (TypeRefComponent t : ed.getType()) {
                    for (UriType u : t.getProfile()) {
                        StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue());
                        if (sd == null) {
                            if (xver != null && xver.matchingUrl(u.getValue()) && xver.status(u.getValue()) == XVerExtensionStatus.Valid) {
                                sd = xver.makeDefinition(u.getValue());
                            }
                        }
                        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));
                            }
                        } else {
                            String wt = t.getWorkingCode();
                            if (ed.getPath().equals("Bundle.entry.response.outcome")) {
                                wt = "OperationOutcome";
                            }
                            if (!sd.getType().equals(wt)) {
                                boolean ok = isCompatibleType(wt, sd);
                                if (!ok) {
                                    String smsg = "The profile " + u.getValue() + " has type " + sd.getType() + " which is not consistent with the stated type " + wt;
                                    if (exception)
                                        throw new DefinitionException(smsg);
                                    else
                                        messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url + "#" + ed.getId(), smsg, IssueSeverity.ERROR));
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            // if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind
            derived.setSnapshot(null);
            derived.clearUserData("profileutils.snapshot.generating");
            throw e;
        }
    } finally {
        derived.clearUserData("profileutils.snapshot.generating");
        snapshotStack.remove(derived.getUrl());
    }
}
Also used : ValidationMessage(org.hl7.fhir.utilities.validation.ValidationMessage) FHIRFormatError(org.hl7.fhir.exceptions.FHIRFormatError) CommaSeparatedStringBuilder(org.hl7.fhir.utilities.CommaSeparatedStringBuilder) ElementDefinitionConstraintComponent(org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent) DefinitionException(org.hl7.fhir.exceptions.DefinitionException) IOException(java.io.IOException) FHIRException(org.hl7.fhir.exceptions.FHIRException) ParseException(java.text.ParseException) FileNotFoundException(java.io.FileNotFoundException) UriType(org.hl7.fhir.r5.model.UriType) StructureDefinition(org.hl7.fhir.r5.model.StructureDefinition) TypeRefComponent(org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent) StructureDefinitionSnapshotComponent(org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent) StructureDefinitionDifferentialComponent(org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionDifferentialComponent) DefinitionException(org.hl7.fhir.exceptions.DefinitionException) ElementDefinition(org.hl7.fhir.r5.model.ElementDefinition) ElementDefinitionMappingComponent(org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent)

Example 10 with StructureDefinitionSnapshotComponent

use of org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionSnapshotComponent in project org.hl7.fhir.core by hapifhir.

the class ProfileUtilities method cloneSnapshot.

private StructureDefinitionSnapshotComponent cloneSnapshot(StructureDefinitionSnapshotComponent source, String baseType, String derivedType) {
    StructureDefinitionSnapshotComponent diff = new StructureDefinitionSnapshotComponent();
    for (ElementDefinition sed : source.getElement()) {
        ElementDefinition ted = sed.copy();
        ted.setId(ted.getId().replaceFirst(baseType, derivedType));
        ted.setPath(ted.getPath().replaceFirst(baseType, derivedType));
        diff.getElement().add(ted);
    }
    return diff;
}
Also used : StructureDefinitionSnapshotComponent(org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent) ElementDefinition(org.hl7.fhir.r5.model.ElementDefinition)

Aggregations

DefinitionException (org.hl7.fhir.exceptions.DefinitionException)12 ElementDefinition (org.hl7.fhir.r5.model.ElementDefinition)10 FHIRFormatError (org.hl7.fhir.exceptions.FHIRFormatError)8 StructureDefinition (org.hl7.fhir.r5.model.StructureDefinition)8 StructureDefinitionSnapshotComponent (org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent)8 StructureDefinitionDifferentialComponent (org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionDifferentialComponent)7 ArrayList (java.util.ArrayList)6 HashSet (java.util.HashSet)6 FHIRException (org.hl7.fhir.exceptions.FHIRException)6 Narrative (org.hl7.fhir.r5.model.Narrative)6 XhtmlNode (org.hl7.fhir.utilities.xhtml.XhtmlNode)6 TypeRefComponent (org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent)5 ElementDefinition (org.hl7.fhir.r4b.model.ElementDefinition)4 UriType (org.hl7.fhir.r5.model.UriType)4 IOException (java.io.IOException)3 List (java.util.List)3 FileNotFoundException (java.io.FileNotFoundException)2 Iterator (java.util.Iterator)2 ElementDefinition (org.hl7.fhir.dstu3.model.ElementDefinition)2 ElementDefinition (org.hl7.fhir.r4.model.ElementDefinition)2