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