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 =
194                                myServerConfiguration.getActiveSearchParams(theResourceName);
195                if (mySearchParamRegistry != null) {
196                        searchParams =
197                                        mySearchParamRegistry.getActiveSearchParams(theResourceName).makeCopy();
198                        if (searchParams == null) {
199                                return ResourceSearchParams.empty(theResourceName);
200                        }
201                        for (String nextBuiltInSpName : serverConfigurationActiveSearchParams.getSearchParamNames()) {
202                                if (nextBuiltInSpName.startsWith("_")
203                                                && !searchParams.containsParamName(nextBuiltInSpName)
204                                                && searchParamEnabled(nextBuiltInSpName)) {
205                                        searchParams.put(nextBuiltInSpName, serverConfigurationActiveSearchParams.get(nextBuiltInSpName));
206                                }
207                        }
208                } else {
209                        searchParams = serverConfigurationActiveSearchParams;
210                }
211
212                return searchParams;
213        }
214
215        protected boolean searchParamEnabled(String theSearchParam) {
216                // Borrowed from
217                // hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ServerCapabilityStatementProvider.java
218                return !Constants.PARAM_FILTER.equals(theSearchParam) || myStorageSettings.isFilterParameterEnabled();
219        }
220
221        private void updateRevIncludesList(
222                        CapabilityStatementRestResourceComponent theNextResource, ResourceSearchParams theSearchParams) {
223                // Add RevInclude to CapabilityStatement.rest.resource
224                if (theNextResource.getSearchRevInclude().isEmpty()) {
225                        String resourcename = theNextResource.getType();
226                        Set<String> allResourceTypes =
227                                        myServerConfiguration.collectMethodBindings().keySet();
228                        for (String otherResourceType : allResourceTypes) {
229                                if (isBlank(otherResourceType)) {
230                                        continue;
231                                }
232                                ResourceSearchParams activeSearchParams =
233                                                mySearchParamRegistry.getActiveSearchParams(otherResourceType);
234                                activeSearchParams.values().stream()
235                                                .filter(t -> isNotBlank(t.getName()))
236                                                .filter(t -> t.getTargets().contains(resourcename))
237                                                .forEach(t -> theNextResource.addSearchRevInclude(otherResourceType + ":" + t.getName()));
238                        }
239                }
240        }
241
242        private void updateIncludesList(
243                        CapabilityStatementRestResourceComponent theResource, ResourceSearchParams theSearchParams) {
244                // Borrowed from
245                // hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ServerCapabilityStatementProvider.java
246                String resourceName = theResource.getType();
247                if (theResource.getSearchInclude().isEmpty()) {
248                        List<String> includes = theSearchParams.values().stream()
249                                        .filter(t -> t.getParamType() == RestSearchParameterTypeEnum.REFERENCE)
250                                        .map(t -> resourceName + ":" + t.getName())
251                                        .sorted()
252                                        .collect(Collectors.toList());
253                        theResource.addSearchInclude("*");
254                        for (String nextInclude : includes) {
255                                theResource.addSearchInclude(nextInclude);
256                        }
257                }
258        }
259
260        public boolean isIncludeResourceCounts() {
261                return myIncludeResourceCounts;
262        }
263
264        public void setIncludeResourceCounts(boolean theIncludeResourceCounts) {
265                myIncludeResourceCounts = theIncludeResourceCounts;
266        }
267
268        /**
269         * Subclasses may override
270         */
271        protected void massage(CapabilityStatement theStatement) {
272                // nothing
273        }
274
275        public void setStorageSettings(JpaStorageSettings theStorageSettings) {
276                this.myStorageSettings = theStorageSettings;
277        }
278
279        @CoverageIgnore
280        public void setImplementationDescription(String theImplDesc) {
281                myImplementationDescription = theImplDesc;
282        }
283
284        @Override
285        public void setRestfulServer(RestfulServer theRestfulServer) {
286                this.myRestfulServer = theRestfulServer;
287                super.setRestfulServer(theRestfulServer);
288        }
289
290        @CoverageIgnore
291        public void setSystemDao(IFhirSystemDao<Bundle, Meta> mySystemDao) {
292                this.mySystemDao = mySystemDao;
293        }
294}