Add history to JPA

This commit is contained in:
jamesagnew 2014-05-29 09:17:12 -04:00
parent 2720e7d273
commit a0b120f70e
24 changed files with 779 additions and 195 deletions

View File

@ -157,7 +157,9 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
} }
IdDt versionId = (IdDt) ResourceMetadataKeyEnum.VERSION_ID.get(nextResource); IdDt versionId = (IdDt) ResourceMetadataKeyEnum.VERSION_ID.get(nextResource);
if (versionId == null || versionId.isEmpty()) { if (versionId == null || versionId.isEmpty()) {
throw new InternalErrorException("Server provided resource at index " + index + " with no Version ID set (using IResource#Resource.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, Object))"); if (nextResource.getId().getUnqualifiedVersionId() == null) {
throw new InternalErrorException("Server provided resource at index " + index + " with no Version ID set (using IResource#setId(IdDt))");
}
} }
index++; index++;
} }

View File

@ -74,10 +74,10 @@ public class CountParameter implements IParameter {
@Override @Override
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) { public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
if (theOuterCollectionType != null) { if (theOuterCollectionType != null) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + "' is annotated with @" + Since.class.getName() + " but can not be of collection type"); throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" +theMethod.getDeclaringClass().getCanonicalName()+ "' is annotated with @" + Since.class.getName() + " but can not be of collection type");
} }
if (!ParameterUtil.getBindableIntegerTypes().contains(theParameterType)) { if (!ParameterUtil.getBindableIntegerTypes().contains(theParameterType)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + "' is annotated with @" + Since.class.getName() + " but type '" + theParameterType + "' is an invalid type, must be one of: " + ParameterUtil.getBindableInstantTypes()); throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" +theMethod.getDeclaringClass().getCanonicalName()+ "' is annotated with @" + Since.class.getName() + " but type '" + theParameterType + "' is an invalid type, must be one of: " + ParameterUtil.getBindableIntegerTypes());
} }
myType = theParameterType; myType = theParameterType;
} }

View File

