001package ca.uhn.fhir.rest.method;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
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.isBlank;
023
024import java.io.IOException;
025import java.io.InputStream;
026import java.io.Reader;
027import java.lang.reflect.InvocationTargetException;
028import java.lang.reflect.Method;
029import java.util.ArrayList;
030import java.util.Collection;
031import java.util.HashSet;
032import java.util.List;
033import java.util.Set;
034import java.util.TreeSet;
035
036import org.apache.commons.io.IOUtils;
037import org.hl7.fhir.instance.model.api.IAnyResource;
038import org.hl7.fhir.instance.model.api.IBaseResource;
039
040import ca.uhn.fhir.context.ConfigurationException;
041import ca.uhn.fhir.context.FhirContext;
042import ca.uhn.fhir.context.FhirVersionEnum;
043import ca.uhn.fhir.model.api.Bundle;
044import ca.uhn.fhir.model.api.IResource;
045import ca.uhn.fhir.model.api.Include;
046import ca.uhn.fhir.model.api.TagList;
047import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
048import ca.uhn.fhir.parser.IParser;
049import ca.uhn.fhir.rest.annotation.AddTags;
050import ca.uhn.fhir.rest.annotation.Create;
051import ca.uhn.fhir.rest.annotation.Delete;
052import ca.uhn.fhir.rest.annotation.DeleteTags;
053import ca.uhn.fhir.rest.annotation.GetPage;
054import ca.uhn.fhir.rest.annotation.GetTags;
055import ca.uhn.fhir.rest.annotation.History;
056import ca.uhn.fhir.rest.annotation.Metadata;
057import ca.uhn.fhir.rest.annotation.Operation;
058import ca.uhn.fhir.rest.annotation.Read;
059import ca.uhn.fhir.rest.annotation.Search;
060import ca.uhn.fhir.rest.annotation.Transaction;
061import ca.uhn.fhir.rest.annotation.Update;
062import ca.uhn.fhir.rest.annotation.Validate;
063import ca.uhn.fhir.rest.api.MethodOutcome;
064import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
065import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
066import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
067import ca.uhn.fhir.rest.server.BundleProviders;
068import ca.uhn.fhir.rest.server.Constants;
069import ca.uhn.fhir.rest.server.EncodingEnum;
070import ca.uhn.fhir.rest.server.IBundleProvider;
071import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider;
072import ca.uhn.fhir.rest.server.IResourceProvider;
073import ca.uhn.fhir.rest.server.IRestfulServer;
074import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
075import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
076import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
077import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
078import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
079import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
080import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
081import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException;
082import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
083import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
084import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
085import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
086import ca.uhn.fhir.util.ReflectionUtil;
087
088public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T> {
089
090        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseMethodBinding.class);
091        private FhirContext myContext;
092        private Method myMethod;
093        private List<IParameter> myParameters;
094        private Object myProvider;
095        private boolean mySupportsConditional;
096        private boolean mySupportsConditionalMultiple;
097
098        public BaseMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
099                assert theMethod != null;
100                assert theContext != null;
101
102                myMethod = theMethod;
103                myContext = theContext;
104                myProvider = theProvider;
105                myParameters = MethodUtil.getResourceParameters(theContext, theMethod, theProvider, getRestOperationType());
106
107                for (IParameter next : myParameters) {
108                        if (next instanceof ConditionalParamBinder) {
109                                mySupportsConditional = true;
110                                if (((ConditionalParamBinder) next).isSupportsMultiple()) {
111                                        mySupportsConditionalMultiple = true;
112                                }
113                                break;
114                        }
115                }
116
117        }
118
119        protected IParser createAppropriateParserForParsingResponse(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode) {
120                EncodingEnum encoding = EncodingEnum.forContentType(theResponseMimeType);
121                if (encoding == null) {
122                        NonFhirResponseException ex = NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
123                        populateException(ex, theResponseReader);
124                        throw ex;
125                }
126
127                IParser parser = encoding.newParser(getContext());
128                return parser;
129        }
130
131        protected IParser createAppropriateParserForParsingServerRequest(RequestDetails theRequest) {
132                String contentTypeHeader = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
133                EncodingEnum encoding;
134                if (isBlank(contentTypeHeader)) {
135                        encoding = EncodingEnum.XML;
136                } else {
137                        int semicolon = contentTypeHeader.indexOf(';');
138                        if (semicolon != -1) {
139                                contentTypeHeader = contentTypeHeader.substring(0, semicolon);
140                        }
141                        encoding = EncodingEnum.forContentType(contentTypeHeader);
142                }
143
144                if (encoding == null) {
145                        throw new InvalidRequestException("Request contins non-FHIR conent-type header value: " + contentTypeHeader);
146                }
147
148                IParser parser = encoding.newParser(getContext());
149                return parser;
150        }
151
152        protected Object[] createParametersForServerRequest(RequestDetails theRequest) {
153                Object[] params = new Object[getParameters().size()];
154                for (int i = 0; i < getParameters().size(); i++) {
155                        IParameter param = getParameters().get(i);
156                        if (param == null) {
157                                continue;
158                        }
159                        params[i] = param.translateQueryParametersIntoServerArgument(theRequest, this);
160                }
161                return params;
162        }
163
164        public List<Class<?>> getAllowableParamAnnotations() {
165                return null;
166        }
167
168        public FhirContext getContext() {
169                return myContext;
170        }
171
172        public Set<String> getIncludes() {
173                Set<String> retVal = new TreeSet<String>();
174                for (IParameter next : myParameters) {
175                        if (next instanceof IncludeParameter) {
176                                retVal.addAll(((IncludeParameter) next).getAllow());
177                        }
178                }
179                return retVal;
180        }
181
182        public Method getMethod() {
183                return myMethod;
184        }
185
186        public List<IParameter> getParameters() {
187                return myParameters;
188        }
189
190        public Object getProvider() {
191                return myProvider;
192        }
193
194        @SuppressWarnings({ "unchecked", "rawtypes" })
195        public Set<Include> getRequestIncludesFromParams(Object[] params) {
196                if (params == null || params.length == 0) {
197                        return null;
198                }
199                int index = 0;
200                boolean match = false;
201                for (IParameter parameter : myParameters) {
202                        if (parameter instanceof IncludeParameter) {
203                                match = true;
204                                break;
205                        }
206                        index++;
207                }
208                if (!match) {
209                        return null;
210                }
211                if (index >= params.length) {
212                        ourLog.warn("index out of parameter range (should never happen");
213                        return null;
214                }
215                if (params[index] instanceof Set) {
216                        return (Set<Include>) params[index];
217                }
218                if (params[index] instanceof Iterable) {
219                        Set includes = new HashSet<Include>();
220                        for (Object o : (Iterable) params[index]) {
221                                if (o instanceof Include) {
222                                        includes.add(o);
223                                }
224                        }
225                        return includes;
226                }
227                ourLog.warn("include params wasn't Set or Iterable, it was {}", params[index].getClass());
228                return null;
229        }
230
231        /**
232         * Returns the name of the resource this method handles, or <code>null</code> if this method is not resource specific
233         */
234        public abstract String getResourceName();
235
236        public abstract RestOperationTypeEnum getRestOperationType();
237
238        /**
239         * Determine which operation is being fired for a specific request
240         * 
241         * @param theRequestDetails
242         *           The request
243         */
244        public RestOperationTypeEnum getRestOperationType(RequestDetails theRequestDetails) {
245                return getRestOperationType();
246        }
247
248        public abstract boolean incomingServerRequestMatchesMethod(RequestDetails theRequest);
249
250        public abstract BaseHttpClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException;
251
252        public abstract Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException;
253
254        protected final Object invokeServerMethod(IRestfulServer<?> theServer, RequestDetails theRequest, Object[] theMethodParams) {
255                // Handle server action interceptors
256                RestOperationTypeEnum operationType = getRestOperationType(theRequest);
257                if (operationType != null) {
258                        for (IServerInterceptor next : theServer.getInterceptors()) {
259                                ActionRequestDetails details = new ActionRequestDetails(theRequest);
260                                populateActionRequestDetailsForInterceptor(theRequest, details, theMethodParams);
261                                next.incomingRequestPreHandled(operationType, details);
262                        }
263                }
264
265                // Actually invoke the method
266                try {
267                        Method method = getMethod();
268                        return method.invoke(getProvider(), theMethodParams);
269                } catch (InvocationTargetException e) {
270                        if (e.getCause() instanceof BaseServerResponseException) {
271                                throw (BaseServerResponseException) e.getCause();
272                        } else {
273                                throw new InternalErrorException("Failed to call access method", e);
274                        }
275                } catch (Exception e) {
276                        throw new InternalErrorException("Failed to call access method", e);
277                }
278        }
279
280        /**
281         * Does this method have a parameter annotated with {@link ConditionalParamBinder}. Note that many operations don't actually support this paramter, so this will only return true occasionally.
282         */
283        public boolean isSupportsConditional() {
284                return mySupportsConditional;
285        }
286
287        /**
288         * Does this method support conditional operations over multiple objects (basically for conditional delete)
289         */
290        public boolean isSupportsConditionalMultiple() {
291                return mySupportsConditionalMultiple;
292        }
293
294        /**
295         * Subclasses may override this method (but should also call super.{@link #populateActionRequestDetailsForInterceptor(RequestDetails, ActionRequestDetails, Object[])} to provide method specifics to the
296         * interceptors.
297         * 
298         * @param theRequestDetails
299         *           The server request details
300         * @param theDetails
301         *           The details object to populate
302         * @param theMethodParams
303         *           The method params as generated by the specific method binding
304         */
305        protected void populateActionRequestDetailsForInterceptor(RequestDetails theRequestDetails, ActionRequestDetails theDetails, Object[] theMethodParams) {
306                // TODO Auto-generated method stub
307
308        }
309
310        protected BaseServerResponseException processNon2xxResponseAndReturnExceptionToThrow(int theStatusCode, String theResponseMimeType, Reader theResponseReader) {
311                BaseServerResponseException ex;
312                switch (theStatusCode) {
313                case Constants.STATUS_HTTP_400_BAD_REQUEST:
314                        ex = new InvalidRequestException("Server responded with HTTP 400");
315                        break;
316                case Constants.STATUS_HTTP_404_NOT_FOUND:
317                        ex = new ResourceNotFoundException("Server responded with HTTP 404");
318                        break;
319                case Constants.STATUS_HTTP_405_METHOD_NOT_ALLOWED:
320                        ex = new MethodNotAllowedException("Server responded with HTTP 405");
321                        break;
322                case Constants.STATUS_HTTP_409_CONFLICT:
323                        ex = new ResourceVersionConflictException("Server responded with HTTP 409");
324                        break;
325                case Constants.STATUS_HTTP_412_PRECONDITION_FAILED:
326                        ex = new PreconditionFailedException("Server responded with HTTP 412");
327                        break;
328                case Constants.STATUS_HTTP_422_UNPROCESSABLE_ENTITY:
329                        IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseReader, theStatusCode);
330                        // TODO: handle if something other than OO comes back
331                        BaseOperationOutcome operationOutcome = (BaseOperationOutcome) parser.parseResource(theResponseReader);
332                        ex = new UnprocessableEntityException(myContext, operationOutcome);
333                        break;
334                default:
335                        ex = new UnclassifiedServerFailureException(theStatusCode, "Server responded with HTTP " + theStatusCode);
336                        break;
337                }
338
339                populateException(ex, theResponseReader);
340                return ex;
341        }
342
343        /** For unit tests only */
344        public void setParameters(List<IParameter> theParameters) {
345                myParameters = theParameters;
346        }
347
348        protected IBundleProvider toResourceList(Object response) throws InternalErrorException {
349                if (response == null) {
350                        return BundleProviders.newEmptyList();
351                } else if (response instanceof IBundleProvider) {
352                        return (IBundleProvider) response;
353                } else if (response instanceof IBaseResource) {
354                        return BundleProviders.newList((IBaseResource) response);
355                } else if (response instanceof Collection) {
356                        List<IBaseResource> retVal = new ArrayList<IBaseResource>();
357                        for (Object next : ((Collection<?>) response)) {
358                                retVal.add((IBaseResource) next);
359                        }
360                        return BundleProviders.newList(retVal);
361                } else if (response instanceof MethodOutcome) {
362                        IBaseResource retVal = ((MethodOutcome) response).getOperationOutcome();
363                        if (retVal == null) {
364                                retVal = getContext().getResourceDefinition("OperationOutcome").newInstance();
365                        }
366                        return BundleProviders.newList(retVal);
367                } else {
368                        throw new InternalErrorException("Unexpected return type: " + response.getClass().getCanonicalName());
369                }
370        }
371
372        @SuppressWarnings("unchecked")
373        public static BaseMethodBinding<?> bindMethod(Method theMethod, FhirContext theContext, Object theProvider) {
374                Read read = theMethod.getAnnotation(Read.class);
375                Search search = theMethod.getAnnotation(Search.class);
376                Metadata conformance = theMethod.getAnnotation(Metadata.class);
377                Create create = theMethod.getAnnotation(Create.class);
378                Update update = theMethod.getAnnotation(Update.class);
379                Delete delete = theMethod.getAnnotation(Delete.class);
380                History history = theMethod.getAnnotation(History.class);
381                Validate validate = theMethod.getAnnotation(Validate.class);
382                GetTags getTags = theMethod.getAnnotation(GetTags.class);
383                AddTags addTags = theMethod.getAnnotation(AddTags.class);
384                DeleteTags deleteTags = theMethod.getAnnotation(DeleteTags.class);
385                Transaction transaction = theMethod.getAnnotation(Transaction.class);
386                Operation operation = theMethod.getAnnotation(Operation.class);
387                GetPage getPage = theMethod.getAnnotation(GetPage.class);
388
389                // ** if you add another annotation above, also add it to the next line:
390                if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance, create, update, delete, history, validate, getTags, addTags, deleteTags, transaction, operation, getPage)) {
391                        return null;
392                }
393
394                if (getPage != null) {
395                        return new PageMethodBinding(theContext, theMethod);
396                }
397                
398                Class<? extends IBaseResource> returnType;
399
400                Class<? extends IBaseResource> returnTypeFromRp = null;
401                if (theProvider instanceof IResourceProvider) {
402                        returnTypeFromRp = ((IResourceProvider) theProvider).getResourceType();
403                        if (!verifyIsValidResourceReturnType(returnTypeFromRp)) {
404                                throw new ConfigurationException("getResourceType() from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returned "
405                                                + toLogString(returnTypeFromRp) + " - Must return a resource type");
406                        }
407                }
408
409                Class<?> returnTypeFromMethod = theMethod.getReturnType();
410                if (getTags != null) {
411                        if (!TagList.class.equals(returnTypeFromMethod)) {
412                                throw new ConfigurationException("Method '" + theMethod.getName() + "' from type " + theMethod.getDeclaringClass().getCanonicalName() + " is annotated with @"
413                                                + GetTags.class.getSimpleName() + " but does not return type " + TagList.class.getName());
414                        }
415                } else if (MethodOutcome.class.equals(returnTypeFromMethod)) {
416                        // returns a method outcome
417                } else if (IBundleProvider.class.equals(returnTypeFromMethod)) {
418                        // returns a bundle provider
419                } else if (Bundle.class.equals(returnTypeFromMethod)) {
420                        // returns a bundle
421                } else if (void.class.equals(returnTypeFromMethod)) {
422                        // returns a bundle
423                } else if (Collection.class.isAssignableFrom(returnTypeFromMethod)) {
424                        returnTypeFromMethod = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod);
425                        if (!verifyIsValidResourceReturnType(returnTypeFromMethod) && !isResourceInterface(returnTypeFromMethod)) {
426                                throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName()
427                                                + " returns a collection with generic type " + toLogString(returnTypeFromMethod)
428                                                + " - Must return a resource type or a collection (List, Set) with a resource type parameter (e.g. List<Patient> or List<IBaseResource> )");
429                        }
430                } else {
431                        if (!isResourceInterface(returnTypeFromMethod) && !verifyIsValidResourceReturnType(returnTypeFromMethod)) {
432                                throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName()
433                                                + " returns " + toLogString(returnTypeFromMethod) + " - Must return a resource type (eg Patient, " + Bundle.class.getSimpleName() + ", " + IBundleProvider.class.getSimpleName()
434                                                + ", etc., see the documentation for more details)");
435                        }
436                }
437
438                Class<? extends IBaseResource> returnTypeFromAnnotation = IBaseResource.class;
439                if (read != null) {
440                        returnTypeFromAnnotation = read.type();
441                } else if (search != null) {
442                        returnTypeFromAnnotation = search.type();
443                } else if (history != null) {
444                        returnTypeFromAnnotation = history.type();
445                } else if (delete != null) {
446                        returnTypeFromAnnotation = delete.type();
447                } else if (create != null) {
448                        returnTypeFromAnnotation = create.type();
449                } else if (update != null) {
450                        returnTypeFromAnnotation = update.type();
451                } else if (validate != null) {
452                        returnTypeFromAnnotation = validate.type();
453                } else if (getTags != null) {
454                        returnTypeFromAnnotation = getTags.type();
455                } else if (addTags != null) {
456                        returnTypeFromAnnotation = addTags.type();
457                } else if (deleteTags != null) {
458                        returnTypeFromAnnotation = deleteTags.type();
459                }
460
461                if (returnTypeFromRp != null) {
462                        if (returnTypeFromAnnotation != null && !isResourceInterface(returnTypeFromAnnotation)) {
463                                if (!returnTypeFromRp.isAssignableFrom(returnTypeFromAnnotation)) {
464                                        throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " returns type "
465                                                        + returnTypeFromMethod.getCanonicalName() + " - Must return " + returnTypeFromRp.getCanonicalName() + " (or a subclass of it) per IResourceProvider contract");
466                                }
467                                if (!returnTypeFromRp.isAssignableFrom(returnTypeFromAnnotation)) {
468                                        throw new ConfigurationException(
469                                                        "Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " claims to return type " + returnTypeFromAnnotation.getCanonicalName()
470                                                                        + " per method annotation - Must return " + returnTypeFromRp.getCanonicalName() + " (or a subclass of it) per IResourceProvider contract");
471                                }
472                                returnType = returnTypeFromAnnotation;
473                        } else {
474                                returnType = returnTypeFromRp;
475                        }
476                } else {
477                        if (!isResourceInterface(returnTypeFromAnnotation)) {
478                                if (!verifyIsValidResourceReturnType(returnTypeFromAnnotation)) {
479                                        throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName()
480                                                        + " returns " + toLogString(returnTypeFromAnnotation) + " according to annotation - Must return a resource type");
481                                }
482                                returnType = returnTypeFromAnnotation;
483                        } else {
484                                // if (IRestfulClient.class.isAssignableFrom(theMethod.getDeclaringClass())) {
485                                // Clients don't define their methods in resource specific types, so they can
486                                // infer their resource type from the method return type.
487                                returnType = (Class<? extends IBaseResource>) returnTypeFromMethod;
488                                // } else {
489                                // This is a plain provider method returning a resource, so it should be
490                                // an operation or global search presumably
491                                // returnType = null;
492                        }
493                }
494
495                if (read != null) {
496                        return new ReadMethodBinding(returnType, theMethod, theContext, theProvider);
497                } else if (search != null) {
498                        if (search.dynamic()) {
499                                IDynamicSearchResourceProvider provider = (IDynamicSearchResourceProvider) theProvider;
500                                return new DynamicSearchMethodBinding(returnType, theMethod, theContext, provider);
501                        } else {
502                                return new SearchMethodBinding(returnType, theMethod, theContext, theProvider);
503                        }
504                } else if (conformance != null) {
505                        return new ConformanceMethodBinding(theMethod, theContext, theProvider);
506                } else if (create != null) {
507                        return new CreateMethodBinding(theMethod, theContext, theProvider);
508                } else if (update != null) {
509                        return new UpdateMethodBinding(theMethod, theContext, theProvider);
510                } else if (delete != null) {
511                        return new DeleteMethodBinding(theMethod, theContext, theProvider);
512                } else if (history != null) {
513                        return new HistoryMethodBinding(theMethod, theContext, theProvider);
514                } else if (validate != null) {
515                        if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
516                                return new ValidateMethodBindingDstu1(theMethod, theContext, theProvider);
517                        } else {
518                                return new ValidateMethodBindingDstu2(returnType, returnTypeFromRp, theMethod, theContext, theProvider, validate);
519                        }
520                } else if (getTags != null) {
521                        return new GetTagsMethodBinding(theMethod, theContext, theProvider, getTags);
522                } else if (addTags != null) {
523                        return new AddTagsMethodBinding(theMethod, theContext, theProvider, addTags);
524                } else if (deleteTags != null) {
525                        return new DeleteTagsMethodBinding(theMethod, theContext, theProvider, deleteTags);
526                } else if (transaction != null) {
527                        return new TransactionMethodBinding(theMethod, theContext, theProvider);
528                } else if (operation != null) {
529                        return new OperationMethodBinding(returnType, returnTypeFromRp, theMethod, theContext, theProvider, operation);
530                } else {
531                        throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
532                }
533
534                // // each operation name must have a request type annotation and be
535                // unique
536                // if (null != read) {
537                // return rm;
538                // }
539                //
540                // SearchMethodBinding sm = new SearchMethodBinding();
541                // if (null != search) {
542                // sm.setRequestType(SearchMethodBinding.RequestType.GET);
543                // } else if (null != theMethod.getAnnotation(PUT.class)) {
544                // sm.setRequestType(SearchMethodBinding.RequestType.PUT);
545                // } else if (null != theMethod.getAnnotation(POST.class)) {
546                // sm.setRequestType(SearchMethodBinding.RequestType.POST);
547                // } else if (null != theMethod.getAnnotation(DELETE.class)) {
548                // sm.setRequestType(SearchMethodBinding.RequestType.DELETE);
549                // } else {
550                // return null;
551                // }
552                //
553                // return sm;
554        }
555
556        private static boolean isResourceInterface(Class<?> theReturnTypeFromMethod) {
557                return theReturnTypeFromMethod.equals(IBaseResource.class) || theReturnTypeFromMethod.equals(IResource.class) || theReturnTypeFromMethod.equals(IAnyResource.class);
558        }
559
560        private static void populateException(BaseServerResponseException theEx, Reader theResponseReader) {
561                try {
562                        String responseText = IOUtils.toString(theResponseReader);
563                        theEx.setResponseBody(responseText);
564                } catch (IOException e) {
565                        ourLog.debug("Failed to read response", e);
566                }
567        }
568
569        private static String toLogString(Class<?> theType) {
570                if (theType == null) {
571                        return null;
572                }
573                return theType.getCanonicalName();
574        }
575
576        private static boolean verifyIsValidResourceReturnType(Class<?> theReturnType) {
577                if (theReturnType == null) {
578                        return false;
579                }
580                if (!IBaseResource.class.isAssignableFrom(theReturnType)) {
581                        return false;
582                }
583                return true;
584                // boolean retVal = Modifier.isAbstract(theReturnType.getModifiers()) == false;
585                // return retVal;
586        }
587
588        public static boolean verifyMethodHasZeroOrOneOperationAnnotation(Method theNextMethod, Object... theAnnotations) {
589                Object obj1 = null;
590                for (Object object : theAnnotations) {
591                        if (object != null) {
592                                if (obj1 == null) {
593                                        obj1 = object;
594                                } else {
595                                        throw new ConfigurationException("Method " + theNextMethod.getName() + " on type '" + theNextMethod.getDeclaringClass().getSimpleName() + " has annotations @"
596                                                        + obj1.getClass().getSimpleName() + " and @" + object.getClass().getSimpleName() + ". Can not have both.");
597                                }
598
599                        }
600                }
601                if (obj1 == null) {
602                        return false;
603                        // throw new ConfigurationException("Method '" +
604                        // theNextMethod.getName() + "' on type '" +
605                        // theNextMethod.getDeclaringClass().getSimpleName() +
606                        // " has no FHIR method annotations.");
607                }
608                return true;
609        }
610
611        /**
612         * @see ServletRequestDetails#getByteStreamRequestContents()
613         */
614        public static class ActiveRequestReader implements IRequestReader {
615                @Override
616                public InputStream getInputStream(RequestDetails theRequestDetails) throws IOException {
617                        return theRequestDetails.getInputStream();
618                }
619        }
620
621        /**
622         * @see ServletRequestDetails#getByteStreamRequestContents()
623         */
624        public static class InactiveRequestReader implements IRequestReader {
625                @Override
626                public InputStream getInputStream(RequestDetails theRequestDetails) {
627                        throw new IllegalStateException("The servlet-api JAR is not found on the classpath. Please check that this library is available.");
628                }
629        }
630
631        /**
632         * @see ServletRequestDetails#getByteStreamRequestContents()
633         */
634        public static interface IRequestReader {
635                InputStream getInputStream(RequestDetails theRequestDetails) throws IOException;
636        }
637
638}