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