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