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.setStatus(PublicationStatus.ACTIVE);
228
229    CapabilityStatementRestComponent rest = retVal.addRest();
230    rest.setMode(RestfulCapabilityMode.SERVER);
231
232    Set<SystemRestfulInteraction> systemOps = new HashSet<>();
233    Set<String> operationNames = new HashSet<>();
234
235    Map<String, List<BaseMethodBinding<?>>> resourceToMethods = collectMethodBindings(theRequestDetails);
236    Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype = serverConfiguration.getNameToSharedSupertype();
237    for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) {
238
239      if (nextEntry.getKey().isEmpty() == false) {
240        Set<TypeRestfulInteraction> resourceOps = new HashSet<>();
241        CapabilityStatementRestResourceComponent resource = rest.addResource();
242        String resourceName = nextEntry.getKey();
243        
244        RuntimeResourceDefinition def;
245        FhirContext context = serverConfiguration.getFhirContext();
246        if (resourceNameToSharedSupertype.containsKey(resourceName)) {
247          def = context.getResourceDefinition(resourceNameToSharedSupertype.get(resourceName));
248        } else {
249          def = context.getResourceDefinition(resourceName);
250        }
251        resource.getTypeElement().setValue(def.getName());
252        resource.getProfile().setReference((def.getResourceProfile(serverBase)));
253
254        TreeSet<String> includes = new TreeSet<>();
255
256        // Map<String, CapabilityStatement.RestResourceSearchParam> nameToSearchParam = new HashMap<String,
257        // CapabilityStatement.RestResourceSearchParam>();
258        for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) {
259          if (nextMethodBinding.getRestOperationType() != null) {
260            String resOpCode = nextMethodBinding.getRestOperationType().getCode();
261            if (resOpCode != null) {
262              TypeRestfulInteraction resOp;
263              try {
264                resOp = TypeRestfulInteraction.fromCode(resOpCode);
265              } catch (Exception e) {
266                resOp = null;
267              }
268              if (resOp != null) {
269                if (resourceOps.contains(resOp) == false) {
270                  resourceOps.add(resOp);
271                  resource.addInteraction().setCode(resOp);
272                }
273                if ("vread".equals(resOpCode)) {
274                  // vread implies read
275                  resOp = TypeRestfulInteraction.READ;
276                  if (resourceOps.contains(resOp) == false) {
277                    resourceOps.add(resOp);
278                    resource.addInteraction().setCode(resOp);
279                  }
280                }
281
282                if (nextMethodBinding.isSupportsConditional()) {
283                  switch (resOp) {
284                    case CREATE:
285                      resource.setConditionalCreate(true);
286                      break;
287                    case DELETE:
288                      if (nextMethodBinding.isSupportsConditionalMultiple()) {
289                        resource.setConditionalDelete(ConditionalDeleteStatus.MULTIPLE);
290                      } else {
291                        resource.setConditionalDelete(ConditionalDeleteStatus.SINGLE);
292                      }
293                      break;
294                    case UPDATE:
295                      resource.setConditionalUpdate(true);
296                      break;
297                    default:
298                      break;
299                  }
300                }
301              }
302            }
303          }
304
305          checkBindingForSystemOps(rest, systemOps, nextMethodBinding);
306
307          if (nextMethodBinding instanceof SearchMethodBinding) {
308            SearchMethodBinding methodBinding = (SearchMethodBinding) nextMethodBinding;
309            if (methodBinding.getQueryName() != null) {
310                String queryName = bindings.getNamedSearchMethodBindingToName().get(methodBinding);
311                if (operationNames.add(queryName)) {
312                    rest.addOperation().setName(methodBinding.getQueryName()).setDefinition(new Reference("OperationDefinition/" + queryName));
313                }
314            } else {
315                handleNamelessSearchMethodBinding(rest, resource, resourceName, def, includes, (SearchMethodBinding) nextMethodBinding, theRequestDetails);
316            }
317          } else if (nextMethodBinding instanceof OperationMethodBinding) {
318            OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
319            String opName = bindings.getOperationBindingToName().get(methodBinding);
320            if (operationNames.add(opName)) {
321              // Only add each operation (by name) once
322              rest.addOperation().setName(methodBinding.getName().substring(1)).setDefinition(new Reference("OperationDefinition/" + opName));
323            }
324          }
325
326          resource.getInteraction().sort(new Comparator<ResourceInteractionComponent>() {
327            @Override
328            public int compare(ResourceInteractionComponent theO1, ResourceInteractionComponent theO2) {
329              TypeRestfulInteraction o1 = theO1.getCode();
330              TypeRestfulInteraction o2 = theO2.getCode();
331              if (o1 == null && o2 == null) {
332                return 0;
333              }
334              if (o1 == null) {
335                return 1;
336              }
337              if (o2 == null) {
338                return -1;
339              }
340              return o1.ordinal() - o2.ordinal();
341            }
342          });
343
344        }
345
346        for (String nextInclude : includes) {
347          resource.addSearchInclude(nextInclude);
348        }
349      } else {
350        for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) {
351          checkBindingForSystemOps(rest, systemOps, nextMethodBinding);
352          if (nextMethodBinding instanceof OperationMethodBinding) {
353            OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
354            String opName = bindings.getOperationBindingToName().get(methodBinding);
355            if (operationNames.add(opName)) {
356              ourLog.debug("Found bound operation: {}", opName);
357              rest.addOperation().setName(methodBinding.getName().substring(1)).setDefinition(new Reference("OperationDefinition/" + opName));
358            }
359          }
360        }
361      }
362    }
363
364    return retVal;
365  }
366
367
368
369  private void handleNamelessSearchMethodBinding(CapabilityStatementRestComponent rest, CapabilityStatementRestResourceComponent resource, String resourceName, RuntimeResourceDefinition def, TreeSet<String> includes,
370                                                 SearchMethodBinding searchMethodBinding, RequestDetails theRequestDetails) {
371    includes.addAll(searchMethodBinding.getIncludes());
372
373    List<IParameter> params = searchMethodBinding.getParameters();
374    List<SearchParameter> searchParameters = new ArrayList<>();
375    for (IParameter nextParameter : params) {
376      if ((nextParameter instanceof SearchParameter)) {
377        searchParameters.add((SearchParameter) nextParameter);
378      }
379    }
380    sortSearchParameters(searchParameters);
381    if (!searchParameters.isEmpty()) {
382      // boolean allOptional = searchParameters.get(0).isRequired() == false;
383      //
384      // OperationDefinition query = null;
385      // if (!allOptional) {
386      // RestOperation operation = rest.addOperation();
387      // query = new OperationDefinition();
388      // operation.setDefinition(new ResourceReferenceDt(query));
389      // query.getDescriptionElement().setValue(searchMethodBinding.getDescription());
390      // query.addUndeclaredExtension(false, ExtensionConstants.QUERY_RETURN_TYPE, new CodeDt(resourceName));
391      // for (String nextInclude : searchMethodBinding.getIncludes()) {
392      // query.addUndeclaredExtension(false, ExtensionConstants.QUERY_ALLOWED_INCLUDE, new StringDt(nextInclude));
393      // }
394      // }
395
396      for (SearchParameter nextParameter : searchParameters) {
397
398        String nextParamName = nextParameter.getName();
399
400        String chain = null;
401        String nextParamUnchainedName = nextParamName;
402        if (nextParamName.contains(".")) {
403          chain = nextParamName.substring(nextParamName.indexOf('.') + 1);
404          nextParamUnchainedName = nextParamName.substring(0, nextParamName.indexOf('.'));
405        }
406
407        String nextParamDescription = nextParameter.getDescription();
408
409        /*
410         * If the parameter has no description, default to the one from the resource
411         */
412        if (StringUtils.isBlank(nextParamDescription)) {
413          RuntimeSearchParam paramDef = def.getSearchParam(nextParamUnchainedName);
414          if (paramDef != null) {
415            nextParamDescription = paramDef.getDescription();
416          }
417        }
418
419        CapabilityStatementRestResourceSearchParamComponent param = resource.addSearchParam();
420        param.setName(nextParamUnchainedName);
421
422//                              if (StringUtils.isNotBlank(chain)) {
423//                                      param.addChain(chain);
424//                              }
425//
426//                              if (nextParameter.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
427//                                      for (String nextWhitelist : new TreeSet<String>(nextParameter.getQualifierWhitelist())) {
428//                                              if (nextWhitelist.startsWith(".")) {
429//                                                      param.addChain(nextWhitelist.substring(1));
430//                                              }
431//                                      }
432//                              }
433
434        param.setDocumentation(nextParamDescription);
435        if (nextParameter.getParamType() != null) {
436          param.getTypeElement().setValueAsString(nextParameter.getParamType().getCode());
437        }
438        for (Class<? extends IBaseResource> nextTarget : nextParameter.getDeclaredTypes()) {
439          RuntimeResourceDefinition targetDef = getServerConfiguration(theRequestDetails).getFhirContext().getResourceDefinition(nextTarget);
440          if (targetDef != null) {
441            ResourceType code;
442            try {
443              code = ResourceType.fromCode(targetDef.getName());
444            } catch (FHIRException e) {
445              code = null;
446            }
447//                                              if (code != null) {
448//                                                      param.addTarget(targetDef.getName());
449//                                              }
450          }
451        }
452      }
453    }
454  }
455
456
457  
458  @Read(type = OperationDefinition.class)
459  public OperationDefinition readOperationDefinition(@IdParam IdType theId, RequestDetails theRequestDetails) {
460    if (theId == null || theId.hasIdPart() == false) {
461      throw new ResourceNotFoundException(theId);
462    }
463
464    RestfulServerConfiguration serverConfiguration = getServerConfiguration(theRequestDetails);
465    Bindings bindings = serverConfiguration.provideBindings();
466
467    List<OperationMethodBinding> operationBindings = bindings.getOperationNameToBindings().get(theId.getIdPart());
468    if (operationBindings != null && !operationBindings.isEmpty()) {
469        return readOperationDefinitionForOperation(operationBindings);
470    }
471    List<SearchMethodBinding> searchBindings = bindings.getSearchNameToBindings().get(theId.getIdPart());
472    if (searchBindings != null && !searchBindings.isEmpty()) {
473        return readOperationDefinitionForNamedSearch(searchBindings);
474    }
475    throw new ResourceNotFoundException(theId);
476  }
477  
478  private OperationDefinition readOperationDefinitionForNamedSearch(List<SearchMethodBinding> bindings) {
479    OperationDefinition op = new OperationDefinition();
480    op.setStatus(PublicationStatus.ACTIVE);
481    op.setKind(OperationKind.QUERY);
482    op.setIdempotent(true);
483
484    op.setSystem(false);
485    op.setType(false);
486    op.setInstance(false);
487
488    Set<String> inParams = new HashSet<>();
489
490    for (SearchMethodBinding binding : bindings) {
491      if (isNotBlank(binding.getDescription())) {
492        op.setDescription(binding.getDescription());
493      }
494      if (isBlank(binding.getResourceProviderResourceName())) {
495        op.setSystem(true);
496      } else {
497        op.setType(true);
498        op.addResourceElement().setValue(binding.getResourceProviderResourceName());
499      }
500      op.setCode(binding.getQueryName());
501      for (IParameter nextParamUntyped : binding.getParameters()) {
502        if (nextParamUntyped instanceof SearchParameter) {
503          SearchParameter nextParam = (SearchParameter) nextParamUntyped;
504          if (!inParams.add(nextParam.getName())) {
505            continue;
506          }
507          OperationDefinitionParameterComponent param = op.addParameter();
508          param.setUse(OperationParameterUse.IN);
509          param.setType("string");
510          param.getSearchTypeElement().setValueAsString(nextParam.getParamType().getCode());
511          param.setMin(nextParam.isRequired() ? 1 : 0);
512          param.setMax("1");
513          param.setName(nextParam.getName());
514        }
515      }
516
517      if (isBlank(op.getName())) {
518        if (isNotBlank(op.getDescription())) {
519          op.setName(op.getDescription());
520        } else {
521          op.setName(op.getCode());
522        }
523      }
524    }
525
526    return op;
527  }
528  
529  private OperationDefinition readOperationDefinitionForOperation(List<OperationMethodBinding> bindings) {
530    OperationDefinition op = new OperationDefinition();
531    op.setStatus(PublicationStatus.ACTIVE);
532    op.setKind(OperationKind.OPERATION);
533    op.setIdempotent(true);
534
535    // We reset these to true below if we find a binding that can handle the level
536    op.setSystem(false);
537    op.setType(false);
538    op.setInstance(false);
539
540    Set<String> inParams = new HashSet<>();
541    Set<String> outParams = new HashSet<>();
542
543    for (OperationMethodBinding sharedDescription : bindings) {
544      if (isNotBlank(sharedDescription.getDescription())) {
545        op.setDescription(sharedDescription.getDescription());
546      }
547      if (sharedDescription.isCanOperateAtInstanceLevel()) {
548        op.setInstance(true);
549      }
550      if (sharedDescription.isCanOperateAtServerLevel()) {
551        op.setSystem(true);
552      }
553      if (sharedDescription.isCanOperateAtTypeLevel()) {
554        op.setType(true);
555      }
556      if (!sharedDescription.isIdempotent()) {
557        op.setIdempotent(sharedDescription.isIdempotent());
558      }
559      op.setCode(sharedDescription.getName().substring(1));
560      if (sharedDescription.isCanOperateAtInstanceLevel()) {
561        op.setInstance(sharedDescription.isCanOperateAtInstanceLevel());
562      }
563      if (sharedDescription.isCanOperateAtServerLevel()) {
564        op.setSystem(sharedDescription.isCanOperateAtServerLevel());
565      }
566      if (isNotBlank(sharedDescription.getResourceName())) {
567        op.addResourceElement().setValue(sharedDescription.getResourceName());
568      }
569
570      for (IParameter nextParamUntyped : sharedDescription.getParameters()) {
571        if (nextParamUntyped instanceof OperationParameter) {
572          OperationParameter nextParam = (OperationParameter) nextParamUntyped;
573          OperationDefinitionParameterComponent param = op.addParameter();
574          if (!inParams.add(nextParam.getName())) {
575            continue;
576          }
577          param.setUse(OperationParameterUse.IN);
578          if (nextParam.getParamType() != null) {
579            param.setType(nextParam.getParamType());
580          }
581          if (nextParam.getSearchParamType() != null) {
582            param.getSearchTypeElement().setValueAsString(nextParam.getSearchParamType());
583          }
584          param.setMin(nextParam.getMin());
585          param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax()));
586          param.setName(nextParam.getName());
587        }
588      }
589
590      for (ReturnType nextParam : sharedDescription.getReturnParams()) {
591        if (!outParams.add(nextParam.getName())) {
592          continue;
593        }
594        OperationDefinitionParameterComponent param = op.addParameter();
595        param.setUse(OperationParameterUse.OUT);
596        if (nextParam.getType() != null) {
597          param.setType(nextParam.getType());
598        }
599        param.setMin(nextParam.getMin());
600        param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax()));
601        param.setName(nextParam.getName());
602      }
603    }
604
605    if (isBlank(op.getName())) {
606      if (isNotBlank(op.getDescription())) {
607        op.setName(op.getDescription());
608      } else {
609        op.setName(op.getCode());
610      }
611    }
612
613    if (op.hasSystem() == false) {
614      op.setSystem(false);
615    }
616    if (op.hasInstance() == false) {
617      op.setInstance(false);
618    }
619
620    return op;
621  }
622
623  /**
624   * Sets the cache property (default is true). If set to true, the same response will be returned for each invocation.
625   * <p>
626   * See the class documentation for an important note if you are extending this class
627   * </p>
628   *
629   * @deprecated Since 4.0.0 this doesn't do anything
630   */
631  public ServerCapabilityStatementProvider setCache(boolean theCache) {
632    return this;
633  }
634
635  @Override
636  public void setRestfulServer(RestfulServer theRestfulServer) {
637    // ignore
638  }
639
640  private void sortSearchParameters(List<SearchParameter> searchParameters) {
641    Collections.sort(searchParameters, new Comparator<SearchParameter>() {
642      @Override
643      public int compare(SearchParameter theO1, SearchParameter theO2) {
644        if (theO1.isRequired() == theO2.isRequired()) {
645          return theO1.getName().compareTo(theO2.getName());
646        }
647        if (theO1.isRequired()) {
648          return -1;
649        }
650        return 1;
651      }
652    });
653  }
654}