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