001package ca.uhn.fhir.rest.server; 002 003/* 004 * #%L 005 * HAPI FHIR - Server Framework 006 * %% 007 * Copyright (C) 2014 - 2019 University Health Network 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.context.FhirContext; 024import ca.uhn.fhir.rest.server.method.BaseMethodBinding; 025import ca.uhn.fhir.rest.server.method.OperationMethodBinding; 026import ca.uhn.fhir.rest.server.method.SearchMethodBinding; 027import ca.uhn.fhir.util.VersionUtil; 028import org.hl7.fhir.instance.model.api.IPrimitiveType; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032import java.util.*; 033 034import static org.apache.commons.lang3.StringUtils.isBlank; 035 036import ca.uhn.fhir.context.RuntimeResourceDefinition; 037import java.util.stream.Collectors; 038import org.hl7.fhir.instance.model.api.IBaseResource; 039 040public class RestfulServerConfiguration { 041 042 private static final Logger ourLog = LoggerFactory.getLogger(RestfulServerConfiguration.class); 043 private Collection<ResourceBinding> resourceBindings; 044 private List<BaseMethodBinding<?>> serverBindings; 045 private Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype; 046 private String implementationDescription; 047 private String serverVersion = VersionUtil.getVersion(); 048 private String serverName = "HAPI FHIR"; 049 private FhirContext fhirContext; 050 private IServerAddressStrategy serverAddressStrategy; 051 private IPrimitiveType<Date> myConformanceDate; 052 053 /** 054 * Constructor 055 */ 056 public RestfulServerConfiguration() { 057 super(); 058 } 059 060 /** 061 * Get the resourceBindings 062 * 063 * @return the resourceBindings 064 */ 065 public Collection<ResourceBinding> getResourceBindings() { 066 return resourceBindings; 067 } 068 069 /** 070 * Set the resourceBindings 071 * 072 * @param resourceBindings the resourceBindings to set 073 */ 074 public RestfulServerConfiguration setResourceBindings(Collection<ResourceBinding> resourceBindings) { 075 this.resourceBindings = resourceBindings; 076 return this; 077 } 078 079 /** 080 * Get the serverBindings 081 * 082 * @return the serverBindings 083 */ 084 public List<BaseMethodBinding<?>> getServerBindings() { 085 return serverBindings; 086 } 087 088 /** 089 * Set the theServerBindings 090 */ 091 public RestfulServerConfiguration setServerBindings(List<BaseMethodBinding<?>> theServerBindings) { 092 this.serverBindings = theServerBindings; 093 return this; 094 } 095 096 public Map<String, Class<? extends IBaseResource>> getNameToSharedSupertype() { 097 return resourceNameToSharedSupertype; 098 } 099 100 public RestfulServerConfiguration setNameToSharedSupertype(Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype) { 101 this.resourceNameToSharedSupertype = resourceNameToSharedSupertype; 102 return this; 103 } 104 105 /** 106 * Get the implementationDescription 107 * 108 * @return the implementationDescription 109 */ 110 public String getImplementationDescription() { 111 if (isBlank(implementationDescription)) { 112 return "HAPI FHIR"; 113 } 114 return implementationDescription; 115 } 116 117 /** 118 * Set the implementationDescription 119 * 120 * @param implementationDescription the implementationDescription to set 121 */ 122 public RestfulServerConfiguration setImplementationDescription(String implementationDescription) { 123 this.implementationDescription = implementationDescription; 124 return this; 125 } 126 127 /** 128 * Get the serverVersion 129 * 130 * @return the serverVersion 131 */ 132 public String getServerVersion() { 133 return serverVersion; 134 } 135 136 /** 137 * Set the serverVersion 138 * 139 * @param serverVersion the serverVersion to set 140 */ 141 public RestfulServerConfiguration setServerVersion(String serverVersion) { 142 this.serverVersion = serverVersion; 143 return this; 144 } 145 146 /** 147 * Get the serverName 148 * 149 * @return the serverName 150 */ 151 public String getServerName() { 152 return serverName; 153 } 154 155 /** 156 * Set the serverName 157 * 158 * @param serverName the serverName to set 159 */ 160 public RestfulServerConfiguration setServerName(String serverName) { 161 this.serverName = serverName; 162 return this; 163 } 164 165 /** 166 * Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain providers should generally use this context if one is needed, as opposed to 167 * creating their own. 168 */ 169 public FhirContext getFhirContext() { 170 return this.fhirContext; 171 } 172 173 /** 174 * Set the fhirContext 175 * 176 * @param fhirContext the fhirContext to set 177 */ 178 public RestfulServerConfiguration setFhirContext(FhirContext fhirContext) { 179 this.fhirContext = fhirContext; 180 return this; 181 } 182 183 /** 184 * Get the serverAddressStrategy 185 * 186 * @return the serverAddressStrategy 187 */ 188 public IServerAddressStrategy getServerAddressStrategy() { 189 return serverAddressStrategy; 190 } 191 192 /** 193 * Set the serverAddressStrategy 194 * 195 * @param serverAddressStrategy the serverAddressStrategy to set 196 */ 197 public void setServerAddressStrategy(IServerAddressStrategy serverAddressStrategy) { 198 this.serverAddressStrategy = serverAddressStrategy; 199 } 200 201 /** 202 * Get the date that will be specified in the conformance profile 203 * exported by this server. Typically this would be populated with 204 * an InstanceType. 205 */ 206 public IPrimitiveType<Date> getConformanceDate() { 207 return myConformanceDate; 208 } 209 210 /** 211 * Set the date that will be specified in the conformance profile 212 * exported by this server. Typically this would be populated with 213 * an InstanceType. 214 */ 215 public void setConformanceDate(IPrimitiveType<Date> theConformanceDate) { 216 myConformanceDate = theConformanceDate; 217 } 218 219 public Bindings provideBindings() { 220 IdentityHashMap<SearchMethodBinding, String> myNamedSearchMethodBindingToName = new IdentityHashMap<>(); 221 HashMap<String, List<SearchMethodBinding>> mySearchNameToBindings = new HashMap<>(); 222 IdentityHashMap<OperationMethodBinding, String> myOperationBindingToName = new IdentityHashMap<>(); 223 HashMap<String, List<OperationMethodBinding>> myOperationNameToBindings = new HashMap<>(); 224 225 Map<String, List<BaseMethodBinding<?>>> resourceToMethods = collectMethodBindings(); 226 for (Map.Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) { 227 List<BaseMethodBinding<?>> nextMethodBindings = nextEntry.getValue(); 228 for (BaseMethodBinding<?> nextMethodBinding : nextMethodBindings) { 229 if (nextMethodBinding instanceof OperationMethodBinding) { 230 OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding; 231 if (myOperationBindingToName.containsKey(methodBinding)) { 232 continue; 233 } 234 235 String name = createOperationName(methodBinding); 236 ourLog.debug("Detected operation: {}", name); 237 238 myOperationBindingToName.put(methodBinding, name); 239 if (myOperationNameToBindings.containsKey(name) == false) { 240 myOperationNameToBindings.put(name, new ArrayList<>()); 241 } 242 myOperationNameToBindings.get(name).add(methodBinding); 243 } else if (nextMethodBinding instanceof SearchMethodBinding) { 244 SearchMethodBinding methodBinding = (SearchMethodBinding) nextMethodBinding; 245 if (myNamedSearchMethodBindingToName.containsKey(methodBinding)) { 246 continue; 247 } 248 249 String name = createNamedQueryName(methodBinding); 250 ourLog.debug("Detected named query: {}", name); 251 252 myNamedSearchMethodBindingToName.put(methodBinding, name); 253 if (!mySearchNameToBindings.containsKey(name)) { 254 mySearchNameToBindings.put(name, new ArrayList<>()); 255 } 256 mySearchNameToBindings.get(name).add(methodBinding); 257 } 258 } 259 } 260 261 return new Bindings(myNamedSearchMethodBindingToName, mySearchNameToBindings, myOperationNameToBindings, myOperationBindingToName); 262 } 263 264 public Map<String, List<BaseMethodBinding<?>>> collectMethodBindings() { 265 Map<String, List<BaseMethodBinding<?>>> resourceToMethods = new TreeMap<String, List<BaseMethodBinding<?>>>(); 266 for (ResourceBinding next : getResourceBindings()) { 267 String resourceName = next.getResourceName(); 268 for (BaseMethodBinding<?> nextMethodBinding : next.getMethodBindings()) { 269 if (resourceToMethods.containsKey(resourceName) == false) { 270 resourceToMethods.put(resourceName, new ArrayList<>()); 271 } 272 resourceToMethods.get(resourceName).add(nextMethodBinding); 273 } 274 } 275 for (BaseMethodBinding<?> nextMethodBinding : getServerBindings()) { 276 String resourceName = ""; 277 if (resourceToMethods.containsKey(resourceName) == false) { 278 resourceToMethods.put(resourceName, new ArrayList<>()); 279 } 280 resourceToMethods.get(resourceName).add(nextMethodBinding); 281 } 282 return resourceToMethods; 283 } 284 285 /* 286 * Populates {@link #resourceNameToSharedSupertype} by scanning the given resource providers. Only resource provider getResourceType values 287 * are taken into account. {@link ProvidesResources} and method return types are deliberately ignored. 288 * 289 * Given a resource name, the common superclass for all getResourceType return values for that name's providers is the common superclass 290 * for all returned/received resources with that name. Since {@link ProvidesResources} resources and method return types must also be 291 * subclasses of this common supertype, they can't affect the result of this method. 292 */ 293 public void computeSharedSupertypeForResourcePerName(Collection<IResourceProvider> providers) { 294 Map<String, CommonResourceSupertypeScanner> resourceNameToScanner = new HashMap<>(); 295 296 List<Class<? extends IBaseResource>> providedResourceClasses = providers.stream() 297 .map(provider -> provider.getResourceType()) 298 .collect(Collectors.toList()); 299 providedResourceClasses.stream() 300 .forEach(resourceClass -> { 301 RuntimeResourceDefinition baseDefinition = getFhirContext().getResourceDefinition(resourceClass).getBaseDefinition(); 302 CommonResourceSupertypeScanner scanner = resourceNameToScanner.computeIfAbsent(baseDefinition.getName(), key -> new CommonResourceSupertypeScanner()); 303 scanner.register(resourceClass); 304 }); 305 306 resourceNameToSharedSupertype = resourceNameToScanner.entrySet().stream() 307 .filter(entry -> entry.getValue().getLowestCommonSuperclass().isPresent()) 308 .collect(Collectors.toMap( 309 entry -> entry.getKey(), 310 entry -> entry.getValue().getLowestCommonSuperclass().get())); 311 } 312 313 private String createOperationName(OperationMethodBinding theMethodBinding) { 314 StringBuilder retVal = new StringBuilder(); 315 if (theMethodBinding.getResourceName() != null) { 316 retVal.append(theMethodBinding.getResourceName()); 317 } 318 319 retVal.append('-'); 320 if (theMethodBinding.isCanOperateAtInstanceLevel()) { 321 retVal.append('i'); 322 } 323 if (theMethodBinding.isCanOperateAtServerLevel()) { 324 retVal.append('s'); 325 } 326 retVal.append('-'); 327 328 // Exclude the leading $ 329 retVal.append(theMethodBinding.getName(), 1, theMethodBinding.getName().length()); 330 331 return retVal.toString(); 332 } 333 334 335 private String createNamedQueryName(SearchMethodBinding searchMethodBinding) { 336 StringBuilder retVal = new StringBuilder(); 337 if (searchMethodBinding.getResourceName() != null) { 338 retVal.append(searchMethodBinding.getResourceName()); 339 } 340 retVal.append("-query-"); 341 retVal.append(searchMethodBinding.getQueryName()); 342 343 return retVal.toString(); 344 } 345 346}