@ -333,17 +333,23 @@ public class RestfulServer extends HttpServlet {
private void findResourceMethods(Object theProvider) throws Exception { private void findResourceMethods(Object theProvider) throws Exception {
ourLog.info("Scanning type for RESTful methods: {}", theProvider.getClass()); ourLog.info("Scanning type for RESTful methods: {}", theProvider.getClass());
int count = 0;
Class<?> clazz = theProvider.getClass(); Class<?> clazz = theProvider.getClass();
Class<?> supertype = clazz.getSuperclass(); Class<?> supertype = clazz.getSuperclass();
if (!Object.class.equals(supertype)) { while (!Object.class.equals(supertype)) {
findResourceMethods(theProvider, supertype); count += findResourceMethods(theProvider, supertype);
supertype = supertype.getSuperclass();
} }
findResourceMethods(theProvider, clazz); count += findResourceMethods(theProvider, clazz);
if (count == 0) {
throw new ConfigurationException("Did not find any annotated RESTful methods on provider class " + theProvider.getClass().getCanonicalName());
}
} }
private void findResourceMethods(Object theProvider, Class<?> clazz) throws ConfigurationException { private int findResourceMethods(Object theProvider, Class<?> clazz) throws ConfigurationException {
int count = 0; int count = 0;
for (Method m : clazz.getDeclaredMethods()) { for (Method m : clazz.getDeclaredMethods()) {
@ -383,9 +389,7 @@ public class RestfulServer extends HttpServlet {
} }
} }
if (count == 0) { return count;
throw new ConfigurationException("Did not find any annotated RESTful methods on provider class " + theProvider.getClass().getCanonicalName());
}
} }
private void findSystemMethods(Object theSystemProvider) { private void findSystemMethods(Object theSystemProvider) {

View File

@ -589,7 +589,7 @@ public interface HistoryClient extends IBasicClient {
* also be included in any of the methods above. * also be included in any of the methods above.
*/ */
@History @History
Bundle getHistoryServerWithCriteria(@Since Date theDate, @Count int theCount); Bundle getHistoryServerWithCriteria(@Since Date theDate, @Count Integer theCount);
} }
//END SNIPPET: historyClient //END SNIPPET: historyClient

View File

@ -0,0 +1,105 @@
package ca.uhn.fhir.rest.server;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.annotation.History;
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.param.StringParam;
import ca.uhn.fhir.testutil.RandomServerPortProvider;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class HistoryTest {
private static CloseableHttpClient ourClient;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(HistoryTest.class);
private static int ourPort;
private static Server ourServer;
@Test
public void testHistory() throws Exception {
{
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/_history");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals(2, new FhirContext().newXmlParser().parseBundle(responseContent).getEntries().size());
}
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = RandomServerPortProvider.findFreePort();
ourServer = new Server(ourPort);
DummyProvider patientProvider = new DummyProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer();
servlet.setPlainProviders(patientProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class DummyProvider {
@History
public List<Patient> findPatient(@Since InstantDt theSince) {
ArrayList<Patient> retVal = new ArrayList<Patient>();
Patient patient = new Patient();
patient.setId("Patient/1/_history/1");
retVal.add(patient);
Patient patient2 = new Patient();
patient2.setId("Patient/1/_history/2");
retVal.add(patient2);
return retVal;
}
}
}

View File

@ -139,8 +139,16 @@
<artifactId>validation-api</artifactId> <artifactId>validation-api</artifactId>
<version>1.1.0.Final</version> <version>1.1.0.Final</version>
</dependency> </dependency>
<!-- Misc -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>17.0</version>
</dependency>
</dependencies> </dependencies>
<properties> <properties>
<junit_version>4.11</junit_version> <junit_version>4.11</junit_version>
<derby_version>10.10.2.0</derby_version> <derby_version>10.10.2.0</derby_version>

View File

@ -4,14 +4,24 @@ import static org.apache.commons.lang3.StringUtils.*;
import java.text.Normalizer; import java.text.Normalizer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType; import javax.persistence.PersistenceContextType;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -20,13 +30,17 @@ import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.entity.BaseHasResource;
import ca.uhn.fhir.jpa.entity.BaseTag;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTable; import ca.uhn.fhir.jpa.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTablePk;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.entity.ResourceLink; import ca.uhn.fhir.jpa.entity.ResourceLink;
import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.TagDefinition;
import ca.uhn.fhir.model.api.IDatatype; import ca.uhn.fhir.model.api.IDatatype;
import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.IPrimitiveDatatype;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
@ -45,10 +59,15 @@ import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum;
import ca.uhn.fhir.model.primitive.BaseDateTimeDt; import ca.uhn.fhir.model.primitive.BaseDateTimeDt;
import ca.uhn.fhir.model.primitive.IdDt; 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.parser.IParser;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.util.FhirTerser;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
public abstract class BaseFhirDao { public abstract class BaseFhirDao {
private FhirContext myContext = new FhirContext(); private FhirContext myContext = new FhirContext();
@PersistenceContext(name = "FHIR_UT", type = PersistenceContextType.TRANSACTION, unitName = "FHIR_UT") @PersistenceContext(name = "FHIR_UT", type = PersistenceContextType.TRANSACTION, unitName = "FHIR_UT")
@ -66,83 +85,128 @@ public abstract class BaseFhirDao {
myContext = theContext; myContext = theContext;
} }
protected ResourceTable updateEntity(final IResource theResource, ResourceTable entity, boolean theUpdateHistory) { private void searchHistoryCurrentVersion(String theResourceName, Long theId, Date theSince, int theLimit, List<HistoryTuple> tuples) {
if (entity.getPublished() == null) { CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
entity.setPublished(new Date()); CriteriaQuery<Tuple> cq = builder.createTupleQuery();
Root<?> from = cq.from(ResourceTable.class);
cq.multiselect(from.get("myId").as(Long.class), from.get("myUpdated").as(Date.class));
List<Predicate> predicates = new ArrayList<Predicate>();
if (theSince != null) {
Predicate low = builder.greaterThanOrEqualTo(from.<Date> get("myUpdated"), theSince);
predicates.add(low);
} }
if (theUpdateHistory) { if (theResourceName != null) {
final ResourceHistoryTable historyEntry = entity.toHistory(getContext()); predicates.add(builder.equal(from.get("myResourceType"), theResourceName));
myEntityManager.persist(historyEntry); }
if (theId != null) {
predicates.add(builder.equal(from.get("myId"), theId));
} }
entity.setVersion(entity.getVersion() + 1); cq.where(builder.and(predicates.toArray(new Predicate[0])));
final List<ResourceIndexedSearchParamString> stringParams = extractSearchParamStrings(entity, theResource); cq.orderBy(builder.desc(from.get("myUpdated")));
final List<ResourceIndexedSearchParamToken> tokenParams = extractSearchParamTokens(entity, theResource); TypedQuery<Tuple> q = myEntityManager.createQuery(cq);
final List<ResourceIndexedSearchParamNumber> numberParams = extractSearchParamNumber(entity, theResource); if (theLimit > 0) {
final List<ResourceIndexedSearchParamDate> dateParams = extractSearchParamDates(entity, theResource); q.setMaxResults(theLimit);
final List<ResourceLink> links = extractResourceLinks(entity, theResource); }
for (Tuple next : q.getResultList()) {
populateResourceIntoEntity(theResource, entity); long id = (Long) next.get(0);
Date updated = (Date) next.get(1);
entity.setUpdated(new Date()); tuples.add(new HistoryTuple(ResourceTable.class, updated, id));
}
if (entity.getId() == null) {
myEntityManager.persist(entity);
} else {
entity = myEntityManager.merge(entity);
} }
if (entity.isParamsStringPopulated()) { private void searchHistoryCurrentVersion(List<HistoryTuple> theTuples, List<BaseHasResource> theRetVal) {
for (ResourceIndexedSearchParamString next : entity.getParamsString()) { Collection<HistoryTuple> tuples = Collections2.filter(theTuples, new com.google.common.base.Predicate<HistoryTuple>() {
myEntityManager.remove(next); @Override
public boolean apply(HistoryTuple theInput) {
return theInput.getTable().equals(ResourceTable.class);
} }
});
Collection<Long> ids = Collections2.transform(tuples, new Function<HistoryTuple, Long>() {
@Override
public Long apply(HistoryTuple theInput) {
return (Long) theInput.getId();
} }
for (ResourceIndexedSearchParamString next : stringParams) { });
myEntityManager.persist(next); if (ids.isEmpty()) {
return;
} }
if (entity.isParamsTokenPopulated()) { CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
for (ResourceIndexedSearchParamToken next : entity.getParamsToken()) { CriteriaQuery<ResourceTable> cq = builder.createQuery(ResourceTable.class);
myEntityManager.remove(next); Root<ResourceTable> from = cq.from(ResourceTable.class);
cq.where(from.get("myId").in(ids));
cq.orderBy(builder.desc(from.get("myUpdated")));
TypedQuery<ResourceTable> q = myEntityManager.createQuery(cq);
for (ResourceTable next : q.getResultList()) {
theRetVal.add(next);
} }
} }
for (ResourceIndexedSearchParamToken next : tokenParams) {
myEntityManager.persist(next);
}
if (entity.isParamsNumberPopulated()) { private void searchHistoryHistory(String theResourceName, Long theId, Date theSince, int theLimit, List<HistoryTuple> tuples) {
for (ResourceIndexedSearchParamNumber next : entity.getParamsNumber()) { CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
myEntityManager.remove(next); CriteriaQuery<Tuple> cq = builder.createTupleQuery();
} Root<?> from = cq.from(ResourceHistoryTable.class);
} cq.multiselect(from.get("myPk").as(ResourceHistoryTablePk.class), from.get("myUpdated").as(Date.class));
for (ResourceIndexedSearchParamNumber next : numberParams) {
myEntityManager.persist(next); List<Predicate> predicates = new ArrayList<Predicate>();
if (theSince != null) {
Predicate low = builder.greaterThanOrEqualTo(from.<Date> get("myUpdated"), theSince);
predicates.add(low);
} }
if (entity.isParamsDatePopulated()) { if (theResourceName != null) {
for (ResourceIndexedSearchParamDate next : entity.getParamsDate()) { predicates.add(builder.equal(from.get("myResourceType"), theResourceName));
myEntityManager.remove(next);
} }
} if (theId != null) {
for (ResourceIndexedSearchParamDate next : dateParams) { predicates.add(builder.equal(from.get("myId"), theId));
myEntityManager.persist(next);
} }
if (entity.isHasLinks()) { cq.where(builder.and(predicates.toArray(new Predicate[0])));
for (ResourceLink next : entity.getResourceLinks()) {
myEntityManager.remove(next); cq.orderBy(builder.desc(from.get("myUpdated")));
TypedQuery<Tuple> q = myEntityManager.createQuery(cq);
if (theLimit > 0) {
q.setMaxResults(theLimit);
} }
for (Tuple next : q.getResultList()) {
ResourceHistoryTablePk id = (ResourceHistoryTablePk) next.get(0);
Date updated = (Date) next.get(1);
tuples.add(new HistoryTuple(ResourceHistoryTable.class, updated, id));
} }
for (ResourceLink next : links) {
myEntityManager.persist(next);
} }
myEntityManager.flush(); private void searchHistoryHistory(List<HistoryTuple> theTuples, List<BaseHasResource> theRetVal) {
theResource.setId(new IdDt(entity.getResourceType(), entity.getId().toString(), Long.toString(entity.getVersion()))); Collection<HistoryTuple> tuples = Collections2.filter(theTuples, new com.google.common.base.Predicate<HistoryTuple>() {
@Override
public boolean apply(HistoryTuple theInput) {
return theInput.getTable().equals(ResourceHistoryTable.class);
}
});
Collection<ResourceHistoryTablePk> ids = Collections2.transform(tuples, new Function<HistoryTuple, ResourceHistoryTablePk>() {
@Override
public ResourceHistoryTablePk apply(HistoryTuple theInput) {
return (ResourceHistoryTablePk) theInput.getId();
}
});
if (ids.isEmpty()) {
return;
}
return entity; CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<ResourceHistoryTable> cq = builder.createQuery(ResourceHistoryTable.class);
Root<ResourceHistoryTable> from = cq.from(ResourceHistoryTable.class);
cq.where(from.get("myPk").in(ids));
cq.orderBy(builder.desc(from.get("myUpdated")));
TypedQuery<ResourceHistoryTable> q = myEntityManager.createQuery(cq);
for (ResourceHistoryTable next : q.getResultList()) {
theRetVal.add(next);
}
} }
protected List<ResourceLink> extractResourceLinks(ResourceTable theEntity, IResource theResource) { protected List<ResourceLink> extractResourceLinks(ResourceTable theEntity, IResource theResource) {
@ -472,6 +536,41 @@ public abstract class BaseFhirDao {
return resourceTypeToDao.get(theType); return resourceTypeToDao.get(theType);
} }
protected ArrayList<IResource> history(String theResourceName, Long theId, Date theSince, int theLimit) {
List<HistoryTuple> tuples = new ArrayList<HistoryTuple>();
// Get list of IDs
searchHistoryCurrentVersion(theResourceName, theId, theSince, theLimit, tuples);
assert tuples.size() < 2 || !tuples.get(tuples.size() - 2).getUpdated().before(tuples.get(tuples.size() - 1).getUpdated());
searchHistoryHistory(theResourceName, theId, theSince, theLimit, tuples);
assert tuples.size() < 2 || !tuples.get(tuples.size() - 2).getUpdated().before(tuples.get(tuples.size() - 1).getUpdated());
// Sort merged list
Collections.sort(tuples, Collections.reverseOrder());
assert tuples.size() < 2 || !tuples.get(tuples.size() - 2).getUpdated().before(tuples.get(tuples.size() - 1).getUpdated());
// Pull actual resources
List<BaseHasResource> resEntities = Lists.newArrayList();
searchHistoryCurrentVersion(tuples, resEntities);
searchHistoryHistory(tuples, resEntities);
Collections.sort(resEntities, new Comparator<BaseHasResource>() {
@Override
public int compare(BaseHasResource theO1, BaseHasResource theO2) {
return theO2.getUpdated().getValue().compareTo(theO1.getUpdated().getValue());
}
});
if (resEntities.size() > theLimit) {
resEntities = resEntities.subList(0, theLimit);
}
ArrayList<IResource> retVal = new ArrayList<IResource>();
for (BaseHasResource next : resEntities) {
retVal.add(toResource(next));
}
return retVal;
}
protected String normalizeString(String theString) { protected String normalizeString(String theString) {
char[] out = new char[theString.length()]; char[] out = new char[theString.length()];
theString = Normalizer.normalize(theString, Normalizer.Form.NFD); theString = Normalizer.normalize(theString, Normalizer.Form.NFD);
@ -485,8 +584,29 @@ public abstract class BaseFhirDao {
return new String(out).toUpperCase(); return new String(out).toUpperCase();
} }
private TagDefinition getTag(String theScheme, String theTerm, String theLabel) {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<TagDefinition> cq = builder.createQuery(TagDefinition.class);
Root<TagDefinition> from = cq.from(TagDefinition.class);
cq.where(builder.and(builder.equal(from.get("myScheme"),theScheme), builder.equal(from.get("myTerm"),theTerm)));
TypedQuery<TagDefinition> q = myEntityManager.createQuery(cq);
try {
return q.getSingleResult();
} catch (NoResultException e) {
TagDefinition retVal = new TagDefinition(theTerm, theLabel, theScheme);
myEntityManager.persist(retVal);
return retVal;
}
}
protected void populateResourceIntoEntity(IResource theResource, ResourceTable theEntity) { protected void populateResourceIntoEntity(IResource theResource, ResourceTable theEntity) {
if (theEntity.getPublished().isEmpty()) {
theEntity.setPublished(new Date());
}
theEntity.setUpdated(new Date());
theEntity.setResourceType(toResourceName(theResource)); theEntity.setResourceType(toResourceName(theResource));
theEntity.setResource(getContext().newJsonParser().encodeResourceToString(theResource)); theEntity.setResource(getContext().newJsonParser().encodeResourceToString(theResource));
theEntity.setEncoding(EncodingEnum.JSON); theEntity.setEncoding(EncodingEnum.JSON);
@ -494,7 +614,8 @@ public abstract class BaseFhirDao {
TagList tagList = (TagList) theResource.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST); TagList tagList = (TagList) theResource.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
if (tagList != null) { if (tagList != null) {
for (Tag next : tagList) { for (Tag next : tagList) {
theEntity.addTag(next.getTerm(), next.getLabel(), next.getScheme()); TagDefinition tag = getTag(next.getScheme(), next.getTerm(),next.getLabel());
theEntity.addTag(tag);
} }
} }
@ -508,8 +629,110 @@ public abstract class BaseFhirDao {
return retVal; return retVal;
} }
protected IResource toResource(BaseHasResource theEntity) {
RuntimeResourceDefinition type = myContext.getResourceDefinition(theEntity.getResourceType());
return toResource(type.getImplementingClass(), theEntity);
}
protected <T extends IResource> T toResource(Class<T> theResourceType, BaseHasResource theEntity) {
String resourceText = theEntity.getResource();
IParser parser = theEntity.getEncoding().newParser(getContext());
T retVal = parser.parseResource(theResourceType, resourceText);
retVal.setId(theEntity.getIdDt());
retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, theEntity.getVersion());
retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, theEntity.getPublished());
retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, theEntity.getUpdated());
if (theEntity.getTags().size() > 0) {
TagList tagList = new TagList();
for (BaseTag next : theEntity.getTags()) {
tagList.add(new Tag(next.getTag().getTerm(), next.getTag().getLabel(), next.getTag().getScheme()));
}
retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tagList);
}
return retVal;
}
protected String toResourceName(IResource theResource) { protected String toResourceName(IResource theResource) {
return myContext.getResourceDefinition(theResource).getName(); return myContext.getResourceDefinition(theResource).getName();
} }
protected ResourceTable updateEntity(final IResource theResource, ResourceTable entity, boolean theUpdateHistory) {
if (entity.getPublished() == null) {
entity.setPublished(new Date());
}
if (theUpdateHistory) {
final ResourceHistoryTable historyEntry = entity.toHistory(getContext());
myEntityManager.persist(historyEntry);
}
entity.setVersion(entity.getVersion() + 1);
final List<ResourceIndexedSearchParamString> stringParams = extractSearchParamStrings(entity, theResource);
final List<ResourceIndexedSearchParamToken> tokenParams = extractSearchParamTokens(entity, theResource);
final List<ResourceIndexedSearchParamNumber> numberParams = extractSearchParamNumber(entity, theResource);
final List<ResourceIndexedSearchParamDate> dateParams = extractSearchParamDates(entity, theResource);
final List<ResourceLink> links = extractResourceLinks(entity, theResource);
populateResourceIntoEntity(theResource, entity);
entity.setUpdated(new Date());
if (entity.getId() == null) {
myEntityManager.persist(entity);
} else {
entity = myEntityManager.merge(entity);
}
if (entity.isParamsStringPopulated()) {
for (ResourceIndexedSearchParamString next : entity.getParamsString()) {
myEntityManager.remove(next);
}
}
for (ResourceIndexedSearchParamString next : stringParams) {
myEntityManager.persist(next);
}
if (entity.isParamsTokenPopulated()) {
for (ResourceIndexedSearchParamToken next : entity.getParamsToken()) {
myEntityManager.remove(next);
}
}
for (ResourceIndexedSearchParamToken next : tokenParams) {
myEntityManager.persist(next);
}
if (entity.isParamsNumberPopulated()) {
for (ResourceIndexedSearchParamNumber next : entity.getParamsNumber()) {
myEntityManager.remove(next);
}
}
for (ResourceIndexedSearchParamNumber next : numberParams) {
myEntityManager.persist(next);
}
if (entity.isParamsDatePopulated()) {
for (ResourceIndexedSearchParamDate next : entity.getParamsDate()) {
myEntityManager.remove(next);
}
}
for (ResourceIndexedSearchParamDate next : dateParams) {
myEntityManager.persist(next);
}
if (entity.isHasLinks()) {
for (ResourceLink next : entity.getResourceLinks()) {
myEntityManager.remove(next);
}
}
for (ResourceLink next : links) {
myEntityManager.persist(next);
}
myEntityManager.flush();
theResource.setId(new IdDt(entity.getResourceType(), entity.getId().toString(), Long.toString(entity.getVersion())));
return entity;
}
} }

View File

@ -15,7 +15,6 @@ import java.util.Set;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.CriteriaQuery;
@ -26,19 +25,14 @@ import javax.persistence.criteria.Root;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required; import org.springframework.beans.factory.annotation.Required;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.RuntimeChildResourceDefinition; import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.entity.BaseHasResource;
import ca.uhn.fhir.jpa.entity.BaseTag;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTable; import ca.uhn.fhir.jpa.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
@ -49,16 +43,12 @@ import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.IPrimitiveDatatype;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu.composite.CodingDt; import ca.uhn.fhir.model.dstu.composite.CodingDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt; import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.composite.QuantityDt; import ca.uhn.fhir.model.dstu.composite.QuantityDt;
import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum; import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt; 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.parser.IParser;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.QualifiedDateParam; import ca.uhn.fhir.rest.param.QualifiedDateParam;
@ -84,10 +74,11 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
@Override @Override
public MethodOutcome create(final T theResource) { public MethodOutcome create(final T theResource) {
final ResourceTable entity = toEntity(theResource); // final ResourceTable entity = toEntity(theResource);
//
entity.setPublished(new Date()); // entity.setPublished(new Date());
entity.setUpdated(entity.getPublished()); // entity.setUpdated(entity.getPublished());
ResourceTable entity = new ResourceTable();
entity.setResourceType(toResourceName(theResource)); entity.setResourceType(toResourceName(theResource));
// final List<ResourceIndexedSearchParamString> stringParams = extractSearchParamStrings(entity, theResource); // final List<ResourceIndexedSearchParamString> stringParams = extractSearchParamStrings(entity, theResource);
@ -147,7 +138,7 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
// myEntityManager.createQuery(criteriaQuery); // myEntityManager.createQuery(criteriaQuery);
List<ResourceHistoryTable> results = q.getResultList(); List<ResourceHistoryTable> results = q.getResultList();
for (ResourceHistoryTable next : results) { for (ResourceHistoryTable next : results) {
retVal.add(toResource(next)); retVal.add(toResource(myResourceType,next));
} }
try { try {
@ -173,7 +164,7 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
public T read(IdDt theId) { public T read(IdDt theId) {
ResourceTable entity = readEntity(theId); ResourceTable entity = readEntity(theId);
T retVal = toResource(entity); T retVal = toResource(myResourceType,entity);
return retVal; return retVal;
} }
@ -187,6 +178,11 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
return search(map); return search(map);
} }
@Override
public List<T> history() {
return null;
}
@Override @Override
public List<T> search(SearchParameterMap theParams) { public List<T> search(SearchParameterMap theParams) {
@ -213,13 +209,23 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
List<T> retVal = new ArrayList<T>(); List<T> retVal = new ArrayList<T>();
for (ResourceTable next : q.getResultList()) { for (ResourceTable next : q.getResultList()) {
T resource = toResource(next); T resource = toResource(myResourceType,next);
retVal.add(resource); retVal.add(resource);
} }
return retVal; return retVal;
} }
} }
@Override
public List<IResource> history(Date theSince, int theLimit) {
return super.history(myResourceName, null, theSince, theLimit);
}
@Override
public List<IResource> history(Long theId, Date theSince, int theLimit) {
return super.history(myResourceName, theId, theSince, theLimit);
}
@Override @Override
public List<T> search(String theParameterName, IQueryParameterType theValue) { public List<T> search(String theParameterName, IQueryParameterType theValue) {
return search(Collections.singletonMap(theParameterName, theValue)); return search(Collections.singletonMap(theParameterName, theValue));
@ -711,21 +717,5 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
return super.getDao(theType); return super.getDao(theType);
} }
private T toResource(BaseHasResource theEntity) {
String resourceText = theEntity.getResource();
IParser parser = theEntity.getEncoding().newParser(getContext());
T retVal = parser.parseResource(myResourceType, resourceText);
retVal.setId(theEntity.getIdDt());
retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, theEntity.getVersion());
retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, theEntity.getPublished());
retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, theEntity.getUpdated());
if (theEntity.getTags().size() > 0) {
TagList tagList = new TagList();
for (BaseTag next : theEntity.getTags()) {
tagList.add(new Tag(next.getTerm(), next.getLabel(), next.getScheme()));
}
retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tagList);
}
return retVal;
}
} }

