001/*
002 * #%L
003 * HAPI FHIR Structures - DSTU2 (FHIR v1.0.0)
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
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 ca.uhn.fhir.rest.server.provider.dstu2;
021
022import ca.uhn.fhir.context.FhirVersionEnum;
023import ca.uhn.fhir.context.RuntimeResourceDefinition;
024import ca.uhn.fhir.context.RuntimeSearchParam;
025import ca.uhn.fhir.i18n.Msg;
026import ca.uhn.fhir.model.dstu2.resource.Conformance;
027import ca.uhn.fhir.model.dstu2.resource.Conformance.Rest;
028import ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource;
029import ca.uhn.fhir.model.dstu2.resource.Conformance.RestResourceInteraction;
030import ca.uhn.fhir.model.dstu2.resource.Conformance.RestResourceSearchParam;
031import ca.uhn.fhir.model.dstu2.resource.OperationDefinition;
032import ca.uhn.fhir.model.dstu2.resource.OperationDefinition.Parameter;
033import ca.uhn.fhir.model.dstu2.valueset.ConditionalDeleteStatusEnum;
034import ca.uhn.fhir.model.dstu2.valueset.ConformanceResourceStatusEnum;
035import ca.uhn.fhir.model.dstu2.valueset.ConformanceStatementKindEnum;
036import ca.uhn.fhir.model.dstu2.valueset.OperationKindEnum;
037import ca.uhn.fhir.model.dstu2.valueset.OperationParameterUseEnum;
038import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
039import ca.uhn.fhir.model.dstu2.valueset.RestfulConformanceModeEnum;
040import ca.uhn.fhir.model.dstu2.valueset.SystemRestfulInteractionEnum;
041import ca.uhn.fhir.model.dstu2.valueset.TypeRestfulInteractionEnum;
042import ca.uhn.fhir.model.dstu2.valueset.UnknownContentCodeEnum;
043import ca.uhn.fhir.model.primitive.DateTimeDt;
044import ca.uhn.fhir.model.primitive.IdDt;
045import ca.uhn.fhir.parser.DataFormatException;
046import ca.uhn.fhir.rest.annotation.IdParam;
047import ca.uhn.fhir.rest.annotation.Metadata;
048import ca.uhn.fhir.rest.annotation.Read;
049import ca.uhn.fhir.rest.api.Constants;
050import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
051import ca.uhn.fhir.rest.api.server.RequestDetails;
052import ca.uhn.fhir.rest.server.Bindings;
053import ca.uhn.fhir.rest.server.IServerConformanceProvider;
054import ca.uhn.fhir.rest.server.ResourceBinding;
055import ca.uhn.fhir.rest.server.RestfulServer;
056import ca.uhn.fhir.rest.server.RestfulServerConfiguration;
057import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
058import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
059import ca.uhn.fhir.rest.server.method.IParameter;
060import ca.uhn.fhir.rest.server.method.OperationMethodBinding;
061import ca.uhn.fhir.rest.server.method.OperationMethodBinding.ReturnType;
062import ca.uhn.fhir.rest.server.method.OperationParameter;
063import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
064import ca.uhn.fhir.rest.server.method.SearchParameter;
065import ca.uhn.fhir.rest.server.util.BaseServerCapabilityStatementProvider;
066import jakarta.servlet.ServletContext;
067import jakarta.servlet.http.HttpServletRequest;
068import org.apache.commons.lang3.StringUtils;
069import org.hl7.fhir.instance.model.api.IBaseResource;
070import org.hl7.fhir.instance.model.api.IPrimitiveType;
071
072import java.util.ArrayList;
073import java.util.Collections;
074import java.util.Comparator;
075import java.util.Date;
076import java.util.HashSet;
077import java.util.List;
078import java.util.Map;
079import java.util.Map.Entry;
080import java.util.Set;
081import java.util.TreeMap;
082import java.util.TreeSet;
083
084import static org.apache.commons.lang3.StringUtils.isBlank;
085import static org.apache.commons.lang3.StringUtils.isNotBlank;
086
087/**
088 * Server FHIR Provider which serves the conformance statement for a RESTful server implementation
089 */
090public class ServerConformanceProvider extends BaseServerCapabilityStatementProvider
091                implements IServerConformanceProvider<Conformance> {
092
093        private String myPublisher = "Not provided";
094
095        /**
096         * No-arg constructor and setter so that the ServerConfirmanceProvider can be Spring-wired with the RestfulService avoiding the potential reference cycle that would happen.
097         */
098        public ServerConformanceProvider() {
099                super();
100        }
101
102        /**
103         * Constructor
104         *
105         * @deprecated Use no-args constructor instead. Deprecated in 4.0.0
106         */
107        @Deprecated
108        public ServerConformanceProvider(RestfulServer theRestfulServer) {
109                this();
110        }
111
112        /**
113         * Constructor
114         */
115        public ServerConformanceProvider(RestfulServerConfiguration theServerConfiguration) {
116                super(theServerConfiguration);
117        }
118
119        private void checkBindingForSystemOps(
120                        Rest rest, Set<SystemRestfulInteractionEnum> systemOps, BaseMethodBinding nextMethodBinding) {
121                if (nextMethodBinding.getRestOperationType() != null) {
122                        String sysOpCode = nextMethodBinding.getRestOperationType().getCode();
123                        if (sysOpCode != null) {
124                                SystemRestfulInteractionEnum sysOp =
125                                                SystemRestfulInteractionEnum.VALUESET_BINDER.fromCodeString(sysOpCode);
126                                if (sysOp == null) {
127                                        return;
128                                }
129                                if (systemOps.contains(sysOp) == false) {
130                                        systemOps.add(sysOp);
131                                        rest.addInteraction().setCode(sysOp);
132                                }
133                        }
134                }
135        }
136
137        private Map<String, List<BaseMethodBinding>> collectMethodBindings(RequestDetails theRequestDetails) {
138                Map<String, List<BaseMethodBinding>> resourceToMethods = new TreeMap<String, List<BaseMethodBinding>>();
139                for (ResourceBinding next : getServerConfiguration(theRequestDetails).getResourceBindings()) {
140                        String resourceName = next.getResourceName();
141                        for (BaseMethodBinding nextMethodBinding : next.getMethodBindings()) {
142                                if (resourceToMethods.containsKey(resourceName) == false) {
143                                        resourceToMethods.put(resourceName, new ArrayList<BaseMethodBinding>());
144                                }
145                                resourceToMethods.get(resourceName).add(nextMethodBinding);
146                        }
147                }
148                for (BaseMethodBinding nextMethodBinding :
149                                getServerConfiguration(theRequestDetails).getServerBindings()) {
150                        String resourceName = "";
151                        if (resourceToMethods.containsKey(resourceName) == false) {
152                                resourceToMethods.put(resourceName, new ArrayList<BaseMethodBinding>());
153                        }
154                        resourceToMethods.get(resourceName).add(nextMethodBinding);
155                }
156                return resourceToMethods;
157        }
158
159        private DateTimeDt conformanceDate(RequestDetails theRequestDetails) {
160                IPrimitiveType<Date> buildDate =
161                                getServerConfiguration(theRequestDetails).getConformanceDate();
162                if (buildDate != null && buildDate.getValue() != null) {
163                        try {
164                                return new DateTimeDt(buildDate.getValueAsString());
165                        } catch (DataFormatException e) {
166                                // fall through
167                        }
168                }
169                return DateTimeDt.withCurrentTime();
170        }
171
172        private String createOperationName(OperationMethodBinding theMethodBinding) {
173                StringBuilder retVal = new StringBuilder();
174                if (theMethodBinding.getResourceName() != null) {
175                        retVal.append(theMethodBinding.getResourceName());
176                }
177
178                retVal.append('-');
179                if (theMethodBinding.isCanOperateAtInstanceLevel()) {
180                        retVal.append('i');
181                }
182                if (theMethodBinding.isCanOperateAtServerLevel()) {
183                        retVal.append('s');
184                }
185                retVal.append('-');
186
187                // Exclude the leading $
188                retVal.append(theMethodBinding.getName(), 1, theMethodBinding.getName().length());
189
190                return retVal.toString();
191        }
192
193        /**
194         * 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
195         * value defaults to "Not provided" but may be set to null, which will cause this element to be omitted.
196         */
197        public String getPublisher() {
198                return myPublisher;
199        }
200
201        /**
202         * 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
203         * value defaults to "Not provided" but may be set to null, which will cause this element to be omitted.
204         */
205        public void setPublisher(String thePublisher) {
206                myPublisher = thePublisher;
207        }
208
209        @SuppressWarnings("EnumSwitchStatementWhichMissesCases")
210        @Override
211        @Metadata
212        public Conformance getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
213                RestfulServerConfiguration serverConfiguration = getServerConfiguration(theRequestDetails);
214                Bindings bindings = serverConfiguration.provideBindings();
215
216                Conformance retVal = new Conformance();
217
218                retVal.setPublisher(myPublisher);
219                retVal.setDate(conformanceDate(theRequestDetails));
220                retVal.setFhirVersion(FhirVersionEnum.DSTU2.getFhirVersionString());
221                retVal.setAcceptUnknown(
222                                UnknownContentCodeEnum
223                                                .UNKNOWN_EXTENSIONS); // TODO: make this configurable - this is a fairly big effort since the
224                // parser
225                // needs to be modified to actually allow it
226
227                ServletContext servletContext = (ServletContext)
228                                (theRequest == null ? null : theRequest.getAttribute(RestfulServer.SERVLET_CONTEXT_ATTRIBUTE));
229                String serverBase =
230                                serverConfiguration.getServerAddressStrategy().determineServerBase(servletContext, theRequest);
231                retVal.getImplementation()
232                                .setUrl(serverBase)
233                                .setDescription(serverConfiguration.getImplementationDescription());
234
235                retVal.setKind(ConformanceStatementKindEnum.INSTANCE);
236                retVal.getSoftware().setName(serverConfiguration.getServerName());
237                retVal.getSoftware().setVersion(serverConfiguration.getServerVersion());
238                retVal.addFormat(Constants.CT_FHIR_XML);
239                retVal.addFormat(Constants.CT_FHIR_JSON);
240
241                Rest rest = retVal.addRest();
242                rest.setMode(RestfulConformanceModeEnum.SERVER);
243
244                Set<SystemRestfulInteractionEnum> systemOps = new HashSet<>();
245                Set<String> operationNames = new HashSet<>();
246
247                Map<String, List<BaseMethodBinding>> resourceToMethods = collectMethodBindings(theRequestDetails);
248                for (Entry<String, List<BaseMethodBinding>> nextEntry : resourceToMethods.entrySet()) {
249
250                        if (nextEntry.getKey().isEmpty() == false) {
251                                Set<TypeRestfulInteractionEnum> resourceOps = new HashSet<>();
252                                RestResource resource = rest.addResource();
253                                String resourceName = nextEntry.getKey();
254                                RuntimeResourceDefinition def =
255                                                serverConfiguration.getFhirContext().getResourceDefinition(resourceName);
256                                resource.getTypeElement().setValue(def.getName());
257                                resource.getProfile().setReference(new IdDt(def.getResourceProfile(serverBase)));
258
259                                TreeSet<String> includes = new TreeSet<>();
260
261                                // Map<String, Conformance.RestResourceSearchParam> nameToSearchParam = new HashMap<String,
262                                // Conformance.RestResourceSearchParam>();
263                                for (BaseMethodBinding nextMethodBinding : nextEntry.getValue()) {
264                                        if (nextMethodBinding.getRestOperationType() != null) {
265                                                String resOpCode =
266                                                                nextMethodBinding.getRestOperationType().getCode();
267                                                if (resOpCode != null) {
268                                                        TypeRestfulInteractionEnum resOp =
269                                                                        TypeRestfulInteractionEnum.VALUESET_BINDER.fromCodeString(resOpCode);
270                                                        if (resOp != null) {
271                                                                if (resourceOps.contains(resOp) == false) {
272                                                                        resourceOps.add(resOp);
273                                                                        resource.addInteraction().setCode(resOp);
274                                                                }
275                                                                if ("vread".equals(resOpCode)) {
276                                                                        // vread implies read
277                                                                        resOp = TypeRestfulInteractionEnum.READ;
278                                                                        if (resourceOps.contains(resOp) == false) {
279                                                                                resourceOps.add(resOp);
280                                                                                resource.addInteraction().setCode(resOp);
281                                                                        }
282                                                                }
283
284                                                                if (nextMethodBinding.isSupportsConditional()) {
285                                                                        switch (resOp) {
286                                                                                case CREATE:
287                                                                                        resource.setConditionalCreate(true);
288                                                                                        break;
289                                                                                case DELETE:
290                                                                                        if (nextMethodBinding.isSupportsConditionalMultiple()) {
291                                                                                                resource.setConditionalDelete(
292                                                                                                                ConditionalDeleteStatusEnum.MULTIPLE_DELETES_SUPPORTED);
293                                                                                        } else {
294                                                                                                resource.setConditionalDelete(
295                                                                                                                ConditionalDeleteStatusEnum.SINGLE_DELETES_SUPPORTED);
296                                                                                        }
297                                                                                        break;
298                                                                                case UPDATE:
299                                                                                        resource.setConditionalUpdate(true);
300                                                                                        break;
301                                                                                default:
302                                                                                        break;
303                                                                        }
304                                                                }
305                                                        }
306                                                }
307                                        }
308
309                                        checkBindingForSystemOps(rest, systemOps, nextMethodBinding);
310
311                                        if (nextMethodBinding instanceof SearchMethodBinding) {
312                                                handleSearchMethodBinding(
313                                                                resource, def, includes, (SearchMethodBinding) nextMethodBinding, theRequestDetails);
314                                        } else if (nextMethodBinding instanceof OperationMethodBinding) {
315                                                OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
316                                                String opName = bindings.getOperationBindingToId().get(methodBinding);
317                                                if (operationNames.add(opName)) {
318                                                        // Only add each operation (by name) once
319                                                        rest.addOperation()
320                                                                        .setName(methodBinding.getName().substring(1))
321                                                                        .getDefinition()
322                                                                        .setReference("OperationDefinition/" + opName);
323                                                }
324                                        }
325
326                                        Collections.sort(resource.getInteraction(), new Comparator<RestResourceInteraction>() {
327                                                @Override
328                                                public int compare(RestResourceInteraction theO1, RestResourceInteraction theO2) {
329                                                        TypeRestfulInteractionEnum o1 =
330                                                                        theO1.getCodeElement().getValueAsEnum();
331                                                        TypeRestfulInteractionEnum o2 =
332                                                                        theO2.getCodeElement().getValueAsEnum();
333                                                        if (o1 == null && o2 == null) {
334                                                                return 0;
335                                                        }
336                                                        if (o1 == null) {
337                                                                return 1;
338                                                        }
339                                                        if (o2 == null) {
340                                                                return -1;
341                                                        }
342                                                        return o1.ordinal() - o2.ordinal();
343                                                }
344                                        });
345                                }
346
347                                for (String nextInclude : includes) {
348                                        resource.addSearchInclude(nextInclude);
349                                }
350                        } else {
351                                for (BaseMethodBinding nextMethodBinding : nextEntry.getValue()) {
352                                        checkBindingForSystemOps(rest, systemOps, nextMethodBinding);
353                                        if (nextMethodBinding instanceof OperationMethodBinding) {
354                                                OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
355                                                String opName = bindings.getOperationBindingToId().get(methodBinding);
356                                                if (operationNames.add(opName)) {
357                                                        rest.addOperation()
358                                                                        .setName(methodBinding.getName().substring(1))
359                                                                        .getDefinition()
360                                                                        .setReference("OperationDefinition/" + opName);
361                                                }
362                                        }
363                                }
364                        }
365                }
366
367                return retVal;
368        }
369
370        private void handleSearchMethodBinding(
371                        RestResource resource,
372                        RuntimeResourceDefinition def,
373                        TreeSet<String> includes,
374                        SearchMethodBinding searchMethodBinding,
375                        RequestDetails theRequestDetails) {
376                includes.addAll(searchMethodBinding.getIncludes());
377
378                List<IParameter> params = searchMethodBinding.getParameters();
379                List<SearchParameter> searchParameters = new ArrayList<>();
380                for (IParameter nextParameter : params) {
381                        if ((nextParameter instanceof SearchParameter)) {
382                                searchParameters.add((SearchParameter) nextParameter);
383                        }
384                }
385                sortSearchParameters(searchParameters);
386                if (!searchParameters.isEmpty()) {
387                        // boolean allOptional = searchParameters.get(0).isRequired() == false;
388                        //
389                        // OperationDefinition query = null;
390                        // if (!allOptional) {
391                        // RestOperation operation = rest.addOperation();
392                        // query = new OperationDefinition();
393                        // operation.setDefinition(new ResourceReferenceDt(query));
394                        // query.getDescriptionElement().setValue(searchMethodBinding.getDescription());
395                        // query.addUndeclaredExtension(false, ExtensionConstants.QUERY_RETURN_TYPE, new CodeDt(resourceName));
396                        // for (String nextInclude : searchMethodBinding.getIncludes()) {
397                        // query.addUndeclaredExtension(false, ExtensionConstants.QUERY_ALLOWED_INCLUDE, new StringDt(nextInclude));
398                        // }
399                        // }
400
401                        for (SearchParameter nextParameter : searchParameters) {
402
403                                String nextParamName = nextParameter.getName();
404
405                                String chain = null;
406                                String nextParamUnchainedName = nextParamName;
407                                if (nextParamName.contains(".")) {
408                                        chain = nextParamName.substring(nextParamName.indexOf('.') + 1);
409                                        nextParamUnchainedName = nextParamName.substring(0, nextParamName.indexOf('.'));
410                                }
411
412                                String nextParamDescription = nextParameter.getDescription();
413
414                                /*
415                                 * If the parameter has no description, default to the one from the resource
416                                 */
417                                if (StringUtils.isBlank(nextParamDescription)) {
418                                        RuntimeSearchParam paramDef = def.getSearchParam(nextParamUnchainedName);
419                                        if (paramDef != null) {
420                                                nextParamDescription = paramDef.getDescription();
421                                        }
422                                }
423
424                                String finalNextParamUnchainedName = nextParamUnchainedName;
425                                RestResourceSearchParam param = resource.getSearchParam().stream()
426                                                .filter(t -> t.getName().equals(finalNextParamUnchainedName))
427                                                .findFirst()
428                                                .orElseGet(() -> resource.addSearchParam());
429
430                                param.setName(nextParamUnchainedName);
431                                if (StringUtils.isNotBlank(chain)) {
432                                        param.addChain(chain);
433                                } else {
434                                        if (nextParameter.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
435                                                for (String nextWhitelist : new TreeSet<>(nextParameter.getQualifierWhitelist())) {
436                                                        if (nextWhitelist.startsWith(".")) {
437                                                                param.addChain(nextWhitelist.substring(1));
438                                                        }
439                                                }
440                                        }
441                                }
442
443                                param.setDocumentation(nextParamDescription);
444                                if (nextParameter.getParamType() != null) {
445                                        param.getTypeElement()
446                                                        .setValueAsString(nextParameter.getParamType().getCode());
447                                }
448                                for (Class<? extends IBaseResource> nextTarget : nextParameter.getDeclaredTypes()) {
449                                        RuntimeResourceDefinition targetDef = getServerConfiguration(theRequestDetails)
450                                                        .getFhirContext()
451                                                        .getResourceDefinition(nextTarget);
452                                        if (targetDef != null) {
453                                                ResourceTypeEnum code = ResourceTypeEnum.VALUESET_BINDER.fromCodeString(targetDef.getName());
454                                                if (code != null) {
455                                                        param.addTarget(code);
456                                                }
457                                        }
458                                }
459                        }
460                }
461        }
462
463        @Read(type = OperationDefinition.class)
464        public OperationDefinition readOperationDefinition(@IdParam IdDt theId, RequestDetails theRequestDetails) {
465                if (theId == null || theId.hasIdPart() == false) {
466                        throw new ResourceNotFoundException(Msg.code(1988) + theId);
467                }
468                RestfulServerConfiguration serverConfiguration = getServerConfiguration(theRequestDetails);
469                Bindings bindings = serverConfiguration.provideBindings();
470
471                List<OperationMethodBinding> sharedDescriptions =
472                                bindings.getOperationIdToBindings().get(theId.getIdPart());
473                if (sharedDescriptions == null || sharedDescriptions.isEmpty()) {
474                        throw new ResourceNotFoundException(Msg.code(1989) + theId);
475                }
476
477                OperationDefinition op = new OperationDefinition();
478                op.setStatus(ConformanceResourceStatusEnum.ACTIVE);
479                op.setKind(OperationKindEnum.OPERATION);
480                op.setIdempotent(true);
481
482                Set<String> inParams = new HashSet<>();
483                Set<String> outParams = new HashSet<>();
484
485                for (OperationMethodBinding sharedDescription : sharedDescriptions) {
486                        if (isNotBlank(sharedDescription.getDescription())) {
487                                op.setDescription(sharedDescription.getDescription());
488                        }
489                        if (!sharedDescription.isIdempotent()) {
490                                op.setIdempotent(sharedDescription.isIdempotent());
491                        }
492                        op.setCode(sharedDescription.getName().substring(1));
493                        if (sharedDescription.isCanOperateAtInstanceLevel()) {
494                                op.setInstance(sharedDescription.isCanOperateAtInstanceLevel());
495                        }
496                        if (sharedDescription.isCanOperateAtServerLevel()) {
497                                op.setSystem(sharedDescription.isCanOperateAtServerLevel());
498                        }
499                        if (isNotBlank(sharedDescription.getResourceName())) {
500                                op.addType().setValue(sharedDescription.getResourceName());
501                        }
502
503                        for (IParameter nextParamUntyped : sharedDescription.getParameters()) {
504                                if (nextParamUntyped instanceof OperationParameter) {
505                                        OperationParameter nextParam = (OperationParameter) nextParamUntyped;
506                                        if (!inParams.add(nextParam.getName())) {
507                                                continue;
508                                        }
509                                        Parameter param = op.addParameter();
510                                        param.setUse(OperationParameterUseEnum.IN);
511                                        if (nextParam.getParamType() != null) {
512                                                param.setType(nextParam.getParamType());
513                                        }
514                                        param.setMin(nextParam.getMin());
515                                        param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax()));
516                                        param.setName(nextParam.getName());
517                                }
518                        }
519
520                        for (ReturnType nextParam : sharedDescription.getReturnParams()) {
521                                if (!outParams.add(nextParam.getName())) {
522                                        continue;
523                                }
524                                Parameter param = op.addParameter();
525                                param.setUse(OperationParameterUseEnum.OUT);
526                                if (nextParam.getType() != null) {
527                                        param.setType(nextParam.getType());
528                                }
529                                param.setMin(nextParam.getMin());
530                                param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax()));
531                                param.setName(nextParam.getName());
532                        }
533                }
534
535                if (isBlank(op.getName())) {
536                        if (isNotBlank(op.getDescription())) {
537                                op.setName(op.getDescription());
538                        } else {
539                                op.setName(op.getCode());
540                        }
541                }
542
543                if (op.getSystem() == null) {
544                        op.setSystem(false);
545                }
546                if (op.getInstance() == null) {
547                        op.setInstance(false);
548                }
549
550                return op;
551        }
552
553        /**
554         * Sets the cache property (default is true). If set to true, the same response will be returned for each invocation.
555         * <p>
556         * See the class documentation for an important note if you are extending this class
557         * </p>
558         * @deprecated Since 4.0.0 this does nothing
559         */
560        @Deprecated
561        public void setCache(boolean theCache) {
562                // nothing
563        }
564
565        @Override
566        public void setRestfulServer(RestfulServer theRestfulServer) {
567                // nothing
568        }
569
570        private void sortSearchParameters(List<SearchParameter> searchParameters) {
571                Collections.sort(searchParameters, new Comparator<SearchParameter>() {
572                        @Override
573                        public int compare(SearchParameter theO1, SearchParameter theO2) {
574                                if (theO1.isRequired() == theO2.isRequired()) {
575                                        return theO1.getName().compareTo(theO2.getName());
576                                }
577                                if (theO1.isRequired()) {
578                                        return -1;
579                                }
580                                return 1;
581                        }
582                });
583        }
584}