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