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