001package org.hl7.fhir.r4.hapi.rest.server; 002 003import ca.uhn.fhir.context.FhirVersionEnum; 004import ca.uhn.fhir.context.RuntimeResourceDefinition; 005import ca.uhn.fhir.context.RuntimeSearchParam; 006import ca.uhn.fhir.parser.DataFormatException; 007import ca.uhn.fhir.rest.annotation.IdParam; 008import ca.uhn.fhir.rest.annotation.Initialize; 009import ca.uhn.fhir.rest.annotation.Metadata; 010import ca.uhn.fhir.rest.annotation.Read; 011import ca.uhn.fhir.rest.api.Constants; 012import ca.uhn.fhir.rest.server.IServerConformanceProvider; 013import ca.uhn.fhir.rest.server.ResourceBinding; 014import ca.uhn.fhir.rest.server.RestfulServer; 015import ca.uhn.fhir.rest.server.RestulfulServerConfiguration; 016import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 017import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; 018import ca.uhn.fhir.rest.server.method.*; 019import ca.uhn.fhir.rest.server.method.OperationMethodBinding.ReturnType; 020import ca.uhn.fhir.rest.server.method.SearchParameter; 021import org.apache.commons.lang3.StringUtils; 022import org.hl7.fhir.exceptions.FHIRException; 023import org.hl7.fhir.instance.model.api.IBaseResource; 024import org.hl7.fhir.instance.model.api.IPrimitiveType; 025import org.hl7.fhir.r4.model.*; 026import org.hl7.fhir.r4.model.CapabilityStatement.*; 027import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; 028import org.hl7.fhir.r4.model.OperationDefinition.OperationDefinitionParameterComponent; 029import org.hl7.fhir.r4.model.OperationDefinition.OperationKind; 030import org.hl7.fhir.r4.model.OperationDefinition.OperationParameterUse; 031import org.hl7.fhir.r4.model.codesystems.UnknownContentCode; 032 033import javax.servlet.ServletContext; 034import javax.servlet.http.HttpServletRequest; 035import java.util.*; 036import java.util.Map.Entry; 037import java.util.concurrent.Callable; 038 039import static org.apache.commons.lang3.StringUtils.isBlank; 040import static org.apache.commons.lang3.StringUtils.isNotBlank; 041 042/* 043 * #%L 044 * HAPI FHIR Structures - DSTU2 (FHIR v1.0.0) 045 * %% 046 * Copyright (C) 2014 - 2015 University Health Network 047 * %% 048 * Licensed under the Apache License, Version 2.0 (the "License"); 049 * you may not use this file except in compliance with the License. 050 * You may obtain a copy of the License at 051 * 052 * http://www.apache.org/licenses/LICENSE-2.0 053 * 054 * Unless required by applicable law or agreed to in writing, software 055 * distributed under the License is distributed on an "AS IS" BASIS, 056 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 057 * See the License for the specific language governing permissions and 058 * limitations under the License. 059 * #L% 060 */ 061 062/** 063 * Server FHIR Provider which serves the conformance statement for a RESTful server implementation 064 * 065 * <p> 066 * Note: This class is safe to extend, but it is important to note that the same instance of {@link CapabilityStatement} is always returned unless {@link #setCache(boolean)} is called with a value of 067 * <code>false</code>. This means that if you are adding anything to the returned conformance instance on each call you should call <code>setCache(false)</code> in your provider constructor. 068 * </p> 069 */ 070public class ServerCapabilityStatementProvider implements IServerConformanceProvider<CapabilityStatement> { 071 072 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerCapabilityStatementProvider.class); 073 private boolean myCache = true; 074 private volatile CapabilityStatement myCapabilityStatement; 075 private IdentityHashMap<SearchMethodBinding, String> myNamedSearchMethodBindingToName; 076 private HashMap<String, List<SearchMethodBinding>> mySearchNameToBindings; 077 private IdentityHashMap<OperationMethodBinding, String> myOperationBindingToName; 078 private HashMap<String, List<OperationMethodBinding>> myOperationNameToBindings; 079 private String myPublisher = "Not provided"; 080 private Callable<RestulfulServerConfiguration> myServerConfiguration; 081 082 /** 083 * No-arg constructor and setter so that the ServerConformanceProvider can be Spring-wired with the RestfulService avoiding the potential reference cycle that would happen. 084 */ 085 public ServerCapabilityStatementProvider() { 086 super(); 087 } 088 089 /** 090 * Constructor 091 */ 092 public ServerCapabilityStatementProvider(RestfulServer theRestfulServer) { 093 this.myServerConfiguration = theRestfulServer::createConfiguration; 094 } 095 096 /** 097 * Constructor 098 */ 099 public ServerCapabilityStatementProvider(RestulfulServerConfiguration theServerConfiguration) { 100 this.myServerConfiguration = () -> theServerConfiguration; 101 } 102 103 private void checkBindingForSystemOps(CapabilityStatementRestComponent rest, Set<SystemRestfulInteraction> systemOps, BaseMethodBinding<?> nextMethodBinding) { 104 if (nextMethodBinding.getRestOperationType() != null) { 105 String sysOpCode = nextMethodBinding.getRestOperationType().getCode(); 106 if (sysOpCode != null) { 107 SystemRestfulInteraction sysOp; 108 try { 109 sysOp = SystemRestfulInteraction.fromCode(sysOpCode); 110 } catch (FHIRException e) { 111 return; 112 } 113 if (sysOp == null) { 114 return; 115 } 116 if (systemOps.contains(sysOp) == false) { 117 systemOps.add(sysOp); 118 rest.addInteraction().setCode(sysOp); 119 } 120 } 121 } 122 } 123 124 private Map<String, List<BaseMethodBinding<?>>> collectMethodBindings() { 125 Map<String, List<BaseMethodBinding<?>>> resourceToMethods = new TreeMap<String, List<BaseMethodBinding<?>>>(); 126 for (ResourceBinding next : getServerConfiguration().getResourceBindings()) { 127 String resourceName = next.getResourceName(); 128 for (BaseMethodBinding<?> nextMethodBinding : next.getMethodBindings()) { 129 if (resourceToMethods.containsKey(resourceName) == false) { 130 resourceToMethods.put(resourceName, new ArrayList<BaseMethodBinding<?>>()); 131 } 132 resourceToMethods.get(resourceName).add(nextMethodBinding); 133 } 134 } 135 for (BaseMethodBinding<?> nextMethodBinding : getServerConfiguration().getServerBindings()) { 136 String resourceName = ""; 137 if (resourceToMethods.containsKey(resourceName) == false) { 138 resourceToMethods.put(resourceName, new ArrayList<>()); 139 } 140 resourceToMethods.get(resourceName).add(nextMethodBinding); 141 } 142 return resourceToMethods; 143 } 144 145 private DateTimeType conformanceDate() { 146 IPrimitiveType<Date> buildDate = getServerConfiguration().getConformanceDate(); 147 if (buildDate != null && buildDate.getValue() != null) { 148 try { 149 return new DateTimeType(buildDate.getValueAsString()); 150 } catch (DataFormatException e) { 151 // fall through 152 } 153 } 154 return DateTimeType.now(); 155 } 156 157 private String createNamedQueryName(SearchMethodBinding searchMethodBinding) { 158 StringBuilder retVal = new StringBuilder(); 159 if (searchMethodBinding.getResourceName() != null) { 160 retVal.append(searchMethodBinding.getResourceName()); 161 } 162 retVal.append("-query-"); 163 retVal.append(searchMethodBinding.getQueryName()); 164 165 return retVal.toString(); 166 } 167 168 private String createOperationName(OperationMethodBinding theMethodBinding) { 169 StringBuilder retVal = new StringBuilder(); 170 if (theMethodBinding.getResourceName() != null) { 171 retVal.append(theMethodBinding.getResourceName()); 172 } 173 174 retVal.append('-'); 175 if (theMethodBinding.isCanOperateAtInstanceLevel()) { 176 retVal.append('i'); 177 } 178 if (theMethodBinding.isCanOperateAtServerLevel()) { 179 retVal.append('s'); 180 } 181 retVal.append('-'); 182 183 // Exclude the leading $ 184 retVal.append(theMethodBinding.getName(), 1, theMethodBinding.getName().length()); 185 186 return retVal.toString(); 187 } 188 189 /** 190 * Gets the value of the "publisher" that will be placed in the generated conformance statement. As this is a mandatory element, the value should not be null (although this is not enforced). The 191 * value defaults to "Not provided" but may be set to null, which will cause this element to be omitted. 192 */ 193 public String getPublisher() { 194 return myPublisher; 195 } 196 197 /** 198 * Sets the value of the "publisher" that will be placed in the generated conformance statement. As this is a mandatory element, the value should not be null (although this is not enforced). The 199 * value defaults to "Not provided" but may be set to null, which will cause this element to be omitted. 200 */ 201 public void setPublisher(String thePublisher) { 202 myPublisher = thePublisher; 203 } 204 205 RestulfulServerConfiguration getServerConfiguration() { 206 try { 207 return myServerConfiguration.call(); 208 } catch (Exception e) { 209 throw new InternalErrorException(e); 210 } 211 } 212 213 @Override 214 @Metadata 215 public CapabilityStatement getServerConformance(HttpServletRequest theRequest) { 216 if (myCapabilityStatement != null && myCache) { 217 return myCapabilityStatement; 218 } 219 220 CapabilityStatement retVal = new CapabilityStatement(); 221 222 retVal.setPublisher(myPublisher); 223 retVal.setDateElement(conformanceDate()); 224 retVal.setFhirVersion(Enumerations.FHIRVersion.fromCode(FhirVersionEnum.R4.getFhirVersionString())); 225 226 ServletContext servletContext = (ServletContext) (theRequest == null ? null : theRequest.getAttribute(RestfulServer.SERVLET_CONTEXT_ATTRIBUTE)); 227 String serverBase = getServerConfiguration().getServerAddressStrategy().determineServerBase(servletContext, theRequest); 228 retVal 229 .getImplementation() 230 .setUrl(serverBase) 231 .setDescription(getServerConfiguration().getImplementationDescription()); 232 233 retVal.setKind(CapabilityStatementKind.INSTANCE); 234 retVal.getSoftware().setName(getServerConfiguration().getServerName()); 235 retVal.getSoftware().setVersion(getServerConfiguration().getServerVersion()); 236 retVal.addFormat(Constants.CT_FHIR_XML_NEW); 237 retVal.addFormat(Constants.CT_FHIR_JSON_NEW); 238 retVal.setStatus(PublicationStatus.ACTIVE); 239 240 CapabilityStatementRestComponent rest = retVal.addRest(); 241 rest.setMode(RestfulCapabilityMode.SERVER); 242 243 Set<SystemRestfulInteraction> systemOps = new HashSet<>(); 244 Set<String> operationNames = new HashSet<>(); 245 246 Map<String, List<BaseMethodBinding<?>>> resourceToMethods = collectMethodBindings(); 247 for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) { 248 249 if (nextEntry.getKey().isEmpty() == false) { 250 Set<TypeRestfulInteraction> resourceOps = new HashSet<>(); 251 CapabilityStatementRestResourceComponent resource = rest.addResource(); 252 String resourceName = nextEntry.getKey(); 253 RuntimeResourceDefinition def = getServerConfiguration().getFhirContext().getResourceDefinition(resourceName); 254 resource.getTypeElement().setValue(def.getName()); 255 resource.getProfileElement().setValue((def.getResourceProfile(serverBase))); 256 257 TreeSet<String> includes = new TreeSet<>(); 258 259 // Map<String, CapabilityStatement.RestResourceSearchParam> nameToSearchParam = new HashMap<String, 260 // CapabilityStatement.RestResourceSearchParam>(); 261 for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) { 262 if (nextMethodBinding.getRestOperationType() != null) { 263 String resOpCode = nextMethodBinding.getRestOperationType().getCode(); 264 if (resOpCode != null) { 265 TypeRestfulInteraction resOp; 266 try { 267 resOp = TypeRestfulInteraction.fromCode(resOpCode); 268 } catch (Exception e) { 269 resOp = null; 270 } 271 if (resOp != null) { 272 if (resourceOps.contains(resOp) == false) { 273 resourceOps.add(resOp); 274 resource.addInteraction().setCode(resOp); 275 } 276 if ("vread".equals(resOpCode)) { 277 // vread implies read 278 resOp = TypeRestfulInteraction.READ; 279 if (resourceOps.contains(resOp) == false) { 280 resourceOps.add(resOp); 281 resource.addInteraction().setCode(resOp); 282 } 283 } 284 285 if (nextMethodBinding.isSupportsConditional()) { 286 switch (resOp) { 287 case CREATE: 288 resource.setConditionalCreate(true); 289 break; 290 case DELETE: 291 if (nextMethodBinding.isSupportsConditionalMultiple()) { 292 resource.setConditionalDelete(ConditionalDeleteStatus.MULTIPLE); 293 } else { 294 resource.setConditionalDelete(ConditionalDeleteStatus.SINGLE); 295 } 296 break; 297 case UPDATE: 298 resource.setConditionalUpdate(true); 299 break; 300 default: 301 break; 302 } 303 } 304 } 305 } 306 } 307 308 checkBindingForSystemOps(rest, systemOps, nextMethodBinding); 309 310 if (nextMethodBinding instanceof SearchMethodBinding) { 311 SearchMethodBinding methodBinding = (SearchMethodBinding) nextMethodBinding; 312 if (methodBinding.getQueryName() != null) { 313 String queryName = myNamedSearchMethodBindingToName.get(methodBinding); 314 if (operationNames.add(queryName)) { 315 rest.addOperation().setName(methodBinding.getQueryName()).setDefinition(("OperationDefinition/" + queryName)); 316 } 317 } else { 318 handleNamelessSearchMethodBinding(rest, resource, resourceName, def, includes, (SearchMethodBinding) nextMethodBinding); 319 } 320 } else if (nextMethodBinding instanceof OperationMethodBinding) { 321 OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding; 322 String opName = myOperationBindingToName.get(methodBinding); 323 if (operationNames.add(opName)) { 324 // Only add each operation (by name) once 325 rest.addOperation().setName(methodBinding.getName().substring(1)).setDefinition(("OperationDefinition/" + opName)); 326 } 327 } 328 329 resource.getInteraction().sort(new Comparator<ResourceInteractionComponent>() { 330 @Override 331 public int compare(ResourceInteractionComponent theO1, ResourceInteractionComponent theO2) { 332 TypeRestfulInteraction o1 = theO1.getCode(); 333 TypeRestfulInteraction o2 = theO2.getCode(); 334 if (o1 == null && o2 == null) { 335 return 0; 336 } 337 if (o1 == null) { 338 return 1; 339 } 340 if (o2 == null) { 341 return -1; 342 } 343 return o1.ordinal() - o2.ordinal(); 344 } 345 }); 346 347 } 348 349 for (String nextInclude : includes) { 350 resource.addSearchInclude(nextInclude); 351 } 352 } else { 353 for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) { 354 checkBindingForSystemOps(rest, systemOps, nextMethodBinding); 355 if (nextMethodBinding instanceof OperationMethodBinding) { 356 OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding; 357 String opName = myOperationBindingToName.get(methodBinding); 358 if (operationNames.add(opName)) { 359 ourLog.debug("Found bound operation: {}", opName); 360 rest.addOperation().setName(methodBinding.getName().substring(1)).setDefinition(("OperationDefinition/" + opName)); 361 } 362 } 363 } 364 } 365 } 366 367 myCapabilityStatement = retVal; 368 return retVal; 369 } 370 371 private void handleNamelessSearchMethodBinding(CapabilityStatementRestComponent rest, CapabilityStatementRestResourceComponent resource, String resourceName, RuntimeResourceDefinition def, TreeSet<String> includes, 372 SearchMethodBinding searchMethodBinding) { 373 includes.addAll(searchMethodBinding.getIncludes()); 374 375 List<IParameter> params = searchMethodBinding.getParameters(); 376 List<SearchParameter> searchParameters = new ArrayList<SearchParameter>(); 377 for (IParameter nextParameter : params) { 378 if ((nextParameter instanceof SearchParameter)) { 379 searchParameters.add((SearchParameter) nextParameter); 380 } 381 } 382 sortSearchParameters(searchParameters); 383 if (!searchParameters.isEmpty()) { 384 // boolean allOptional = searchParameters.get(0).isRequired() == false; 385 // 386 // OperationDefinition query = null; 387 // if (!allOptional) { 388 // RestOperation operation = rest.addOperation(); 389 // query = new OperationDefinition(); 390 // operation.setDefinition(new ResourceReferenceDt(query)); 391 // query.getDescriptionElement().setValue(searchMethodBinding.getDescription()); 392 // query.addUndeclaredExtension(false, ExtensionConstants.QUERY_RETURN_TYPE, new CodeDt(resourceName)); 393 // for (String nextInclude : searchMethodBinding.getIncludes()) { 394 // query.addUndeclaredExtension(false, ExtensionConstants.QUERY_ALLOWED_INCLUDE, new StringDt(nextInclude)); 395 // } 396 // } 397 398 for (SearchParameter nextParameter : searchParameters) { 399 400 String nextParamName = nextParameter.getName(); 401 402 String chain = null; 403 String nextParamUnchainedName = nextParamName; 404 if (nextParamName.contains(".")) { 405 chain = nextParamName.substring(nextParamName.indexOf('.') + 1); 406 nextParamUnchainedName = nextParamName.substring(0, nextParamName.indexOf('.')); 407 } 408 409 String nextParamDescription = nextParameter.getDescription(); 410 411 /* 412 * If the parameter has no description, default to the one from the resource 413 */ 414 if (StringUtils.isBlank(nextParamDescription)) { 415 RuntimeSearchParam paramDef = def.getSearchParam(nextParamUnchainedName); 416 if (paramDef != null) { 417 nextParamDescription = paramDef.getDescription(); 418 } 419 } 420 421 CapabilityStatementRestResourceSearchParamComponent param = resource.addSearchParam(); 422 param.setName(nextParamUnchainedName); 423 424// if (StringUtils.isNotBlank(chain)) { 425// param.addChain(chain); 426// } 427// 428// if (nextParameter.getParamType() == RestSearchParameterTypeEnum.REFERENCE) { 429// for (String nextWhitelist : new TreeSet<String>(nextParameter.getQualifierWhitelist())) { 430// if (nextWhitelist.startsWith(".")) { 431// param.addChain(nextWhitelist.substring(1)); 432// } 433// } 434// } 435 436 param.setDocumentation(nextParamDescription); 437 if (nextParameter.getParamType() != null) { 438 param.getTypeElement().setValueAsString(nextParameter.getParamType().getCode()); 439 } 440 for (Class<? extends IBaseResource> nextTarget : nextParameter.getDeclaredTypes()) { 441 RuntimeResourceDefinition targetDef = getServerConfiguration().getFhirContext().getResourceDefinition(nextTarget); 442 if (targetDef != null) { 443 ResourceType code; 444 try { 445 code = ResourceType.fromCode(targetDef.getName()); 446 } catch (FHIRException e) { 447 code = null; 448 } 449// if (code != null) { 450// param.addTarget(targetDef.getName()); 451// } 452 } 453 } 454 } 455 } 456 } 457 458 @Initialize 459 public void initializeOperations() { 460 myNamedSearchMethodBindingToName = new IdentityHashMap<>(); 461 mySearchNameToBindings = new HashMap<>(); 462 myOperationBindingToName = new IdentityHashMap<>(); 463 myOperationNameToBindings = new HashMap<>(); 464 465 Map<String, List<BaseMethodBinding<?>>> resourceToMethods = collectMethodBindings(); 466 for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) { 467 List<BaseMethodBinding<?>> nextMethodBindings = nextEntry.getValue(); 468 for (BaseMethodBinding<?> nextMethodBinding : nextMethodBindings) { 469 if (nextMethodBinding instanceof OperationMethodBinding) { 470 OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding; 471 if (myOperationBindingToName.containsKey(methodBinding)) { 472 continue; 473 } 474 475 String name = createOperationName(methodBinding); 476 ourLog.debug("Detected operation: {}", name); 477 478 myOperationBindingToName.put(methodBinding, name); 479 if (myOperationNameToBindings.containsKey(name) == false) { 480 myOperationNameToBindings.put(name, new ArrayList<>()); 481 } 482 myOperationNameToBindings.get(name).add(methodBinding); 483 } else if (nextMethodBinding instanceof SearchMethodBinding) { 484 SearchMethodBinding methodBinding = (SearchMethodBinding) nextMethodBinding; 485 if (myNamedSearchMethodBindingToName.containsKey(methodBinding)) { 486 continue; 487 } 488 489 String name = createNamedQueryName(methodBinding); 490 ourLog.debug("Detected named query: {}", name); 491 492 myNamedSearchMethodBindingToName.put(methodBinding, name); 493 if (!mySearchNameToBindings.containsKey(name)) { 494 mySearchNameToBindings.put(name, new ArrayList<>()); 495 } 496 mySearchNameToBindings.get(name).add(methodBinding); 497 } 498 } 499 } 500 } 501 502 @Read(type = OperationDefinition.class) 503 public OperationDefinition readOperationDefinition(@IdParam IdType theId) { 504 if (theId == null || theId.hasIdPart() == false) { 505 throw new ResourceNotFoundException(theId); 506 } 507 List<OperationMethodBinding> operationBindings = myOperationNameToBindings.get(theId.getIdPart()); 508 if (operationBindings != null && !operationBindings.isEmpty()) { 509 return readOperationDefinitionForOperation(operationBindings); 510 } 511 List<SearchMethodBinding> searchBindings = mySearchNameToBindings.get(theId.getIdPart()); 512 if (searchBindings != null && !searchBindings.isEmpty()) { 513 return readOperationDefinitionForNamedSearch(searchBindings); 514 } 515 throw new ResourceNotFoundException(theId); 516 } 517 518 private OperationDefinition readOperationDefinitionForNamedSearch(List<SearchMethodBinding> bindings) { 519 OperationDefinition op = new OperationDefinition(); 520 op.setStatus(PublicationStatus.ACTIVE); 521 op.setKind(OperationKind.QUERY); 522 op.setAffectsState(false); 523 524 op.setSystem(false); 525 op.setType(false); 526 op.setInstance(false); 527 528 Set<String> inParams = new HashSet<>(); 529 530 for (SearchMethodBinding binding : bindings) { 531 if (isNotBlank(binding.getDescription())) { 532 op.setDescription(binding.getDescription()); 533 } 534 if (isBlank(binding.getResourceProviderResourceName())) { 535 op.setSystem(true); 536 } else { 537 op.setType(true); 538 op.addResourceElement().setValue(binding.getResourceProviderResourceName()); 539 } 540 op.setCode(binding.getQueryName()); 541 for (IParameter nextParamUntyped : binding.getParameters()) { 542 if (nextParamUntyped instanceof SearchParameter) { 543 SearchParameter nextParam = (SearchParameter) nextParamUntyped; 544 if (!inParams.add(nextParam.getName())) { 545 continue; 546 } 547 OperationDefinitionParameterComponent param = op.addParameter(); 548 param.setUse(OperationParameterUse.IN); 549 param.setType("string"); 550 param.getSearchTypeElement().setValueAsString(nextParam.getParamType().getCode()); 551 param.setMin(nextParam.isRequired() ? 1 : 0); 552 param.setMax("1"); 553 param.setName(nextParam.getName()); 554 } 555 } 556 557 if (isBlank(op.getName())) { 558 if (isNotBlank(op.getDescription())) { 559 op.setName(op.getDescription()); 560 } else { 561 op.setName(op.getCode()); 562 } 563 } 564 } 565 566 return op; 567 } 568 569 private OperationDefinition readOperationDefinitionForOperation(List<OperationMethodBinding> bindings) { 570 OperationDefinition op = new OperationDefinition(); 571 op.setStatus(PublicationStatus.ACTIVE); 572 op.setKind(OperationKind.OPERATION); 573 op.setAffectsState(false); 574 575 // We reset these to true below if we find a binding that can handle the level 576 op.setSystem(false); 577 op.setType(false); 578 op.setInstance(false); 579 580 Set<String> inParams = new HashSet<>(); 581 Set<String> outParams = new HashSet<>(); 582 583 for (OperationMethodBinding sharedDescription : bindings) { 584 if (isNotBlank(sharedDescription.getDescription())) { 585 op.setDescription(sharedDescription.getDescription()); 586 } 587 if (sharedDescription.isCanOperateAtInstanceLevel()) { 588 op.setInstance(true); 589 } 590 if (sharedDescription.isCanOperateAtServerLevel()) { 591 op.setSystem(true); 592 } 593 if (sharedDescription.isCanOperateAtTypeLevel()) { 594 op.setType(true); 595 } 596 if (!sharedDescription.isIdempotent()) { 597 op.setAffectsState(!sharedDescription.isIdempotent()); 598 } 599 op.setCode(sharedDescription.getName().substring(1)); 600 if (sharedDescription.isCanOperateAtInstanceLevel()) { 601 op.setInstance(sharedDescription.isCanOperateAtInstanceLevel()); 602 } 603 if (sharedDescription.isCanOperateAtServerLevel()) { 604 op.setSystem(sharedDescription.isCanOperateAtServerLevel()); 605 } 606 if (isNotBlank(sharedDescription.getResourceName())) { 607 op.addResourceElement().setValue(sharedDescription.getResourceName()); 608 } 609 610 for (IParameter nextParamUntyped : sharedDescription.getParameters()) { 611 if (nextParamUntyped instanceof OperationParameter) { 612 OperationParameter nextParam = (OperationParameter) nextParamUntyped; 613 OperationDefinitionParameterComponent param = op.addParameter(); 614 if (!inParams.add(nextParam.getName())) { 615 continue; 616 } 617 param.setUse(OperationParameterUse.IN); 618 if (nextParam.getParamType() != null) { 619 param.setType(nextParam.getParamType()); 620 } 621 if (nextParam.getSearchParamType() != null) { 622 param.getSearchTypeElement().setValueAsString(nextParam.getSearchParamType()); 623 } 624 param.setMin(nextParam.getMin()); 625 param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax())); 626 param.setName(nextParam.getName()); 627 } 628 } 629 630 for (ReturnType nextParam : sharedDescription.getReturnParams()) { 631 if (!outParams.add(nextParam.getName())) { 632 continue; 633 } 634 OperationDefinitionParameterComponent param = op.addParameter(); 635 param.setUse(OperationParameterUse.OUT); 636 if (nextParam.getType() != null) { 637 param.setType(nextParam.getType()); 638 } 639 param.setMin(nextParam.getMin()); 640 param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax())); 641 param.setName(nextParam.getName()); 642 } 643 } 644 645 if (isBlank(op.getName())) { 646 if (isNotBlank(op.getDescription())) { 647 op.setName(op.getDescription()); 648 } else { 649 op.setName(op.getCode()); 650 } 651 } 652 653 if (op.hasSystem() == false) { 654 op.setSystem(false); 655 } 656 if (op.hasInstance() == false) { 657 op.setInstance(false); 658 } 659 660 return op; 661 } 662 663 /** 664 * Sets the cache property (default is true). If set to true, the same response will be returned for each invocation. 665 * <p> 666 * See the class documentation for an important note if you are extending this class 667 * </p> 668 */ 669 public ServerCapabilityStatementProvider setCache(boolean theCache) { 670 myCache = theCache; 671 return this; 672 } 673 674 @Override 675 public void setRestfulServer(RestfulServer theRestfulServer) { 676 myServerConfiguration = theRestfulServer::createConfiguration; 677 } 678 679 private void sortRuntimeSearchParameters(List<RuntimeSearchParam> searchParameters) { 680 Collections.sort(searchParameters, new Comparator<RuntimeSearchParam>() { 681 @Override 682 public int compare(RuntimeSearchParam theO1, RuntimeSearchParam theO2) { 683 return theO1.getName().compareTo(theO2.getName()); 684 } 685 }); 686 } 687 688 private void sortSearchParameters(List<SearchParameter> searchParameters) { 689 Collections.sort(searchParameters, new Comparator<SearchParameter>() { 690 @Override 691 public int compare(SearchParameter theO1, SearchParameter theO2) { 692 if (theO1.isRequired() == theO2.isRequired()) { 693 return theO1.getName().compareTo(theO2.getName()); 694 } 695 if (theO1.isRequired()) { 696 return -1; 697 } 698 return 1; 699 } 700 }); 701 } 702}