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