View File

@ -1,13 +1,19 @@
package ca.uhn.fhir.jpa.dao; package ca.uhn.fhir.jpa.dao;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType; import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -15,15 +21,14 @@ import org.springframework.transaction.annotation.Transactional;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.util.FhirTerser;
public class FhirSystemDao extends BaseFhirDao implements IFhirSystemDao { public class FhirSystemDao extends BaseFhirDao implements IFhirSystemDao {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDao.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDao.class);
//name = "FHIR_UT", type = PersistenceContextType.TRANSACTION, unitName = "FHIR_UT"
@PersistenceContext() @PersistenceContext()
private EntityManager myEntityManager; private EntityManager myEntityManager;
@ -94,4 +99,45 @@ public class FhirSystemDao extends BaseFhirDao implements IFhirSystemDao {
} }
@Override
public List<IResource> history(Date theSince, int theLimit) {
return super.history(null, null, theSince, theLimit);
}
@Override
public TagList getAllTags() {
// CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
// CriteriaQuery<Tuple> cq = builder.createQuery(Tag)
// Root<?> from = cq.from(ResourceTable.class);
// cq.multiselect(from.get("myId").as(Long.class), from.get("myUpdated").as(Date.class));
//
// List<Predicate> predicates = new ArrayList<Predicate>();
// if (theSince != null) {
// Predicate low = builder.greaterThanOrEqualTo(from.<Date> get("myUpdated"), theSince);
// predicates.add(low);
// }
//
// if (theResourceName != null) {
// predicates.add(builder.equal(from.get("myResourceType"), theResourceName));
// }
// if (theId != null) {
// predicates.add(builder.equal(from.get("myId"), theId));
// }
//
// cq.where(builder.and(predicates.toArray(new Predicate[0])));
//
// cq.orderBy(builder.desc(from.get("myUpdated")));
// TypedQuery<Tuple> q = myEntityManager.createQuery(cq);
// if (theLimit > 0) {
// q.setMaxResults(theLimit);
// }
// for (Tuple next : q.getResultList()) {
// long id = (Long) next.get(0);
// Date updated = (Date) next.get(1);
// tuples.add(new HistoryTuple(ResourceTable.class, updated, id));
// }
return null;
}
} }

