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