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