diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java index bc8ddb9e6f3..3257485eef3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java @@ -111,13 +111,13 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { throw new NullPointerException("ID can not be null"); } } - + HttpGetClientInvocation retVal = createHistoryInvocation(resourceName, id); if (theArgs != null) { for (int idx = 0; idx < theArgs.length; idx++) { IParameter nextParam = getParameters().get(idx); - nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], retVal.getParameters(),retVal); + nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], retVal.getParameters(), retVal); } } @@ -150,18 +150,20 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { Object response = invokeServerMethod(theMethodParams); List resources = toResourceList(response); - int index=0; + int index = 0; for (IResource nextResource : resources) { if (nextResource.getId() == null || nextResource.getId().isEmpty()) { throw new InternalErrorException("Server provided resource at index " + index + " with no ID set (using IResource#setId(IdDt))"); } IdDt versionId = (IdDt) ResourceMetadataKeyEnum.VERSION_ID.get(nextResource); - 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 (versionId == null || versionId.isEmpty()) { + if (nextResource.getId().getUnqualifiedVersionId() == null) { + throw new InternalErrorException("Server provided resource at index " + index + " with no Version ID set (using IResource#setId(IdDt))"); + } } index++; } - + return resources; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CountParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CountParameter.java index c55fdc7e4c2..7d6124135bc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CountParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CountParameter.java @@ -74,10 +74,10 @@ public class CountParameter implements IParameter { @Override public void initializeTypes(Method theMethod, Class> theOuterCollectionType, Class> theInnerCollectionType, Class theParameterType) { 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)) { - 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; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index d2b5bdb4e33..49870e5b83c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -333,17 +333,23 @@ public class RestfulServer extends HttpServlet { private void findResourceMethods(Object theProvider) throws Exception { ourLog.info("Scanning type for RESTful methods: {}", theProvider.getClass()); + int count = 0; Class clazz = theProvider.getClass(); Class supertype = clazz.getSuperclass(); - if (!Object.class.equals(supertype)) { - findResourceMethods(theProvider, supertype); + while (!Object.class.equals(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; for (Method m : clazz.getDeclaredMethods()) { @@ -383,9 +389,7 @@ public class RestfulServer extends HttpServlet { } } - if (count == 0) { - throw new ConfigurationException("Did not find any annotated RESTful methods on provider class " + theProvider.getClass().getCanonicalName()); - } + return count; } private void findSystemMethods(Object theSystemProvider) { diff --git a/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java b/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java index dc2f1a001de..46869577bb0 100644 --- a/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java +++ b/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java @@ -589,7 +589,7 @@ public interface HistoryClient extends IBasicClient { * also be included in any of the methods above. */ @History - Bundle getHistoryServerWithCriteria(@Since Date theDate, @Count int theCount); + Bundle getHistoryServerWithCriteria(@Since Date theDate, @Count Integer theCount); } //END SNIPPET: historyClient diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/HistoryTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/HistoryTest.java new file mode 100644 index 00000000000..35129173cef --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/HistoryTest.java @@ -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 findPatient(@Since InstantDt theSince) { + ArrayList retVal = new ArrayList(); + + 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; + } + + + } + +} diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 67b66ee9a88..f823cb36fb6 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -139,8 +139,16 @@ validation-api 1.1.0.Final + + + + com.google.guava + guava + 17.0 + + 4.11 10.10.2.0 diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java index a2f4dc19413..4e4ddd88de2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java @@ -4,14 +4,24 @@ import static org.apache.commons.lang3.StringUtils.*; import java.text.Normalizer; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.persistence.EntityManager; +import javax.persistence.NoResultException; 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.apache.commons.lang3.StringUtils; 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.RuntimeResourceDefinition; 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.ResourceHistoryTablePk; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.entity.ResourceLink; 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.IPrimitiveDatatype; 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.IdDt; 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.exceptions.InvalidRequestException; 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 { private FhirContext myContext = new FhirContext(); @PersistenceContext(name = "FHIR_UT", type = PersistenceContextType.TRANSACTION, unitName = "FHIR_UT") @@ -66,83 +85,128 @@ public abstract class BaseFhirDao { myContext = theContext; } - protected ResourceTable updateEntity(final IResource theResource, ResourceTable entity, boolean theUpdateHistory) { - if (entity.getPublished() == null) { - entity.setPublished(new Date()); - } + private void searchHistoryCurrentVersion(String theResourceName, Long theId, Date theSince, int theLimit, List tuples) { + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createTupleQuery(); + Root from = cq.from(ResourceTable.class); + cq.multiselect(from.get("myId").as(Long.class), from.get("myUpdated").as(Date.class)); - if (theUpdateHistory) { - final ResourceHistoryTable historyEntry = entity.toHistory(getContext()); - myEntityManager.persist(historyEntry); + List predicates = new ArrayList(); + if (theSince != null) { + Predicate low = builder.greaterThanOrEqualTo(from. get("myUpdated"), theSince); + predicates.add(low); } - - entity.setVersion(entity.getVersion() + 1); - - final List stringParams = extractSearchParamStrings(entity, theResource); - final List tokenParams = extractSearchParamTokens(entity, theResource); - final List numberParams = extractSearchParamNumber(entity, theResource); - final List dateParams = extractSearchParamDates(entity, theResource); - final List 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; + 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 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)); + } + } + + private void searchHistoryCurrentVersion(List theTuples, List theRetVal) { + Collection tuples = Collections2.filter(theTuples, new com.google.common.base.Predicate() { + @Override + public boolean apply(HistoryTuple theInput) { + return theInput.getTable().equals(ResourceTable.class); + } + }); + Collection ids = Collections2.transform(tuples, new Function() { + @Override + public Long apply(HistoryTuple theInput) { + return (Long) theInput.getId(); + } + }); + if (ids.isEmpty()) { + return; + } + + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createQuery(ResourceTable.class); + Root from = cq.from(ResourceTable.class); + cq.where(from.get("myId").in(ids)); + + cq.orderBy(builder.desc(from.get("myUpdated"))); + TypedQuery q = myEntityManager.createQuery(cq); + for (ResourceTable next : q.getResultList()) { + theRetVal.add(next); + } + } + + private void searchHistoryHistory(String theResourceName, Long theId, Date theSince, int theLimit, List tuples) { + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createTupleQuery(); + Root from = cq.from(ResourceHistoryTable.class); + cq.multiselect(from.get("myPk").as(ResourceHistoryTablePk.class), from.get("myUpdated").as(Date.class)); + + List predicates = new ArrayList(); + if (theSince != null) { + Predicate low = builder.greaterThanOrEqualTo(from. 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 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)); + } + } + + private void searchHistoryHistory(List theTuples, List theRetVal) { + Collection tuples = Collections2.filter(theTuples, new com.google.common.base.Predicate() { + @Override + public boolean apply(HistoryTuple theInput) { + return theInput.getTable().equals(ResourceHistoryTable.class); + } + }); + Collection ids = Collections2.transform(tuples, new Function() { + @Override + public ResourceHistoryTablePk apply(HistoryTuple theInput) { + return (ResourceHistoryTablePk) theInput.getId(); + } + }); + if (ids.isEmpty()) { + return; + } + + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createQuery(ResourceHistoryTable.class); + Root from = cq.from(ResourceHistoryTable.class); + cq.where(from.get("myPk").in(ids)); + + cq.orderBy(builder.desc(from.get("myUpdated"))); + TypedQuery q = myEntityManager.createQuery(cq); + for (ResourceHistoryTable next : q.getResultList()) { + theRetVal.add(next); + } } protected List extractResourceLinks(ResourceTable theEntity, IResource theResource) { @@ -472,6 +536,41 @@ public abstract class BaseFhirDao { return resourceTypeToDao.get(theType); } + protected ArrayList history(String theResourceName, Long theId, Date theSince, int theLimit) { + List tuples = new ArrayList(); + + // 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 resEntities = Lists.newArrayList(); + searchHistoryCurrentVersion(tuples, resEntities); + searchHistoryHistory(tuples, resEntities); + + Collections.sort(resEntities, new Comparator() { + @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 retVal = new ArrayList(); + for (BaseHasResource next : resEntities) { + retVal.add(toResource(next)); + } + return retVal; + } + protected String normalizeString(String theString) { char[] out = new char[theString.length()]; theString = Normalizer.normalize(theString, Normalizer.Form.NFD); @@ -485,8 +584,29 @@ public abstract class BaseFhirDao { return new String(out).toUpperCase(); } + + private TagDefinition getTag(String theScheme, String theTerm, String theLabel) { + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createQuery(TagDefinition.class); + Root from = cq.from(TagDefinition.class); + cq.where(builder.and(builder.equal(from.get("myScheme"),theScheme), builder.equal(from.get("myTerm"),theTerm))); + TypedQuery 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) { + if (theEntity.getPublished().isEmpty()) { + theEntity.setPublished(new Date()); + } + theEntity.setUpdated(new Date()); + theEntity.setResourceType(toResourceName(theResource)); theEntity.setResource(getContext().newJsonParser().encodeResourceToString(theResource)); theEntity.setEncoding(EncodingEnum.JSON); @@ -494,7 +614,8 @@ public abstract class BaseFhirDao { TagList tagList = (TagList) theResource.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST); if (tagList != null) { 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; } + protected IResource toResource(BaseHasResource theEntity) { + RuntimeResourceDefinition type = myContext.getResourceDefinition(theEntity.getResourceType()); + return toResource(type.getImplementingClass(), theEntity); + } + + protected T toResource(Class 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) { 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 stringParams = extractSearchParamStrings(entity, theResource); + final List tokenParams = extractSearchParamTokens(entity, theResource); + final List numberParams = extractSearchParamNumber(entity, theResource); + final List dateParams = extractSearchParamDates(entity, theResource); + final List 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; + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java index 9ad6a0c9ac3..dd6cd9a7fc3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java @@ -15,7 +15,6 @@ import java.util.Set; import javax.annotation.PostConstruct; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; -import javax.persistence.PersistenceContextType; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; 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.Required; import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Propagation; 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.ConfigurationException; import ca.uhn.fhir.context.RuntimeChildResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition; 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.ResourceIndexedSearchParamDate; 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.IQueryParameterType; 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.IdentifierDt; import ca.uhn.fhir.model.dstu.composite.QuantityDt; import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum; import ca.uhn.fhir.model.primitive.IdDt; 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.param.DateRangeParam; import ca.uhn.fhir.rest.param.QualifiedDateParam; @@ -84,10 +74,11 @@ public class FhirResourceDao extends BaseFhirDao implements @Override public MethodOutcome create(final T theResource) { - final ResourceTable entity = toEntity(theResource); - - entity.setPublished(new Date()); - entity.setUpdated(entity.getPublished()); +// final ResourceTable entity = toEntity(theResource); +// +// entity.setPublished(new Date()); +// entity.setUpdated(entity.getPublished()); + ResourceTable entity = new ResourceTable(); entity.setResourceType(toResourceName(theResource)); // final List stringParams = extractSearchParamStrings(entity, theResource); @@ -147,7 +138,7 @@ public class FhirResourceDao extends BaseFhirDao implements // myEntityManager.createQuery(criteriaQuery); List results = q.getResultList(); for (ResourceHistoryTable next : results) { - retVal.add(toResource(next)); + retVal.add(toResource(myResourceType,next)); } try { @@ -173,7 +164,7 @@ public class FhirResourceDao extends BaseFhirDao implements public T read(IdDt theId) { ResourceTable entity = readEntity(theId); - T retVal = toResource(entity); + T retVal = toResource(myResourceType,entity); return retVal; } @@ -187,6 +178,11 @@ public class FhirResourceDao extends BaseFhirDao implements return search(map); } + @Override + public List history() { + return null; + } + @Override public List search(SearchParameterMap theParams) { @@ -213,13 +209,23 @@ public class FhirResourceDao extends BaseFhirDao implements List retVal = new ArrayList(); for (ResourceTable next : q.getResultList()) { - T resource = toResource(next); + T resource = toResource(myResourceType,next); retVal.add(resource); } return retVal; } } + @Override + public List history(Date theSince, int theLimit) { + return super.history(myResourceName, null, theSince, theLimit); + } + + @Override + public List history(Long theId, Date theSince, int theLimit) { + return super.history(myResourceName, theId, theSince, theLimit); + } + @Override public List search(String theParameterName, IQueryParameterType theValue) { return search(Collections.singletonMap(theParameterName, theValue)); @@ -711,21 +717,5 @@ public class FhirResourceDao extends BaseFhirDao implements 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; - } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDao.java index ee1ee332fc3..fdc648d57df 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDao.java @@ -1,13 +1,19 @@ package ca.uhn.fhir.jpa.dao; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.persistence.EntityManager; 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.Transactional; @@ -15,15 +21,14 @@ import org.springframework.transaction.annotation.Transactional; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.entity.ResourceTable; 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.primitive.IdDt; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.FhirTerser; public class FhirSystemDao extends BaseFhirDao implements IFhirSystemDao { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDao.class); - //name = "FHIR_UT", type = PersistenceContextType.TRANSACTION, unitName = "FHIR_UT" @PersistenceContext() private EntityManager myEntityManager; @@ -60,13 +65,13 @@ public class FhirSystemDao extends BaseFhirDao implements IFhirSystemDao { } else { entity = myEntityManager.find(ResourceTable.class, nextId.asLong()); } - + if (entity == null) { entity = toEntity(nextResource); myEntityManager.persist(entity); myEntityManager.flush(); } - + IdDt newId = new IdDt(resourceName + '/' + entity.getId()); ourLog.info("Incoming ID[{}] has been assigned ID[{}]", nextId, newId); idConversions.put(nextId, newId); @@ -94,4 +99,45 @@ public class FhirSystemDao extends BaseFhirDao implements IFhirSystemDao { } + @Override + public List history(Date theSince, int theLimit) { + return super.history(null, null, theSince, theLimit); + } + + @Override + public TagList getAllTags() { +// CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); +// CriteriaQuery 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 predicates = new ArrayList(); +// if (theSince != null) { +// Predicate low = builder.greaterThanOrEqualTo(from. 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 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; + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/HistoryTuple.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/HistoryTuple.java new file mode 100644 index 00000000000..d737a4cf687 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/HistoryTuple.java @@ -0,0 +1,47 @@ +package ca.uhn.fhir.jpa.dao; + +import java.util.Date; + +class HistoryTuple implements Comparable { + + 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; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java index ce00776482a..a1915ea74a0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.dao; +import java.util.Date; import java.util.List; import java.util.Map; 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.IResource; 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.ResourceNotFoundException; @@ -40,5 +42,11 @@ public interface IFhirResourceDao { Set searchForIds(Map theParams); Set searchForIdsWithAndOr(Map>> theMap); + + List history(); + + List history(Date theDate, int theLimit); + + List history(Long theId, Date theSince, int theLimit); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirSystemDao.java index 799956dddfc..f1bbf925df7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirSystemDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirSystemDao.java @@ -1,11 +1,17 @@ package ca.uhn.fhir.jpa.dao; +import java.util.Date; import java.util.List; import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.TagList; public interface IFhirSystemDao { void transaction(List theResources); + List history(Date theDate, int theLimit); + + TagList getAllTags(); + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java index 41e1b6d7620..70714efebb2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java @@ -9,6 +9,7 @@ import javax.persistence.MappedSuperclass; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.rest.server.EncodingEnum; @@ -16,25 +17,27 @@ import ca.uhn.fhir.rest.server.EncodingEnum; @MappedSuperclass public abstract class BaseHasResource { - @Column(name = "ENCODING") + @Column(name = "RES_ENCODING", nullable = false) private EncodingEnum myEncoding; @Temporal(TemporalType.TIMESTAMP) - @Column(name = "PUBLISHED") + @Column(name = "RES_PUBLISHED", nullable = false) private Date myPublished; - @Column(name = "RESOURCE_TEXT", length = Integer.MAX_VALUE - 1) + @Column(name = "RES_TEXT", length = Integer.MAX_VALUE - 1, nullable = false) @Lob() private String myResource; @Temporal(TemporalType.TIMESTAMP) - @Column(name = "UPDATED") + @Column(name = "RES_UPDATED", nullable = false) private Date myUpdated; public EncodingEnum getEncoding() { return myEncoding; } + public abstract String getResourceType(); + public abstract Collection getTags(); public abstract IdDt getIdDt(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseTag.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseTag.java index 1ae8b2f2790..6e1c9a70510 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseTag.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseTag.java @@ -2,7 +2,8 @@ package ca.uhn.fhir.jpa.entity; import java.io.Serializable; -import javax.persistence.Column; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; import javax.persistence.MappedSuperclass; @MappedSuperclass @@ -10,37 +11,17 @@ public class BaseTag implements Serializable { private static final long serialVersionUID = 1L; - @Column(name="TAG_LABEL", length=200) - private String myLabel; + @ManyToOne(cascade= {}) + @JoinColumn(name="TAG_ID", nullable=false) + private TagDefinition myTag; - @Column(name="TAG_SCHEME", length=200) - private String myScheme; - - @Column(name="TAG_TERM", length=200) - private String myTerm; - - public String getLabel() { - return myLabel; + public TagDefinition getTag() { + return myTag; } - public String getScheme() { - return myScheme; + public void setTag(TagDefinition theTag) { + 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; - } - + + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTable.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTable.java index 0e347b9e9ec..1e084249e04 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTable.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTable.java @@ -5,20 +5,21 @@ import java.util.ArrayList; import java.util.Collection; import javax.persistence.CascadeType; +import javax.persistence.Column; import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.OneToMany; import javax.persistence.Table; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.dstu.resource.Patient; +import org.hibernate.annotations.Index; + import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.server.Constants; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @Entity @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 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 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) private Collection myTags; @@ -41,12 +48,13 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl } @SuppressWarnings("unchecked") - public Class getResourceType() { - try { - return (Class) Class.forName(Patient.class.getPackage().getName() + "." + myPk.getResourceType()); - } catch (ClassNotFoundException e) { - throw new InternalErrorException(e); - } + public String getResourceType() { + return myPk.getResourceType(); +// try { +// return (Class) Class.forName(Patient.class.getPackage().getName() + "." + myPk.getResourceType()); +// } catch (ClassNotFoundException e) { +// throw new InternalErrorException(e); +// } } public Collection getTags() { @@ -65,13 +73,26 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl myPk = thePk; } - public void addTag(String theTerm, String theLabel, String theScheme) { + public void addTag(ResourceHistoryTag theTag) { for (ResourceHistoryTag next : getTags()) { - if (next.getTerm().equals(theTerm)) { + if (next.getTag().getTerm().equals(theTag)) { 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())); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTag.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTag.java index 3743c23899a..1395070bfc5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTag.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTag.java @@ -32,11 +32,10 @@ public class ResourceHistoryTag extends BaseTag implements Serializable { public ResourceHistoryTag() { } - public ResourceHistoryTag(ResourceHistoryTable theResourceHistory, String theTerm, String theLabel, String theScheme) { - myResourceHistory = theResourceHistory; - setTerm(theTerm); - setLabel(theLabel); - setScheme(theScheme); + + public ResourceHistoryTag(ResourceHistoryTable theResourceHistoryTable, TagDefinition theTag) { + myResourceHistory=theResourceHistoryTable; + setTag(theTag); } public ResourceHistoryTable getResourceHistory() { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java index 718cba0e59d..4fc696e57dd 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java @@ -17,13 +17,17 @@ import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Version; +import org.hibernate.annotations.Index; + import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.Tag; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.server.Constants; @Entity @Table(name = "HFJ_RESOURCE", uniqueConstraints = {}) @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 { private static final long serialVersionUID = 1L; @@ -75,13 +79,13 @@ public class ResourceTable extends BaseHasResource implements Serializable { @Column(name = "RES_VER") 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()) { - if (next.getTerm().equals(theTerm)) { - return; + if (next.getTag().getTerm().equals(theTerm)) { + return true; } } - getTags().add(new ResourceTag(this, theTerm, theLabel, theScheme)); + return false; } public IdDt getIdDt() { @@ -221,9 +225,13 @@ public class ResourceTable extends BaseHasResource implements Serializable { retVal.setResource(getResource()); for (ResourceTag next : getTags()) { - retVal.addTag(next.getTerm(), next.getLabel(), next.getScheme()); + retVal.addTag(next); } return retVal; } + + public void addTag(TagDefinition theTag) { + getTags().add(new ResourceTag(this, theTag)); + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTag.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTag.java index 79701c49a9c..24778de410c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTag.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTag.java @@ -27,11 +27,9 @@ public class ResourceTag extends BaseTag { public ResourceTag() { } - public ResourceTag(ResourceTable theResource, String theTerm, String theLabel, String theScheme) { - myResource = theResource; - setTerm(theTerm); - setLabel(theLabel); - setScheme(theScheme); + public ResourceTag(ResourceTable theResourceTable, TagDefinition theTag) { + myResource=theResourceTable; + setTag(theTag); } public ResourceTable getResource() { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TagDefinition.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TagDefinition.java new file mode 100644 index 00000000000..d24ba8918c9 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TagDefinition.java @@ -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; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProvider.java index a18101228ea..b6de2aa230d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProvider.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.provider; +import java.util.Date; import java.util.HashMap; 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.IResource; 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.History; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Read; import ca.uhn.fhir.rest.annotation.ResourceParam; 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.api.MethodOutcome; import ca.uhn.fhir.rest.server.IResourceProvider; @@ -76,4 +79,14 @@ public class JpaResourceProvider implements IResourceProvid return myDao.getResourceType(); } + @History + public List getHistoryServerWithCriteria(@Since Date theDate, @Count Integer theCount) { + return myDao.history(theDate, theCount); + } + + @History + public List getHistoryServerWithCriteria(@IdParam IdDt theId, @Since Date theDate, @Count Integer theCount) { + return myDao.history(theId.asLong(), theDate, theCount); + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProvider.java index 0dab636f5af..eca33572de3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProvider.java @@ -1,11 +1,16 @@ package ca.uhn.fhir.jpa.provider; +import java.util.Date; import java.util.List; import org.springframework.beans.factory.annotation.Required; 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.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.TransactionParam; @@ -14,14 +19,13 @@ public class JpaSystemProvider { private IFhirSystemDao myDao; public JpaSystemProvider() { - //nothing + // nothing } public JpaSystemProvider(IFhirSystemDao theDao) { - myDao=theDao; + myDao = theDao; } - @Required public void setDao(IFhirSystemDao theDao) { myDao = theDao; @@ -33,4 +37,8 @@ public class JpaSystemProvider { return theResources; } + @History + List getHistoryServerWithCriteria(@Since Date theDate, @Count Integer theCount) { + return myDao.history(theDate, theCount); + } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoTest.java index dd14f51d4f4..3a6c515b7a6 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoTest.java @@ -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.Patient; 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; public class FhirSystemDaoTest { @@ -37,6 +39,50 @@ public class FhirSystemDaoTest { private static Date ourTestStarted; 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 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 public void testPersistWithSimpleLink() { Patient patient = new Patient(); diff --git a/hapi-fhir-jpaserver-base/src/test/resources/META-INF/persistence.xml b/hapi-fhir-jpaserver-base/src/test/resources/META-INF/persistence.xml index 2ca26391ec5..520df7bef9c 100644 --- a/hapi-fhir-jpaserver-base/src/test/resources/META-INF/persistence.xml +++ b/hapi-fhir-jpaserver-base/src/test/resources/META-INF/persistence.xml @@ -17,6 +17,7 @@ ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken ca.uhn.fhir.jpa.entity.ResourceLink ca.uhn.fhir.jpa.entity.ResourceTag + ca.uhn.fhir.jpa.entity.TagDefinition false diff --git a/hapi-fhir-jpaserver-test/src/test/resources/fhir_jpatest_persistence.xml b/hapi-fhir-jpaserver-test/src/test/resources/fhir_jpatest_persistence.xml index cd25b565b8d..6481cfbfd08 100644 --- a/hapi-fhir-jpaserver-test/src/test/resources/fhir_jpatest_persistence.xml +++ b/hapi-fhir-jpaserver-test/src/test/resources/fhir_jpatest_persistence.xml @@ -6,7 +6,6 @@ org.hibernate.ejb.HibernatePersistence - ca.uhn.test.jpasrv.PatientResourceTable ca.uhn.fhir.jpa.entity.ResourceHistoryTable ca.uhn.fhir.jpa.entity.ResourceHistoryTag ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate @@ -16,6 +15,7 @@ ca.uhn.fhir.jpa.entity.ResourceLink ca.uhn.fhir.jpa.entity.ResourceTable ca.uhn.fhir.jpa.entity.ResourceTag + ca.uhn.fhir.jpa.entity.TagDefinition true