View File

@ -0,0 +1,47 @@
package ca.uhn.fhir.jpa.dao;
import java.util.Date;
class HistoryTuple implements Comparable<HistoryTuple> {
private Object myId;
private Class<?> myTable;
private Date myUpdated;
public HistoryTuple(Class<?> theTable, Date theUpdated, Object theId) {
super();
myTable = theTable;
myUpdated = theUpdated;
myId = theId;
}
@Override
public int compareTo(HistoryTuple theO) {
return myUpdated.compareTo(theO.myUpdated);
}
public Object getId() {
return myId;
}
public Class<?> getTable() {
return myTable;
}
public Date getUpdated() {
return myUpdated;
}
public void setId(Object theId) {
myId = theId;
}
public void setTable(Class<?> theTable) {
myTable = theTable;
}
public void setUpdated(Date theUpdated) {
myUpdated = theUpdated;
}
}

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.dao; package ca.uhn.fhir.jpa.dao;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -8,6 +9,7 @@ import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
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.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
@ -41,4 +43,10 @@ public interface IFhirResourceDao<T extends IResource> {
Set<Long> searchForIdsWithAndOr(Map<String, List<List<IQueryParameterType>>> theMap); Set<Long> searchForIdsWithAndOr(Map<String, List<List<IQueryParameterType>>> theMap);
List<T> history();
List<IResource> history(Date theDate, int theLimit);
List<IResource> history(Long theId, Date theSince, int theLimit);
} }

View File

@ -1,11 +1,17 @@
package ca.uhn.fhir.jpa.dao; package ca.uhn.fhir.jpa.dao;
import java.util.Date;
import java.util.List; import java.util.List;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
public interface IFhirSystemDao { public interface IFhirSystemDao {
void transaction(List<IResource> theResources); void transaction(List<IResource> theResources);
List<IResource> history(Date theDate, int theLimit);
TagList getAllTags();
} }

View File

@ -9,6 +9,7 @@ import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import ca.uhn.fhir.model.api.IResource;
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.InstantDt;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
@ -16,25 +17,27 @@ import ca.uhn.fhir.rest.server.EncodingEnum;
@MappedSuperclass @MappedSuperclass
public abstract class BaseHasResource { public abstract class BaseHasResource {
@Column(name = "ENCODING") @Column(name = "RES_ENCODING", nullable = false)
private EncodingEnum myEncoding; private EncodingEnum myEncoding;
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
@Column(name = "PUBLISHED") @Column(name = "RES_PUBLISHED", nullable = false)
private Date myPublished; private Date myPublished;
@Column(name = "RESOURCE_TEXT", length = Integer.MAX_VALUE - 1) @Column(name = "RES_TEXT", length = Integer.MAX_VALUE - 1, nullable = false)
@Lob() @Lob()
private String myResource; private String myResource;
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
@Column(name = "UPDATED") @Column(name = "RES_UPDATED", nullable = false)
private Date myUpdated; private Date myUpdated;
public EncodingEnum getEncoding() { public EncodingEnum getEncoding() {
return myEncoding; return myEncoding;
} }
public abstract String getResourceType();
public abstract Collection<? extends BaseTag> getTags(); public abstract Collection<? extends BaseTag> getTags();
public abstract IdDt getIdDt(); public abstract IdDt getIdDt();

View File

@ -2,7 +2,8 @@ package ca.uhn.fhir.jpa.entity;
import java.io.Serializable; import java.io.Serializable;
import javax.persistence.Column; import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass; import javax.persistence.MappedSuperclass;
@MappedSuperclass @MappedSuperclass
@ -10,37 +11,17 @@ public class BaseTag implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Column(name="TAG_LABEL", length=200) @ManyToOne(cascade= {})
private String myLabel; @JoinColumn(name="TAG_ID", nullable=false)
private TagDefinition myTag;
@Column(name="TAG_SCHEME", length=200) public TagDefinition getTag() {
private String myScheme; return myTag;
@Column(name="TAG_TERM", length=200)
private String myTerm;
public String getLabel() {
return myLabel;
} }
public String getScheme() { public void setTag(TagDefinition theTag) {
return myScheme; myTag = theTag;
} }
public String getTerm() {
return myTerm;
}
public void setLabel(String theLabel) {
myLabel = theLabel;
}
public void setScheme(String theScheme) {
myScheme = theScheme;
}
public void setTerm(String theTerm) {
myTerm = theTerm;
}
} }

