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