package ca.uhn.fhir.rest.method;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete;
import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.annotation.Metadata;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingUtil;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.ReflectionUtil;
public abstract class BaseMethodBinding {
private Method myMethod;
private FhirContext myContext;
private Object myProvider;
public BaseMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) {
assert theMethod != null;
assert theConetxt != null;
myMethod = theMethod;
myContext = theConetxt;
myProvider = theProvider;
}
/**
* 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 Object getProvider() {
return myProvider;
}
public FhirContext getContext() {
return myContext;
}
public Method getMethod() {
return myMethod;
}
public abstract BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException;
public abstract RestfulOperationTypeEnum getResourceOperationType();
public abstract RestfulOperationSystemEnum getSystemOperationType();
public abstract boolean matches(Request theRequest);
protected IParser createAppropriateParser(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode) throws IOException {
IParser parser;
if (Constants.CT_ATOM_XML.equals(theResponseMimeType)) {
parser = getContext().newXmlParser();
} else if (Constants.CT_FHIR_XML.equals(theResponseMimeType)) {
parser = getContext().newXmlParser();
} else {
throw new NonFhirResponseException("Response contains non-FHIR content-type: " + theResponseMimeType, theResponseMimeType, theResponseStatusCode, IOUtils.toString(theResponseReader));
}
return parser;
}
@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);
// ** if you add another annotation above, also add it to the next line:
if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance, create, update, delete, history)) {
return null;
}
Class<? extends IResource> returnType;
Class<? extends IResource> 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 (MethodOutcome.class.equals(returnTypeFromMethod)) {
// returns a method outcome
}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 (!verifyIsValidResourceReturnType(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) of a resource type");
}
} else {
if (!verifyIsValidResourceReturnType(returnTypeFromMethod)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName()
+ " returns " + toLogString(returnTypeFromMethod) + " - Must return a resource type");
}
}
Class<? extends IResource> returnTypeFromAnnotation = IResource.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 (create != null) {
returnTypeFromAnnotation = create.type();
} else if (update != null) {
returnTypeFromAnnotation = update.type();
}
if (returnTypeFromRp != null) {
if (returnTypeFromAnnotation != null && returnTypeFromAnnotation != IResource.class) {
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 (returnTypeFromAnnotation != IResource.class) {
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 {
returnType = (Class<? extends IResource>) returnTypeFromMethod;
}
}
if (read != null) {
return new ReadMethodBinding(returnType, theMethod, theContext, theProvider);
} else if (search != null) {
String queryName = search.queryName();
return new SearchMethodBinding(returnType, theMethod, queryName, 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 (history != null) {
return new HistoryMethodBinding(theMethod, theContext, theProvider);
} 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 verifyIsValidResourceReturnType(Class<?> theReturnType) {
if (theReturnType == null) {
return false;
}
if (!IResource.class.isAssignableFrom(theReturnType)) {
return false;
}
boolean retVal = Modifier.isAbstract(theReturnType.getModifiers()) == false;
return retVal;
}
private static String toLogString(Class<?> theType) {
if (theType == null) {
return null;
}
return theType.getCanonicalName();
}
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;
}
protected static List<IResource> toResourceList(Object response) throws InternalErrorException {
if (response == null) {
return Collections.emptyList();
} else if (response instanceof IResource) {
return Collections.singletonList((IResource) response);
} else if (response instanceof Collection) {
List<IResource> retVal = new ArrayList<IResource>();
for (Object next : ((Collection<?>) response)) {
retVal.add((IResource) next);
}
return retVal;
} else {
throw new InternalErrorException("Unexpected return type: " + response.getClass().getCanonicalName());
}
}
public static EncodingUtil determineResponseEncoding(HttpServletRequest theRequest, Map<String, String[]> theParams) {
String[] format = theParams.remove(Constants.PARAM_FORMAT);
if (format != null) {
for (String nextFormat : format) {
EncodingUtil retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextFormat);
if (retVal != null) {
return retVal;
}
}
}
Enumeration<String> acceptValues = theRequest.getHeaders("Accept");
if (acceptValues != null) {
while (acceptValues.hasMoreElements()) {
EncodingUtil retVal = Constants.FORMAT_VAL_TO_ENCODING.get(acceptValues.nextElement());
if (retVal != null) {
return retVal;
}
}
}
return EncodingUtil.XML;
}
public abstract void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException;
public abstract Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
BaseServerResponseException;
}