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