001/*
002 * #%L
003 * HAPI FHIR JPA Server
004 * %%
005 * Copyright (C) 2014 - 2024 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.provider.dstu3;
021
022import ca.uhn.fhir.context.RuntimeSearchParam;
023import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
024import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
025import ca.uhn.fhir.rest.api.Constants;
026import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
027import ca.uhn.fhir.rest.api.server.RequestDetails;
028import ca.uhn.fhir.rest.server.RestfulServer;
029import ca.uhn.fhir.rest.server.RestfulServerConfiguration;
030import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
031import ca.uhn.fhir.rest.server.util.ResourceSearchParams;
032import ca.uhn.fhir.util.CoverageIgnore;
033import ca.uhn.fhir.util.ExtensionConstants;
034import jakarta.servlet.http.HttpServletRequest;
035import org.hl7.fhir.dstu3.model.Bundle;
036import org.hl7.fhir.dstu3.model.CapabilityStatement;
037import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestComponent;
038import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
039import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
040import org.hl7.fhir.dstu3.model.CapabilityStatement.ConditionalDeleteStatus;
041import org.hl7.fhir.dstu3.model.CapabilityStatement.ResourceVersionPolicy;
042import org.hl7.fhir.dstu3.model.DecimalType;
043import org.hl7.fhir.dstu3.model.Enumerations.SearchParamType;
044import org.hl7.fhir.dstu3.model.Extension;
045import org.hl7.fhir.dstu3.model.Meta;
046
047import java.util.Collections;
048import java.util.List;
049import java.util.Map;
050import java.util.Set;
051import java.util.stream.Collectors;
052
053import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
054import static org.apache.commons.lang3.StringUtils.isBlank;
055import static org.apache.commons.lang3.StringUtils.isNotBlank;
056
057public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.server.ServerCapabilityStatementProvider {
058
059        private volatile CapabilityStatement myCachedValue;
060        private JpaStorageSettings myStorageSettings;
061        private ISearchParamRegistry mySearchParamRegistry;
062        private String myImplementationDescription;
063        private boolean myIncludeResourceCounts;
064        private RestfulServer myRestfulServer;
065        private IFhirSystemDao<Bundle, Meta> mySystemDao;
066
067        private RestfulServerConfiguration myServerConfiguration;
068
069        /**
070         * Constructor
071         */
072        @CoverageIgnore
073        public JpaConformanceProviderDstu3() {
074                super();
075                super.setCache(false);
076                setIncludeResourceCounts(true);
077        }
078
079        /**
080         * Constructor
081         */
082        public JpaConformanceProviderDstu3(
083                        RestfulServer theRestfulServer,
084                        IFhirSystemDao<Bundle, Meta> theSystemDao,
085                        JpaStorageSettings theStorageSettings,
086                        ISearchParamRegistry theSearchParamRegistry) {
087                super(theRestfulServer);
088                myRestfulServer = theRestfulServer;
089                mySystemDao = theSystemDao;
090                myStorageSettings = theStorageSettings;
091                myServerConfiguration = theRestfulServer.createConfiguration();
092                super.setCache(false);
093                setSearchParamRegistry(theSearchParamRegistry);
094                setIncludeResourceCounts(true);
095        }
096
097        public void setSearchParamRegistry(ISearchParamRegistry theSearchParamRegistry) {
098                mySearchParamRegistry = theSearchParamRegistry;
099        }
100
101        @Override
102        public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
103                CapabilityStatement retVal = myCachedValue;
104
105                Map<String, Long> counts = null;
106                if (myIncludeResourceCounts) {
107                        counts = mySystemDao.getResourceCountsFromCache();
108                }
109                counts = defaultIfNull(counts, Collections.emptyMap());
110
111                retVal = super.getServerConformance(theRequest, theRequestDetails);
112                for (CapabilityStatementRestComponent nextRest : retVal.getRest()) {
113
114                        for (CapabilityStatementRestResourceComponent nextResource : nextRest.getResource()) {
115
116                                nextResource.setVersioning(ResourceVersionPolicy.VERSIONEDUPDATE);
117
118                                ConditionalDeleteStatus conditionalDelete = nextResource.getConditionalDelete();
119                                if (conditionalDelete == ConditionalDeleteStatus.MULTIPLE
120                                                && myStorageSettings.isAllowMultipleDelete() == false) {
121                                        nextResource.setConditionalDelete(ConditionalDeleteStatus.SINGLE);
122                                }
123
124                                // Add resource counts
125                                Long count = counts.get(nextResource.getTypeElement().getValueAsString());
126                                if (count != null) {
127                                        nextResource.addExtension(
128                                                        new Extension(ExtensionConstants.CONF_RESOURCE_COUNT, new DecimalType(count)));
129                                }
130
131                                nextResource.getSearchParam().clear();
132                                String resourceName = nextResource.getType();
133                                ResourceSearchParams searchParams = constructCompleteSearchParamList(resourceName);
134                                for (RuntimeSearchParam runtimeSp : searchParams.values()) {
135                                        CapabilityStatementRestResourceSearchParamComponent confSp = nextResource.addSearchParam();
136
137                                        confSp.setName(runtimeSp.getName());
138                                        confSp.setDocumentation(runtimeSp.getDescription());
139                                        confSp.setDefinition(runtimeSp.getUri());
140                                        switch (runtimeSp.getParamType()) {
141                                                case COMPOSITE:
142                                                        confSp.setType(SearchParamType.COMPOSITE);
143                                                        break;
144                                                case DATE:
145                                                        confSp.setType(SearchParamType.DATE);
146                                                        break;
147                                                case NUMBER:
148                                                        confSp.setType(SearchParamType.NUMBER);
149                                                        break;
150                                                case QUANTITY:
151                                                        confSp.setType(SearchParamType.QUANTITY);
152                                                        break;
153                                                case REFERENCE:
154                                                        confSp.setType(SearchParamType.REFERENCE);
155                                                        break;
156                                                case STRING:
157                                                        confSp.setType(SearchParamType.STRING);
158                                                        break;
159                                                case TOKEN:
160                                                        confSp.setType(SearchParamType.TOKEN);
161                                                        break;
162                                                case URI:
163                                                        confSp.setType(SearchParamType.URI);
164                                                        break;
165                                                case HAS:
166                                                        // Shouldn't happen
167                                                        break;
168                                        }
169                                }
170
171                                updateIncludesList(nextResource, searchParams);
172                                updateRevIncludesList(nextResource, searchParams);
173                        }
174                }
175
176                massage(retVal);
177
178                retVal.getImplementation().setDescription(myImplementationDescription);
179                myCachedValue = retVal;
180                return retVal;
181        }
182
183        private ResourceSearchParams constructCompleteSearchParamList(String theResourceName) {
184                // Borrowed from
185                // hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ServerCapabilityStatementProvider.java
186
187                /*
188                 * If we have an explicit registry (which will be the case in the JPA server) we use it as priority,
189                 * but also fill in any gaps using params from the server itself. This makes sure we include
190                 * global params like _lastUpdated
191                 */
192                ResourceSearchParams searchParams;
193                ResourceSearchParams serverConfigurationActiveSearchParams = myServerConfiguration.getActiveSearchParams(
194                                theResourceName, ISearchParamRegistry.SearchParamLookupContextEnum.SEARCH);
195                if (mySearchParamRegistry != null) {
196                        searchParams = mySearchParamRegistry
197                                        .getActiveSearchParams(theResourceName, ISearchParamRegistry.SearchParamLookupContextEnum.SEARCH)
198                                        .makeCopy();
199                        if (searchParams == null) {
200                                return ResourceSearchParams.empty(theResourceName);
201                        }
202                        for (String nextBuiltInSpName : serverConfigurationActiveSearchParams.getSearchParamNames()) {
203                                if (nextBuiltInSpName.startsWith("_")
204                                                && !searchParams.containsParamName(nextBuiltInSpName)
205                                                && searchParamEnabled(nextBuiltInSpName)) {
206                                        searchParams.put(nextBuiltInSpName, serverConfigurationActiveSearchParams.get(nextBuiltInSpName));
207                                }
208                        }
209                } else {
210                        searchParams = serverConfigurationActiveSearchParams;
211                }
212
213                return searchParams;
214        }
215
216        protected boolean searchParamEnabled(String theSearchParam) {
217                // Borrowed from
218                // hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ServerCapabilityStatementProvider.java
219                return !Constants.PARAM_FILTER.equals(theSearchParam) || myStorageSettings.isFilterParameterEnabled();
220        }
221
222        private void updateRevIncludesList(
223                        CapabilityStatementRestResourceComponent theNextResource, ResourceSearchParams theSearchParams) {
224                // Add RevInclude to CapabilityStatement.rest.resource
225                if (theNextResource.getSearchRevInclude().isEmpty()) {
226                        String resourcename = theNextResource.getType();
227                        Set<String> allResourceTypes =
228                                        myServerConfiguration.collectMethodBindings().keySet();
229                        for (String otherResourceType : allResourceTypes) {
230                                if (isBlank(otherResourceType)) {
231                                        continue;
232                                }
233                                ResourceSearchParams activeSearchParams = mySearchParamRegistry.getActiveSearchParams(
234                                                otherResourceType, ISearchParamRegistry.SearchParamLookupContextEnum.SEARCH);
235                                activeSearchParams.values().stream()
236                                                .filter(t -> isNotBlank(t.getName()))
237                                                .filter(t -> t.getTargets().contains(resourcename))
238                                                .forEach(t -> theNextResource.addSearchRevInclude(otherResourceType + ":" + t.getName()));
239                        }
240                }
241        }
242
243        private void updateIncludesList(
244                        CapabilityStatementRestResourceComponent theResource, ResourceSearchParams theSearchParams) {
245                // Borrowed from
246                // hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ServerCapabilityStatementProvider.java
247                String resourceName = theResource.getType();
248                if (theResource.getSearchInclude().isEmpty()) {
249                        List<String> includes = theSearchParams.values().stream()
250                                        .filter(t -> t.getParamType() == RestSearchParameterTypeEnum.REFERENCE)
251                                        .map(t -> resourceName + ":" + t.getName())
252                                        .sorted()
253                                        .collect(Collectors.toList());
254                        theResource.addSearchInclude("*");
255                        for (String nextInclude : includes) {
256                                theResource.addSearchInclude(nextInclude);
257                        }
258                }
259        }
260
261        public boolean isIncludeResourceCounts() {
262                return myIncludeResourceCounts;
263        }
264
265        public void setIncludeResourceCounts(boolean theIncludeResourceCounts) {
266                myIncludeResourceCounts = theIncludeResourceCounts;
267        }
268
269        /**
270         * Subclasses may override
271         */
272        protected void massage(CapabilityStatement theStatement) {
273                // nothing
274        }
275
276        public void setStorageSettings(JpaStorageSettings theStorageSettings) {
277                this.myStorageSettings = theStorageSettings;
278        }
279
280        @CoverageIgnore
281        public void setImplementationDescription(String theImplDesc) {
282                myImplementationDescription = theImplDesc;
283        }
284
285        @Override
286        public void setRestfulServer(RestfulServer theRestfulServer) {
287                this.myRestfulServer = theRestfulServer;
288                super.setRestfulServer(theRestfulServer);
289        }
290
291        @CoverageIgnore
292        public void setSystemDao(IFhirSystemDao<Bundle, Meta> mySystemDao) {
293                this.mySystemDao = mySystemDao;
294        }
295}