001/*-
002 * #%L
003 * HAPI FHIR Storage api
004 * %%
005 * Copyright (C) 2014 - 2025 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.jpa.validation;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.context.RuntimeResourceDefinition;
024import ca.uhn.fhir.context.support.IValidationSupport;
025import ca.uhn.fhir.i18n.Msg;
026import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
027import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
028import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
029import ca.uhn.fhir.rest.api.server.RequestDetails;
030import ca.uhn.fhir.rest.param.TokenParam;
031import ca.uhn.fhir.rest.param.UriParam;
032import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
033import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
034import org.hl7.fhir.common.hapi.validation.validator.VersionSpecificWorkerContextWrapper;
035import org.hl7.fhir.exceptions.FHIRException;
036import org.hl7.fhir.instance.model.api.IBaseResource;
037import org.hl7.fhir.r4.model.IdType;
038import org.hl7.fhir.r5.elementmodel.Element;
039import org.hl7.fhir.r5.elementmodel.JsonParser;
040import org.hl7.fhir.r5.model.CanonicalResource;
041import org.hl7.fhir.r5.model.CanonicalType;
042import org.hl7.fhir.r5.utils.validation.IResourceValidator;
043import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
044import org.hl7.fhir.utilities.CanonicalPair;
045import org.slf4j.Logger;
046import org.slf4j.LoggerFactory;
047
048import java.util.Collections;
049import java.util.List;
050import java.util.Locale;
051import java.util.Set;
052
053/**
054 * Please note that this bean is not currently used as part of the $validate operation.
055 * The FHIR Core validation library uses {@link VersionSpecificWorkerContextWrapper} to retrieve validation resources.
056 */
057public class ValidatorResourceFetcher implements IValidatorResourceFetcher {
058
059        private static final Logger ourLog = LoggerFactory.getLogger(ValidatorResourceFetcher.class);
060
061        private final FhirContext myFhirContext;
062        private final DaoRegistry myDaoRegistry;
063        private final VersionSpecificWorkerContextWrapper myVersionSpecificContextWrapper;
064
065        public ValidatorResourceFetcher(
066                        FhirContext theFhirContext, IValidationSupport theValidationSupport, DaoRegistry theDaoRegistry) {
067                myFhirContext = theFhirContext;
068                myDaoRegistry = theDaoRegistry;
069                myVersionSpecificContextWrapper =
070                                VersionSpecificWorkerContextWrapper.newVersionSpecificWorkerContextWrapper(theValidationSupport);
071        }
072
073        @Override
074        public Element fetch(IResourceValidator iResourceValidator, Object appContext, String theUrl) throws FHIRException {
075                IdType id = new IdType(theUrl);
076                String resourceType = id.getResourceType();
077                IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceType);
078                IBaseResource target;
079                try {
080                        target = dao.read(id, (RequestDetails) appContext);
081                } catch (ResourceNotFoundException e) {
082                        ourLog.info("Failed to resolve local reference: {}", theUrl);
083
084                        RuntimeResourceDefinition def = myFhirContext.getResourceDefinition(resourceType);
085                        if (def.getChildByName("url") != null) {
086                                try {
087                                        target = fetchByUrl(theUrl, dao, (RequestDetails) appContext);
088                                } catch (ResourceNotFoundException e2) {
089                                        ourLog.info("Failed to find resource by URL: {}", theUrl);
090                                        return null;
091                                }
092                        } else {
093                                return null;
094                        }
095                }
096                try {
097                        return new JsonParser(myVersionSpecificContextWrapper)
098                                        .parse(myFhirContext.newJsonParser().encodeResourceToString(target), resourceType);
099                } catch (Exception e) {
100                        throw new FHIRException(Msg.code(576) + e, e);
101                }
102        }
103
104        private IBaseResource fetchByUrl(String url, IFhirResourceDao<?> dao, RequestDetails requestDetails)
105                        throws ResourceNotFoundException {
106                CanonicalPair pair = new CanonicalPair(url);
107                SearchParameterMap searchParameterMap = new SearchParameterMap();
108                searchParameterMap.add("url", new UriParam(pair.getUrl()));
109                String version = pair.getVersion();
110                if (version != null && !version.isEmpty()) {
111                        searchParameterMap.add("version", new TokenParam(version));
112                }
113                List<IBaseResource> results = null;
114                try {
115                        results = dao.search(searchParameterMap, requestDetails).getAllResources();
116                } catch (InvalidRequestException e) {
117                        ourLog.info("Resource does not support 'url' or 'version' Search Parameters");
118                }
119                if (results != null && !results.isEmpty()) {
120                        if (results.size() > 1) {
121                                ourLog.warn("Multiple results found for URL '{}', only the first will be considered.", url);
122                        }
123                        return results.get(0);
124                } else {
125                        throw new ResourceNotFoundException(Msg.code(2444) + "Failed to find resource by URL: " + url);
126                }
127        }
128
129        @Override
130        public boolean resolveURL(
131                        IResourceValidator iResourceValidator,
132                        Object o,
133                        String s,
134                        String s1,
135                        String s2,
136                        boolean isCanonical,
137                        List<CanonicalType> targets) {
138                return true;
139        }
140
141        @Override
142        public byte[] fetchRaw(IResourceValidator iResourceValidator, String s) throws UnsupportedOperationException {
143                throw new UnsupportedOperationException(Msg.code(577));
144        }
145
146        @Override
147        public IValidatorResourceFetcher setLocale(Locale locale) {
148                // ignore
149                return this;
150        }
151
152        @Override
153        public CanonicalResource fetchCanonicalResource(IResourceValidator validator, Object appContext, String url) {
154                return null;
155        }
156
157        @Override
158        public boolean fetchesCanonicalResource(IResourceValidator iResourceValidator, String s) {
159                return false;
160        }
161
162        @Override
163        public Set<String> fetchCanonicalResourceVersions(IResourceValidator validator, Object appContext, String url) {
164                return Collections.emptySet();
165        }
166}