001package org.hl7.fhir.common.hapi.validation.support;
002
003import ca.uhn.fhir.i18n.Msg;
004import ca.uhn.fhir.context.FhirContext;
005import ca.uhn.fhir.context.FhirVersionEnum;
006import ca.uhn.fhir.context.support.IValidationSupport;
007import ca.uhn.fhir.context.support.ValidationSupportContext;
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 org.apache.commons.lang3.Validate;
012import org.hl7.fhir.common.hapi.validation.validator.ProfileKnowledgeWorkerR5;
013import org.hl7.fhir.common.hapi.validation.validator.VersionSpecificWorkerContextWrapper;
014import org.hl7.fhir.common.hapi.validation.validator.VersionTypeConverterDstu3;
015import org.hl7.fhir.common.hapi.validation.validator.VersionTypeConverterR4;
016import org.hl7.fhir.instance.model.api.IBaseResource;
017import org.hl7.fhir.r5.conformance.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 javax.annotation.Nullable;
024import java.util.ArrayList;
025
026import static org.apache.commons.lang3.StringUtils.isBlank;
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
042        /**
043         * Constructor
044         */
045        public SnapshotGeneratingValidationSupport(FhirContext theCtx) {
046                Validate.notNull(theCtx);
047                myCtx = theCtx;
048        }
049
050        @Override
051        public IBaseResource generateSnapshot(ValidationSupportContext theValidationSupportContext, IBaseResource theInput, String theUrl, String theWebUrl, String theProfileName) {
052
053                String inputUrl = null;
054                try {
055                        FhirVersionEnum version = theInput.getStructureFhirVersionEnum();
056                        assert version == myCtx.getVersion().getVersion();
057
058                        VersionSpecificWorkerContextWrapper.IVersionTypeConverter converter = newVersionTypeConverter(version);
059                        Validate.notNull(converter, "Can not generate snapshot for version: %s", version);
060
061                        org.hl7.fhir.r5.model.StructureDefinition inputCanonical = (org.hl7.fhir.r5.model.StructureDefinition) converter.toCanonical(theInput);
062
063                        inputUrl = inputCanonical.getUrl();
064                        if (theValidationSupportContext.getCurrentlyGeneratingSnapshots().contains(inputUrl)) {
065                                ourLog.warn("Detected circular dependency, already generating snapshot for: {}", inputUrl);
066                                return theInput;
067                        }
068                        theValidationSupportContext.getCurrentlyGeneratingSnapshots().add(inputUrl);
069
070                        String baseDefinition = inputCanonical.getBaseDefinition();
071                        if (isBlank(baseDefinition)) {
072                                throw new PreconditionFailedException(Msg.code(704) + "StructureDefinition[id=" + inputCanonical.getIdElement().getId() + ", url=" + inputCanonical.getUrl() + "] has no base");
073                        }
074
075                        IBaseResource base = theValidationSupportContext.getRootValidationSupport().fetchStructureDefinition(baseDefinition);
076                        if (base == null) {
077                                throw new PreconditionFailedException(Msg.code(705) + "Unknown base definition: " + baseDefinition);
078                        }
079
080                        org.hl7.fhir.r5.model.StructureDefinition baseCanonical = (org.hl7.fhir.r5.model.StructureDefinition) converter.toCanonical(base);
081
082                        if (baseCanonical.getSnapshot().getElement().isEmpty()) {
083                                // If the base definition also doesn't have a snapshot, generate that first
084                                theValidationSupportContext.getRootValidationSupport().generateSnapshot(theValidationSupportContext, base, null, null, null);
085                                baseCanonical = (org.hl7.fhir.r5.model.StructureDefinition) converter.toCanonical(base);
086                        }
087
088                        ArrayList<ValidationMessage> messages = new ArrayList<>();
089                        org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider profileKnowledgeProvider = new ProfileKnowledgeWorkerR5(myCtx);
090                        IWorkerContext context = new VersionSpecificWorkerContextWrapper(theValidationSupportContext, converter);
091                        ProfileUtilities profileUtilities = new ProfileUtilities(context, messages, profileKnowledgeProvider);
092                        profileUtilities.generateSnapshot(baseCanonical, inputCanonical, theUrl, theWebUrl, theProfileName);
093
094                        switch (version) {
095                                case DSTU3:
096                                        org.hl7.fhir.dstu3.model.StructureDefinition generatedDstu3 = (org.hl7.fhir.dstu3.model.StructureDefinition) converter.fromCanonical(inputCanonical);
097                                        ((org.hl7.fhir.dstu3.model.StructureDefinition) theInput).getSnapshot().getElement().clear();
098                                        ((org.hl7.fhir.dstu3.model.StructureDefinition) theInput).getSnapshot().getElement().addAll(generatedDstu3.getSnapshot().getElement());
099                                        break;
100                                case R4:
101                                        org.hl7.fhir.r4.model.StructureDefinition generatedR4 = (org.hl7.fhir.r4.model.StructureDefinition) converter.fromCanonical(inputCanonical);
102                                        ((org.hl7.fhir.r4.model.StructureDefinition) theInput).getSnapshot().getElement().clear();
103                                        ((org.hl7.fhir.r4.model.StructureDefinition) theInput).getSnapshot().getElement().addAll(generatedR4.getSnapshot().getElement());
104                                        break;
105                                case R5:
106                                        org.hl7.fhir.r5.model.StructureDefinition generatedR5 = (org.hl7.fhir.r5.model.StructureDefinition) converter.fromCanonical(inputCanonical);
107                                        ((org.hl7.fhir.r5.model.StructureDefinition) theInput).getSnapshot().getElement().clear();
108                                        ((org.hl7.fhir.r5.model.StructureDefinition) theInput).getSnapshot().getElement().addAll(generatedR5.getSnapshot().getElement());
109                                        break;
110                                case DSTU2:
111                                case DSTU2_HL7ORG:
112                                case DSTU2_1:
113                                default:
114                                        throw new IllegalStateException(Msg.code(706) + "Can not generate snapshot for version: " + version);
115                        }
116
117                        return theInput;
118
119                } catch (BaseServerResponseException e) {
120                        throw e;
121                } catch (Exception e) {
122                        throw new InternalErrorException(Msg.code(707) + "Failed to generate snapshot", e);
123                } finally {
124                        if (inputUrl != null) {
125                                theValidationSupportContext.getCurrentlyGeneratingSnapshots().remove(inputUrl);
126                        }
127                }
128        }
129
130        @Override
131        public FhirContext getFhirContext() {
132                return myCtx;
133        }
134
135        @Nullable
136        public static VersionSpecificWorkerContextWrapper.IVersionTypeConverter newVersionTypeConverter(FhirVersionEnum version) {
137                VersionSpecificWorkerContextWrapper.IVersionTypeConverter converter;
138                switch (version) {
139                        case DSTU3:
140                                converter = new VersionTypeConverterDstu3();
141                                break;
142                        case R4:
143                                converter = new VersionTypeConverterR4();
144                                break;
145                        case R5:
146                                converter = VersionSpecificWorkerContextWrapper.IDENTITY_VERSION_TYPE_CONVERTER;
147                                break;
148                        case DSTU2:
149                        case DSTU2_HL7ORG:
150                        case DSTU2_1:
151                        default:
152                                return null;
153                }
154                return converter;
155        }
156
157
158}