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