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