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}