001package ca.uhn.fhir.rest.server.provider.dstu2; 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 - 2016 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.instance.model.api.IBaseResource; 043 044import ca.uhn.fhir.context.RuntimeResourceDefinition; 045import ca.uhn.fhir.context.RuntimeSearchParam; 046import ca.uhn.fhir.model.dstu2.resource.Conformance; 047import ca.uhn.fhir.model.dstu2.resource.Conformance.Rest; 048import ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource; 049import ca.uhn.fhir.model.dstu2.resource.Conformance.RestResourceInteraction; 050import ca.uhn.fhir.model.dstu2.resource.Conformance.RestResourceSearchParam; 051import ca.uhn.fhir.model.dstu2.resource.OperationDefinition; 052import ca.uhn.fhir.model.dstu2.resource.OperationDefinition.Parameter; 053import ca.uhn.fhir.model.dstu2.valueset.ConditionalDeleteStatusEnum; 054import ca.uhn.fhir.model.dstu2.valueset.ConformanceResourceStatusEnum; 055import ca.uhn.fhir.model.dstu2.valueset.ConformanceStatementKindEnum; 056import ca.uhn.fhir.model.dstu2.valueset.OperationKindEnum; 057import ca.uhn.fhir.model.dstu2.valueset.OperationParameterUseEnum; 058import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; 059import ca.uhn.fhir.model.dstu2.valueset.RestfulConformanceModeEnum; 060import ca.uhn.fhir.model.dstu2.valueset.SystemRestfulInteractionEnum; 061import ca.uhn.fhir.model.dstu2.valueset.TypeRestfulInteractionEnum; 062import ca.uhn.fhir.model.dstu2.valueset.UnknownContentCodeEnum; 063import ca.uhn.fhir.model.primitive.DateTimeDt; 064import ca.uhn.fhir.model.primitive.IdDt; 065import ca.uhn.fhir.parser.DataFormatException; 066import ca.uhn.fhir.rest.annotation.IdParam; 067import ca.uhn.fhir.rest.annotation.Initialize; 068import ca.uhn.fhir.rest.annotation.Metadata; 069import ca.uhn.fhir.rest.annotation.Read; 070import ca.uhn.fhir.rest.method.BaseMethodBinding; 071import ca.uhn.fhir.rest.method.DynamicSearchMethodBinding; 072import ca.uhn.fhir.rest.method.IParameter; 073import ca.uhn.fhir.rest.method.OperationMethodBinding; 074import ca.uhn.fhir.rest.method.OperationMethodBinding.ReturnType; 075import ca.uhn.fhir.rest.method.OperationParameter; 076import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum; 077import ca.uhn.fhir.rest.method.SearchMethodBinding; 078import ca.uhn.fhir.rest.method.SearchParameter; 079import ca.uhn.fhir.rest.server.Constants; 080import ca.uhn.fhir.rest.server.IServerConformanceProvider; 081import ca.uhn.fhir.rest.server.ResourceBinding; 082import ca.uhn.fhir.rest.server.RestfulServer; 083import ca.uhn.fhir.rest.server.RestulfulServerConfiguration; 084import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; 085 086/** 087 * Server FHIR Provider which serves the conformance statement for a RESTful server implementation 088 * 089 * <p> 090 * Note: This class is safe to extend, but it is important to note that the same instance of {@link Conformance} is always returned unless {@link #setCache(boolean)} is called with a value of 091 * <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. 092 * </p> 093 */ 094public class ServerConformanceProvider implements IServerConformanceProvider<Conformance> { 095 096 private boolean myCache = true; 097 private volatile Conformance myConformance; 098 private IdentityHashMap<OperationMethodBinding, String> myOperationBindingToName; 099 private HashMap<String, List<OperationMethodBinding>> myOperationNameToBindings; 100 private String myPublisher = "Not provided"; 101 private RestulfulServerConfiguration myServerConfiguration; 102 103 public ServerConformanceProvider(RestfulServer theRestfulServer) { 104 this.myServerConfiguration = theRestfulServer.createConfiguration(); 105 } 106 107 public ServerConformanceProvider(RestulfulServerConfiguration theServerConfiguration) { 108 this.myServerConfiguration = theServerConfiguration; 109 } 110 111 /* 112 * 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. 113 */ 114 public ServerConformanceProvider() { 115 super(); 116 } 117 118 public void setRestfulServer(RestfulServer theRestfulServer) { 119 myServerConfiguration = theRestfulServer.createConfiguration(); 120 } 121 122 private void checkBindingForSystemOps(Rest rest, Set<SystemRestfulInteractionEnum> systemOps, BaseMethodBinding<?> nextMethodBinding) { 123 if (nextMethodBinding.getRestOperationType() != null) { 124 String sysOpCode = nextMethodBinding.getRestOperationType().getCode(); 125 if (sysOpCode != null) { 126 SystemRestfulInteractionEnum sysOp = SystemRestfulInteractionEnum.VALUESET_BINDER.fromCodeString(sysOpCode); 127 if (sysOp == null) { 128 return; 129 } 130 if (systemOps.contains(sysOp) == false) { 131 systemOps.add(sysOp); 132 rest.addInteraction().setCode(sysOp); 133 } 134 } 135 } 136 } 137 138 private Map<String, List<BaseMethodBinding<?>>> collectMethodBindings() { 139 Map<String, List<BaseMethodBinding<?>>> resourceToMethods = new TreeMap<String, List<BaseMethodBinding<?>>>(); 140 for (ResourceBinding next : myServerConfiguration.getResourceBindings()) { 141 String resourceName = next.getResourceName(); 142 for (BaseMethodBinding<?> nextMethodBinding : next.getMethodBindings()) { 143 if (resourceToMethods.containsKey(resourceName) == false) { 144 resourceToMethods.put(resourceName, new ArrayList<BaseMethodBinding<?>>()); 145 } 146 resourceToMethods.get(resourceName).add(nextMethodBinding); 147 } 148 } 149 for (BaseMethodBinding<?> nextMethodBinding : myServerConfiguration.getServerBindings()) { 150 String resourceName = ""; 151 if (resourceToMethods.containsKey(resourceName) == false) { 152 resourceToMethods.put(resourceName, new ArrayList<BaseMethodBinding<?>>()); 153 } 154 resourceToMethods.get(resourceName).add(nextMethodBinding); 155 } 156 return resourceToMethods; 157 } 158 159 private String createOperationName(OperationMethodBinding theMethodBinding) { 160 StringBuilder retVal = new StringBuilder(); 161 if (theMethodBinding.getResourceName() != null) { 162 retVal.append(theMethodBinding.getResourceName()); 163 } 164 165 retVal.append('-'); 166 if (theMethodBinding.isCanOperateAtInstanceLevel()) { 167 retVal.append('i'); 168 } 169 if (theMethodBinding.isCanOperateAtServerLevel()) { 170 retVal.append('s'); 171 } 172 retVal.append('-'); 173 174 // Exclude the leading $ 175 retVal.append(theMethodBinding.getName(), 1, theMethodBinding.getName().length()); 176 177 return retVal.toString(); 178 } 179 180 /** 181 * 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 182 * value defaults to "Not provided" but may be set to null, which will cause this element to be omitted. 183 */ 184 public String getPublisher() { 185 return myPublisher; 186 } 187 188 @Override 189 @Metadata 190 public Conformance getServerConformance(HttpServletRequest theRequest) { 191 if (myConformance != null && myCache) { 192 return myConformance; 193 } 194 195 Conformance retVal = new Conformance(); 196 197 retVal.setPublisher(myPublisher); 198 retVal.setDate(conformanceDate()); 199 retVal.setFhirVersion("1.0.2"); // TODO: pull from model 200 retVal.setAcceptUnknown(UnknownContentCodeEnum.UNKNOWN_EXTENSIONS); // TODO: make this configurable - this is a fairly big effort since the parser 201 // needs to be modified to actually allow it 202 203 retVal.getImplementation().setDescription(myServerConfiguration.getImplementationDescription()); 204 retVal.setKind(ConformanceStatementKindEnum.INSTANCE); 205 retVal.getSoftware().setName(myServerConfiguration.getServerName()); 206 retVal.getSoftware().setVersion(myServerConfiguration.getServerVersion()); 207 retVal.addFormat(Constants.CT_FHIR_XML); 208 retVal.addFormat(Constants.CT_FHIR_JSON); 209 210 Rest rest = retVal.addRest(); 211 rest.setMode(RestfulConformanceModeEnum.SERVER); 212 213 Set<SystemRestfulInteractionEnum> systemOps = new HashSet<SystemRestfulInteractionEnum>(); 214 Set<String> operationNames = new HashSet<String>(); 215 216 Map<String, List<BaseMethodBinding<?>>> resourceToMethods = collectMethodBindings(); 217 for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) { 218 219 if (nextEntry.getKey().isEmpty() == false) { 220 Set<TypeRestfulInteractionEnum> resourceOps = new HashSet<TypeRestfulInteractionEnum>(); 221 RestResource resource = rest.addResource(); 222 String resourceName = nextEntry.getKey(); 223 RuntimeResourceDefinition def = myServerConfiguration.getFhirContext().getResourceDefinition(resourceName); 224 resource.getTypeElement().setValue(def.getName()); 225 ServletContext servletContext = (ServletContext) (theRequest == null ? null : theRequest.getAttribute(RestfulServer.SERVLET_CONTEXT_ATTRIBUTE)); 226 String serverBase = myServerConfiguration.getServerAddressStrategy().determineServerBase(servletContext, theRequest); 227 resource.getProfile().setReference(new IdDt(def.getResourceProfile(serverBase))); 228 229 TreeSet<String> includes = new TreeSet<String>(); 230 231 // Map<String, Conformance.RestResourceSearchParam> nameToSearchParam = new HashMap<String, 232 // Conformance.RestResourceSearchParam>(); 233 for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) { 234 if (nextMethodBinding.getRestOperationType() != null) { 235 String resOpCode = nextMethodBinding.getRestOperationType().getCode(); 236 if (resOpCode != null) { 237 TypeRestfulInteractionEnum resOp = TypeRestfulInteractionEnum.VALUESET_BINDER.fromCodeString(resOpCode); 238 if (resOp != null) { 239 if (resourceOps.contains(resOp) == false) { 240 resourceOps.add(resOp); 241 resource.addInteraction().setCode(resOp); 242 } 243 if ("vread".equals(resOpCode)) { 244 // vread implies read 245 resOp = TypeRestfulInteractionEnum.READ; 246 if (resourceOps.contains(resOp) == false) { 247 resourceOps.add(resOp); 248 resource.addInteraction().setCode(resOp); 249 } 250 } 251 252 if (nextMethodBinding.isSupportsConditional()) { 253 switch (resOp) { 254 case CREATE: 255 resource.setConditionalCreate(true); 256 break; 257 case DELETE: 258 if (nextMethodBinding.isSupportsConditionalMultiple()) { 259 resource.setConditionalDelete(ConditionalDeleteStatusEnum.MULTIPLE_DELETES_SUPPORTED); 260 } else { 261 resource.setConditionalDelete(ConditionalDeleteStatusEnum.SINGLE_DELETES_SUPPORTED); 262 } 263 break; 264 case UPDATE: 265 resource.setConditionalUpdate(true); 266 break; 267 default: 268 break; 269 } 270 } 271 } 272 } 273 } 274 275 checkBindingForSystemOps(rest, systemOps, nextMethodBinding); 276 277 if (nextMethodBinding instanceof SearchMethodBinding) { 278 handleSearchMethodBinding(rest, resource, resourceName, def, includes, (SearchMethodBinding) nextMethodBinding); 279 } else if (nextMethodBinding instanceof DynamicSearchMethodBinding) { 280 handleDynamicSearchMethodBinding(resource, def, includes, (DynamicSearchMethodBinding) nextMethodBinding); 281 } else if (nextMethodBinding instanceof OperationMethodBinding) { 282 OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding; 283 String opName = myOperationBindingToName.get(methodBinding); 284 if (operationNames.add(opName)) { 285 // Only add each operation (by name) once 286 rest.addOperation().setName(methodBinding.getName().substring(1)).getDefinition().setReference("OperationDefinition/" + opName); 287 } 288 } 289 290 Collections.sort(resource.getInteraction(), new Comparator<RestResourceInteraction>() { 291 @Override 292 public int compare(RestResourceInteraction theO1, RestResourceInteraction theO2) { 293 TypeRestfulInteractionEnum o1 = theO1.getCodeElement().getValueAsEnum(); 294 TypeRestfulInteractionEnum o2 = theO2.getCodeElement().getValueAsEnum(); 295 if (o1 == null && o2 == null) { 296 return 0; 297 } 298 if (o1 == null) { 299 return 1; 300 } 301 if (o2 == null) { 302 return -1; 303 } 304 return o1.ordinal() - o2.ordinal(); 305 } 306 }); 307 308 } 309 310 for (String nextInclude : includes) { 311 resource.addSearchInclude(nextInclude); 312 } 313 } else { 314 for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) { 315 checkBindingForSystemOps(rest, systemOps, nextMethodBinding); 316 if (nextMethodBinding instanceof OperationMethodBinding) { 317 OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding; 318 String opName = myOperationBindingToName.get(methodBinding); 319 if (operationNames.add(opName)) { 320 rest.addOperation().setName(methodBinding.getName().substring(1)).getDefinition().setReference("OperationDefinition/" + opName); 321 } 322 } 323 } 324 } 325 } 326 327 myConformance = retVal; 328 return retVal; 329 } 330 331 private DateTimeDt conformanceDate() { 332 String buildDate = myServerConfiguration.getConformanceDate(); 333 if (buildDate != null) { 334 try { 335 return new DateTimeDt(buildDate); 336 } catch (DataFormatException e) { 337 // fall through 338 } 339 } 340 return DateTimeDt.withCurrentTime(); 341 } 342 343 private void handleDynamicSearchMethodBinding(RestResource resource, RuntimeResourceDefinition def, TreeSet<String> includes, DynamicSearchMethodBinding searchMethodBinding) { 344 includes.addAll(searchMethodBinding.getIncludes()); 345 346 List<RuntimeSearchParam> searchParameters = new ArrayList<RuntimeSearchParam>(); 347 searchParameters.addAll(searchMethodBinding.getSearchParams()); 348 sortRuntimeSearchParameters(searchParameters); 349 350 if (!searchParameters.isEmpty()) { 351 352 for (RuntimeSearchParam nextParameter : searchParameters) { 353 354 String nextParamName = nextParameter.getName(); 355 356 // String chain = null; 357 String nextParamUnchainedName = nextParamName; 358 if (nextParamName.contains(".")) { 359 // chain = nextParamName.substring(nextParamName.indexOf('.') + 1); 360 nextParamUnchainedName = nextParamName.substring(0, nextParamName.indexOf('.')); 361 } 362 363 String nextParamDescription = nextParameter.getDescription(); 364 365 /* 366 * If the parameter has no description, default to the one from the resource 367 */ 368 if (StringUtils.isBlank(nextParamDescription)) { 369 RuntimeSearchParam paramDef = def.getSearchParam(nextParamUnchainedName); 370 if (paramDef != null) { 371 nextParamDescription = paramDef.getDescription(); 372 } 373 } 374 375 RestResourceSearchParam param; 376 param = resource.addSearchParam(); 377 378 param.setName(nextParamName); 379 // if (StringUtils.isNotBlank(chain)) { 380 // param.addChain(chain); 381 // } 382 param.setDocumentation(nextParamDescription); 383 // param.setType(nextParameter.getParamType()); 384 } 385 } 386 } 387 388 private void handleSearchMethodBinding(Rest rest, RestResource resource, String resourceName, RuntimeResourceDefinition def, TreeSet<String> includes, SearchMethodBinding searchMethodBinding) { 389 includes.addAll(searchMethodBinding.getIncludes()); 390 391 List<IParameter> params = searchMethodBinding.getParameters(); 392 List<SearchParameter> searchParameters = new ArrayList<SearchParameter>(); 393 for (IParameter nextParameter : params) { 394 if ((nextParameter instanceof SearchParameter)) { 395 searchParameters.add((SearchParameter) nextParameter); 396 } 397 } 398 sortSearchParameters(searchParameters); 399 if (!searchParameters.isEmpty()) { 400 // boolean allOptional = searchParameters.get(0).isRequired() == false; 401 // 402 // OperationDefinition query = null; 403 // if (!allOptional) { 404 // RestOperation operation = rest.addOperation(); 405 // query = new OperationDefinition(); 406 // operation.setDefinition(new ResourceReferenceDt(query)); 407 // query.getDescriptionElement().setValue(searchMethodBinding.getDescription()); 408 // query.addUndeclaredExtension(false, ExtensionConstants.QUERY_RETURN_TYPE, new CodeDt(resourceName)); 409 // for (String nextInclude : searchMethodBinding.getIncludes()) { 410 // query.addUndeclaredExtension(false, ExtensionConstants.QUERY_ALLOWED_INCLUDE, new StringDt(nextInclude)); 411 // } 412 // } 413 414 for (SearchParameter nextParameter : searchParameters) { 415 416 String nextParamName = nextParameter.getName(); 417 418 String chain = null; 419 String nextParamUnchainedName = nextParamName; 420 if (nextParamName.contains(".")) { 421 chain = nextParamName.substring(nextParamName.indexOf('.') + 1); 422 nextParamUnchainedName = nextParamName.substring(0, nextParamName.indexOf('.')); 423 } 424 425 String nextParamDescription = nextParameter.getDescription(); 426 427 /* 428 * If the parameter has no description, default to the one from the resource 429 */ 430 if (StringUtils.isBlank(nextParamDescription)) { 431 RuntimeSearchParam paramDef = def.getSearchParam(nextParamUnchainedName); 432 if (paramDef != null) { 433 nextParamDescription = paramDef.getDescription(); 434 } 435 } 436 437 RestResourceSearchParam param = resource.addSearchParam(); 438 param.setName(nextParamUnchainedName); 439 if (StringUtils.isNotBlank(chain)) { 440 param.addChain(chain); 441 } 442 443 if (nextParameter.getParamType() == RestSearchParameterTypeEnum.REFERENCE) { 444 for (String nextWhitelist : new TreeSet<String>(nextParameter.getQualifierWhitelist())) { 445 if (nextWhitelist.startsWith(".")) { 446 param.addChain(nextWhitelist.substring(1)); 447 } 448 } 449 } 450 451 param.setDocumentation(nextParamDescription); 452 if (nextParameter.getParamType() != null) { 453 param.getTypeElement().setValueAsString(nextParameter.getParamType().getCode()); 454 } 455 for (Class<? extends IBaseResource> nextTarget : nextParameter.getDeclaredTypes()) { 456 RuntimeResourceDefinition targetDef = myServerConfiguration.getFhirContext().getResourceDefinition(nextTarget); 457 if (targetDef != null) { 458 ResourceTypeEnum code = ResourceTypeEnum.VALUESET_BINDER.fromCodeString(targetDef.getName()); 459 if (code != null) { 460 param.addTarget(code); 461 } 462 } 463 } 464 } 465 } 466 } 467 468 @Initialize 469 public void initializeOperations() { 470 myOperationBindingToName = new IdentityHashMap<OperationMethodBinding, String>(); 471 myOperationNameToBindings = new HashMap<String, List<OperationMethodBinding>>(); 472 473 Map<String, List<BaseMethodBinding<?>>> resourceToMethods = collectMethodBindings(); 474 for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) { 475 List<BaseMethodBinding<?>> nextMethodBindings = nextEntry.getValue(); 476 for (BaseMethodBinding<?> nextMethodBinding : nextMethodBindings) { 477 if (nextMethodBinding instanceof OperationMethodBinding) { 478 OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding; 479 if (myOperationBindingToName.containsKey(methodBinding)) { 480 continue; 481 } 482 483 String name = createOperationName(methodBinding); 484 myOperationBindingToName.put(methodBinding, name); 485 if (myOperationNameToBindings.containsKey(name) == false) { 486 myOperationNameToBindings.put(name, new ArrayList<OperationMethodBinding>()); 487 } 488 myOperationNameToBindings.get(name).add(methodBinding); 489 } 490 } 491 } 492 } 493 494 @Read(type = OperationDefinition.class) 495 public OperationDefinition readOperationDefinition(@IdParam IdDt theId) { 496 if (theId == null || theId.hasIdPart() == false) { 497 throw new ResourceNotFoundException(theId); 498 } 499 List<OperationMethodBinding> sharedDescriptions = myOperationNameToBindings.get(theId.getIdPart()); 500 if (sharedDescriptions == null || sharedDescriptions.isEmpty()) { 501 throw new ResourceNotFoundException(theId); 502 } 503 504 OperationDefinition op = new OperationDefinition(); 505 op.setStatus(ConformanceResourceStatusEnum.ACTIVE); 506 op.setKind(OperationKindEnum.OPERATION); 507 op.setIdempotent(true); 508 509 Set<String> inParams = new HashSet<String>(); 510 Set<String> outParams = new HashSet<String>(); 511 512 for (OperationMethodBinding sharedDescription : sharedDescriptions) { 513 if (isNotBlank(sharedDescription.getDescription())) { 514 op.setDescription(sharedDescription.getDescription()); 515 } 516 if (!sharedDescription.isIdempotent()) { 517 op.setIdempotent(sharedDescription.isIdempotent()); 518 } 519 op.setCode(sharedDescription.getName().substring(1)); 520 if (sharedDescription.isCanOperateAtInstanceLevel()) { 521 op.setInstance(sharedDescription.isCanOperateAtInstanceLevel()); 522 } 523 if (sharedDescription.isCanOperateAtServerLevel()) { 524 op.setSystem(sharedDescription.isCanOperateAtServerLevel()); 525 } 526 if (isNotBlank(sharedDescription.getResourceName())) { 527 op.addType().setValue(sharedDescription.getResourceName()); 528 } 529 530 for (IParameter nextParamUntyped : sharedDescription.getParameters()) { 531 if (nextParamUntyped instanceof OperationParameter) { 532 OperationParameter nextParam = (OperationParameter) nextParamUntyped; 533 Parameter param = op.addParameter(); 534 if (!inParams.add(nextParam.getName())) { 535 continue; 536 } 537 param.setUse(OperationParameterUseEnum.IN); 538 if (nextParam.getParamType() != null) { 539 param.setType(nextParam.getParamType()); 540 } 541 param.setMin(nextParam.getMin()); 542 param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax())); 543 param.setName(nextParam.getName()); 544 } 545 } 546 547 for (ReturnType nextParam : sharedDescription.getReturnParams()) { 548 if (!outParams.add(nextParam.getName())) { 549 continue; 550 } 551 Parameter param = op.addParameter(); 552 param.setUse(OperationParameterUseEnum.OUT); 553 if (nextParam.getType() != null) { 554 param.setType(nextParam.getType()); 555 } 556 param.setMin(nextParam.getMin()); 557 param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax())); 558 param.setName(nextParam.getName()); 559 } 560 } 561 562 if (isBlank(op.getName())) { 563 if (isNotBlank(op.getDescription())) { 564 op.setName(op.getDescription()); 565 } else { 566 op.setName(op.getCode()); 567 } 568 } 569 570 if (op.getSystem() == null) { 571 op.setSystem(false); 572 } 573 if (op.getInstance() == null) { 574 op.setInstance(false); 575 } 576 577 return op; 578 } 579 580 /** 581 * Sets the cache property (default is true). If set to true, the same response will be returned for each invocation. 582 * <p> 583 * See the class documentation for an important note if you are extending this class 584 * </p> 585 */ 586 public void setCache(boolean theCache) { 587 myCache = theCache; 588 } 589 590 /** 591 * 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 592 * value defaults to "Not provided" but may be set to null, which will cause this element to be omitted. 593 */ 594 public void setPublisher(String thePublisher) { 595 myPublisher = thePublisher; 596 } 597 598 private void sortRuntimeSearchParameters(List<RuntimeSearchParam> searchParameters) { 599 Collections.sort(searchParameters, new Comparator<RuntimeSearchParam>() { 600 @Override 601 public int compare(RuntimeSearchParam theO1, RuntimeSearchParam theO2) { 602 return theO1.getName().compareTo(theO2.getName()); 603 } 604 }); 605 } 606 607 private void sortSearchParameters(List<SearchParameter> searchParameters) { 608 Collections.sort(searchParameters, new Comparator<SearchParameter>() { 609 @Override 610 public int compare(SearchParameter theO1, SearchParameter theO2) { 611 if (theO1.isRequired() == theO2.isRequired()) { 612 return theO1.getName().compareTo(theO2.getName()); 613 } 614 if (theO1.isRequired()) { 615 return -1; 616 } 617 return 1; 618 } 619 }); 620 } 621}