ca.uhn.fhir.rest.method.BaseMethodBinding.java Source code

Java tutorial

Introduction

Here is the source code for ca.uhn.fhir.rest.method.BaseMethodBinding.java

Source

package ca.uhn.fhir.rest.method;

/*
 * #%L
 * HAPI FHIR - Core Library
 * %%
 * Copyright (C) 2014 - 2016 University Health Network
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
import static org.apache.commons.lang3.StringUtils.isBlank;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;

import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
import ca.uhn.fhir.rest.server.BundleProviders;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.IRestfulServer;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.ReflectionUtil;

public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T> {

    private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseMethodBinding.class);
    private FhirContext myContext;
    private Method myMethod;
    private List<IParameter> myParameters;
    private Object myProvider;
    private boolean mySupportsConditional;
    private boolean mySupportsConditionalMultiple;

    public BaseMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
        assert theMethod != null;
        assert theContext != null;

        myMethod = theMethod;
        myContext = theContext;
        myProvider = theProvider;
        myParameters = MethodUtil.getResourceParameters(theContext, theMethod, theProvider, getRestOperationType());

        for (IParameter next : myParameters) {
            if (next instanceof ConditionalParamBinder) {
                mySupportsConditional = true;
                if (((ConditionalParamBinder) next).isSupportsMultiple()) {
                    mySupportsConditionalMultiple = true;
                }
                break;
            }
        }

    }

    protected IParser createAppropriateParserForParsingResponse(String theResponseMimeType,
            Reader theResponseReader, int theResponseStatusCode,
            List<Class<? extends IBaseResource>> thePreferTypes) {
        EncodingEnum encoding = EncodingEnum.forContentType(theResponseMimeType);
        if (encoding == null) {
            NonFhirResponseException ex = NonFhirResponseException.newInstance(theResponseStatusCode,
                    theResponseMimeType, theResponseReader);
            populateException(ex, theResponseReader);
            throw ex;
        }

        IParser parser = encoding.newParser(getContext());

        parser.setPreferTypes(thePreferTypes);

        return parser;
    }

    protected IParser createAppropriateParserForParsingServerRequest(RequestDetails theRequest) {
        String contentTypeHeader = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
        EncodingEnum encoding;
        if (isBlank(contentTypeHeader)) {
            encoding = EncodingEnum.XML;
        } else {
            int semicolon = contentTypeHeader.indexOf(';');
            if (semicolon != -1) {
                contentTypeHeader = contentTypeHeader.substring(0, semicolon);
            }
            encoding = EncodingEnum.forContentType(contentTypeHeader);
        }

        if (encoding == null) {
            throw new InvalidRequestException(
                    "Request contins non-FHIR conent-type header value: " + contentTypeHeader);
        }

        IParser parser = encoding.newParser(getContext());
        return parser;
    }

    protected Object[] createParametersForServerRequest(RequestDetails theRequest) {
        Object[] params = new Object[getParameters().size()];
        for (int i = 0; i < getParameters().size(); i++) {
            IParameter param = getParameters().get(i);
            if (param == null) {
                continue;
            }
            params[i] = param.translateQueryParametersIntoServerArgument(theRequest, this);
        }
        return params;
    }

    public List<Class<?>> getAllowableParamAnnotations() {
        return null;
    }

    public FhirContext getContext() {
        return myContext;
    }

    public Set<String> getIncludes() {
        Set<String> retVal = new TreeSet<String>();
        for (IParameter next : myParameters) {
            if (next instanceof IncludeParameter) {
                retVal.addAll(((IncludeParameter) next).getAllow());
            }
        }
        return retVal;
    }

    public Method getMethod() {
        return myMethod;
    }

    public List<IParameter> getParameters() {
        return myParameters;
    }

    public Object getProvider() {
        return myProvider;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public Set<Include> getRequestIncludesFromParams(Object[] params) {
        if (params == null || params.length == 0) {
            return null;
        }
        int index = 0;
        boolean match = false;
        for (IParameter parameter : myParameters) {
            if (parameter instanceof IncludeParameter) {
                match = true;
                break;
            }
            index++;
        }
        if (!match) {
            return null;
        }
        if (index >= params.length) {
            ourLog.warn("index out of parameter range (should never happen");
            return null;
        }
        if (params[index] instanceof Set) {
            return (Set<Include>) params[index];
        }
        if (params[index] instanceof Iterable) {
            Set includes = new HashSet<Include>();
            for (Object o : (Iterable) params[index]) {
                if (o instanceof Include) {
                    includes.add(o);
                }
            }
            return includes;
        }
        ourLog.warn("include params wasn't Set or Iterable, it was {}", params[index].getClass());
        return null;
    }

    /**
     * Returns the name of the resource this method handles, or <code>null</code> if this method is not resource specific
     */
    public abstract String getResourceName();

    public abstract RestOperationTypeEnum getRestOperationType();

    /**
     * Determine which operation is being fired for a specific request
     * 
     * @param theRequestDetails
     *           The request
     */
    public RestOperationTypeEnum getRestOperationType(RequestDetails theRequestDetails) {
        return getRestOperationType();
    }

    public abstract boolean incomingServerRequestMatchesMethod(RequestDetails theRequest);

    public abstract BaseHttpClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException;

    public abstract Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest)
            throws BaseServerResponseException, IOException;

    protected final Object invokeServerMethod(IRestfulServer<?> theServer, RequestDetails theRequest,
            Object[] theMethodParams) {
        // Handle server action interceptors
        RestOperationTypeEnum operationType = getRestOperationType(theRequest);
        if (operationType != null) {
            for (IServerInterceptor next : theServer.getInterceptors()) {
                ActionRequestDetails details = new ActionRequestDetails(theRequest);
                populateActionRequestDetailsForInterceptor(theRequest, details, theMethodParams);
                next.incomingRequestPreHandled(operationType, details);
            }
        }

        // Actually invoke the method
        try {
            Method method = getMethod();
            return method.invoke(getProvider(), theMethodParams);
        } catch (InvocationTargetException e) {
            if (e.getCause() instanceof BaseServerResponseException) {
                throw (BaseServerResponseException) e.getCause();
            } else {
                throw new InternalErrorException("Failed to call access method", e);
            }
        } catch (Exception e) {
            throw new InternalErrorException("Failed to call access method", e);
        }
    }

    /**
     * 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.
     */
    public boolean isSupportsConditional() {
        return mySupportsConditional;
    }

    /**
     * Does this method support conditional operations over multiple objects (basically for conditional delete)
     */
    public boolean isSupportsConditionalMultiple() {
        return mySupportsConditionalMultiple;
    }

    /**
     * Subclasses may override this method (but should also call super.{@link #populateActionRequestDetailsForInterceptor(RequestDetails, ActionRequestDetails, Object[])} to provide method specifics to the
     * interceptors.
     * 
     * @param theRequestDetails
     *           The server request details
     * @param theDetails
     *           The details object to populate
     * @param theMethodParams
     *           The method params as generated by the specific method binding
     */
    protected void populateActionRequestDetailsForInterceptor(RequestDetails theRequestDetails,
            ActionRequestDetails theDetails, Object[] theMethodParams) {
        // nothing by default
    }

    protected BaseServerResponseException processNon2xxResponseAndReturnExceptionToThrow(int theStatusCode,
            String theResponseMimeType, Reader theResponseReader) {
        BaseServerResponseException ex;
        switch (theStatusCode) {
        case Constants.STATUS_HTTP_400_BAD_REQUEST:
            ex = new InvalidRequestException("Server responded with HTTP 400");
            break;
        case Constants.STATUS_HTTP_404_NOT_FOUND:
            ex = new ResourceNotFoundException("Server responded with HTTP 404");
            break;
        case Constants.STATUS_HTTP_405_METHOD_NOT_ALLOWED:
            ex = new MethodNotAllowedException("Server responded with HTTP 405");
            break;
        case Constants.STATUS_HTTP_409_CONFLICT:
            ex = new ResourceVersionConflictException("Server responded with HTTP 409");
            break;
        case Constants.STATUS_HTTP_412_PRECONDITION_FAILED:
            ex = new PreconditionFailedException("Server responded with HTTP 412");
            break;
        case Constants.STATUS_HTTP_422_UNPROCESSABLE_ENTITY:
            IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseReader,
                    theStatusCode, null);
            // TODO: handle if something other than OO comes back
            BaseOperationOutcome operationOutcome = (BaseOperationOutcome) parser.parseResource(theResponseReader);
            ex = new UnprocessableEntityException(myContext, operationOutcome);
            break;
        default:
            ex = new UnclassifiedServerFailureException(theStatusCode,
                    "Server responded with HTTP " + theStatusCode);
            break;
        }

        populateException(ex, theResponseReader);
        return ex;
    }

    /** For unit tests only */
    public void setParameters(List<IParameter> theParameters) {
        myParameters = theParameters;
    }

    protected IBundleProvider toResourceList(Object response) throws InternalErrorException {
        if (response == null) {
            return BundleProviders.newEmptyList();
        } else if (response instanceof IBundleProvider) {
            return (IBundleProvider) response;
        } else if (response instanceof IBaseResource) {
            return BundleProviders.newList((IBaseResource) response);
        } else if (response instanceof Collection) {
            List<IBaseResource> retVal = new ArrayList<IBaseResource>();
            for (Object next : ((Collection<?>) response)) {
                retVal.add((IBaseResource) next);
            }
            return BundleProviders.newList(retVal);
        } else if (response instanceof MethodOutcome) {
            IBaseResource retVal = ((MethodOutcome) response).getOperationOutcome();
            if (retVal == null) {
                retVal = getContext().getResourceDefinition("OperationOutcome").newInstance();
            }
            return BundleProviders.newList(retVal);
        } else {
            throw new InternalErrorException("Unexpected return type: " + response.getClass().getCanonicalName());
        }
    }

    @SuppressWarnings("unchecked")
    public static BaseMethodBinding<?> bindMethod(Method theMethod, FhirContext theContext, Object theProvider) {
        Read read = theMethod.getAnnotation(Read.class);
        Search search = theMethod.getAnnotation(Search.class);
        Metadata conformance = theMethod.getAnnotation(Metadata.class);
        Create create = theMethod.getAnnotation(Create.class);
        Update update = theMethod.getAnnotation(Update.class);
        Delete delete = theMethod.getAnnotation(Delete.class);
        History history = theMethod.getAnnotation(History.class);
        Validate validate = theMethod.getAnnotation(Validate.class);
        GetTags getTags = theMethod.getAnnotation(GetTags.class);
        AddTags addTags = theMethod.getAnnotation(AddTags.class);
        DeleteTags deleteTags = theMethod.getAnnotation(DeleteTags.class);
        Transaction transaction = theMethod.getAnnotation(Transaction.class);
        Operation operation = theMethod.getAnnotation(Operation.class);
        GetPage getPage = theMethod.getAnnotation(GetPage.class);
        Patch patch = theMethod.getAnnotation(Patch.class);

        // ** if you add another annotation above, also add it to the next line:
        if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance, create, update,
                delete, history, validate, getTags, addTags, deleteTags, transaction, operation, getPage, patch)) {
            return null;
        }

        if (getPage != null) {
            return new PageMethodBinding(theContext, theMethod);
        }

        Class<? extends IBaseResource> returnType;

        Class<? extends IBaseResource> returnTypeFromRp = null;
        if (theProvider instanceof IResourceProvider) {
            returnTypeFromRp = ((IResourceProvider) theProvider).getResourceType();
            if (!verifyIsValidResourceReturnType(returnTypeFromRp)) {
                throw new ConfigurationException("getResourceType() from " + IResourceProvider.class.getSimpleName()
                        + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returned "
                        + toLogString(returnTypeFromRp) + " - Must return a resource type");
            }
        }

        Class<?> returnTypeFromMethod = theMethod.getReturnType();
        if (getTags != null) {
            if (!TagList.class.equals(returnTypeFromMethod)) {
                throw new ConfigurationException("Method '" + theMethod.getName() + "' from type "
                        + theMethod.getDeclaringClass().getCanonicalName() + " is annotated with @"
                        + GetTags.class.getSimpleName() + " but does not return type " + TagList.class.getName());
            }
        } else if (MethodOutcome.class.isAssignableFrom(returnTypeFromMethod)) {
            // returns a method outcome
        } else if (IBundleProvider.class.equals(returnTypeFromMethod)) {
            // returns a bundle provider
        } else if (Bundle.class.equals(returnTypeFromMethod)) {
            // returns a bundle
        } else if (void.class.equals(returnTypeFromMethod)) {
            // returns a bundle
        } else if (Collection.class.isAssignableFrom(returnTypeFromMethod)) {
            returnTypeFromMethod = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod);
            if (returnTypeFromMethod == null) {
                ourLog.trace("Method {} returns a non-typed list, can't verify return type", theMethod);
            } else if (!verifyIsValidResourceReturnType(returnTypeFromMethod)
                    && !isResourceInterface(returnTypeFromMethod)) {
                throw new ConfigurationException("Method '" + theMethod.getName() + "' from "
                        + IResourceProvider.class.getSimpleName() + " type "
                        + theMethod.getDeclaringClass().getCanonicalName()
                        + " returns a collection with generic type " + toLogString(returnTypeFromMethod)
                        + " - Must return a resource type or a collection (List, Set) with a resource type parameter (e.g. List<Patient> or List<IBaseResource> )");
            }
        } else {
            if (!isResourceInterface(returnTypeFromMethod)
                    && !verifyIsValidResourceReturnType(returnTypeFromMethod)) {
                throw new ConfigurationException(
                        "Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName()
                                + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns "
                                + toLogString(returnTypeFromMethod) + " - Must return a resource type (eg Patient, "
                                + Bundle.class.getSimpleName() + ", " + IBundleProvider.class.getSimpleName()
                                + ", etc., see the documentation for more details)");
            }
        }

        Class<? extends IBaseResource> returnTypeFromAnnotation = IBaseResource.class;
        if (read != null) {
            returnTypeFromAnnotation = read.type();
        } else if (search != null) {
            returnTypeFromAnnotation = search.type();
        } else if (history != null) {
            returnTypeFromAnnotation = history.type();
        } else if (delete != null) {
            returnTypeFromAnnotation = delete.type();
        } else if (patch != null) {
            returnTypeFromAnnotation = patch.type();
        } else if (create != null) {
            returnTypeFromAnnotation = create.type();
        } else if (update != null) {
            returnTypeFromAnnotation = update.type();
        } else if (validate != null) {
            returnTypeFromAnnotation = validate.type();
        } else if (getTags != null) {
            returnTypeFromAnnotation = getTags.type();
        } else if (addTags != null) {
            returnTypeFromAnnotation = addTags.type();
        } else if (deleteTags != null) {
            returnTypeFromAnnotation = deleteTags.type();
        }

        if (returnTypeFromRp != null) {
            if (returnTypeFromAnnotation != null && !isResourceInterface(returnTypeFromAnnotation)) {
                if (!returnTypeFromRp.isAssignableFrom(returnTypeFromAnnotation)) {
                    throw new ConfigurationException("Method '" + theMethod.getName() + "' in type "
                            + theMethod.getDeclaringClass().getCanonicalName() + " returns type "
                            + returnTypeFromMethod.getCanonicalName() + " - Must return "
                            + returnTypeFromRp.getCanonicalName()
                            + " (or a subclass of it) per IResourceProvider contract");
                }
                if (!returnTypeFromRp.isAssignableFrom(returnTypeFromAnnotation)) {
                    throw new ConfigurationException("Method '" + theMethod.getName() + "' in type "
                            + theMethod.getDeclaringClass().getCanonicalName() + " claims to return type "
                            + returnTypeFromAnnotation.getCanonicalName() + " per method annotation - Must return "
                            + returnTypeFromRp.getCanonicalName()
                            + " (or a subclass of it) per IResourceProvider contract");
                }
                returnType = returnTypeFromAnnotation;
            } else {
                returnType = returnTypeFromRp;
            }
        } else {
            if (!isResourceInterface(returnTypeFromAnnotation)) {
                if (!verifyIsValidResourceReturnType(returnTypeFromAnnotation)) {
                    throw new ConfigurationException(
                            "Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName()
                                    + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns "
                                    + toLogString(returnTypeFromAnnotation)
                                    + " according to annotation - Must return a resource type");
                }
                returnType = returnTypeFromAnnotation;
            } else {
                // if (IRestfulClient.class.isAssignableFrom(theMethod.getDeclaringClass())) {
                // Clients don't define their methods in resource specific types, so they can
                // infer their resource type from the method return type.
                returnType = (Class<? extends IBaseResource>) returnTypeFromMethod;
                // } else {
                // This is a plain provider method returning a resource, so it should be
                // an operation or global search presumably
                // returnType = null;
            }
        }

        if (read != null) {
            return new ReadMethodBinding(returnType, theMethod, theContext, theProvider);
        } else if (search != null) {
            if (search.dynamic()) {
                IDynamicSearchResourceProvider provider = (IDynamicSearchResourceProvider) theProvider;
                return new DynamicSearchMethodBinding(returnType, theMethod, theContext, provider);
            } else {
                return new SearchMethodBinding(returnType, theMethod, theContext, theProvider);
            }
        } else if (conformance != null) {
            return new ConformanceMethodBinding(theMethod, theContext, theProvider);
        } else if (create != null) {
            return new CreateMethodBinding(theMethod, theContext, theProvider);
        } else if (update != null) {
            return new UpdateMethodBinding(theMethod, theContext, theProvider);
        } else if (delete != null) {
            return new DeleteMethodBinding(theMethod, theContext, theProvider);
        } else if (patch != null) {
            return new PatchMethodBinding(theMethod, theContext, theProvider);
        } else if (history != null) {
            return new HistoryMethodBinding(theMethod, theContext, theProvider);
        } else if (validate != null) {
            if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
                return new ValidateMethodBindingDstu1(theMethod, theContext, theProvider);
            } else {
                return new ValidateMethodBindingDstu2Plus(returnType, returnTypeFromRp, theMethod, theContext,
                        theProvider, validate);
            }
        } else if (getTags != null) {
            return new GetTagsMethodBinding(theMethod, theContext, theProvider, getTags);
        } else if (addTags != null) {
            return new AddTagsMethodBinding(theMethod, theContext, theProvider, addTags);
        } else if (deleteTags != null) {
            return new DeleteTagsMethodBinding(theMethod, theContext, theProvider, deleteTags);
        } else if (transaction != null) {
            return new TransactionMethodBinding(theMethod, theContext, theProvider);
        } else if (operation != null) {
            return new OperationMethodBinding(returnType, returnTypeFromRp, theMethod, theContext, theProvider,
                    operation);
        } else {
            throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName()
                    + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
        }

        // // each operation name must have a request type annotation and be
        // unique
        // if (null != read) {
        // return rm;
        // }
        //
        // SearchMethodBinding sm = new SearchMethodBinding();
        // if (null != search) {
        // sm.setRequestType(SearchMethodBinding.RequestType.GET);
        // } else if (null != theMethod.getAnnotation(PUT.class)) {
        // sm.setRequestType(SearchMethodBinding.RequestType.PUT);
        // } else if (null != theMethod.getAnnotation(POST.class)) {
        // sm.setRequestType(SearchMethodBinding.RequestType.POST);
        // } else if (null != theMethod.getAnnotation(DELETE.class)) {
        // sm.setRequestType(SearchMethodBinding.RequestType.DELETE);
        // } else {
        // return null;
        // }
        //
        // return sm;
    }

    private static boolean isResourceInterface(Class<?> theReturnTypeFromMethod) {
        return theReturnTypeFromMethod.equals(IBaseResource.class)
                || theReturnTypeFromMethod.equals(IResource.class)
                || theReturnTypeFromMethod.equals(IAnyResource.class);
    }

    private static void populateException(BaseServerResponseException theEx, Reader theResponseReader) {
        try {
            String responseText = IOUtils.toString(theResponseReader);
            theEx.setResponseBody(responseText);
        } catch (IOException e) {
            ourLog.debug("Failed to read response", e);
        }
    }

    private static String toLogString(Class<?> theType) {
        if (theType == null) {
            return null;
        }
        return theType.getCanonicalName();
    }

    private static boolean verifyIsValidResourceReturnType(Class<?> theReturnType) {
        if (theReturnType == null) {
            return false;
        }
        if (!IBaseResource.class.isAssignableFrom(theReturnType)) {
            return false;
        }
        return true;
        // boolean retVal = Modifier.isAbstract(theReturnType.getModifiers()) == false;
        // return retVal;
    }

    public static boolean verifyMethodHasZeroOrOneOperationAnnotation(Method theNextMethod,
            Object... theAnnotations) {
        Object obj1 = null;
        for (Object object : theAnnotations) {
            if (object != null) {
                if (obj1 == null) {
                    obj1 = object;
                } else {
                    throw new ConfigurationException("Method " + theNextMethod.getName() + " on type '"
                            + theNextMethod.getDeclaringClass().getSimpleName() + " has annotations @"
                            + obj1.getClass().getSimpleName() + " and @" + object.getClass().getSimpleName()
                            + ". Can not have both.");
                }

            }
        }
        if (obj1 == null) {
            return false;
            // throw new ConfigurationException("Method '" +
            // theNextMethod.getName() + "' on type '" +
            // theNextMethod.getDeclaringClass().getSimpleName() +
            // " has no FHIR method annotations.");
        }
        return true;
    }

    /**
     * @see ServletRequestDetails#getByteStreamRequestContents()
     */
    public static class ActiveRequestReader implements IRequestReader {
        @Override
        public InputStream getInputStream(RequestDetails theRequestDetails) throws IOException {
            return theRequestDetails.getInputStream();
        }
    }

    /**
     * @see ServletRequestDetails#getByteStreamRequestContents()
     */
    public static class InactiveRequestReader implements IRequestReader {
        @Override
        public InputStream getInputStream(RequestDetails theRequestDetails) {
            throw new IllegalStateException(
                    "The servlet-api JAR is not found on the classpath. Please check that this library is available.");
        }
    }

    /**
     * @see ServletRequestDetails#getByteStreamRequestContents()
     */
    public static interface IRequestReader {
        InputStream getInputStream(RequestDetails theRequestDetails) throws IOException;
    }

}