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