Documentation updates
This commit is contained in:
parent
b325a5bfb1
commit
ea73770433
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue