Adding tester module - not nearly complete though
This commit is contained in:
parent
2287011601
commit
5615f9b7d7
|
@ -24,6 +24,12 @@ import ca.uhn.fhir.model.primitive.InstantDt;
|
||||||
|
|
||||||
public enum ResourceMetadataKeyEnum {
|
public enum ResourceMetadataKeyEnum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value for this key is the version ID of the resource object.
|
||||||
|
* <p>
|
||||||
|
* Values for this key are of type <b>{@link IdDt}</b>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
VERSION_ID,
|
VERSION_ID,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,7 +37,7 @@ public enum ResourceMetadataKeyEnum {
|
||||||
* is defined by FHIR as "Time resource copied into the feed", which is generally
|
* is defined by FHIR as "Time resource copied into the feed", which is generally
|
||||||
* best left to the current time.
|
* best left to the current time.
|
||||||
* <p>
|
* <p>
|
||||||
* Values for this key are of type {@link InstantDt}
|
* Values for this key are of type <b>{@link InstantDt}</b>
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* <b>Server Note</b>: In servers, it is generally advisable to leave this
|
* <b>Server Note</b>: In servers, it is generally advisable to leave this
|
||||||
|
@ -49,7 +55,7 @@ public enum ResourceMetadataKeyEnum {
|
||||||
* used for populating the "Last-Modified" header in the case of methods
|
* used for populating the "Last-Modified" header in the case of methods
|
||||||
* that return a single resource (read, vread, etc.)
|
* that return a single resource (read, vread, etc.)
|
||||||
* <p>
|
* <p>
|
||||||
* Values for this key are of type {@link InstantDt}
|
* Values for this key are of type <b>{@link InstantDt}</b>
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @see InstantDt
|
* @see InstantDt
|
||||||
|
|
|
@ -29,7 +29,7 @@ import ca.uhn.fhir.model.api.annotation.DatatypeDef;
|
||||||
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
|
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
|
|
||||||
@DatatypeDef(name="instant")
|
@DatatypeDef(name = "instant")
|
||||||
public class InstantDt extends BaseDateTimeDt {
|
public class InstantDt extends BaseDateTimeDt {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,12 +38,34 @@ public class InstantDt extends BaseDateTimeDt {
|
||||||
public static final TemporalPrecisionEnum DEFAULT_PRECISION = TemporalPrecisionEnum.MILLI;
|
public static final TemporalPrecisionEnum DEFAULT_PRECISION = TemporalPrecisionEnum.MILLI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor which creates an InstantDt with <b>no timne value</b>. Note that unlike the default constructor for the Java {@link Date} or {@link Calendar} objects, this constructor does not
|
||||||
|
* initialize the object with the current time.
|
||||||
|
*
|
||||||
|
* @see #withCurrentTime() to create a new object that has been initialized with the current time.
|
||||||
*/
|
*/
|
||||||
public InstantDt() {
|
public InstantDt() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new DateTimeDt
|
||||||
|
*/
|
||||||
|
public InstantDt(Calendar theCalendar) {
|
||||||
|
setValue(theCalendar.getTime());
|
||||||
|
setPrecision(DEFAULT_PRECISION);
|
||||||
|
setTimeZone(theCalendar.getTimeZone());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new DateTimeDt
|
||||||
|
*/
|
||||||
|
@SimpleSetter(suffix = "WithMillisPrecision")
|
||||||
|
public InstantDt(@SimpleSetter.Parameter(name = "theDate") Date theDate) {
|
||||||
|
setValue(theDate);
|
||||||
|
setPrecision(DEFAULT_PRECISION);
|
||||||
|
setTimeZone(TimeZone.getDefault());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor which accepts a date value and a precision value. Valid precisions values for this type are:
|
* Constructor which accepts a date value and a precision value. Valid precisions values for this type are:
|
||||||
* <ul>
|
* <ul>
|
||||||
|
@ -58,31 +80,12 @@ public class InstantDt extends BaseDateTimeDt {
|
||||||
setTimeZone(TimeZone.getDefault());
|
setTimeZone(TimeZone.getDefault());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new DateTimeDt
|
|
||||||
*/
|
|
||||||
@SimpleSetter(suffix="WithMillisPrecision")
|
|
||||||
public InstantDt(@SimpleSetter.Parameter(name="theDate") Date theDate) {
|
|
||||||
setValue(theDate);
|
|
||||||
setPrecision(DEFAULT_PRECISION);
|
|
||||||
setTimeZone(TimeZone.getDefault());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new DateTimeDt
|
|
||||||
*/
|
|
||||||
public InstantDt(Calendar theCalendar) {
|
|
||||||
setValue(theCalendar.getTime());
|
|
||||||
setPrecision(DEFAULT_PRECISION);
|
|
||||||
setTimeZone(theCalendar.getTimeZone());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new InstantDt from a string value
|
* Create a new InstantDt from a string value
|
||||||
*
|
*
|
||||||
* @param theString The string representation of the string. Must be in
|
* @param theString
|
||||||
* a valid format according to the FHIR specification
|
* The string representation of the string. Must be in a valid format according to the FHIR specification
|
||||||
* @throws DataFormatException
|
* @throws DataFormatException
|
||||||
*/
|
*/
|
||||||
public InstantDt(String theString) {
|
public InstantDt(String theString) {
|
||||||
setValueAsString(theString);
|
setValueAsString(theString);
|
||||||
|
@ -100,13 +103,19 @@ public class InstantDt extends BaseDateTimeDt {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the value of this instant to the current time (from the system clock)
|
* Sets the value of this instant to the current time (from the system clock) and the local/default timezone (as retrieved using {@link TimeZone#getDefault()}. This TimeZone is generally obtained
|
||||||
* and the local/default timezone (as retrieved using {@link TimeZone#getDefault()}. This
|
* from the underlying OS.
|
||||||
* TimeZone is generally obtained from the underlying OS.
|
|
||||||
*/
|
*/
|
||||||
public void setToCurrentTimeInLocalTimeZone() {
|
public void setToCurrentTimeInLocalTimeZone() {
|
||||||
setValue(new Date());
|
setValue(new Date());
|
||||||
setTimeZone(TimeZone.getDefault());
|
setTimeZone(TimeZone.getDefault());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method which creates a new InstantDt and initializes it with the current time.
|
||||||
|
*/
|
||||||
|
public static InstantDt withCurrentTime() {
|
||||||
|
return new InstantDt(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,24 +30,33 @@ import ca.uhn.fhir.rest.client.api.IBasicClient;
|
||||||
import ca.uhn.fhir.rest.client.api.IRestfulClient;
|
import ca.uhn.fhir.rest.client.api.IRestfulClient;
|
||||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RESTful method annotation to be used for the FHIR <a href="http://hl7.org/implement/standards/fhir/http.html#read">read</a> and <a
|
||||||
|
* href="http://hl7.org/implement/standards/fhir/http.html#vread">vread</a> method.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If this method has a parameter annotated with the {@link IdParam} annotation and a parameter annotated with the {@link VersionIdParam} annotation, the method will be treated as a vread method. If
|
||||||
|
* the method has only a parameter annotated with the {@link IdParam} annotation, it will be treated as a read operation.
|
||||||
|
* the
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* If you wish for your server to support both read and vread operations, you will need
|
||||||
|
* two methods annotated with this annotation.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.METHOD)
|
@Target(ElementType.METHOD)
|
||||||
public @interface Read {
|
public @interface Read {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The return type for this method. This generally does not need
|
* The return type for this method. This generally does not need to be populated for {@link IResourceProvider resource providers} in a server implementation, but often does need to be populated in
|
||||||
* to be populated for {@link IResourceProvider resource providers} in a server implementation,
|
* client implementations using {@link IBasicClient} or {@link IRestfulClient}, or in plain providers on a server.
|
||||||
* but often does need to be populated in client implementations using {@link IBasicClient} or
|
|
||||||
* {@link IRestfulClient}, or in plain providers on a server.
|
|
||||||
* <p>
|
* <p>
|
||||||
* This value also does not need to be populated if the return type for a method annotated with
|
* This value also does not need to be populated if the return type for a method annotated with this annotation is sufficient to determine the type of resource provided. E.g. if the method returns
|
||||||
* this annotation is sufficient to determine the type of resource provided. E.g. if the
|
* <code>Patient</code> or <code>List<Patient></code>, the server/client will automatically determine that the Patient resource is the return type, and this value may be left blank.
|
||||||
* method returns <code>Patient</code> or <code>List<Patient></code>, the server/client
|
|
||||||
* will automatically determine that the Patient resource is the return type, and this value
|
|
||||||
* may be left blank.
|
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
// NB: Read, Search (maybe others) share this annotation, so update the javadocs everywhere
|
// NB: Read, Search (maybe others) share this annotation, so update the javadocs everywhere
|
||||||
Class<? extends IResource> type() default IResource.class;
|
Class<? extends IResource> type() default IResource.class;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,9 +40,11 @@ import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
import ca.uhn.fhir.rest.annotation.Metadata;
|
import ca.uhn.fhir.rest.annotation.Metadata;
|
||||||
import ca.uhn.fhir.rest.method.BaseMethodBinding;
|
import ca.uhn.fhir.rest.method.BaseMethodBinding;
|
||||||
|
import ca.uhn.fhir.rest.method.ReadMethodBinding;
|
||||||
import ca.uhn.fhir.rest.method.SearchMethodBinding;
|
import ca.uhn.fhir.rest.method.SearchMethodBinding;
|
||||||
import ca.uhn.fhir.rest.param.IParameter;
|
import ca.uhn.fhir.rest.param.IParameter;
|
||||||
import ca.uhn.fhir.rest.param.BaseQueryParameter;
|
import ca.uhn.fhir.rest.param.BaseQueryParameter;
|
||||||
|
import ca.uhn.fhir.rest.server.Constants;
|
||||||
import ca.uhn.fhir.rest.server.ResourceBinding;
|
import ca.uhn.fhir.rest.server.ResourceBinding;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import ca.uhn.fhir.util.ExtensionConstants;
|
import ca.uhn.fhir.util.ExtensionConstants;
|
||||||
|
@ -65,6 +67,8 @@ public class ServerConformanceProvider {
|
||||||
Conformance retVal = new Conformance();
|
Conformance retVal = new Conformance();
|
||||||
retVal.getSoftware().setName(myRestfulServer.getServerName());
|
retVal.getSoftware().setName(myRestfulServer.getServerName());
|
||||||
retVal.getSoftware().setVersion(myRestfulServer.getServerVersion());
|
retVal.getSoftware().setVersion(myRestfulServer.getServerVersion());
|
||||||
|
retVal.addFormat(Constants.CT_FHIR_XML);
|
||||||
|
retVal.addFormat(Constants.CT_FHIR_JSON);
|
||||||
|
|
||||||
Rest rest = retVal.addRest();
|
Rest rest = retVal.addRest();
|
||||||
rest.setMode(RestfulConformanceModeEnum.SERVER);
|
rest.setMode(RestfulConformanceModeEnum.SERVER);
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
package ca.uhn.fhir.rest.server.tester;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import javax.servlet.ServletConfig;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServlet;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.thymeleaf.TemplateEngine;
|
||||||
|
import org.thymeleaf.TemplateProcessingParameters;
|
||||||
|
import org.thymeleaf.context.WebContext;
|
||||||
|
import org.thymeleaf.resourceresolver.IResourceResolver;
|
||||||
|
import org.thymeleaf.standard.StandardDialect;
|
||||||
|
import org.thymeleaf.templateresolver.TemplateResolver;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.model.dstu.resource.Conformance;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Metadata;
|
||||||
|
import ca.uhn.fhir.rest.client.api.IBasicClient;
|
||||||
|
|
||||||
|
public class PublicTesterServlet extends HttpServlet {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PublicTesterServlet.class);
|
||||||
|
private TemplateEngine myTemplateEngine;
|
||||||
|
private HashMap<String, String> myStaticResources;
|
||||||
|
private String myServerBase;
|
||||||
|
private FhirContext myCtx;
|
||||||
|
|
||||||
|
public void setServerBase(String theServerBase) {
|
||||||
|
myServerBase = theServerBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PublicTesterServlet() {
|
||||||
|
myStaticResources = new HashMap<String, String>();
|
||||||
|
myStaticResources.put("jquery-2.1.0.min.js", "text/javascript");
|
||||||
|
myStaticResources.put("PublicTester.css", "text/css");
|
||||||
|
myStaticResources.put("hapi_fhir_banner.png", "image/png");
|
||||||
|
myStaticResources.put("hapi_fhir_banner_right.png", "image/png");
|
||||||
|
|
||||||
|
myCtx = new FhirContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException {
|
||||||
|
myTemplateEngine.getCacheManager().clearAllCaches();
|
||||||
|
|
||||||
|
ourLog.info("RequestURI: {}", theReq.getPathInfo());
|
||||||
|
|
||||||
|
String resName = theReq.getPathInfo().substring(1);
|
||||||
|
if (myStaticResources.containsKey(resName)) {
|
||||||
|
streamResponse(resName, myStaticResources.get(resName), theResp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConformanceClient client = myCtx.newRestfulClient(ConformanceClient.class, myServerBase);
|
||||||
|
Conformance conformance = client.getConformance();
|
||||||
|
|
||||||
|
WebContext ctx = new WebContext(theReq, theResp, theReq.getServletContext(), theReq.getLocale());
|
||||||
|
ctx.setVariable("conf", conformance);
|
||||||
|
ctx.setVariable("base", myServerBase);
|
||||||
|
myTemplateEngine.process(theReq.getPathInfo(), ctx, theResp.getWriter());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface ConformanceClient extends IBasicClient
|
||||||
|
{
|
||||||
|
@Metadata
|
||||||
|
Conformance getConformance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(ServletConfig theConfig) throws ServletException {
|
||||||
|
myTemplateEngine = new TemplateEngine();
|
||||||
|
TemplateResolver resolver = new TemplateResolver();
|
||||||
|
resolver.setResourceResolver(new ProfileResourceResolver());
|
||||||
|
myTemplateEngine.setTemplateResolver(resolver);
|
||||||
|
StandardDialect dialect = new StandardDialect();
|
||||||
|
myTemplateEngine.setDialect(dialect);
|
||||||
|
myTemplateEngine.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ProfileResourceResolver implements IResourceResolver {
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return getClass().getCanonicalName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getResourceAsStream(TemplateProcessingParameters theTemplateProcessingParameters, String theName) {
|
||||||
|
ourLog.info("Loading template: {}", theName);
|
||||||
|
if ("/".equals(theName)) {
|
||||||
|
return PublicTesterServlet.class.getResourceAsStream("/ca/uhn/fhir/rest/server/tester/PublicTester.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void streamResponse(String theResourceName, String theContentType, HttpServletResponse theResp) throws IOException {
|
||||||
|
InputStream res = PublicTesterServlet.class.getResourceAsStream("/ca/uhn/fhir/rest/server/tester/" + theResourceName);
|
||||||
|
theResp.setContentType(theContentType);
|
||||||
|
IOUtils.copy(res, theResp.getOutputStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
BODY {
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
DIV.bodyHeaderBlock {
|
||||||
|
background-color: #E0E0E0;
|
||||||
|
margin-top: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
TD.propertyKeyCell {
|
||||||
|
background-color: #E0E0FF;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!--/*
|
||||||
|
************************************************************
|
||||||
|
This file is a Thymeleaf template for the
|
||||||
|
************************************************************
|
||||||
|
*/-->
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script type="text/javascript" src="jquery-2.1.0.min.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="PublicTester.css"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td align="left"><img src="hapi_fhir_banner.png"/></td>
|
||||||
|
<td align="right"><img src="hapi_fhir_banner_right.png"/></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="bodyHeaderBlock">
|
||||||
|
This is a RESTful server tester, which can be used to send requests to, and receive responses
|
||||||
|
from the server at the following URL:<br/>
|
||||||
|
<a href="http://foo.com/fhir" th:href="${base}"><th:block th:text="${base}">http://foo.com/fhir</th:block></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" style="margin-top: 4px;">
|
||||||
|
<tr>
|
||||||
|
<td width="29%" valign="top">
|
||||||
|
|
||||||
|
<div class="bodyHeaderBlock">
|
||||||
|
Software Details
|
||||||
|
</div>
|
||||||
|
<table border="0">
|
||||||
|
<tr>
|
||||||
|
<td class="propertyKeyCell">Software</td>
|
||||||
|
<td th:text="${conf.software.name}">HAPI Restful Server</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="propertyKeyCell">Version</td>
|
||||||
|
<td th:text="${conf.software.version}">1.1.1</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td width="1%"/>
|
||||||
|
|
||||||
|
<td width="70%" valign="top">
|
||||||
|
|
||||||
|
<th:block th:each="rest : ${conf.rest}">
|
||||||
|
<th:block th:each="resource : ${rest.resource}">
|
||||||
|
<div class="bodyHeaderBlock">
|
||||||
|
Resource: <th:block th:text="${resource.type.valueAsString}"/>
|
||||||
|
</div>
|
||||||
|
<table border="0">
|
||||||
|
<tr>
|
||||||
|
<td valign="top" class="propertyKeyCell">Supports operations:</td>
|
||||||
|
<td valign="top">
|
||||||
|
<th:block th:each="operation : ${resource.operation}">
|
||||||
|
<th:block th:text="${operation.code.value}"/>
|
||||||
|
</th:block>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</th:block>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.0 KiB |
4
hapi-fhir-base/src/main/resources/ca/uhn/fhir/rest/server/tester/jquery-2.1.0.min.js
vendored
Normal file
4
hapi-fhir-base/src/main/resources/ca/uhn/fhir/rest/server/tester/jquery-2.1.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -45,9 +45,9 @@ import ca.uhn.fhir.rest.annotation.Search;
|
||||||
import ca.uhn.fhir.rest.annotation.Since;
|
import ca.uhn.fhir.rest.annotation.Since;
|
||||||
import ca.uhn.fhir.testutil.RandomServerPortProvider;
|
import ca.uhn.fhir.testutil.RandomServerPortProvider;
|
||||||
|
|
||||||
public class NonResourceProviderServerTest {
|
public class PlainProviderTest {
|
||||||
|
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(NonResourceProviderServerTest.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PlainProviderTest.class);
|
||||||
private int myPort;
|
private int myPort;
|
||||||
private Server myServer;
|
private Server myServer;
|
||||||
private CloseableHttpClient myClient;
|
private CloseableHttpClient myClient;
|
|
@ -0,0 +1,199 @@
|
||||||
|
package ca.uhn.fhir.rest.server;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHandler;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
|
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||||
|
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
|
||||||
|
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
|
||||||
|
import ca.uhn.fhir.model.dstu.resource.Organization;
|
||||||
|
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||||
|
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
|
||||||
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
|
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||||
|
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||||
|
import ca.uhn.fhir.model.primitive.UriDt;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Count;
|
||||||
|
import ca.uhn.fhir.rest.annotation.History;
|
||||||
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Read;
|
||||||
|
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Search;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Since;
|
||||||
|
import ca.uhn.fhir.rest.server.tester.PublicTesterServlet;
|
||||||
|
import ca.uhn.fhir.testutil.RandomServerPortProvider;
|
||||||
|
|
||||||
|
public class TesterTest {
|
||||||
|
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TesterTest.class);
|
||||||
|
private int myPort;
|
||||||
|
private Server myServer;
|
||||||
|
private FhirContext myCtx;
|
||||||
|
private RestfulServer myRestfulServer;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() throws Exception {
|
||||||
|
myPort = RandomServerPortProvider.findFreePort();
|
||||||
|
myPort = 8888;
|
||||||
|
myServer = new Server(myPort);
|
||||||
|
myCtx = new FhirContext(Patient.class);
|
||||||
|
myRestfulServer = new RestfulServer();
|
||||||
|
ServletContextHandler proxyHandler = new ServletContextHandler();
|
||||||
|
proxyHandler.setContextPath("/");
|
||||||
|
|
||||||
|
PublicTesterServlet testerServlet = new PublicTesterServlet();
|
||||||
|
testerServlet.setServerBase("http://localhost:" + myPort + "/fhir/context");
|
||||||
|
testerServlet.setServerBase("http://fhir.healthintersections.com.au/open");
|
||||||
|
ServletHolder handler = new ServletHolder();
|
||||||
|
handler.setServlet(testerServlet);
|
||||||
|
proxyHandler.addServlet(handler, "/fhir/tester/*");
|
||||||
|
|
||||||
|
ServletHolder servletHolder = new ServletHolder();
|
||||||
|
servletHolder.setServlet(myRestfulServer);
|
||||||
|
proxyHandler.addServlet(servletHolder, "/fhir/context/*");
|
||||||
|
|
||||||
|
myServer.setHandler(proxyHandler);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void after() throws Exception {
|
||||||
|
myServer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTester() throws Exception {
|
||||||
|
if (true) return;
|
||||||
|
|
||||||
|
myRestfulServer.setProviders(new SearchProvider(), new GlobalHistoryProvider());
|
||||||
|
myServer.start();
|
||||||
|
|
||||||
|
Thread.sleep(9999999L);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by dsotnikov on 2/25/2014.
|
||||||
|
*/
|
||||||
|
public static class SearchProvider {
|
||||||
|
|
||||||
|
public Map<String, Patient> getIdToPatient() {
|
||||||
|
Map<String, Patient> idToPatient = new HashMap<String, Patient>();
|
||||||
|
{
|
||||||
|
Patient patient = createPatient();
|
||||||
|
idToPatient.put("1", patient);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.getIdentifier().add(new IdentifierDt());
|
||||||
|
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
|
||||||
|
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
|
||||||
|
patient.getIdentifier().get(0).setValue("00002");
|
||||||
|
patient.getName().add(new HumanNameDt());
|
||||||
|
patient.getName().get(0).addFamily("Test");
|
||||||
|
patient.getName().get(0).addGiven("PatientTwo");
|
||||||
|
patient.getGender().setText("F");
|
||||||
|
idToPatient.put("2", patient);
|
||||||
|
}
|
||||||
|
return idToPatient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Search(type = Patient.class)
|
||||||
|
public Patient findPatient(@RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) {
|
||||||
|
for (Patient next : getIdToPatient().values()) {
|
||||||
|
for (IdentifierDt nextId : next.getIdentifier()) {
|
||||||
|
if (nextId.matchesSystemAndValue(theIdentifier)) {
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the resource by its identifier
|
||||||
|
*
|
||||||
|
* @param theId
|
||||||
|
* The resource identity
|
||||||
|
* @return The resource
|
||||||
|
*/
|
||||||
|
@Read(type = Patient.class)
|
||||||
|
public Patient getPatientById(@IdParam IdDt theId) {
|
||||||
|
return getIdToPatient().get(theId.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GlobalHistoryProvider {
|
||||||
|
|
||||||
|
private InstantDt myLastSince;
|
||||||
|
private IntegerDt myLastCount;
|
||||||
|
|
||||||
|
@History
|
||||||
|
public List<IResource> getGlobalHistory(@Since InstantDt theSince, @Count IntegerDt theCount) {
|
||||||
|
myLastSince = theSince;
|
||||||
|
myLastCount = theCount;
|
||||||
|
ArrayList<IResource> retVal = new ArrayList<IResource>();
|
||||||
|
|
||||||
|
IResource p = createPatient();
|
||||||
|
p.setId(new IdDt("1"));
|
||||||
|
p.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, new IdDt("A"));
|
||||||
|
p.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new InstantDt("2012-01-01T00:00:01"));
|
||||||
|
p.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt("2012-01-01T01:00:01"));
|
||||||
|
retVal.add(p);
|
||||||
|
|
||||||
|
p = createPatient();
|
||||||
|
p.setId(new IdDt("1"));
|
||||||
|
p.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, new IdDt("B"));
|
||||||
|
p.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new InstantDt("2012-01-01T00:00:01"));
|
||||||
|
p.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt("2012-01-01T01:00:03"));
|
||||||
|
retVal.add(p);
|
||||||
|
|
||||||
|
p = createOrganization();
|
||||||
|
p.setId(new IdDt("1"));
|
||||||
|
p.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, new IdDt("A"));
|
||||||
|
p.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new InstantDt("2013-01-01T00:00:01"));
|
||||||
|
p.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt("2013-01-01T01:00:01"));
|
||||||
|
retVal.add(p);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Patient createPatient() {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addIdentifier();
|
||||||
|
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
|
||||||
|
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
|
||||||
|
patient.getIdentifier().get(0).setValue("00001");
|
||||||
|
patient.addName();
|
||||||
|
patient.getName().get(0).addFamily("Test");
|
||||||
|
patient.getName().get(0).addGiven("PatientOne");
|
||||||
|
patient.getGender().setText("M");
|
||||||
|
return patient;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Organization createOrganization() {
|
||||||
|
Organization retVal = new Organization();
|
||||||
|
retVal.addIdentifier();
|
||||||
|
retVal.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
|
||||||
|
retVal.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
|
||||||
|
retVal.getIdentifier().get(0).setValue("00001");
|
||||||
|
retVal.getName().setValue("Test Org");
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -20,9 +20,11 @@ public class ExampleRestfulServlet extends RestfulServer {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* This method is called automatically when the
|
||||||
|
* servlet is initializing.
|
||||||
*/
|
*/
|
||||||
public ExampleRestfulServlet() {
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Two resource providers are defined
|
* Two resource providers are defined
|
||||||
|
|
|
@ -1,35 +1,49 @@
|
||||||
package ca.uhn.example.rest;
|
package ca.uhn.example.rest;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.Deque;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||||
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
|
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
|
||||||
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
|
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
|
||||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||||
import ca.uhn.fhir.model.dstu.valueset.AdministrativeGenderCodesEnum;
|
import ca.uhn.fhir.model.dstu.valueset.AdministrativeGenderCodesEnum;
|
||||||
import ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum;
|
import ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
|
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
import ca.uhn.fhir.model.primitive.UriDt;
|
import ca.uhn.fhir.model.primitive.UriDt;
|
||||||
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
import ca.uhn.fhir.rest.annotation.Create;
|
import ca.uhn.fhir.rest.annotation.Create;
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Read;
|
import ca.uhn.fhir.rest.annotation.Read;
|
||||||
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Search;
|
import ca.uhn.fhir.rest.annotation.Search;
|
||||||
|
import ca.uhn.fhir.rest.annotation.VersionIdParam;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All resource providers must implement IResourceProvider
|
* This is a resource provider which stores Patient resources in memory using a HashMap. This is obviously not a production-ready solution for many reasons, but it is useful to help illustrate how to
|
||||||
|
* build a fully-functional server.
|
||||||
*/
|
*/
|
||||||
public class RestfulPatientResourceProvider implements IResourceProvider {
|
public class RestfulPatientResourceProvider implements IResourceProvider {
|
||||||
|
|
||||||
private Map<Long, Patient> myIdToPatientMap = new HashMap<Long, Patient>();
|
/**
|
||||||
|
* This map has a resource ID as a key, and each key maps to a Deque list containing all versions of the resource with that ID.
|
||||||
|
*/
|
||||||
|
private Map<Long, Deque<Patient>> myIdToPatientVersions = new HashMap<Long, Deque<Patient>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used to generate new IDs
|
||||||
|
*/
|
||||||
private long myNextId = 1;
|
private long myNextId = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,37 +57,70 @@ public class RestfulPatientResourceProvider implements IResourceProvider {
|
||||||
patient.addName().addFamily("Test");
|
patient.addName().addFamily("Test");
|
||||||
patient.getName().get(0).addGiven("PatientOne");
|
patient.getName().get(0).addGiven("PatientOne");
|
||||||
patient.setGender(AdministrativeGenderCodesEnum.F);
|
patient.setGender(AdministrativeGenderCodesEnum.F);
|
||||||
myIdToPatientMap.put(myNextId++, patient);
|
|
||||||
|
LinkedList<Patient> list = new LinkedList<Patient>();
|
||||||
|
list.add(patient);
|
||||||
|
myIdToPatientVersions.put(myNextId++, list);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The getResourceType method comes from IResourceProvider, and must be overridden to indicate what type of resource this provider supplies.
|
* Stores a new version of the patient in memory so that it
|
||||||
*/
|
* can be retrieved later.
|
||||||
@Override
|
|
||||||
public Class<Patient> getResourceType() {
|
|
||||||
return Patient.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The "@Read" annotation indicates that this method supports the read operation. Read operations should return a single resource instance.
|
|
||||||
*
|
*
|
||||||
* @param theId
|
* @param thePatient The patient resource to store
|
||||||
* The read operation takes one parameter, which must be of type IdDt and must be annotated with the "@Read.IdParam" annotation.
|
* @param theId The ID of the patient to retrieve
|
||||||
* @return Returns a resource matching this identifier, or null if none exists.
|
|
||||||
*/
|
*/
|
||||||
@Read()
|
private void addNewVersion(Patient thePatient, Long theId) {
|
||||||
public Patient getPatientById(@IdParam IdDt theId) {
|
InstantDt publishedDate;
|
||||||
Patient retVal;
|
if (!myIdToPatientVersions.containsKey(theId)) {
|
||||||
try {
|
myIdToPatientVersions.put(theId, new LinkedList<Patient>());
|
||||||
retVal = myIdToPatientMap.get(theId.asLong());
|
publishedDate = InstantDt.withCurrentTime();
|
||||||
} catch (NumberFormatException e) {
|
} else {
|
||||||
/*
|
Patient currentPatitne = myIdToPatientVersions.get(theId).getLast();
|
||||||
* If we can't parse the ID as a long, it's not valid so this is an unknown resource
|
Map<ResourceMetadataKeyEnum, Object> resourceMetadata = currentPatitne.getResourceMetadata();
|
||||||
*/
|
publishedDate = (InstantDt) resourceMetadata.get(ResourceMetadataKeyEnum.PUBLISHED);
|
||||||
throw new ResourceNotFoundException(theId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PUBLISHED time will always be set to the time that the first
|
||||||
|
* version was stored. UPDATED time is set to the time that the new
|
||||||
|
* version was stored.
|
||||||
|
*/
|
||||||
|
thePatient.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, publishedDate);
|
||||||
|
thePatient.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, InstantDt.withCurrentTime());
|
||||||
|
|
||||||
|
Deque<Patient> existingVersions = myIdToPatientVersions.get(theId);
|
||||||
|
|
||||||
return retVal;
|
/*
|
||||||
|
* We just use the current number of versions as the next version number
|
||||||
|
*/
|
||||||
|
IdDt version = new IdDt(existingVersions.size());
|
||||||
|
thePatient.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, version);
|
||||||
|
|
||||||
|
existingVersions.add(thePatient);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "@Search" annotation indicates that this method supports the search operation. You may have many different method annotated with this annotation, to support many different search criteria.
|
||||||
|
* This example searches by family name.
|
||||||
|
*
|
||||||
|
* @param theIdentifier
|
||||||
|
* This operation takes one parameter which is the search criteria. It is annotated with the "@Required" annotation. This annotation takes one argument, a string containing the name of
|
||||||
|
* the search criteria. The datatype here is StringDt, but there are other possible parameter types depending on the specific search criteria.
|
||||||
|
* @return This method returns a list of Patients. This list may contain multiple matching resources, or it may also be empty.
|
||||||
|
*/
|
||||||
|
@Create()
|
||||||
|
public MethodOutcome createPatient(@ResourceParam Patient thePatient) {
|
||||||
|
validateResource(thePatient);
|
||||||
|
|
||||||
|
// Here we are just generating IDs sequentially
|
||||||
|
long id = myNextId++;
|
||||||
|
|
||||||
|
addNewVersion(thePatient, id);
|
||||||
|
|
||||||
|
// Let the caller know the ID of the newly created resource
|
||||||
|
return new MethodOutcome(new IdDt(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -87,14 +134,14 @@ public class RestfulPatientResourceProvider implements IResourceProvider {
|
||||||
*/
|
*/
|
||||||
@Search()
|
@Search()
|
||||||
public List<Patient> findPatientsByName(@RequiredParam(name = Patient.SP_FAMILY) StringDt theFamilyName) {
|
public List<Patient> findPatientsByName(@RequiredParam(name = Patient.SP_FAMILY) StringDt theFamilyName) {
|
||||||
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
LinkedList<Patient> retVal = new LinkedList<Patient>();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Look for all patients matching the name
|
* Look for all patients matching the name
|
||||||
*/
|
*/
|
||||||
for (Patient nextPatient : myIdToPatientMap.values()) {
|
for (Deque<Patient> nextPatientList : myIdToPatientVersions.values()) {
|
||||||
NAMELOOP:
|
Patient nextPatient = nextPatientList.getLast();
|
||||||
for (HumanNameDt nextName : nextPatient.getName()) {
|
NAMELOOP: for (HumanNameDt nextName : nextPatient.getName()) {
|
||||||
for (StringDt nextFamily : nextName.getFamily()) {
|
for (StringDt nextFamily : nextName.getFamily()) {
|
||||||
if (theFamilyName.equals(nextFamily)) {
|
if (theFamilyName.equals(nextFamily)) {
|
||||||
retVal.add(nextPatient);
|
retVal.add(nextPatient);
|
||||||
|
@ -103,64 +150,128 @@ public class RestfulPatientResourceProvider implements IResourceProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The "@Create" annotation indicates that this method supports creating a new resource
|
* The getResourceType method comes from IResourceProvider, and must be overridden to indicate what type of resource this provider supplies.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Class<Patient> getResourceType() {
|
||||||
|
return Patient.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the "read" operation.
|
||||||
|
* The "@Read" annotation indicates that this method supports the read and/or vread operation.
|
||||||
|
* <p>
|
||||||
|
* Read operations take a single parameter annotated with the {@link IdParam} paramater, and
|
||||||
|
* should return a single resource instance.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @param thePatient This is the actual resource to save
|
* @param theId
|
||||||
|
* The read operation takes one parameter, which must be of type IdDt and must be annotated with the "@Read.IdParam" annotation.
|
||||||
|
* @return Returns a resource matching this identifier, or null if none exists.
|
||||||
|
*/
|
||||||
|
@Read()
|
||||||
|
public Patient readPatient(@IdParam IdDt theId) {
|
||||||
|
Deque<Patient> retVal;
|
||||||
|
try {
|
||||||
|
retVal = myIdToPatientVersions.get(theId.asLong());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
/*
|
||||||
|
* If we can't parse the ID as a long, it's not valid so this is an unknown resource
|
||||||
|
*/
|
||||||
|
throw new ResourceNotFoundException(theId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal.getLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "@Update" annotation indicates that this method supports replacing an existing resource (by ID) with a new instance of that resource.
|
||||||
|
*
|
||||||
|
* @param theId
|
||||||
|
* This is the ID of the patient to update
|
||||||
|
* @param thePatient
|
||||||
|
* This is the actual resource to save
|
||||||
* @return This method returns a "MethodOutcome"
|
* @return This method returns a "MethodOutcome"
|
||||||
*/
|
*/
|
||||||
@Create()
|
@Create()
|
||||||
public MethodOutcome createPatient(@ResourceParam Patient thePatient) {
|
public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient thePatient) {
|
||||||
/*
|
validateResource(thePatient);
|
||||||
* Our server will have a rule that patients must
|
|
||||||
* have a family name or we will reject them
|
|
||||||
*/
|
|
||||||
if (thePatient.getNameFirstRep().getFamilyFirstRep().isEmpty()) {
|
|
||||||
OperationOutcome outcome = new OperationOutcome();
|
|
||||||
outcome.addIssue().setSeverity(IssueSeverityEnum.FATAL).setDetails("No last name provided");
|
|
||||||
throw new UnprocessableEntityException(outcome);
|
|
||||||
}
|
|
||||||
|
|
||||||
long id = myNextId++;
|
|
||||||
myIdToPatientMap.put(id, thePatient);
|
|
||||||
|
|
||||||
// Let the caller know the ID of the newly created resource
|
Long id;
|
||||||
return new MethodOutcome(new IdDt(id));
|
try {
|
||||||
|
id = theId.asLong();
|
||||||
|
} catch (DataFormatException e) {
|
||||||
|
throw new InvalidRequestException("Invalid ID " + theId.getValue() + " - Must be numeric");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Throw an exception (HTTP 404) if the ID is not known
|
||||||
|
*/
|
||||||
|
if (!myIdToPatientVersions.containsKey(id)) {
|
||||||
|
throw new ResourceNotFoundException(theId);
|
||||||
|
}
|
||||||
|
|
||||||
|
addNewVersion(thePatient, id);
|
||||||
|
|
||||||
|
return new MethodOutcome();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The "@Search" annotation indicates that this method supports the search operation. You may have many different method annotated with this annotation, to support many different search criteria.
|
* This method just provides simple business validation for resources we are storing.
|
||||||
* This example searches by family name.
|
|
||||||
*
|
*
|
||||||
* @param theIdentifier
|
* @param thePatient
|
||||||
* This operation takes one parameter which is the search criteria. It is annotated with the "@Required" annotation. This annotation takes one argument, a string containing the name of
|
* The patient to validate
|
||||||
* the search criteria. The datatype here is StringDt, but there are other possible parameter types depending on the specific search criteria.
|
|
||||||
* @return This method returns a list of Patients. This list may contain multiple matching resources, or it may also be empty.
|
|
||||||
*/
|
*/
|
||||||
@Create()
|
private void validateResource(Patient thePatient) {
|
||||||
public MethodOutcome createPatient(@ResourceParam Patient thePatient) {
|
|
||||||
/*
|
/*
|
||||||
* Our server will have a rule that patients must
|
* Our server will have a rule that patients must have a family name or we will reject them
|
||||||
* have a family name or we will reject them
|
|
||||||
*/
|
*/
|
||||||
if (thePatient.getNameFirstRep().getFamilyFirstRep().isEmpty()) {
|
if (thePatient.getNameFirstRep().getFamilyFirstRep().isEmpty()) {
|
||||||
OperationOutcome outcome = new OperationOutcome();
|
OperationOutcome outcome = new OperationOutcome();
|
||||||
outcome.addIssue().setSeverity(IssueSeverityEnum.FATAL).setDetails("No last name provided");
|
outcome.addIssue().setSeverity(IssueSeverityEnum.FATAL).setDetails("No family name provided, Patient resources must have at least one family name.");
|
||||||
throw new UnprocessableEntityException(outcome);
|
throw new UnprocessableEntityException(outcome);
|
||||||
}
|
}
|
||||||
|
|
||||||
long id = myNextId++;
|
|
||||||
myIdToPatientMap.put(id, thePatient);
|
|
||||||
|
|
||||||
// Let the caller know the ID of the newly created resource
|
|
||||||
return new MethodOutcome(new IdDt(id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
/**
|
||||||
// END SNIPPET: provider
|
* This is the "vread" operation.
|
||||||
|
* The "@Read" annotation indicates that this method supports the read and/or vread operation.
|
||||||
|
* <p>
|
||||||
|
* VRead operations take a parameter annotated with the {@link IdParam} paramater,
|
||||||
|
* and a paramater annotated with the {@link VersionIdParam} parmeter,
|
||||||
|
* and should return a single resource instance.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param theId
|
||||||
|
* The read operation takes one parameter, which must be of type IdDt and must be annotated with the "@Read.IdParam" annotation.
|
||||||
|
* @return Returns a resource matching this identifier, or null if none exists.
|
||||||
|
*/
|
||||||
|
@Read()
|
||||||
|
public Patient vreadPatient(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId) {
|
||||||
|
Deque<Patient> versions;
|
||||||
|
try {
|
||||||
|
versions = myIdToPatientVersions.get(theId.asLong());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
/*
|
||||||
|
* If we can't parse the ID as a long, it's not valid so this is an unknown resource
|
||||||
|
*/
|
||||||
|
throw new ResourceNotFoundException(theId);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Patient nextVersion : versions) {
|
||||||
|
IdDt nextVersionId = (IdDt) nextVersion.getResourceMetadata().get(ResourceMetadataKeyEnum.VERSION_ID);
|
||||||
|
if (theVersionId.equals(nextVersionId)) {
|
||||||
|
return nextVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ResourceNotFoundException("Unknown version " + theVersionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue