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