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