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(ValidationSupportContext theValidationSupportContext, IBaseResource theInput, String theUrl, String theWebUrl, String theProfileName) {
055
056                String inputUrl = null;
057                try {
058                        FhirVersionEnum version = theInput.getStructureFhirVersionEnum();
059
060                        org.hl7.fhir.r5.model.StructureDefinition inputCanonical = myVersionCanonicalizer.structureDefinitionToCanonical(theInput);
061
062                        inputUrl = inputCanonical.getUrl();
063                        if (theValidationSupportContext.getCurrentlyGeneratingSnapshots().contains(inputUrl)) {
064                                ourLog.warn("Detected circular dependency, already generating snapshot for: {}", inputUrl);
065                                return theInput;
066                        }
067                        theValidationSupportContext.getCurrentlyGeneratingSnapshots().add(inputUrl);
068
069                        String baseDefinition = inputCanonical.getBaseDefinition();
070                        if (isBlank(baseDefinition)) {
071                                throw new PreconditionFailedException(Msg.code(704) + "StructureDefinition[id=" + inputCanonical.getIdElement().getId() + ", url=" + inputCanonical.getUrl() + "] has no base");
072                        }
073
074                        IBaseResource base = theValidationSupportContext.getRootValidationSupport().fetchStructureDefinition(baseDefinition);
075                        if (base == null) {
076                                throw new PreconditionFailedException(Msg.code(705) + "Unknown base definition: " + baseDefinition);
077                        }
078
079                        org.hl7.fhir.r5.model.StructureDefinition baseCanonical = myVersionCanonicalizer.structureDefinitionToCanonical(base);
080
081                        if (baseCanonical.getSnapshot().getElement().isEmpty()) {
082                                // If the base definition also doesn't have a snapshot, generate that first
083                                theValidationSupportContext.getRootValidationSupport().generateSnapshot(theValidationSupportContext, base, null, null, null);
084                                baseCanonical = myVersionCanonicalizer.structureDefinitionToCanonical(base);
085                        }
086
087                        ArrayList<ValidationMessage> messages = new ArrayList<>();
088                        ProfileKnowledgeProvider profileKnowledgeProvider = new ProfileKnowledgeWorkerR5(myCtx);
089                        IWorkerContext context = new VersionSpecificWorkerContextWrapper(theValidationSupportContext, myVersionCanonicalizer);
090                        ProfileUtilities profileUtilities = new ProfileUtilities(context, messages, profileKnowledgeProvider);
091                        profileUtilities.generateSnapshot(baseCanonical, inputCanonical, theUrl, theWebUrl, theProfileName);
092
093                        switch (getFhirVersionEnum(theValidationSupportContext.getRootValidationSupport().getFhirContext(), theInput)) {
094                                case DSTU3:
095                                        org.hl7.fhir.dstu3.model.StructureDefinition generatedDstu3 = (org.hl7.fhir.dstu3.model.StructureDefinition) myVersionCanonicalizer.structureDefinitionFromCanonical(inputCanonical);
096                                        ((org.hl7.fhir.dstu3.model.StructureDefinition) theInput).getSnapshot().getElement().clear();
097                                        ((org.hl7.fhir.dstu3.model.StructureDefinition) theInput).getSnapshot().getElement().addAll(generatedDstu3.getSnapshot().getElement());
098                                        break;
099                                case R4:
100                                        org.hl7.fhir.r4.model.StructureDefinition generatedR4 = (org.hl7.fhir.r4.model.StructureDefinition) myVersionCanonicalizer.structureDefinitionFromCanonical(inputCanonical);
101                                        ((org.hl7.fhir.r4.model.StructureDefinition) theInput).getSnapshot().getElement().clear();
102                                        ((org.hl7.fhir.r4.model.StructureDefinition) theInput).getSnapshot().getElement().addAll(generatedR4.getSnapshot().getElement());
103                                        break;
104                                case R4B:
105                                        org.hl7.fhir.r4b.model.StructureDefinition generatedR4b = (org.hl7.fhir.r4b.model.StructureDefinition) myVersionCanonicalizer.structureDefinitionFromCanonical(inputCanonical);
106                                        ((org.hl7.fhir.r4b.model.StructureDefinition) theInput).getSnapshot().getElement().clear();
107                                        ((org.hl7.fhir.r4b.model.StructureDefinition) theInput).getSnapshot().getElement().addAll(generatedR4b.getSnapshot().getElement());
108                                        break;
109                                case R5:
110                                        org.hl7.fhir.r5.model.StructureDefinition generatedR5 = (org.hl7.fhir.r5.model.StructureDefinition) myVersionCanonicalizer.structureDefinitionFromCanonical(inputCanonical);
111                                        ((org.hl7.fhir.r5.model.StructureDefinition) theInput).getSnapshot().getElement().clear();
112                                        ((org.hl7.fhir.r5.model.StructureDefinition) theInput).getSnapshot().getElement().addAll(generatedR5.getSnapshot().getElement());
113                                        break;
114                                case DSTU2:
115                                case DSTU2_HL7ORG:
116                                case DSTU2_1:
117                                default:
118                                        throw new IllegalStateException(Msg.code(706) + "Can not generate snapshot for version: " + version);
119                        }
120
121                        return theInput;
122
123                } catch (BaseServerResponseException e) {
124                        throw e;
125                } catch (Exception e) {
126                        throw new InternalErrorException(Msg.code(707) + "Failed to generate snapshot", e);
127                } finally {
128                        if (inputUrl != null) {
129                                theValidationSupportContext.getCurrentlyGeneratingSnapshots().remove(inputUrl);
130                        }
131                }
132        }
133
134        @Override
135        public FhirContext getFhirContext() {
136                return myCtx;
137        }
138
139}