View File

@ -5,20 +5,21 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.EmbeddedId; import javax.persistence.EmbeddedId;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType; import javax.persistence.FetchType;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import ca.uhn.fhir.model.api.IResource; import org.hibernate.annotations.Index;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@Entity @Entity
@Table(name = "HFJ_RES_VER", uniqueConstraints = {}) @Table(name = "HFJ_RES_VER", uniqueConstraints = {})
@org.hibernate.annotations.Table(appliesTo="HFJ_RES_VER", indexes= {@Index(name="IDX_RES_VER_DATE", columnNames= {"RES_UPDATED"})})
public class ResourceHistoryTable extends BaseHasResource implements Serializable { public class ResourceHistoryTable extends BaseHasResource implements Serializable {
public static final String Q_GETALL = "SELECT h FROM ResourceHistoryTable h WHERE h.myPk.myId = :PID AND h.myPk.myResourceType = :RESTYPE ORDER BY h.myUpdated ASC"; public static final String Q_GETALL = "SELECT h FROM ResourceHistoryTable h WHERE h.myPk.myId = :PID AND h.myPk.myResourceType = :RESTYPE ORDER BY h.myUpdated ASC";
@ -28,6 +29,12 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
@EmbeddedId @EmbeddedId
private ResourceHistoryTablePk myPk; private ResourceHistoryTablePk myPk;
@Column(name="RES_TYPE",insertable=false, updatable=false)
private String myResourceType;
@Column(name="PID", insertable=false, updatable=false)
private Long myId;
@OneToMany(mappedBy = "myResourceHistory", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true) @OneToMany(mappedBy = "myResourceHistory", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Collection<ResourceHistoryTag> myTags; private Collection<ResourceHistoryTag> myTags;
@ -41,12 +48,13 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Class<IResource> getResourceType() { public String getResourceType() {
try { return myPk.getResourceType();
return (Class<IResource>) Class.forName(Patient.class.getPackage().getName() + "." + myPk.getResourceType()); // try {
} catch (ClassNotFoundException e) { // return (Class<IResource>) Class.forName(Patient.class.getPackage().getName() + "." + myPk.getResourceType());
throw new InternalErrorException(e); // } catch (ClassNotFoundException e) {
} // throw new InternalErrorException(e);
// }
} }
public Collection<ResourceHistoryTag> getTags() { public Collection<ResourceHistoryTag> getTags() {
@ -65,13 +73,26 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
myPk = thePk; myPk = thePk;
} }
public void addTag(String theTerm, String theLabel, String theScheme) { public void addTag(ResourceHistoryTag theTag) {
for (ResourceHistoryTag next : getTags()) { for (ResourceHistoryTag next : getTags()) {
if (next.getTerm().equals(theTerm)) { if (next.getTag().getTerm().equals(theTag)) {
return; return;
} }
} }
getTags().add(new ResourceHistoryTag(this, theTerm, theLabel, theScheme)); getTags().add(theTag);
}
public boolean hasTag(String theTerm, String theLabel, String theScheme) {
for (ResourceHistoryTag next : getTags()) {
if (next.getTag().getScheme().equals(theScheme) && next.getTag().getTerm().equals(theTerm)) {
return true;
}
}
return false;
}
public void addTag(ResourceTag theTag) {
getTags().add(new ResourceHistoryTag(this, theTag.getTag()));
} }
} }

View File

@ -32,11 +32,10 @@ public class ResourceHistoryTag extends BaseTag implements Serializable {
public ResourceHistoryTag() { public ResourceHistoryTag() {
} }
public ResourceHistoryTag(ResourceHistoryTable theResourceHistory, String theTerm, String theLabel, String theScheme) {
myResourceHistory = theResourceHistory; public ResourceHistoryTag(ResourceHistoryTable theResourceHistoryTable, TagDefinition theTag) {
setTerm(theTerm); myResourceHistory=theResourceHistoryTable;
setLabel(theLabel); setTag(theTag);
setScheme(theScheme);
} }
public ResourceHistoryTable getResourceHistory() { public ResourceHistoryTable getResourceHistory() {

View File

@ -17,13 +17,17 @@ import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Version; import javax.persistence.Version;
import org.hibernate.annotations.Index;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
@Entity @Entity
@Table(name = "HFJ_RESOURCE", uniqueConstraints = {}) @Table(name = "HFJ_RESOURCE", uniqueConstraints = {})
@Inheritance(strategy = InheritanceType.JOINED) @Inheritance(strategy = InheritanceType.JOINED)
@org.hibernate.annotations.Table(appliesTo="HFJ_RESOURCE", indexes= {@Index(name="IDX_RES_DATE", columnNames= {"RES_UPDATED"})})
public class ResourceTable extends BaseHasResource implements Serializable { public class ResourceTable extends BaseHasResource implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -75,13 +79,13 @@ public class ResourceTable extends BaseHasResource implements Serializable {
@Column(name = "RES_VER") @Column(name = "RES_VER")
private long myVersion; private long myVersion;
public void addTag(String theTerm, String theLabel, String theScheme) { public boolean hasTag(String theTerm, String theLabel, String theScheme) {
for (ResourceTag next : getTags()) { for (ResourceTag next : getTags()) {
if (next.getTerm().equals(theTerm)) { if (next.getTag().getTerm().equals(theTerm)) {
return; return true;
} }
} }
getTags().add(new ResourceTag(this, theTerm, theLabel, theScheme)); return false;
} }
public IdDt getIdDt() { public IdDt getIdDt() {
@ -221,9 +225,13 @@ public class ResourceTable extends BaseHasResource implements Serializable {
retVal.setResource(getResource()); retVal.setResource(getResource());
for (ResourceTag next : getTags()) { for (ResourceTag next : getTags()) {
retVal.addTag(next.getTerm(), next.getLabel(), next.getScheme()); retVal.addTag(next);
} }
return retVal; return retVal;
} }
public void addTag(TagDefinition theTag) {
getTags().add(new ResourceTag(this, theTag));
}
} }

View File

@ -27,11 +27,9 @@ public class ResourceTag extends BaseTag {
public ResourceTag() { public ResourceTag() {
} }
public ResourceTag(ResourceTable theResource, String theTerm, String theLabel, String theScheme) { public ResourceTag(ResourceTable theResourceTable, TagDefinition theTag) {
myResource = theResource; myResource=theResourceTable;
setTerm(theTerm); setTag(theTag);
setLabel(theLabel);
setScheme(theScheme);
} }
public ResourceTable getResource() { public ResourceTable getResource() {

View File

@ -0,0 +1,67 @@
package ca.uhn.fhir.jpa.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
@Entity
@Table(name = "HFJ_TAG_DEF", uniqueConstraints= {@UniqueConstraint(columnNames= {"TAG_SCHEME","TAG_TERM"})})
//@org.hibernate.annotations.Table(appliesTo="HFJ_TAG", indexes= {@Index(name)})
public class TagDefinition implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "TAG_ID")
private Long myId;
@Column(name = "TAG_LABEL", length = 200)
private String myLabel;
@Column(name = "TAG_SCHEME", length = 200)
private String myScheme;
@Column(name = "TAG_TERM", length = 200)
private String myTerm;
public TagDefinition() {
}
public TagDefinition(String theTerm, String theLabel, String theScheme) {
setTerm(theTerm);
setScheme(theScheme);
setLabel(theLabel);
}
public void setLabel(String theLabel) {
myLabel = theLabel;
}
public void setScheme(String theScheme) {
myScheme = theScheme;
}
public String getLabel() {
return myLabel;
}
public String getScheme() {
return myScheme;
}
public String getTerm() {
return myTerm;
}
public void setTerm(String theTerm) {
myTerm = theTerm;
}
}

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.provider; package ca.uhn.fhir.jpa.provider;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -10,12 +11,14 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Count;
import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.History; import ca.uhn.fhir.rest.annotation.History;
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.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.Since;
import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.annotation.Update;
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;
@ -76,4 +79,14 @@ public class JpaResourceProvider<T extends IResource> implements IResourceProvid
return myDao.getResourceType(); return myDao.getResourceType();
} }
@History
public List<IResource> getHistoryServerWithCriteria(@Since Date theDate, @Count Integer theCount) {
return myDao.history(theDate, theCount);
}
@History
public List<IResource> getHistoryServerWithCriteria(@IdParam IdDt theId, @Since Date theDate, @Count Integer theCount) {
return myDao.history(theId.asLong(), theDate, theCount);
}
} }

View File

@ -1,11 +1,16 @@
package ca.uhn.fhir.jpa.provider; package ca.uhn.fhir.jpa.provider;
import java.util.Date;
import java.util.List; import java.util.List;
import org.springframework.beans.factory.annotation.Required; import org.springframework.beans.factory.annotation.Required;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.annotation.Count;
import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.annotation.Since;
import ca.uhn.fhir.rest.annotation.Transaction; import ca.uhn.fhir.rest.annotation.Transaction;
import ca.uhn.fhir.rest.annotation.TransactionParam; import ca.uhn.fhir.rest.annotation.TransactionParam;
@ -21,7 +26,6 @@ public class JpaSystemProvider {
myDao = theDao; myDao = theDao;
} }
@Required @Required
public void setDao(IFhirSystemDao theDao) { public void setDao(IFhirSystemDao theDao) {
myDao = theDao; myDao = theDao;
@ -33,4 +37,8 @@ public class JpaSystemProvider {
return theResources; return theResources;
} }
@History
List<IResource> getHistoryServerWithCriteria(@Since Date theDate, @Count Integer theCount) {
return myDao.history(theDate, theCount);
}
} }

View File

@ -22,6 +22,8 @@ import ca.uhn.fhir.model.dstu.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.Organization; import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.resource.Patient;
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.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class FhirSystemDaoTest { public class FhirSystemDaoTest {
@ -37,6 +39,50 @@ public class FhirSystemDaoTest {
private static Date ourTestStarted; private static Date ourTestStarted;
private static IFhirSystemDao ourSystemDao; private static IFhirSystemDao ourSystemDao;
@Test
public void testHistory() throws Exception {
Date start = new Date();
Thread.sleep(10);
Patient patient = new Patient();
patient.addIdentifier("urn:system", "testHistory");
patient.addName().addFamily("Tester").addGiven("Joe");
IdDt pid = ourPatientDao.create(patient).getId();
Thread.sleep(10);
IdDt newpid = ourPatientDao.update(patient, pid).getId();
Thread.sleep(10);
IdDt newpid2 = ourPatientDao.update(patient, pid).getId();
Thread.sleep(10);
IdDt newpid3 = ourPatientDao.update(patient, pid).getId();
List<IResource> values = ourSystemDao.history(start, 1000);
assertEquals(4, values.size());
assertEquals(newpid3, values.get(0).getId());
assertEquals(newpid2, values.get(1).getId());
assertEquals(newpid, values.get(2).getId());
assertEquals(pid, values.get(3).getId());
Location loc = new Location();
loc.getAddress().addLine("AAA");
IdDt lid = ourLocationDao.create(loc).getId();
Location loc2 = new Location();
loc2.getAddress().addLine("AAA");
ourLocationDao.create(loc2).getId();
values = ourLocationDao.history(start, 1000);
assertEquals(2, values.size());
values = ourLocationDao.history(lid.asLong(), start, 1000);
assertEquals(1, values.size());
}
@Test @Test
public void testPersistWithSimpleLink() { public void testPersistWithSimpleLink() {
Patient patient = new Patient(); Patient patient = new Patient();

View File

@ -17,6 +17,7 @@
<class>ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken</class> <class>ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken</class>
<class>ca.uhn.fhir.jpa.entity.ResourceLink</class> <class>ca.uhn.fhir.jpa.entity.ResourceLink</class>
<class>ca.uhn.fhir.jpa.entity.ResourceTag</class> <class>ca.uhn.fhir.jpa.entity.ResourceTag</class>
<class>ca.uhn.fhir.jpa.entity.TagDefinition</class>
<exclude-unlisted-classes>false</exclude-unlisted-classes> <exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties> <properties>

View File

@ -6,7 +6,6 @@
<persistence-unit name="FHIR_UT" transaction-type="RESOURCE_LOCAL"> <persistence-unit name="FHIR_UT" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider> <provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>ca.uhn.test.jpasrv.PatientResourceTable</class>
<class>ca.uhn.fhir.jpa.entity.ResourceHistoryTable</class> <class>ca.uhn.fhir.jpa.entity.ResourceHistoryTable</class>
<class>ca.uhn.fhir.jpa.entity.ResourceHistoryTag</class> <class>ca.uhn.fhir.jpa.entity.ResourceHistoryTag</class>
<class>ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate</class> <class>ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate</class>
@ -16,6 +15,7 @@
<class>ca.uhn.fhir.jpa.entity.ResourceLink</class> <class>ca.uhn.fhir.jpa.entity.ResourceLink</class>
<class>ca.uhn.fhir.jpa.entity.ResourceTable</class> <class>ca.uhn.fhir.jpa.entity.ResourceTable</class>
<class>ca.uhn.fhir.jpa.entity.ResourceTag</class> <class>ca.uhn.fhir.jpa.entity.ResourceTag</class>
<class>ca.uhn.fhir.jpa.entity.TagDefinition</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes> <exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties> <properties>