Documentation updates

This commit is contained in:
jamesagnew 2014-03-28 17:11:56 -04:00
parent b325a5bfb1
commit ea73770433
27 changed files with 10819 additions and 261 deletions

File diff suppressed because it is too large Load Diff

View File

@ -18,15 +18,32 @@ import ca.uhn.fhir.rest.client.IRestfulClientFactory;
import ca.uhn.fhir.rest.client.RestfulClientFactory;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
/**
* The FHIR context is the central starting point for the use of the HAPI FHIR API. It should be created once, and then used as a factory for various other types of objects (parsers, clients, etc.).
*
* <p>
* Important usage notes:
* <ul>
* <li>Thread safety: <b>This class is thread safe</b> and may be shared between multiple processing threads.</li>
* <li>
* Performance: <b>This class is expensive</b> to create, as it scans every resource class it needs to parse or encode to build up an internal model of those classes. For that reason, you should try
* to create one FhirContext instance which remains for the life of your application and reuse that instance. Note that it will not cause problems to create multiple instances (ie. resources
* originating from one FhirContext may be passed to parsers originating from another) but you will incur a performance penalty if a new FhirContext is created for every message you parse/encode.</li>
* </ul>
* </p>
*/
public class FhirContext {
private volatile Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> myClassToElementDefinition = Collections.emptyMap();
private volatile Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = Collections.emptyMap();
private volatile Map<String, RuntimeResourceDefinition> myNameToElementDefinition = Collections.emptyMap();
private INarrativeGenerator myNarrativeGenerator;
private IRestfulClientFactory myRestfulClientFactory;
private volatile INarrativeGenerator myNarrativeGenerator;
private volatile IRestfulClientFactory myRestfulClientFactory;
private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
/**
* Default constructor. In most cases this is the right constructor to use.
*/
public FhirContext() {
}
@ -41,7 +58,11 @@ public class FhirContext {
public FhirContext(Collection<Class<? extends IResource>> theResourceTypes) {
scanResourceTypes(theResourceTypes);
}
/**
* Returns the scanned runtime model for the given type. This is an advanced feature
* which is generally only needed for extending the core library.
*/
public BaseRuntimeElementDefinition<?> getElementDefinition(Class<? extends IElement> theElementType) {
return myClassToElementDefinition.get(theElementType);
}
@ -50,6 +71,10 @@ public class FhirContext {
return myNarrativeGenerator;
}
/**
* Returns the scanned runtime model for the given type. This is an advanced feature
* which is generally only needed for extending the core library.
*/
public RuntimeResourceDefinition getResourceDefinition(Class<? extends IResource> theResourceType) {
RuntimeResourceDefinition retVal = (RuntimeResourceDefinition) myClassToElementDefinition.get(theResourceType);
if (retVal == null) {
@ -57,15 +82,23 @@ public class FhirContext {
}
return retVal;
}
/**
* Returns the scanned runtime model for the given type. This is an advanced feature
* which is generally only needed for extending the core library.
*/
public RuntimeResourceDefinition getResourceDefinition(IResource theResource) {
return getResourceDefinition(theResource.getClass());
}
/**
* Returns the scanned runtime model for the given type. This is an advanced feature
* which is generally only needed for extending the core library.
*/
@SuppressWarnings("unchecked")
public RuntimeResourceDefinition getResourceDefinition(String theResourceName) {
RuntimeResourceDefinition retVal = myNameToElementDefinition.get(theResourceName);
if (retVal == null) {
try {
String candidateName = Patient.class.getPackage().getName() + "." + theResourceName;
@ -77,20 +110,28 @@ public class FhirContext {
return null;
}
}
return retVal;
}
/**
* Returns the scanned runtime model for the given type. This is an advanced feature
* which is generally only needed for extending the core library.
*/
public RuntimeResourceDefinition getResourceDefinitionById(String theId) {
return myIdToResourceDefinition.get(theId);
}
/**
* Returns the scanned runtime models. This is an advanced feature
* which is generally only needed for extending the core library.
*/
public Collection<RuntimeResourceDefinition> getResourceDefinitions() {
return myIdToResourceDefinition.values();
}
public IRestfulClientFactory getRestfulClientFactory() {
if (myRestfulClientFactory==null) {
if (myRestfulClientFactory == null) {
myRestfulClientFactory = new RestfulClientFactory(this);
}
return myRestfulClientFactory;
@ -100,12 +141,25 @@ public class FhirContext {
return myRuntimeChildUndeclaredExtensionDefinition;
}
/**
* Create and return a new JSON parser.
*
* <p>
* Performance Note: <b>This class is cheap</b> to create, and may be called once for
* every message being processed without incurring any performance penalty
* </p>
*/
public IParser newJsonParser() {
return new JsonParser(this);
}
/**
* Instantiates a new client instance
* Instantiates a new client instance.
*
* <p>
* Performance Note: <b>This class is cheap</b> to create, and may be called once for
* every message being processed without incurring any performance penalty
* </p>
*
* @param theClientType
* The client type, which is an interface type to be instantiated
@ -119,14 +173,22 @@ public class FhirContext {
return getRestfulClientFactory().newClient(theClientType, theServerBase);
}
/**
* Create and return a new JSON parser.
*
* <p>
* Performance Note: <b>This class is cheap</b> to create, and may be called once for
* every message being processed without incurring any performance penalty
* </p>
*/
public IParser newXmlParser() {
return new XmlParser(this);
}
public void setNarrativeGenerator(INarrativeGenerator theNarrativeGenerator) {
myNarrativeGenerator = theNarrativeGenerator;
}
private RuntimeResourceDefinition scanResourceType(Class<? extends IResource> theResourceType) {
ArrayList<Class<? extends IResource>> resourceTypes = new ArrayList<Class<? extends IResource>>();
resourceTypes.add(theResourceType);
@ -139,11 +201,11 @@ public class FhirContext {
if (myRuntimeChildUndeclaredExtensionDefinition == null) {
myRuntimeChildUndeclaredExtensionDefinition = scanner.getRuntimeChildUndeclaredExtensionDefinition();
}
Map<String, RuntimeResourceDefinition> nameToElementDefinition = new HashMap<String, RuntimeResourceDefinition>();
nameToElementDefinition.putAll(myNameToElementDefinition);
nameToElementDefinition.putAll(scanner.getNameToResourceDefinitions());
Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> classToElementDefinition = new HashMap<Class<? extends IElement>, BaseRuntimeElementDefinition<?>>();
classToElementDefinition.putAll(myClassToElementDefinition);
classToElementDefinition.putAll(scanner.getClassToElementDefinitions());
@ -155,7 +217,7 @@ public class FhirContext {
myNameToElementDefinition = nameToElementDefinition;
myClassToElementDefinition = classToElementDefinition;
myIdToResourceDefinition = idToElementDefinition;
return classToElementDefinition;
}

View File

@ -550,6 +550,30 @@ class ParserState<T extends IElement> {
}
private class SwallowChildrenWholeState extends BaseState
{
private int myDepth;
public SwallowChildrenWholeState(PreResourceState thePreResourceState) {
super(thePreResourceState);
}
@Override
public void endingElement() throws DataFormatException {
myDepth--;
if (myDepth < 0) {
pop();
}
}
@Override
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
myDepth++;
}
}
private class ElementCompositeState extends BaseState {
private BaseRuntimeElementCompositeDefinition<?> myDefinition;
@ -579,7 +603,17 @@ class ParserState<T extends IElement> {
@Override
public void enteringNewElement(String theNamespace, String theChildName) throws DataFormatException {
BaseRuntimeChildDefinition child = myDefinition.getChildByNameOrThrowDataFormatException(theChildName);
BaseRuntimeChildDefinition child;
try {
child = myDefinition.getChildByNameOrThrowDataFormatException(theChildName);
} catch (DataFormatException e) {
if (false) {// TODO: make this configurable
throw e;
}
ourLog.warn(e.getMessage());
push(new SwallowChildrenWholeState(getPreResourceState()));
return;
}
BaseRuntimeElementDefinition<?> target = child.getChildByName(theChildName);
if (target == null) {
throw new DataFormatException("Found unexpected element '" + theChildName + "' in parent element '" + myDefinition.getName() + "'. Valid names are: " + child.getValidChildNames());

View File

@ -14,6 +14,7 @@ import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
@ -90,7 +91,7 @@ public class ClientInvocationHandler implements InvocationHandler {
list.add(next.getValue());
}
}
return binding.invokeClient(mimeType, reader, response.getStatusLine().getStatusCode(), headers);
} finally {
@ -101,9 +102,15 @@ public class ClientInvocationHandler implements InvocationHandler {
}
public static Reader createReaderFromResponse(HttpResponse theResponse) throws IllegalStateException, IOException {
ContentType ct = ContentType.get(theResponse.getEntity());
Charset charset = ct.getCharset();
HttpEntity entity = theResponse.getEntity();
if (entity == null) {
return new StringReader("");
}
Charset charset = null;
if (entity.getContentType().getElements() != null) {
ContentType ct = ContentType.get(entity);
charset = ct.getCharset();
}
if (charset == null) {
ourLog.warn("Response did not specify a charset.");
charset = Charset.forName("UTF-8");

View File

@ -18,16 +18,23 @@ public interface IRestfulClientFactory {
* @throws ConfigurationException
* If the interface type is not an interface
*/
public <T extends IRestfulClient> T newClient(Class<T> theClientType, String theServerBase);
<T extends IRestfulClient> T newClient(Class<T> theClientType, String theServerBase);
/**
* Sets the Apache HTTP client instance to be used by any new restful clients created by
* this factory. If set to <code>null</code>, which is the default, a new HTTP client with
* this factory. If set to <code>null</code>, a new HTTP client with
* default settings will be created.
*
* @param theHttpClient An HTTP client instance to use, or <code>null</code>
*/
public void setHttpClient(HttpClient theHttpClient);
void setHttpClient(HttpClient theHttpClient);
/**
* Returns the Apache HTTP client instance. This method will not return null.
*
* @see #setHttpClient(HttpClient)
*/
HttpClient getHttpClient();
}

View File

@ -3,6 +3,8 @@ package ca.uhn.fhir.rest.client;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.http.client.HttpClient;
@ -20,6 +22,7 @@ public class RestfulClientFactory implements IRestfulClientFactory {
private FhirContext myContext;
private HttpClient myHttpClient;
private Map<Class<? extends IRestfulClient>, ClientInvocationHandler> myInvocationHandlers = new HashMap<Class<? extends IRestfulClient>, ClientInvocationHandler>();
/**
* Constructor
@ -50,59 +53,65 @@ public class RestfulClientFactory implements IRestfulClientFactory {
*/
@SuppressWarnings("unchecked")
@Override
public <T extends IRestfulClient> T newClient(Class<T> theClientType, String theServerBase) {
public synchronized <T extends IRestfulClient> T newClient(Class<T> theClientType, String theServerBase) {
if (!theClientType.isInterface()) {
throw new ConfigurationException(theClientType.getCanonicalName() + " is not an interface");
}
HttpClient client;
if (myHttpClient != null) {
client = myHttpClient;
} else {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
client = builder.build();
}
HttpClient client = getHttpClient();
String serverBase = theServerBase;
if (!serverBase.endsWith("/")) {
serverBase = serverBase + "/";
}
ClientInvocationHandler invocationHandler = new ClientInvocationHandler(client, myContext, serverBase, theClientType);
ClientInvocationHandler invocationHandler = myInvocationHandlers.get(theClientType);
if (invocationHandler == null) {
invocationHandler = new ClientInvocationHandler(client, myContext, serverBase, theClientType);
for (Method nextMethod : theClientType.getMethods()) {
Class<? extends IResource> resReturnType = null;
Class<?> returnType = nextMethod.getReturnType();
if (IResource.class.isAssignableFrom(returnType)) {
resReturnType = (Class<? extends IResource>) returnType;
} else if (java.util.Collection.class.isAssignableFrom(returnType)) {
Class<?> returnTypeColl = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(nextMethod);
if (!IResource.class.isAssignableFrom(returnTypeColl)) {
throw new ConfigurationException("Generic type of collection for method '" + nextMethod + "' is not a subclass of IResource");
for (Method nextMethod : theClientType.getMethods()) {
Class<? extends IResource> resReturnType = null;
Class<?> returnType = nextMethod.getReturnType();
if (IResource.class.isAssignableFrom(returnType)) {
resReturnType = (Class<? extends IResource>) returnType;
} else if (java.util.Collection.class.isAssignableFrom(returnType)) {
Class<?> returnTypeColl = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(nextMethod);
if (!IResource.class.isAssignableFrom(returnTypeColl)) {
throw new ConfigurationException("Generic type of collection for method '" + nextMethod + "' is not a subclass of IResource");
}
resReturnType = (Class<? extends IResource>) returnTypeColl;
}
resReturnType = (Class<? extends IResource>) returnTypeColl;
BaseMethodBinding binding = BaseMethodBinding.bindMethod(resReturnType, nextMethod, myContext);
invocationHandler.addBinding(nextMethod, binding);
}
BaseMethodBinding binding = BaseMethodBinding.bindMethod(resReturnType, nextMethod, myContext);
invocationHandler.addBinding(nextMethod, binding);
myInvocationHandlers.put(theClientType, invocationHandler);
}
T proxy = instantiateProxy(theClientType, invocationHandler);
return proxy;
}
@Override
public synchronized HttpClient getHttpClient() {
if (myHttpClient == null) {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
myHttpClient = builder.build();
}
return myHttpClient;
}
/**
* Sets the Apache HTTP client instance to be used by any new restful
* clients created by this factory. If set to <code>null</code>, which is
* the default, a new HTTP client with default settings will be created.
* Sets the Apache HTTP client instance to be used by any new restful clients created by this factory. If set to <code>null</code>, which is the default, a new HTTP client with default settings
* will be created.
*
* @param theHttpClient
* An HTTP client instance to use, or <code>null</code>
*/
@Override
public void setHttpClient(HttpClient theHttpClient) {
public synchronized void setHttpClient(HttpClient theHttpClient) {
myHttpClient = theHttpClient;
}

View File

@ -201,4 +201,8 @@ public abstract class BaseMethodBinding {
public abstract Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException;
public static BaseMethodBinding bindSystemMethod(Method theMethod, FhirContext theContext) {
return bindMethod(null, theMethod, theContext);
}
}

View File

@ -66,8 +66,8 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin
resourceParameter = (ResourceParameter) next;
myResourceName = theContext.getResourceDefinition(resourceParameter.getResourceType()).getName();
myResourceParameterIndex = index;
index++;
}
index++;
}
if (resourceParameter == null) {

View File

@ -14,7 +14,6 @@ import java.util.UUID;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.context.ConfigurationException;
@ -27,10 +26,8 @@ import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException;
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.RestfulServer.NarrativeModeEnum;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
@ -117,7 +114,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
}
public abstract List<IResource> invokeServer(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException, InternalErrorException;
public abstract List<IResource> invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException, InternalErrorException;
@Override
public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException {

View File

@ -14,7 +14,6 @@ import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.client.GetClientInvocation;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -22,10 +21,11 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding
public ConformanceMethodBinding(Method theMethod, FhirContext theContext) {
super(Conformance.class, theMethod, theContext);
if (getMethodReturnType() != MethodReturnTypeEnum.RESOURCE) {
throw new ConfigurationException("Conformance resource provider '" + theMethod.getName() + "' should return type " + Conformance.class);
if (getMethodReturnType() != MethodReturnTypeEnum.RESOURCE || theMethod.getReturnType() != Conformance.class) {
throw new ConfigurationException("Conformance resource provider method '" + theMethod.getName() + "' should return type " + Conformance.class);
}
}
@Override
@ -35,18 +35,19 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding
@Override
public GetClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
return new GetClientInvocation("metadata");
return new GetClientInvocation("metadata");
}
@Override
public List<IResource> invokeServer(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException, InternalErrorException {
public List<IResource> invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException,
InternalErrorException {
IResource conf;
try {
conf = (Conformance) getMethod().invoke(theResourceProvider);
} catch (Exception e) {
throw new InternalErrorException("Failed to call access method",e);
throw new InternalErrorException("Failed to call access method", e);
}
return Collections.singletonList(conf);
}
@ -55,11 +56,11 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding
if (theRequest.getRequestType() == RequestType.OPTIONS) {
return true;
}
if (theRequest.getRequestType() == RequestType.GET && "metadata".equals(theRequest.getOperation())) {
return true;
}
return false;
}

View File

@ -15,7 +15,6 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.client.GetClientInvocation;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -81,7 +80,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding {
}
@Override
public List<IResource> invokeServer(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException,
public List<IResource> invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException,
InternalErrorException {
Object[] params = new Object[myParameterCount];
params[myIdIndex] = theId;

View File

@ -9,7 +9,6 @@ import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.server.IResourceProvider;
public class Request {
@ -21,7 +20,7 @@ public class Request {
private Map<String, String[]> myParameters;
private RequestType myRequestType;
private String myResourceName;
private IResourceProvider myResourceProvider;
private Object myResourceProvider;
private IdDt myVersion;
private HttpServletRequest myServletRequest;
@ -57,7 +56,7 @@ public class Request {
return myResourceName;
}
public IResourceProvider getResourceProvider() {
public Object getResourceProvider() {
return myResourceProvider;
}
@ -99,8 +98,8 @@ public class Request {
myResourceName = theResourceName;
}
public void setResourceProvider(IResourceProvider theResourceProvider) {
myResourceProvider=theResourceProvider;
public void setResourceProvider(Object theProvider) {
myResourceProvider=theProvider;
}
public void setVersion(IdDt theVersion) {

View File

@ -20,7 +20,6 @@ import ca.uhn.fhir.rest.client.GetClientInvocation;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.IQueryParameter;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -85,7 +84,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
}
@Override
public List<IResource> invokeServer(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> parameterValues) throws InvalidRequestException,
public List<IResource> invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> parameterValues) throws InvalidRequestException,
InternalErrorException {
assert theId == null;
assert theVersionId == null;

View File

@ -93,14 +93,14 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBinding {
PutClientInvocation retVal = new PutClientInvocation(getContext(), resource, urlExtension.toString());
if (myVersionIdParameterIndex != null) {
IdDt versionIdDt = (IdDt) theArgs[myIdParameterIndex];
IdDt versionIdDt = (IdDt) theArgs[myVersionIdParameterIndex];
if (versionIdDt != null) {
String versionId = versionIdDt.getValue();
if (StringUtils.isNotBlank(versionId)) {
StringBuilder b = new StringBuilder();
b.append('/');
b.append(urlExtension);
b.append('/');
b.append("/_history/");
b.append(versionId);
retVal.addHeader(Constants.HEADER_CONTENT_LOCATION, b.toString());
}

View File

@ -21,6 +21,7 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.narrative.INarrativeGenerator;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.ConformanceMethodBinding;
import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.method.SearchMethodBinding;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
@ -28,6 +29,7 @@ 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.MethodNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.provider.ServerConformanceProvider;
import ca.uhn.fhir.rest.server.provider.ServerProfileProvider;
import ca.uhn.fhir.util.VersionUtil;
@ -40,143 +42,21 @@ public abstract class RestfulServer extends HttpServlet {
private static final long serialVersionUID = 1L;
private FhirContext myFhirContext;
private INarrativeGenerator myNarrativeGenerator;
private Map<String, ResourceBinding> myResourceNameToProvider = new HashMap<String, ResourceBinding>();
private Object myServerConformanceProvider;
private Map<Class<? extends IResource>, IResourceProvider> myTypeToProvider = new HashMap<Class<? extends IResource>, IResourceProvider>();
private boolean myUseBrowserFriendlyContentTypes;
// map of request handler resources keyed by resource name
private Map<String, ResourceBinding> resources = new HashMap<String, ResourceBinding>();
private ISecurityManager securityManager;
public FhirContext getFhirContext() {
return myFhirContext;
private BaseMethodBinding myServerConformanceMethod;
public RestfulServer() {
myServerConformanceProvider=new ServerConformanceProvider(this);
}
public INarrativeGenerator getNarrativeGenerator() {
return myNarrativeGenerator;
}
public Collection<ResourceBinding> getResourceBindings() {
return resources.values();
}
/**
* This method must be overridden to provide one or more resource providers
*/
public abstract Collection<IResourceProvider> getResourceProviders();
/**
* This method should be overridden to provide a security manager instance. By default, returns null.
*/
public ISecurityManager getSecurityManager() {
return null;
}
public IResourceProvider getServerConformanceProvider() {
return new ServerConformanceProvider(this);
}
public IResourceProvider getServerProfilesProvider() {
return new ServerProfileProvider(getFhirContext());
}
@Override
public final void init() throws ServletException {
initialize();
try {
ourLog.info("Initializing HAPI FHIR restful server");
securityManager = getSecurityManager();
if (null == securityManager) {
ourLog.warn("No security manager has been provided, requests will not be authenticated!");
}
Collection<IResourceProvider> resourceProvider = getResourceProviders();
for (IResourceProvider nextProvider : resourceProvider) {
Class<? extends IResource> resourceType = nextProvider.getResourceType();
if (resourceType==null) {
throw new NullPointerException("getResourceType() on class '" + nextProvider.getClass().getCanonicalName() + "' returned null");
}
if (myTypeToProvider.containsKey(resourceType)) {
throw new ServletException("Multiple providers for type: " + resourceType.getCanonicalName());
}
myTypeToProvider.put(resourceType, nextProvider);
}
ourLog.info("Got {} resource providers", myTypeToProvider.size());
myFhirContext = new FhirContext(myTypeToProvider.keySet());
myFhirContext.setNarrativeGenerator(myNarrativeGenerator);
for (IResourceProvider provider : myTypeToProvider.values()) {
findResourceMethods(provider);
}
findResourceMethods(getServerProfilesProvider());
findResourceMethods(getServerConformanceProvider());
} catch (Exception ex) {
ourLog.error("An error occurred while loading request handlers!", ex);
throw new ServletException("Failed to initialize FHIR Restful server", ex);
}
ourLog.info("A FHIR has been lit on this server");
}
public boolean isUseBrowserFriendlyContentTypes() {
return myUseBrowserFriendlyContentTypes;
}
/**
* Sets the {@link INarrativeGenerator Narrative Generator} to use when serializing responses from this server, or <code>null</code> (which is the default) to disable narrative generation.
*
* @throws IllegalStateException
* Note that this method can only be called prior to {@link #init() initialization} and will throw an {@link IllegalStateException} if called after that.
*/
public void setNarrativeGenerator(INarrativeGenerator theNarrativeGenerator) {
if (myFhirContext != null) {
throw new IllegalStateException("Server has already been initialized, can not change this property");
}
myNarrativeGenerator = theNarrativeGenerator;
}
/**
* If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of standard FHIR ones) when it detects that the request is coming from a browser
* instead of a FHIR
*/
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
}
private void findResourceMethods(IResourceProvider theProvider) throws Exception {
Class<? extends IResource> resourceType = theProvider.getResourceType();
RuntimeResourceDefinition definition = myFhirContext.getResourceDefinition(resourceType);
ResourceBinding r = new ResourceBinding();
r.setResourceProvider(theProvider);
r.setResourceName(definition.getName());
resources.put(definition.getName(), r);
ourLog.info("Scanning type for RESTful methods: {}", theProvider.getClass());
Class<?> clazz = theProvider.getClass();
for (Method m : clazz.getDeclaredMethods()) {
if (Modifier.isPublic(m.getModifiers())) {
ourLog.debug("Scanning public method: {}#{}", theProvider.getClass(), m.getName());
BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(theProvider.getResourceType(), m, myFhirContext);
if (foundMethodBinding != null) {
r.addMethod(foundMethodBinding);
ourLog.info(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName());
} else {
ourLog.debug(" * Method: {}#{} is not a handler", theProvider.getClass(), m.getName());
}
}
}
public void addHapiHeader(HttpServletResponse theHttpResponse) {
theHttpResponse.addHeader("X-CatchingFhir", "HAPI FHIR " + VersionUtil.getVersion());
}
@Override
@ -203,7 +83,97 @@ public abstract class RestfulServer extends HttpServlet {
protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethodBinding.RequestType.PUT, request, response);
}
private void findResourceMethods(IResourceProvider theProvider) throws Exception {
Class<? extends IResource> resourceType = theProvider.getResourceType();
RuntimeResourceDefinition definition = myFhirContext.getResourceDefinition(resourceType);
ResourceBinding r = new ResourceBinding();
r.setResourceProvider(theProvider);
r.setResourceName(definition.getName());
myResourceNameToProvider.put(definition.getName(), r);
ourLog.info("Scanning type for RESTful methods: {}", theProvider.getClass());
Class<?> clazz = theProvider.getClass();
for (Method m : clazz.getDeclaredMethods()) {
if (Modifier.isPublic(m.getModifiers())) {
ourLog.debug("Scanning public method: {}#{}", theProvider.getClass(), m.getName());
BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(theProvider.getResourceType(), m, myFhirContext);
if (foundMethodBinding != null) {
r.addMethod(foundMethodBinding);
ourLog.info(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName());
} else {
ourLog.debug(" * Method: {}#{} is not a handler", theProvider.getClass(), m.getName());
}
}
}
}
private void findSystemMethods(Object theSystemProvider) {
Class<?> clazz = theSystemProvider.getClass();
for (Method m : clazz.getDeclaredMethods()) {
if (Modifier.isPublic(m.getModifiers())) {
ourLog.debug("Scanning public method: {}#{}", theSystemProvider.getClass(), m.getName());
BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindSystemMethod(m, myFhirContext);
if (foundMethodBinding != null) {
if (foundMethodBinding instanceof ConformanceMethodBinding) {
myServerConformanceMethod = foundMethodBinding;
}
ourLog.info(" * Method: {}#{} is a handler", theSystemProvider.getClass(), m.getName());
} else {
ourLog.debug(" * Method: {}#{} is not a handler", theSystemProvider.getClass(), m.getName());
}
}
}
}
public FhirContext getFhirContext() {
return myFhirContext;
}
public INarrativeGenerator getNarrativeGenerator() {
return myNarrativeGenerator;
}
public Collection<ResourceBinding> getResourceBindings() {
return myResourceNameToProvider.values();
}
/**
* This method must be overridden to provide one or more resource providers
*/
public abstract Collection<IResourceProvider> getResourceProviders();
/**
* This method should be overridden to provide a security manager instance. By default, returns null.
*/
public ISecurityManager getSecurityManager() {
return null;
}
/**
* Returns the server conformance provider, which is the provider that
* is used to generate the server's conformance (metadata) statement.
* <p>
* By default, the {@link ServerConformanceProvider} is used, but
* this can be changed, or set to <code>null</code> if you do not wish
* to export a conformance statement.
* </p>
*/
public Object getServerConformanceProvider() {
return myServerConformanceProvider;
}
public IResourceProvider getServerProfilesProvider() {
return new ServerProfileProvider(getFhirContext());
}
protected void handleRequest(SearchMethodBinding.RequestType requestType, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
@ -261,17 +231,23 @@ public abstract class RestfulServer extends HttpServlet {
}
resourceName = tok.nextToken();
ResourceBinding resourceBinding;
Object provider=null;
ResourceBinding resourceBinding=null;
BaseMethodBinding resourceMethod=null;
if ("metadata".equals(resourceName)) {
operation = "metadata";
resourceBinding = resources.get("Conformance");
provider = myServerConformanceProvider;
if (provider==null) {
throw new ResourceNotFoundException("This server does not support 'metadata' query");
}
resourceMethod = myServerConformanceMethod;
} else {
resourceBinding = resources.get(resourceName);
resourceBinding = myResourceNameToProvider.get(resourceName);
if (resourceBinding == null) {
throw new MethodNotFoundException("Unknown resource type '" + resourceName+"' - Server knows how to handle: "+myResourceNameToProvider.keySet());
}
provider = resourceBinding.getResourceProvider();
}
if (resourceBinding == null) {
throw new MethodNotFoundException("Unknown resource type '" + resourceName+"' - Server knows how to handle: "+resources.keySet());
}
if (tok.hasMoreTokens()) {
String nextString = tok.nextToken();
@ -302,13 +278,15 @@ public abstract class RestfulServer extends HttpServlet {
r.setOperation(operation);
r.setParameters(params);
r.setRequestType(requestType);
r.setResourceProvider(resourceBinding.getResourceProvider());
r.setResourceProvider(provider);
r.setInputReader(request.getReader());
r.setFhirServerBase(fhirServerBase);
r.setCompleteUrl(completeUrl);
r.setServletRequest(request);
BaseMethodBinding resourceMethod = resourceBinding.getMethod(r);
if (resourceMethod == null && resourceBinding != null) {
resourceMethod = resourceBinding.getMethod(r);
}
if (null == resourceMethod) {
throw new MethodNotFoundException("No resource method available for the supplied parameters " + params);
}
@ -344,6 +322,49 @@ public abstract class RestfulServer extends HttpServlet {
}
@Override
public final void init() throws ServletException {
initialize();
try {
ourLog.info("Initializing HAPI FHIR restful server");
securityManager = getSecurityManager();
if (null == securityManager) {
ourLog.warn("No security manager has been provided, requests will not be authenticated!");
}
Collection<IResourceProvider> resourceProvider = getResourceProviders();
for (IResourceProvider nextProvider : resourceProvider) {
Class<? extends IResource> resourceType = nextProvider.getResourceType();
if (resourceType==null) {
throw new NullPointerException("getResourceType() on class '" + nextProvider.getClass().getCanonicalName() + "' returned null");
}
if (myTypeToProvider.containsKey(resourceType)) {
throw new ServletException("Multiple providers for type: " + resourceType.getCanonicalName());
}
myTypeToProvider.put(resourceType, nextProvider);
}
ourLog.info("Got {} resource providers", myTypeToProvider.size());
myFhirContext = new FhirContext(myTypeToProvider.keySet());
myFhirContext.setNarrativeGenerator(myNarrativeGenerator);
for (IResourceProvider provider : myTypeToProvider.values()) {
findResourceMethods(provider);
}
findResourceMethods(getServerProfilesProvider());
findSystemMethods(getServerConformanceProvider());
} catch (Exception ex) {
ourLog.error("An error occurred while loading request handlers!", ex);
throw new ServletException("Failed to initialize FHIR Restful server", ex);
}
ourLog.info("A FHIR has been lit on this server");
}
/**
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the server being used.
*/
@ -351,6 +372,52 @@ public abstract class RestfulServer extends HttpServlet {
// nothing by default
}
public boolean isUseBrowserFriendlyContentTypes() {
return myUseBrowserFriendlyContentTypes;
}
/**
* Sets the {@link INarrativeGenerator Narrative Generator} to use when serializing responses from this server, or <code>null</code> (which is the default) to disable narrative generation.
* Note that this method can only be called before the server is initialized.
*
* @throws IllegalStateException
* Note that this method can only be called prior to {@link #init() initialization} and will throw an {@link IllegalStateException} if called after that.
*/
public void setNarrativeGenerator(INarrativeGenerator theNarrativeGenerator) {
if (myFhirContext != null) {
throw new IllegalStateException("Server has already been initialized, can not change this property");
}
myNarrativeGenerator = theNarrativeGenerator;
}
/**
* Returns the server conformance provider, which is the provider that
* is used to generate the server's conformance (metadata) statement.
* <p>
* By default, the {@link ServerConformanceProvider} is used, but
* this can be changed, or set to <code>null</code> if you do not wish
* to export a conformance statement.
* </p>
* Note that this method can only be called before the server is initialized.
*
* @throws IllegalStateException
* Note that this method can only be called prior to {@link #init() initialization} and will throw an {@link IllegalStateException} if called after that.
*/
public void setServerConformanceProvider(Object theServerConformanceProvider) {
if (myFhirContext!=null) {
throw new IllegalStateException("Server is already started");
}
myServerConformanceProvider = theServerConformanceProvider;
}
/**
* If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of standard FHIR ones) when it detects that the request is coming from a browser
* instead of a FHIR
*/
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
}
public enum NarrativeModeEnum {
NORMAL,
ONLY,
@ -361,8 +428,4 @@ public abstract class RestfulServer extends HttpServlet {
}
}
public void addHapiHeader(HttpServletResponse theHttpResponse) {
theHttpResponse.addHeader("X-CatchingFhir", "HAPI FHIR " + VersionUtil.getVersion());
}
}

View File

@ -24,13 +24,12 @@ import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.SearchMethodBinding;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.IQueryParameter;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.ResourceBinding;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.ExtensionConstants;
import ca.uhn.fhir.util.VersionUtil;
public class ServerConformanceProvider implements IResourceProvider {
public class ServerConformanceProvider {
private volatile Conformance myConformance;
private final RestfulServer myRestfulServer;
@ -142,9 +141,4 @@ public class ServerConformanceProvider implements IResourceProvider {
return retVal;
}
@Override
public Class<? extends IResource> getResourceType() {
return Conformance.class;
}
}

View File

@ -1,5 +1,8 @@
package example;
import static ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum.OFFICIAL;
import static ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum.SECONDARY;
import java.io.IOException;
import java.util.List;
@ -7,7 +10,6 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.AdministrativeGenderCodesEnum;
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.RequiredParam;
@ -15,10 +17,10 @@ import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import static ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum.*;
public class QuickUsage {
@SuppressWarnings("unused")
public static void main(String[] args) throws DataFormatException, IOException {
Patient patient = new Patient();

View File

@ -1,15 +1,18 @@
package example;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.PathSpecification;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.dstu.composite.CodingDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
import ca.uhn.fhir.model.dstu.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.Organization;
@ -17,26 +20,32 @@ import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.IncludeParam;
import ca.uhn.fhir.rest.annotation.Metadata;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.ITestClient;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.param.CodingListParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.QualifiedDateParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
@SuppressWarnings("unused")
public abstract class RestfulPatientResourceProviderMore implements IResourceProvider {
private boolean detectedVersionConflict;
//START SNIPPET: searchAll
@Search
public List<Organization> getAllOrganizations() {
@ -236,9 +245,97 @@ public abstract MethodOutcome createNewPatient(@ResourceParam Patient thePatient
//END SNIPPET: createClient
//START SNIPPET: update
@Update
public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient thePatient) {
/*
* First we might want to do business validation. The UnprocessableEntityException
* results in an HTTP 422, which is appropriate for business rule failure
*/
if (thePatient.getIdentifierFirstRep().isEmpty()) {
throw new UnprocessableEntityException("No identifier supplied");
}
// Save this patient to the database...
savePatientToDatabase(theId, thePatient);
// This method returns a MethodOutcome object which contains
// the ID and Version ID for the newly saved resource
MethodOutcome retVal = new MethodOutcome();
retVal.setCreated(true);
retVal.setId(theId);
retVal.setVersionId(new IdDt("2")); // Leave this blank if the server doesn't version
return retVal;
}
//END SNIPPET: update
//START SNIPPET: updateClient
@Update
public abstract MethodOutcome updateSomePatient(@IdParam IdDt theId, @ResourceParam Patient thePatient);
//END SNIPPET: updateClient
//START SNIPPET: updateVersion
@Update
public MethodOutcome updatePatient(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId, @ResourceParam Patient thePatient) {
// ..Process..
if (detectedVersionConflict) {
throw new ResourceVersionConflictException("Invalid version");
}
MethodOutcome retVal = new MethodOutcome();
return retVal;
}
//END SNIPPET: updateVersion
public static void main(String[] args) throws DataFormatException, IOException {
}
private void savePatientToDatabase(Patient thePatient) {
// nothing
}
private void savePatientToDatabase(IdDt theId, Patient thePatient) {
// nothing
}
//START SNIPPET: metadataProvider
public class ConformanceProvider {
@Metadata
public Conformance getServerMetadata() {
Conformance retVal = new Conformance();
// ..populate..
return retVal;
}
}
//END SNIPPET: metadataProvider
//START SNIPPET: metadataClient
public interface MetadataClient extends IRestfulClient {
@Metadata
Conformance getServerMetadata();
// ....Other methods can also be added as usual....
}
//END SNIPPET: metadataClient
public void bbbbb() throws DataFormatException, IOException {
//START SNIPPET: metadataClientUsage
FhirContext ctx = new FhirContext();
MetadataClient client = ctx.newRestfulClient(MetadataClient.class, "http://spark.furore.com/fhir");
Conformance metadata = client.getServerMetadata();
System.out.println(ctx.newXmlParser().encodeResourceToString(metadata));
//END SNIPPET: metadataClientUsage
}

View File

@ -24,12 +24,147 @@
implementations, but client methods will follow the same patterns.
</p>
<macro name="toc">
</macro>
<p>
The following table lists the operations supported by
HAPI FHIR RESTful Servers and Clients.
</p>
<table>
<thead>
<tr style="font-weight: bold; font-size: 1.2em;">
<td>Operation</td>
<td>Definition</td>
</tr>
</thead>
<tbody>
<tr>
<td>
<a href="#instance_read">Instance - Read</a>
</td>
<td>
Read the current state of the resource
</td>
</tr>
<tr>
<td>
<a href="#instance_vread">Instance - VRead</a>
</td>
<td>
Read the state of a specific version of the resource
</td>
</tr>
<tr>
<td>
<a href="#instance_update">Instance - Update</a>
</td>
<td>
Read the state of a specific version of the resource
</td>
</tr>
<tr>
<td>
<a href="#instance_delete">Instance - Delete</a>
</td>
<td>
Delete a resource
</td>
</tr>
<tr>
<td>
<a href="#instance_history">Instance - History</a>
</td>
<td>
Retrieve the update history for a particular resource
</td>
</tr>
<tr>
<td>
<a href="#type_create">Type - Create</a>
</td>
<td>
Create a new resource with a server assigned id
</td>
</tr>
<tr>
<td>
<a href="#instance_update">Type - Search</a>
<macro name="toc">
<param name="section" value="8"/>
<param name="fromDepth" value="2"/>
</macro>
</td>
<td>
Search the resource type based on some filter criteria
</td>
</tr>
<tr>
<td>
<a href="#type_search">Type - Search</a>
</td>
<td>
Search the resource type based on some filter criteria
</td>
</tr>
<tr>
<td>
<a href="#type_history">Type - History</a>
</td>
<td>
Retrieve the update history for a particular resource type
</td>
</tr>
<tr>
<td>
<a href="#type_history">Type - Validate</a>
</td>
<td>
Check that the content would be acceptable as an update
</td>
</tr>
<tr>
<td>
<a href="#system_conformance">System - Conformance</a>
</td>
<td>
Get a conformance statement for the system
</td>
</tr>
<tr>
<td>
<a href="#system_transaction">System - Transaction</a>
</td>
<td>
Update, create or delete a set of resources as a single transaction
</td>
</tr>
<tr>
<td>
<a href="#system_history">System - History</a>
</td>
<td>
Retrieve the update history for all resources
</td>
</tr>
<tr>
<td>
<a href="#system_search">System - Search</a>
</td>
<td>
Search across all resource types based on some filter criteria
</td>
</tr>
</tbody>
</table>
</section>
<section name="Instance Level - read">
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="Instance Level - Read">
<a name="instance_read"/>
<p>
The
@ -53,7 +188,12 @@
</section>
<section name="Instance Level - vread">
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="Instance Level - VRead">
<a name="instance_vread"/>
<p>
The
@ -77,7 +217,158 @@
</section>
<section name="Type Level - search">
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="Instance Level - Update">
<a name="instance_update"/>
<p>
The
<a href="http://hl7.org/implement/standards/fhir/http.html#update"><b>update</b></a>
operation updates a specific resource instance (using its ID), and optionally
accepts a version ID as well (which can be used to detect version conflicts).
</p>
<p>
Update methods must be annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/Update.html">@Update</a>
annotation, and have a parameter annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/ResourceParam.html">@Resource</a>
annotation. This parameter contains the resource instance to be created.
</p>
<p>
In addition, the method must have a parameter annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/IdParam.html">@IdParam</a>
annotation, and optionally may have a parameter annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/VersionIdParam.html">@VersionIdParam</a>
</p>
<p>
Update methods must return an object of type
<a href="./apidocs/ca/uhn/fhir/rest/api/MethodOutcome.html">MethodOutcome</a>. This
object contains the identity of the created resource.
</p>
<p>
The following snippet shows how to define an update method on a server:
</p>
<macro name="snippet">
<param name="id" value="update" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<p>
Example URL to invoke this method (this would be invoked using an HTTP PUT,
with the resource in the PUT body):<br/>
<code>http://fhir.example.com/Patient</code>
</p>
<p>
The following snippet shows how the corresponding client interface
would look:
</p>
<macro name="snippet">
<param name="id" value="updateClient" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<p>
In the case of a server which is able to do version conflict checking, an
extra parameter would be added:
</p>
<macro name="snippet">
<param name="id" value="updateVersion" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
</section>
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="Instance Level - Delete">
<a name="instance_delete"/>
<p>
Not yet implemented
</p>
</section>
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="Instance Level - History">
<a name="instance_history"/>
<p>
Not yet implemented
</p>
</section>
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="Type Level - Create">
<a name="type_create"/>
<p>
The
<a href="http://hl7.org/implement/standards/fhir/http.html#create"><b>create</b></a>
operation saves a new resource to the server, allowing the server to
give that resource an ID and version ID.
</p>
<p>
Create methods must be annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/Create.html">@Create</a>
annotation, and have a single parameter annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/ResourceParam.html">@Resource</a>
annotation. This parameter contains the resource instance to be created.
</p>
<p>
Create methods must return an object of type
<a href="./apidocs/ca/uhn/fhir/rest/api/MethodOutcome.html">MethodOutcome</a>. This
object contains the identity of the created resource.
</p>
<p>
The following snippet shows how to define a server create method:
</p>
<macro name="snippet">
<param name="id" value="create" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<p>
Example URL to invoke this method (this would be invoked using an HTTP POST,
with the resource in the POST body):<br/>
<code>http://fhir.example.com/Patient</code>
</p>
<p>
The following snippet shows how the corresponding client interface
would look:
</p>
<macro name="snippet">
<param name="id" value="createClient" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
</section>
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="Type Level - Search">
<a name="type_search"/>
<p>
The
@ -152,7 +443,7 @@
</p>
</subsection>
<subsection name="Chained Parameters">
<subsection name="Multiple Parameters">
<p>
Search methods may take multiple parameters, and these parameters
@ -375,50 +666,128 @@
</subsection>
</section>
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="Type Level - History">
<a name="type_history"/>
<section name="Type Level - create">
<p>
The
<a href="http://hl7.org/implement/standards/fhir/http.html#create"><b>create</b></a>
operation saves a new resource to the server, allowing the server to
give that resource an ID and version ID.
Not yet implemented
</p>
</section>
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="Type Level - Validate">
<a name="type_validate"/>
<p>
Create methods must be annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/Create.html">@Create</a>
annotation, and have a single parameter annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/ResourceParam.html">@Resource</a>
annotation. This parameter contains the resource instance to be created.
Not yet implemented
</p>
</section>
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="System Level - Conformance">
<a name="system_conformance"/>
<p>
FHIR defines that a FHIR Server must be able to export a conformance statement,
which is an instance of the
<a href="http://hl7.org/implement/standards/fhir/conformance.html">Conformance</a>
resource describing the server itself.
</p>
<p>
The following snippet shows how to define a server create method:
The HAPI FHIR RESTful server will automatically export such
a conformance statement. See the
<a href="./doc_rest_server.html">RESTful Server</a>
documentation for more information.
</p>
<p>
If you wish to override this default behaviour by creating
your own metadata provider, you simply need to define a class
with a method annotated using the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/Metadata.html">@Metadata</a>
annotation.
</p>
<macro name="snippet">
<param name="id" value="create" />
<param name="id" value="metadataProvider" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<p>
To create a Client which can retrieve a Server's conformance
statement is simple. First, define your Client Interface, using
the @Metadata annotation:
</p>
<macro name="snippet">
<param name="id" value="metadataClient" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<p>
Example URL to invoke this method (this would be invoked using an HTTP POST,
with the resource in the POST body):<br/>
<code>http://fhir.example.com/Patient</code>
Then use the standard
<a href="doc_rest_client.html">RESTful Client</a> mechanism for instantiating
a client:
</p>
<p>
The following snippet shows how the corresponding client interface
would look:
</p>
<macro name="snippet">
<param name="id" value="createClient" />
<param name="id" value="metadataClientUsage" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
</section>
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="System Level - Transaction">
<a name="system_transaction"/>
<p>
Not yet implemented
</p>
</section>
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="System Level - History">
<a name="system_history"/>
<p>
Not yet implemented
</p>
</section>
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="System Level - Search">
<a name="system_search"/>
<p>
Not yet implemented
</p>
</section>
</body>
</document>

View File

@ -60,7 +60,7 @@
</macro>
<p>
You will probable wish to add more methods
You will probably wish to add more methods
to your resource provider. See
<a href="./doc_rest_operations.html">RESTful Operations</a> for
lots more examples of how to add methods for various operations.
@ -101,6 +101,26 @@
</section>
<section name="Conformance/Metadata Statement">
<p>
The HAPI FHIR RESTful Server will automatically export a
<a href="http://hl7.org/implement/standards/fhir/conformance.html">conformance statement</a>,
as required by the
<a href="http://hl7.org/implement/standards/fhir/http.html#conformance">FHIR Specification</a>.
</p>
<p>
This statement is automatically generated based on the various annotated methods which are
provided to the server. This behaviour may be modified by creating a new class
containing a method annotated with a
<a href="doc_rest_operations.html#system_conformance">@Metadata Operation</a>
and then passing an instance of that class to the
<a href="./apidocs/ca/uhn/fhir/rest/server/RestfulServer.html#setServerConformanceProvider(java.lang.Object)">setServerConformanceProvider</a> method
on your server.
</p>
</section>
</body>
</document>

View File

@ -57,7 +57,7 @@ String jsonEncoded = ctx.newJsonParser().encodeResourceToString(patient);
that will be familiar to users of JAX-WS.
</p>
<source><![CDATA[public interface MyClientInterface
<source><![CDATA[public interface MyClientInterface extends IRestfulClient
{
/** A FHIR search */
@Search

View File

@ -23,11 +23,14 @@ import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
import ca.uhn.fhir.model.dstu.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.resource.Specimen;
import ca.uhn.fhir.model.dstu.resource.ValueSet;
import ca.uhn.fhir.model.dstu.resource.Conformance.RestResource;
import ca.uhn.fhir.model.dstu.valueset.NarrativeStatusEnum;
import ca.uhn.fhir.model.primitive.DecimalDt;
import ca.uhn.fhir.model.primitive.XhtmlDt;
@ -36,6 +39,21 @@ import ca.uhn.fhir.narrative.INarrativeGenerator;
public class JsonParserTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonParserTest.class);
/**
* This sample has extra elements in <searchParam> that are not actually
* a part of the spec any more..
*/
@Test
public void testParseFuroreMetadataWithExtraElements() throws IOException {
String msg = IOUtils.toString(JsonParserTest.class.getResourceAsStream("/furore-conformance.json"));
IParser p = new FhirContext(ValueSet.class).newJsonParser();
Conformance conf = p.parseResource(Conformance.class, msg);
RestResource res = conf.getRestFirstRep().getResourceFirstRep();
assertEquals("_id", res.getSearchParam().get(1).getName().getValue());
}
@Test
public void testEncodeResourceRef() throws DataFormatException, IOException {

View File

@ -24,6 +24,8 @@ import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.dstu.resource.Conformance.RestResource;
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
import ca.uhn.fhir.model.dstu.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.Organization;
@ -284,6 +286,21 @@ public class XmlParserTest {
assertEquals("LOINC Codes for Cholesterol", resource.getName().getValue());
assertEquals(summaryText.trim(), entry.getSummary().getValueAsString().trim());
}
/**
* This sample has extra elements in <searchParam> that are not actually
* a part of the spec any more..
*/
@Test
public void testParseFuroreMetadataWithExtraElements() throws IOException {
String msg = IOUtils.toString(XmlParserTest.class.getResourceAsStream("/furore-conformance.xml"));
IParser p = new FhirContext(ValueSet.class).newXmlParser();
Conformance conf = p.parseResource(Conformance.class, msg);
RestResource res = conf.getRestFirstRep().getResourceFirstRep();
assertEquals("_id", res.getSearchParam().get(1).getName().getValue());
}
@Test
public void testLoadAndAncodeMessage() throws SAXException, IOException {

View File

@ -15,10 +15,12 @@ import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicStatusLine;
import org.hamcrest.core.StringContains;
import org.hamcrest.core.StringEndsWith;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@ -40,6 +42,7 @@ import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.QualifiedDateParam;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
public class ClientTest {
@ -113,6 +116,73 @@ public class ClientTest {
assertEquals("200", response.getVersionId().getValue());
}
@Test
public void testUpdate() throws Exception {
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location" , "http://example.com/fhir/Patient/100/_history/200"));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
MethodOutcome response = client.updatePatient(new IdDt("100"), patient);
assertEquals(HttpPut.class, capt.getValue().getClass());
HttpPut post = (HttpPut) capt.getValue();
assertThat(post.getURI().toASCIIString(), StringEndsWith.endsWith("/Patient/100"));
assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString("<Patient"));
assertEquals("100", response.getId().getValue());
assertEquals("200", response.getVersionId().getValue());
}
@Test
public void testUpdateWithVersion() throws Exception {
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location" , "http://example.com/fhir/Patient/100/_history/200"));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
MethodOutcome response = client.updatePatient(new IdDt("100"), new IdDt("200"), patient);
assertEquals(HttpPut.class, capt.getValue().getClass());
HttpPut post = (HttpPut) capt.getValue();
assertThat(post.getURI().toASCIIString(), StringEndsWith.endsWith("/Patient/100"));
assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString("<Patient"));
assertThat(post.getFirstHeader("Content-Location").getValue(), StringEndsWith.endsWith("/Patient/100/_history/200"));
assertEquals("100", response.getId().getValue());
assertEquals("200", response.getVersionId().getValue());
}
@Test(expected=ResourceVersionConflictException.class)
public void testUpdateWithResourceConflict() throws Exception {
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), Constants.STATUS_HTTP_409_CONFLICT, "Conflict"));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.updatePatient(new IdDt("100"), new IdDt("200"), patient);
}
@Test
public void testCreateBad() throws Exception {

View File

@ -62,4 +62,7 @@ public interface ITestClient extends IBasicClient {
@Update
public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient thePatient);
@Update
public MethodOutcome updatePatient(@IdParam IdDt theId, @VersionIdParam IdDt theVersion, @ResourceParam Patient thePatient);
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff