Add HashMapResourcePrvider
This commit is contained in:
parent
ef6ee83744
commit
84c72203b7
|
@ -0,0 +1,173 @@
|
|||
package ca.uhn.fhir.rest.server.provider;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* This class is a simple implementation of the resource provider
|
||||
* interface that uses a HashMap to store all resources in memory.
|
||||
* <p>
|
||||
* This class currently supports the following FHIR operations:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Create</li>
|
||||
* <li>Update existing resource</li>
|
||||
* <li>Update non-existing resource (e.g. create with client-supplied ID)</li>
|
||||
* <li>Delete</li>
|
||||
* <li>Search by resource type with no parameters</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param <T> The resource type to support
|
||||
*/
|
||||
public class HashMapResourceProvider<T extends IBaseResource> implements IResourceProvider {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(HashMapResourceProvider.class);
|
||||
private final Class<T> myResourceType;
|
||||
private final FhirContext myFhirContext;
|
||||
private final String myResourceName;
|
||||
private Map<String, TreeMap<Long, T>> myIdToVersionToResourceMap = new HashMap<>();
|
||||
private long myNextId;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param theFhirContext The FHIR context
|
||||
* @param theResourceType The resource type to support
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public HashMapResourceProvider(FhirContext theFhirContext, Class<T> theResourceType) {
|
||||
myFhirContext = theFhirContext;
|
||||
myResourceType = theResourceType;
|
||||
myResourceName = myFhirContext.getResourceDefinition(theResourceType).getName();
|
||||
clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all data held in this resource provider
|
||||
*/
|
||||
public void clear() {
|
||||
myNextId = 1;
|
||||
myIdToVersionToResourceMap.clear();
|
||||
}
|
||||
|
||||
@Create
|
||||
public MethodOutcome create(@ResourceParam T theResource) {
|
||||
long idPart = myNextId++;
|
||||
String idPartAsString = Long.toString(idPart);
|
||||
Long versionIdPart = 1L;
|
||||
|
||||
IIdType id = store(theResource, idPartAsString, versionIdPart);
|
||||
|
||||
return new MethodOutcome()
|
||||
.setCreated(true)
|
||||
.setId(id);
|
||||
}
|
||||
|
||||
@Delete
|
||||
public MethodOutcome delete(@IdParam IIdType theId) {
|
||||
TreeMap<Long, T> versions = myIdToVersionToResourceMap.get(theId.getIdPart());
|
||||
if (versions == null || versions.isEmpty()) {
|
||||
throw new ResourceNotFoundException(theId);
|
||||
}
|
||||
|
||||
long nextVersion = versions.lastEntry().getKey() + 1L;
|
||||
IIdType id = store(null, theId.getIdPart(), nextVersion);
|
||||
|
||||
return new MethodOutcome()
|
||||
.setId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return myResourceType;
|
||||
}
|
||||
|
||||
private TreeMap<Long, T> getVersionToResource(String theIdPart) {
|
||||
if (!myIdToVersionToResourceMap.containsKey(theIdPart)) {
|
||||
myIdToVersionToResourceMap.put(theIdPart, new TreeMap<Long, T>());
|
||||
}
|
||||
return myIdToVersionToResourceMap.get(theIdPart);
|
||||
}
|
||||
|
||||
@Read(version = true)
|
||||
public IBaseResource read(@IdParam IIdType theId) {
|
||||
TreeMap<Long, T> versions = myIdToVersionToResourceMap.get(theId.getIdPart());
|
||||
if (versions == null || versions.isEmpty()) {
|
||||
throw new ResourceNotFoundException(theId);
|
||||
}
|
||||
|
||||
if (theId.hasVersionIdPart()) {
|
||||
Long versionId = theId.getVersionIdPartAsLong();
|
||||
if (!versions.containsKey(versionId)) {
|
||||
throw new ResourceNotFoundException(theId);
|
||||
} else {
|
||||
T resource = versions.get(versionId);
|
||||
if (resource == null) {
|
||||
throw new ResourceGoneException(theId);
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
} else {
|
||||
return versions.lastEntry().getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@Search
|
||||
public List<IBaseResource> search() {
|
||||
List<IBaseResource> retVal = new ArrayList<>();
|
||||
|
||||
for (TreeMap<Long, T> next : myIdToVersionToResourceMap.values()) {
|
||||
if (next.isEmpty() == false) {
|
||||
retVal.add(next.lastEntry().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private IIdType store(@ResourceParam T theResource, String theIdPart, Long theVersionIdPart) {
|
||||
IIdType id = myFhirContext.getVersion().newIdType();
|
||||
id.setParts(null, myResourceName, theIdPart, Long.toString(theVersionIdPart));
|
||||
|
||||
TreeMap<Long, T> versionToResource = getVersionToResource(theIdPart);
|
||||
versionToResource.put(theVersionIdPart, theResource);
|
||||
|
||||
ourLog.info("Storing resource with ID: {}", id.getValue());
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
@Update
|
||||
public MethodOutcome update(@ResourceParam T theResource) {
|
||||
|
||||
String idPartAsString = theResource.getIdElement().getIdPart();
|
||||
TreeMap<Long, T> versionToResource = getVersionToResource(idPartAsString);
|
||||
|
||||
Long versionIdPart;
|
||||
boolean created;
|
||||
if (versionToResource.isEmpty()) {
|
||||
versionIdPart = 1L;
|
||||
created = true;
|
||||
} else {
|
||||
versionIdPart = versionToResource.lastKey() + 1L;
|
||||
created = false;
|
||||
}
|
||||
|
||||
IIdType id = store(theResource, idPartAsString, versionIdPart);
|
||||
|
||||
return new MethodOutcome()
|
||||
.setCreated(created)
|
||||
.setId(id);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
package ca.uhn.fhir.rest.server.provider;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import static org.hamcrest.Matchers.matchesPattern;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class HashMapResourceProviderTest {
|
||||
|
||||
private static MyRestfulServer ourRestServer;
|
||||
private static Server ourListenerServer;
|
||||
private static IGenericClient ourClient;
|
||||
private static FhirContext ourCtx = FhirContext.forR4();
|
||||
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
ourRestServer.clearData();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateAndRead() {
|
||||
// Create
|
||||
Patient p = new Patient();
|
||||
p.setActive(true);
|
||||
IIdType id = ourClient.create().resource(p).execute().getId();
|
||||
assertThat(id.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("1", id.getVersionIdPart());
|
||||
|
||||
// Read
|
||||
p = (Patient) ourClient.read().resource("Patient").withId(id).execute();
|
||||
assertEquals(true, p.getActive());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete() {
|
||||
// Create
|
||||
Patient p = new Patient();
|
||||
p.setActive(true);
|
||||
IIdType id = ourClient.create().resource(p).execute().getId();
|
||||
assertThat(id.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("1", id.getVersionIdPart());
|
||||
|
||||
ourClient.delete().resourceById(id.toUnqualifiedVersionless()).execute();
|
||||
|
||||
// Read
|
||||
ourClient.read().resource("Patient").withId(id.withVersion("1")).execute();
|
||||
try {
|
||||
ourClient.read().resource("Patient").withId(id.withVersion("2")).execute();
|
||||
fail();
|
||||
} catch (ResourceGoneException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchAll() {
|
||||
// Create
|
||||
for (int i = 0; i < 100; i++) {
|
||||
Patient p = new Patient();
|
||||
p.addName().setFamily("FAM" + i);
|
||||
IIdType id = ourClient.create().resource(p).execute().getId();
|
||||
assertThat(id.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("1", id.getVersionIdPart());
|
||||
}
|
||||
|
||||
// Search
|
||||
Bundle resp = ourClient.search().forResource("Patient").returnBundle(Bundle.class).execute();
|
||||
assertEquals(100, resp.getTotal());
|
||||
assertEquals(100, resp.getEntry().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdate() {
|
||||
// Create
|
||||
Patient p = new Patient();
|
||||
p.setActive(true);
|
||||
IIdType id = ourClient.create().resource(p).execute().getId();
|
||||
assertThat(id.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("1", id.getVersionIdPart());
|
||||
|
||||
// Update
|
||||
p = new Patient();
|
||||
p.setId(id);
|
||||
p.setActive(false);
|
||||
id = ourClient.update().resource(p).execute().getId();
|
||||
assertThat(id.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("2", id.getVersionIdPart());
|
||||
|
||||
// Read
|
||||
p = (Patient) ourClient.read().resource("Patient").withId(id.withVersion("1")).execute();
|
||||
assertEquals(true, p.getActive());
|
||||
p = (Patient) ourClient.read().resource("Patient").withId(id.withVersion("2")).execute();
|
||||
assertEquals(false, p.getActive());
|
||||
try {
|
||||
ourClient.read().resource("Patient").withId(id.withVersion("3")).execute();
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
ourListenerServer.stop();
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void startListenerServer() throws Exception {
|
||||
int ourListenerPort = PortUtil.findFreePort();
|
||||
ourRestServer = new MyRestfulServer();
|
||||
String ourBase = "http://localhost:" + ourListenerPort + "/";
|
||||
ourListenerServer = new Server(ourListenerPort);
|
||||
ourClient = ourCtx.newRestfulGenericClient(ourBase);
|
||||
|
||||
ServletContextHandler proxyHandler = new ServletContextHandler();
|
||||
proxyHandler.setContextPath("/");
|
||||
|
||||
ServletHolder servletHolder = new ServletHolder();
|
||||
servletHolder.setServlet(ourRestServer);
|
||||
proxyHandler.addServlet(servletHolder, "/*");
|
||||
|
||||
ourListenerServer.setHandler(proxyHandler);
|
||||
ourListenerServer.start();
|
||||
}
|
||||
|
||||
private static class MyRestfulServer extends RestfulServer {
|
||||
MyRestfulServer() {
|
||||
super(ourCtx);
|
||||
}
|
||||
|
||||
void clearData() {
|
||||
for (IResourceProvider next : getResourceProviders()) {
|
||||
if (next instanceof HashMapResourceProvider) {
|
||||
((HashMapResourceProvider) next).clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize() throws ServletException {
|
||||
super.initialize();
|
||||
|
||||
registerProvider(new HashMapResourceProvider<>(ourCtx, Patient.class));
|
||||
registerProvider(new HashMapResourceProvider<>(ourCtx, Observation.class));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -165,6 +165,17 @@
|
|||
if a parameter being used also matched that type. Thanks
|
||||
to Dave Carlson for reporting!
|
||||
</action>
|
||||
<action type="add">
|
||||
A new IResourceProvider implementation called
|
||||
<![CDATA[
|
||||
<code>HashMapResourceProvider</code>
|
||||
]]>
|
||||
has been added. This is a complete resource provider
|
||||
implementation that uses a HashMap as a backing store. This class
|
||||
is probably of limited use in real production systems, but it
|
||||
cam be useful for tests or for static servers with small amounts
|
||||
of data.
|
||||
</action>
|
||||
</release>
|
||||
<release version="3.2.0" date="2018-01-13">
|
||||
<action type="add">
|
||||
|
|
Loading…
Reference in New Issue