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