mirror of
https://github.com/hapifhir/hapi-fhir.git
synced 2025-02-16 18:05:19 +00:00
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
|
if a parameter being used also matched that type. Thanks
|
||||||
to Dave Carlson for reporting!
|
to Dave Carlson for reporting!
|
||||||
</action>
|
</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>
|
||||||
<release version="3.2.0" date="2018-01-13">
|
<release version="3.2.0" date="2018-01-13">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user