001package org.hl7.fhir.common.hapi.validation.support;
002
003import ca.uhn.fhir.context.FhirContext;
004import ca.uhn.fhir.context.FhirVersionEnum;
005import ca.uhn.fhir.context.support.IValidationSupport;
006import ca.uhn.fhir.context.support.ValidationSupportContext;
007import ca.uhn.fhir.i18n.Msg;
008import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
009import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
010import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
011import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
012import org.apache.commons.lang3.Validate;
013import org.hl7.fhir.common.hapi.validation.validator.ProfileKnowledgeWorkerR5;
014import org.hl7.fhir.common.hapi.validation.validator.VersionSpecificWorkerContextWrapper;
015import org.hl7.fhir.instance.model.api.IBaseResource;
016import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider;
017import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
018import org.hl7.fhir.r5.context.IWorkerContext;
019import org.hl7.fhir.utilities.validation.ValidationMessage;
020import org.slf4j.Logger;
021import org.slf4j.LoggerFactory;
022
023import java.util.ArrayList;
024
025import static org.apache.commons.lang3.StringUtils.isBlank;
026import static org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService.getFhirVersionEnum;
027
028/**
029 * Simple validation support module that handles profile snapshot generation.
030 * <p>
031 * This module currently supports the following FHIR versions:
032 * <ul>
033 *    <li>DSTU3</li>
034 *    <li>R4</li>
035 *    <li>R5</li>
036 * </ul>
037 */
038public class SnapshotGeneratingValidationSupport implements IValidationSupport {
039        private static final Logger ourLog = LoggerFactory.getLogger(SnapshotGeneratingValidationSupport.class);
040        private final FhirContext myCtx;
041        private final VersionCanonicalizer myVersionCanonicalizer;
042
043        /**
044         * Constructor
045         */
046        public SnapshotGeneratingValidationSupport(FhirContext theCtx) {
047                Validate.notNull(theCtx);
048                myCtx = theCtx;
049                myVersionCanonicalizer = new VersionCanonicalizer(theCtx);
050        }
051
052        @SuppressWarnings("EnhancedSwitchMigration")
053        @Override
054        public IBaseResource generateSnapshot(
055                        ValidationSupportContext theValidationSupportContext,
056                        IBaseResource theInput,
057                        String theUrl,
058                        String theWebUrl,
059                        String theProfileName) {
060
061                String inputUrl = null;
062                try {
063                        FhirVersionEnum version = theInput.getStructureFhirVersionEnum();
064
065                        org.hl7.fhir.r5.model.StructureDefinition inputCanonical =
066                                        myVersionCanonicalizer.structureDefinitionToCanonical(theInput);
067
068                        inputUrl = inputCanonical.getUrl();
069                        if (theValidationSupportContext.getCurrentlyGeneratingSnapshots().contains(inputUrl)) {
070                                ourLog.warn("Detected circular dependency, already generating snapshot for: {}", inputUrl);
071                                return theInput;
072                        }
073                        theValidationSupportContext.getCurrentlyGeneratingSnapshots().add(inputUrl);
074
075                        String baseDefinition = inputCanonical.getBaseDefinition();
076                        if (isBlank(baseDefinition)) {
077                                throw new PreconditionFailedException(Msg.code(704) + "StructureDefinition[id="
078                                                + inputCanonical.getIdElement().getId() + ", url=" + inputCanonical.getUrl() + "] has no base");
079                        }
080
081                        IBaseResource base =
082                                        theValidationSupportContext.getRootValidationSupport().fetchStructureDefinition(baseDefinition);
083                        if (base == null) {
084                                throw new PreconditionFailedException(Msg.code(705) + "Unknown base definition: " + baseDefinition);
085                        }
086
087                        org.hl7.fhir.r5.model.StructureDefinition baseCanonical =
088                                        myVersionCanonicalizer.structureDefinitionToCanonical(base);
089
090                        if (baseCanonical.getSnapshot().getElement().isEmpty()) {
091                                // If the base definition also doesn't have a snapshot, generate that first
092                                theValidationSupportContext
093                                                .getRootValidationSupport()
094                                                .generateSnapshot(theValidationSupportContext, base, null, null, null);
095                                baseCanonical = myVersionCanonicalizer.structureDefinitionToCanonical(base);
096                        }
097
098                        ArrayList<ValidationMessage> messages = new ArrayList<>();
099                        ProfileKnowledgeProvider profileKnowledgeProvider = new ProfileKnowledgeWorkerR5(myCtx);
100                        IWorkerContext context =
101                                        new VersionSpecificWorkerContextWrapper(theValidationSupportContext, myVersionCanonicalizer);
102                        ProfileUtilities profileUtilities = new ProfileUtilities(context, messages, profileKnowledgeProvider);
103                        profileUtilities.generateSnapshot(baseCanonical, inputCanonical, theUrl, theWebUrl, theProfileName);
104
105                        switch (getFhirVersionEnum(
106                                        theValidationSupportContext.getRootValidationSupport().getFhirContext(), theInput)) {
107                                case DSTU3:
108                                        org.hl7.fhir.dstu3.model.StructureDefinition generatedDstu3 =
109                                                        (org.hl7.fhir.dstu3.model.StructureDefinition)
110                                                                        myVersionCanonicalizer.structureDefinitionFromCanonical(inputCanonical);
111                                        ((org.hl7.fhir.dstu3.model.StructureDefinition) theInput)
112                                                        .getSnapshot()
113                                                        .getElement()
114                                                        .clear();
115                                        ((org.hl7.fhir.dstu3.model.StructureDefinition) theInput)
116                                                        .getSnapshot()
117                                                        .getElement()
118                                                        .addAll(generatedDstu3.getSnapshot().getElement());
119                                        break;
120                                case R4:
121                                        org.hl7.fhir.r4.model.StructureDefinition generatedR4 = (org.hl7.fhir.r4.model.StructureDefinition)
122                                                        myVersionCanonicalizer.structureDefinitionFromCanonical(inputCanonical);
123                                        ((org.hl7.fhir.r4.model.StructureDefinition) theInput)
124                                                        .getSnapshot()
125                                                        .getElement()
126                                                        .clear();
127                                        ((org.hl7.fhir.r4.model.StructureDefinition) theInput)
128                                                        .getSnapshot()
129                                                        .getElement()
130                                                        .addAll(generatedR4.getSnapshot().getElement());
131                                        break;
132                                case R4B:
133                                        org.hl7.fhir.r4b.model.StructureDefinition generatedR4b =
134                                                        (org.hl7.fhir.r4b.model.StructureDefinition)
135                                                                        myVersionCanonicalizer.structureDefinitionFromCanonical(inputCanonical);
136                                        ((org.hl7.fhir.r4b.model.StructureDefinition) theInput)
137                                                        .getSnapshot()
138                                                        .getElement()
139                                                        .clear();
140                                        ((org.hl7.fhir.r4b.model.StructureDefinition) theInput)
141                                                        .getSnapshot()
142                                                        .getElement()
143                                                        .addAll(generatedR4b.getSnapshot().getElement());
144                                        break;
145                                case R5:
146                                        org.hl7.fhir.r5.model.StructureDefinition generatedR5 = (org.hl7.fhir.r5.model.StructureDefinition)
147                                                        myVersionCanonicalizer.structureDefinitionFromCanonical(inputCanonical);
148                                        ((org.hl7.fhir.r5.model.StructureDefinition) theInput)
149                                                        .getSnapshot()
150                                                        .getElement()
151                                                        .clear();
152                                        ((org.hl7.fhir.r5.model.StructureDefinition) theInput)
153                                                        .getSnapshot()
154                                                        .getElement()
155                                                        .addAll(generatedR5.getSnapshot().getElement());
156                                        break;
157                                case DSTU2:
158                                case DSTU2_HL7ORG:
159                                case DSTU2_1:
160                                default:
161                                        throw new IllegalStateException(
162                                                        Msg.code(706) + "Can not generate snapshot for version: " + version);
163                        }
164
165                        return theInput;
166
167                } catch (BaseServerResponseException e) {
168                        throw e;
169                } catch (Exception e) {
170                        throw new InternalErrorException(Msg.code(707) + "Failed to generate snapshot", e);
171                } finally {
172                        if (inputUrl != null) {
173                                theValidationSupportContext.getCurrentlyGeneratingSnapshots().remove(inputUrl);
174                        }
175                }
176        }
177
178        @Override
179        public FhirContext getFhirContext() {
180                return myCtx;
181        }
182
183        @Override
184        public String getName() {
185                return getFhirContext().getVersion().getVersion() + " Snapshot Generating Validation Support";
186        }
187}