Start working on JPA subscriptions
This commit is contained in:
parent
8fee057de3
commit
04c2cce13f
|
@ -6,6 +6,9 @@ import java.net.URL;
|
|||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
|
@ -158,5 +161,92 @@ public class UrlUtil {
|
|||
throw new Error("UTF-8 not supported on this platform");
|
||||
}
|
||||
}
|
||||
|
||||
//@formatter:off
|
||||
/**
|
||||
* Parse a URL in one of the following forms:
|
||||
* <ul>
|
||||
* <li>[Resource Type]?[Search Params]
|
||||
* <li>[Resource Type]/[Resource ID]
|
||||
* <li>[Resource Type]/[Resource ID]/_history/[Version ID]
|
||||
* </ul>
|
||||
*/
|
||||
//@formatter:on
|
||||
public static UrlParts parseUrl(String theUrl) {
|
||||
UrlParts retVal = new UrlParts();
|
||||
|
||||
int nextStart = 0;
|
||||
boolean nextIsHistory = false;
|
||||
|
||||
for (int idx = 0; idx < theUrl.length(); idx++) {
|
||||
char nextChar = theUrl.charAt(idx);
|
||||
boolean atEnd = (idx + 1) == theUrl.length();
|
||||
if (nextChar == '?' || nextChar == '/' || atEnd) {
|
||||
int endIdx = atEnd ? idx + 1 : idx;
|
||||
String nextSubstring = theUrl.substring(nextStart, endIdx);
|
||||
if (retVal.getResourceType() == null) {
|
||||
retVal.setResourceType(nextSubstring);
|
||||
} else if (retVal.getResourceId() == null) {
|
||||
retVal.setResourceId(nextSubstring);
|
||||
} else if (nextIsHistory) {
|
||||
retVal.setVersionId(nextSubstring);
|
||||
} else {
|
||||
if (nextSubstring.equals(Constants.URL_TOKEN_HISTORY)) {
|
||||
nextIsHistory = true;
|
||||
} else {
|
||||
throw new InvalidRequestException("Invalid FHIR resource URL: " + theUrl);
|
||||
}
|
||||
}
|
||||
if (nextChar == '?') {
|
||||
if (theUrl.length() > idx + 1) {
|
||||
retVal.setParams(theUrl.substring(idx + 1, theUrl.length()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
nextStart = idx + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public static class UrlParts {
|
||||
private String myParams;
|
||||
private String myResourceId;
|
||||
private String myResourceType;
|
||||
private String myVersionId;
|
||||
|
||||
public String getParams() {
|
||||
return myParams;
|
||||
}
|
||||
|
||||
public String getResourceId() {
|
||||
return myResourceId;
|
||||
}
|
||||
|
||||
public String getResourceType() {
|
||||
return myResourceType;
|
||||
}
|
||||
|
||||
public String getVersionId() {
|
||||
return myVersionId;
|
||||
}
|
||||
|
||||
public void setParams(String theParams) {
|
||||
myParams = theParams;
|
||||
}
|
||||
|
||||
public void setResourceId(String theResourceId) {
|
||||
myResourceId = theResourceId;
|
||||
}
|
||||
|
||||
public void setResourceType(String theResourceType) {
|
||||
myResourceType = theResourceType;
|
||||
}
|
||||
|
||||
public void setVersionId(String theVersionId) {
|
||||
myVersionId = theVersionId;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -284,72 +284,6 @@
|
|||
<runOrder>alphabetical</runOrder>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>de.juplo</groupId>
|
||||
<artifactId>hibernate4-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<force>true</force>
|
||||
<target>SCRIPT</target>
|
||||
<skip>${skip-hib4}</skip>
|
||||
</configuration>
|
||||
<!--
|
||||
This needs to be uncommented in order for this plugin to work with
|
||||
Hibernate 4.3+ (as of hibernate4-maven-plugin version 1.0.5)
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
<version>${hibernate_version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
-->
|
||||
<executions>
|
||||
<execution>
|
||||
<id>o10g</id>
|
||||
<goals>
|
||||
<goal>export</goal>
|
||||
</goals>
|
||||
<phase>test</phase>
|
||||
<configuration>
|
||||
<hibernateDialect>org.hibernate.dialect.Oracle10gDialect</hibernateDialect>
|
||||
<outputFile>${project.build.directory}/schema_oracle_10g.sql</outputFile>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>derby</id>
|
||||
<goals>
|
||||
<goal>export</goal>
|
||||
</goals>
|
||||
<phase>test</phase>
|
||||
<configuration>
|
||||
<hibernateDialect>org.hibernate.dialect.DerbyTenSevenDialect</hibernateDialect>
|
||||
<outputFile>${project.build.directory}/schema_derby.sql</outputFile>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>hsql</id>
|
||||
<goals>
|
||||
<goal>export</goal>
|
||||
</goals>
|
||||
<phase>test</phase>
|
||||
<configuration>
|
||||
<hibernateDialect>org.hibernate.dialect.HSQLDialect</hibernateDialect>
|
||||
<outputFile>${project.build.directory}/schema_hsql.sql</outputFile>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>mysql5</id>
|
||||
<goals>
|
||||
<goal>export</goal>
|
||||
</goals>
|
||||
<phase>test</phase>
|
||||
<configuration>
|
||||
<hibernateDialect>org.hibernate.dialect.MySQL5Dialect</hibernateDialect>
|
||||
<outputFile>${project.build.directory}/schema_mysql_5.sql</outputFile>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-tinder-plugin</artifactId>
|
||||
|
@ -427,6 +361,79 @@
|
|||
<skip-hib4>true</skip-hib4>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>DIST</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>de.juplo</groupId>
|
||||
<artifactId>hibernate4-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<force>true</force>
|
||||
<target>SCRIPT</target>
|
||||
<skip>${skip-hib4}</skip>
|
||||
</configuration>
|
||||
<!--
|
||||
This needs to be uncommented in order for this plugin to work with
|
||||
Hibernate 4.3+ (as of hibernate4-maven-plugin version 1.0.5)
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
<version>${hibernate_version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
-->
|
||||
<executions>
|
||||
<execution>
|
||||
<id>o10g</id>
|
||||
<goals>
|
||||
<goal>export</goal>
|
||||
</goals>
|
||||
<phase>test</phase>
|
||||
<configuration>
|
||||
<hibernateDialect>org.hibernate.dialect.Oracle10gDialect</hibernateDialect>
|
||||
<outputFile>${project.build.directory}/schema_oracle_10g.sql</outputFile>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>derby</id>
|
||||
<goals>
|
||||
<goal>export</goal>
|
||||
</goals>
|
||||
<phase>test</phase>
|
||||
<configuration>
|
||||
<hibernateDialect>org.hibernate.dialect.DerbyTenSevenDialect</hibernateDialect>
|
||||
<outputFile>${project.build.directory}/schema_derby.sql</outputFile>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>hsql</id>
|
||||
<goals>
|
||||
<goal>export</goal>
|
||||
</goals>
|
||||
<phase>test</phase>
|
||||
<configuration>
|
||||
<hibernateDialect>org.hibernate.dialect.HSQLDialect</hibernateDialect>
|
||||
<outputFile>${project.build.directory}/schema_hsql.sql</outputFile>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>mysql5</id>
|
||||
<goals>
|
||||
<goal>export</goal>
|
||||
</goals>
|
||||
<phase>test</phase>
|
||||
<configuration>
|
||||
<hibernateDialect>org.hibernate.dialect.MySQL5Dialect</hibernateDialect>
|
||||
<outputFile>${project.build.directory}/schema_mysql_5.sql</outputFile>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -87,6 +87,7 @@ import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri;
|
|||
import ca.uhn.fhir.jpa.entity.ResourceLink;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTag;
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionCandidateResource;
|
||||
import ca.uhn.fhir.jpa.entity.TagDefinition;
|
||||
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
|
||||
import ca.uhn.fhir.jpa.util.StopWatch;
|
||||
|
@ -520,7 +521,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
}
|
||||
throw e;
|
||||
}
|
||||
IResource resource = (IResource) toResource(type.getImplementingClass(), next);
|
||||
IResource resource = (IResource) toResource(type.getImplementingClass(), next, true);
|
||||
retVal.add(resource);
|
||||
}
|
||||
return retVal;
|
||||
|
@ -558,12 +559,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
}
|
||||
|
||||
protected void populateResourceIntoEntity(IResource theResource, ResourceTable theEntity) {
|
||||
|
||||
if (theEntity.getPublished().isEmpty()) {
|
||||
theEntity.setPublished(new Date());
|
||||
}
|
||||
theEntity.setUpdated(new Date());
|
||||
|
||||
theEntity.setResourceType(toResourceName(theResource));
|
||||
|
||||
List<BaseResourceReferenceDt> refs = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, BaseResourceReferenceDt.class);
|
||||
|
@ -936,13 +931,13 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
protected IBaseResource toResource(BaseHasResource theEntity) {
|
||||
protected IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation) {
|
||||
RuntimeResourceDefinition type = myContext.getResourceDefinition(theEntity.getResourceType());
|
||||
return toResource(type.getImplementingClass(), theEntity);
|
||||
return toResource(type.getImplementingClass(), theEntity, theForHistoryOperation);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <R extends IBaseResource> R toResource(Class<R> theResourceType, BaseHasResource theEntity) {
|
||||
protected <R extends IBaseResource> R toResource(Class<R> theResourceType, BaseHasResource theEntity, boolean theForHistoryOperation) {
|
||||
String resourceText = null;
|
||||
switch (theEntity.getEncoding()) {
|
||||
case JSON:
|
||||
|
@ -983,9 +978,23 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
res = (IResource) myContext.getResourceDefinition(theResourceType).newInstance();
|
||||
retVal = (R) res;
|
||||
ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted()));
|
||||
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.DELETE);
|
||||
if (theForHistoryOperation) {
|
||||
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.DELETE);
|
||||
}
|
||||
} else if (theForHistoryOperation) {
|
||||
/*
|
||||
* If the create and update times match, this was when the resource was created
|
||||
* so we should mark it as a POST. Otherwise, it's a PUT.
|
||||
*/
|
||||
Date published = theEntity.getPublished().getValue();
|
||||
Date updated = theEntity.getUpdated().getValue();
|
||||
if (published.equals(updated)) {
|
||||
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.POST);
|
||||
} else {
|
||||
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.PUT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
res.setId(theEntity.getIdDt());
|
||||
|
||||
ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion()));
|
||||
|
@ -1063,25 +1072,28 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
}
|
||||
}
|
||||
|
||||
protected ResourceTable updateEntity(final IResource theResource, ResourceTable entity, boolean theUpdateHistory, Date theDeletedTimestampOrNull) {
|
||||
return updateEntity(theResource, entity, theUpdateHistory, theDeletedTimestampOrNull, true, true);
|
||||
protected ResourceTable updateEntity(final IResource theResource, ResourceTable entity, boolean theUpdateHistory, Date theDeletedTimestampOrNull, Date theUpdateTime) {
|
||||
return updateEntity(theResource, entity, theUpdateHistory, theDeletedTimestampOrNull, true, true, theUpdateTime);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected ResourceTable updateEntity(final IResource theResource, ResourceTable theEntity, boolean theUpdateHistory, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion) {
|
||||
|
||||
if (theEntity.getPublished() == null) {
|
||||
theEntity.setPublished(new Date());
|
||||
}
|
||||
|
||||
protected ResourceTable updateEntity(final IResource theResource, ResourceTable theEntity, boolean theUpdateHistory, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion, Date theUpdateTime) {
|
||||
|
||||
/*
|
||||
* This should be the very first thing..
|
||||
*/
|
||||
if (theResource != null) {
|
||||
validateResourceForStorage((T) theResource);
|
||||
validateResourceForStorage((T) theResource, theEntity);
|
||||
String resourceType = myContext.getResourceDefinition(theResource).getName();
|
||||
if (isNotBlank(theEntity.getResourceType()) && !theEntity.getResourceType().equals(resourceType)) {
|
||||
throw new UnprocessableEntityException("Existing resource ID[" + theEntity.getIdDt().toUnqualifiedVersionless() + "] is of type[" + theEntity.getResourceType() + "] - Cannot update with [" + resourceType + "]");
|
||||
}
|
||||
}
|
||||
|
||||
if (theEntity.getPublished() == null) {
|
||||
theEntity.setPublished(theUpdateTime);
|
||||
}
|
||||
|
||||
if (theUpdateHistory) {
|
||||
final ResourceHistoryTable historyEntry = theEntity.toHistory();
|
||||
myEntityManager.persist(historyEntry);
|
||||
|
@ -1159,7 +1171,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
links = extractResourceLinks(theEntity, theResource);
|
||||
populateResourceIntoEntity(theResource, theEntity);
|
||||
|
||||
theEntity.setUpdated(new Date());
|
||||
theEntity.setUpdated(theUpdateTime);
|
||||
theEntity.setLanguage(theResource.getLanguage().getValue());
|
||||
theEntity.setParamsString(stringParams);
|
||||
theEntity.setParamsStringPopulated(stringParams.isEmpty() == false);
|
||||
|
@ -1178,11 +1190,11 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
theEntity.setResourceLinks(links);
|
||||
theEntity.setHasLinks(links.isEmpty() == false);
|
||||
theEntity.setIndexStatus(INDEX_STATUS_INDEXED);
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
populateResourceIntoEntity(theResource, theEntity);
|
||||
theEntity.setUpdated(new Date());
|
||||
theEntity.setUpdated(theUpdateTime);
|
||||
theEntity.setLanguage(theResource.getLanguage().getValue());
|
||||
theEntity.setIndexStatus(null);
|
||||
|
||||
|
@ -1197,8 +1209,24 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
myEntityManager.persist(theEntity.getForcedId());
|
||||
}
|
||||
|
||||
postPersist(theEntity, (T) theResource);
|
||||
|
||||
} else {
|
||||
theEntity = myEntityManager.merge(theEntity);
|
||||
|
||||
postUpdate(theEntity, (T) theResource);
|
||||
}
|
||||
|
||||
/*
|
||||
* When subscription is enabled, for each resource we store we also
|
||||
* store a subscription candidate. These are examined by the subscription
|
||||
* module and then deleted.
|
||||
*/
|
||||
if (myConfig.isSubscriptionEnabled() && thePerformIndexing) {
|
||||
SubscriptionCandidateResource candidate = new SubscriptionCandidateResource();
|
||||
candidate.setResource(theEntity);
|
||||
candidate.setResourceVersion(theEntity.getVersion());
|
||||
myEntityManager.persist(candidate);
|
||||
}
|
||||
|
||||
if (thePerformIndexing) {
|
||||
|
@ -1289,6 +1317,30 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
return theEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the
|
||||
* first time.
|
||||
*
|
||||
* @param theEntity
|
||||
* The resource
|
||||
* @param theResource The resource being persisted
|
||||
*/
|
||||
protected void postUpdate(ResourceTable theEntity, T theResource) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the
|
||||
* first time.
|
||||
*
|
||||
* @param theEntity
|
||||
* The resource
|
||||
* @param theResource The resource being persisted
|
||||
*/
|
||||
protected void postPersist(ResourceTable theEntity, T theResource) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is invoked immediately before storing a new resource, or an update to an existing resource to allow
|
||||
* the DAO to ensure that it is valid for persistence. By default, checks for the "subsetted" tag and rejects
|
||||
|
@ -1296,8 +1348,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
*
|
||||
* @param theResource
|
||||
* The resource that is about to be persisted
|
||||
* @param theEntityToSave TODO
|
||||
*/
|
||||
protected void validateResourceForStorage(T theResource) {
|
||||
protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) {
|
||||
IResource res = (IResource) theResource;
|
||||
TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(res);
|
||||
if (tagList != null) {
|
||||
|
|
|
@ -142,7 +142,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
|||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirResourceDao.class);
|
||||
|
||||
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
||||
private EntityManager myEntityManager;
|
||||
protected EntityManager myEntityManager;
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager myPlatformTransactionManager;
|
||||
|
@ -1004,7 +1004,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
|||
}
|
||||
}
|
||||
|
||||
return doCreate(theResource, theIfNoneExist, thePerformIndexing);
|
||||
return doCreate(theResource, theIfNoneExist, thePerformIndexing, new Date());
|
||||
}
|
||||
|
||||
private Predicate createCompositeParamPart(CriteriaBuilder builder, Root<ResourceTable> from, RuntimeSearchParam left, IQueryParameterType leftValue) {
|
||||
|
@ -1268,7 +1268,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
|||
ActionRequestDetails requestDetails = new ActionRequestDetails(theId, theId.getResourceType());
|
||||
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
|
||||
|
||||
ResourceTable savedEntity = updateEntity(null, entity, true, new Date());
|
||||
Date updateTime = new Date();
|
||||
ResourceTable savedEntity = updateEntity(null, entity, true, updateTime, updateTime);
|
||||
|
||||
notifyWriteCompleted();
|
||||
|
||||
|
@ -1298,14 +1299,15 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
|||
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
|
||||
|
||||
// Perform delete
|
||||
ResourceTable savedEntity = updateEntity(null, entity, true, new Date());
|
||||
Date updateTime = new Date();
|
||||
ResourceTable savedEntity = updateEntity(null, entity, true, updateTime, updateTime);
|
||||
notifyWriteCompleted();
|
||||
|
||||
ourLog.info("Processed delete on {} in {}ms", theUrl, w.getMillisAndRestart());
|
||||
return toMethodOutcome(savedEntity, null);
|
||||
}
|
||||
|
||||
private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing) {
|
||||
private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing, Date theUpdateTime) {
|
||||
StopWatch w = new StopWatch();
|
||||
|
||||
preProcessResourceForStorage(theResource);
|
||||
|
@ -1346,7 +1348,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
|||
ActionRequestDetails requestDetails = new ActionRequestDetails(theResource.getId(), toResourceName(theResource), theResource);
|
||||
notifyInterceptors(RestOperationTypeEnum.CREATE, requestDetails);
|
||||
|
||||
updateEntity(theResource, entity, false, null, thePerformIndexing, true);
|
||||
updateEntity(theResource, entity, false, null, thePerformIndexing, true, theUpdateTime);
|
||||
|
||||
DaoMethodOutcome outcome = toMethodOutcome(entity, theResource).setCreated(true);
|
||||
|
||||
|
@ -1419,7 +1421,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
|||
try {
|
||||
BaseHasResource entity = readEntity(theId.toVersionless(), false);
|
||||
validateResourceType(entity);
|
||||
currentTmp = toResource(myResourceType, entity);
|
||||
currentTmp = toResource(myResourceType, entity, true);
|
||||
if (ResourceMetadataKeyEnum.UPDATED.get(currentTmp).after(end.getValue())) {
|
||||
currentTmp = null;
|
||||
}
|
||||
|
@ -1496,7 +1498,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
|||
if (retVal.size() == maxResults) {
|
||||
break;
|
||||
}
|
||||
retVal.add(toResource(myResourceType, next));
|
||||
retVal.add(toResource(myResourceType, next, true));
|
||||
}
|
||||
|
||||
return retVal;
|
||||
|
@ -1527,7 +1529,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids) {
|
||||
private void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation) {
|
||||
if (theIncludePids.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -1546,7 +1548,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
|||
|
||||
for (ResourceTable next : q.getResultList()) {
|
||||
Class<? extends IBaseResource> resourceType = getContext().getResourceDefinition(next.getResourceType()).getImplementingClass();
|
||||
IResource resource = (IResource) toResource(resourceType, next);
|
||||
IResource resource = (IResource) toResource(resourceType, next, theForHistoryOperation);
|
||||
Integer index = position.get(next.getId());
|
||||
if (index == null) {
|
||||
ourLog.warn("Got back unexpected resource PID {}", next.getId());
|
||||
|
@ -1827,7 +1829,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
|||
BaseHasResource entity = readEntity(theId);
|
||||
validateResourceType(entity);
|
||||
|
||||
T retVal = toResource(myResourceType, entity);
|
||||
T retVal = toResource(myResourceType, entity, false);
|
||||
|
||||
InstantDt deleted = ResourceMetadataKeyEnum.DELETED_AT.get(retVal);
|
||||
if (deleted != null && !deleted.isEmpty()) {
|
||||
|
@ -2065,7 +2067,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
|||
|
||||
// Execute the query and make sure we return distinct results
|
||||
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
|
||||
loadResourcesByPid(pidsSubList, retVal, revIncludedPids);
|
||||
loadResourcesByPid(pidsSubList, retVal, revIncludedPids, false);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
@ -2381,7 +2383,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
|||
if (resourceId.isIdPartValidLong()) {
|
||||
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getId().getIdPart()));
|
||||
}
|
||||
return doCreate(theResource, null, thePerformIndexing);
|
||||
return doCreate(theResource, null, thePerformIndexing, new Date());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2398,7 +2400,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
|||
notifyInterceptors(RestOperationTypeEnum.UPDATE, requestDetails);
|
||||
|
||||
// Perform update
|
||||
ResourceTable savedEntity = updateEntity(theResource, entity, true, null, thePerformIndexing, true);
|
||||
ResourceTable savedEntity = updateEntity(theResource, entity, true, null, thePerformIndexing, true, new Date());
|
||||
|
||||
notifyWriteCompleted();
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ public abstract class BaseHapiFhirSystemDao<T> extends BaseHapiFhirDao<IBaseReso
|
|||
for (ResourceTable resourceTable : resources) {
|
||||
final IBaseResource resource;
|
||||
try {
|
||||
resource = toResource(resourceTable);
|
||||
resource = toResource(resourceTable, false);
|
||||
} catch (DataFormatException e) {
|
||||
ourLog.warn("Failure parsing resource: {}", e.toString());
|
||||
throw new UnprocessableEntityException(Long.toString(resourceTable.getId()));
|
||||
|
|
|
@ -35,6 +35,7 @@ public class DaoConfig {
|
|||
private int myIncludeLimit = 2000;
|
||||
private List<IServerInterceptor> myInterceptors;
|
||||
private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC;
|
||||
private boolean mySubscriptionEnabled;
|
||||
|
||||
/**
|
||||
* See {@link #setIncludeLimit(int)}
|
||||
|
@ -64,6 +65,13 @@ public class DaoConfig {
|
|||
return myResourceEncoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #setSubscriptionEnabled(boolean)}
|
||||
*/
|
||||
public boolean isSubscriptionEnabled() {
|
||||
return mySubscriptionEnabled;
|
||||
}
|
||||
|
||||
public void setHardSearchLimit(int theHardSearchLimit) {
|
||||
myHardSearchLimit = theHardSearchLimit;
|
||||
}
|
||||
|
@ -90,12 +98,12 @@ public class DaoConfig {
|
|||
* ID).
|
||||
* </p>
|
||||
*/
|
||||
public void setInterceptors(List<IServerInterceptor> theInterceptors) {
|
||||
myInterceptors = theInterceptors;
|
||||
}
|
||||
|
||||
public void setResourceEncoding(ResourceEncodingEnum theResourceEncoding) {
|
||||
myResourceEncoding = theResourceEncoding;
|
||||
public void setInterceptors(IServerInterceptor... theInterceptor) {
|
||||
if (theInterceptor == null || theInterceptor.length==0){
|
||||
setInterceptors(new ArrayList<IServerInterceptor>());
|
||||
} else {
|
||||
setInterceptors(Arrays.asList(theInterceptor));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -107,12 +115,23 @@ public class DaoConfig {
|
|||
* ID).
|
||||
* </p>
|
||||
*/
|
||||
public void setInterceptors(IServerInterceptor... theInterceptor) {
|
||||
if (theInterceptor == null || theInterceptor.length==0){
|
||||
setInterceptors(new ArrayList<IServerInterceptor>());
|
||||
} else {
|
||||
setInterceptors(Arrays.asList(theInterceptor));
|
||||
}
|
||||
public void setInterceptors(List<IServerInterceptor> theInterceptors) {
|
||||
myInterceptors = theInterceptors;
|
||||
}
|
||||
|
||||
public void setResourceEncoding(ResourceEncodingEnum theResourceEncoding) {
|
||||
myResourceEncoding = theResourceEncoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this server support subscription? If set to true, the server
|
||||
* will enable the subscription monitoring mode, which adds a bit of
|
||||
* overhead. Note that if this is enabled, you must also include
|
||||
* Spring task scanning to your XML config for the scheduled tasks
|
||||
* used by the subscription module.
|
||||
*/
|
||||
public void setSubscriptionEnabled(boolean theSubscriptionEnabled) {
|
||||
mySubscriptionEnabled = theSubscriptionEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
|
|||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Questionnaire;
|
||||
import ca.uhn.fhir.model.dstu2.resource.QuestionnaireResponse;
|
||||
|
@ -58,8 +59,8 @@ public class FhirResourceDaoQuestionnaireResponseDstu2 extends FhirResourceDaoDs
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void validateResourceForStorage(QuestionnaireResponse theResource) {
|
||||
super.validateResourceForStorage(theResource);
|
||||
protected void validateResourceForStorage(QuestionnaireResponse theResource, ResourceTable theEntityToSave) {
|
||||
super.validateResourceForStorage(theResource, theEntityToSave);
|
||||
if (!myValidateResponses) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
package ca.uhn.fhir.jpa.dao;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.Query;
|
||||
import javax.persistence.TypedQuery;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
import ca.uhn.fhir.util.UrlUtil.UrlParts;
|
||||
|
||||
public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subscription>implements IFhirResourceDaoSubscription<Subscription> {
|
||||
|
||||
private static final ResourceMetadataKeyEnum<Object> ALLOW_STATUS_CHANGE = new ResourceMetadataKeyEnum<Object>(FhirResourceDaoSubscriptionDstu2.class.getName() + "_ALLOW_STATUS_CHANGE") {
|
||||
private static final long serialVersionUID = 1;
|
||||
|
||||
@Override
|
||||
public Object get(IResource theResource) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(IResource theResource, Object theObject) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSubscriptionDstu2.class);
|
||||
|
||||
private void createSubscriptionTable(ResourceTable theEntity, Subscription theSubscription) {
|
||||
SubscriptionTable subscriptionEntity = new SubscriptionTable();
|
||||
subscriptionEntity.setSubscriptionResource(theEntity);
|
||||
subscriptionEntity.setNextCheck(theEntity.getPublished().getValue());
|
||||
subscriptionEntity.setNextCheckSince(theEntity.getPublished().getValue());
|
||||
subscriptionEntity.setStatus(theSubscription.getStatusElement().getValueAsEnum());
|
||||
myEntityManager.persist(subscriptionEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubscriptionTable getSubscriptionByResourceId(long theSubscriptionResourceId) {
|
||||
TypedQuery<SubscriptionTable> q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_GET_BY_RES", SubscriptionTable.class);
|
||||
q.setParameter("res_id", theSubscriptionResourceId);
|
||||
return q.getSingleResult();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND)
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
@Override
|
||||
public void pollForNewUndeliveredResources() {
|
||||
if (getConfig().isSubscriptionEnabled() == false) {
|
||||
return;
|
||||
}
|
||||
ourLog.trace("Beginning pollForNewUndeliveredResources()");
|
||||
|
||||
TypedQuery<SubscriptionTable> q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_NEXT_CHECK", SubscriptionTable.class);
|
||||
q.setParameter("next_check", new Date());
|
||||
q.setParameter("status", SubscriptionStatusEnum.ACTIVE);
|
||||
List<SubscriptionTable> subscriptions = q.getResultList();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postPersist(ResourceTable theEntity, Subscription theSubscription) {
|
||||
super.postPersist(theEntity, theSubscription);
|
||||
|
||||
createSubscriptionTable(theEntity, theSubscription);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSubscriptionStatus(Long theResourceId, SubscriptionStatusEnum theStatus) {
|
||||
Validate.notNull(theResourceId);
|
||||
Validate.notNull(theStatus);
|
||||
|
||||
ResourceTable existing = readEntityLatestVersion(new IdDt("Subscription", theResourceId));
|
||||
Subscription existingRes = toResource(Subscription.class, existing, false);
|
||||
|
||||
existingRes.getResourceMetadata().put(ALLOW_STATUS_CHANGE, new Object());
|
||||
existingRes.setStatus(theStatus);
|
||||
|
||||
update(existingRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ResourceTable updateEntity(IResource theResource, ResourceTable theEntity, boolean theUpdateHistory, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion, Date theUpdateTime) {
|
||||
ResourceTable retVal = super.updateEntity(theResource, theEntity, theUpdateHistory, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime);
|
||||
|
||||
Subscription resource = (Subscription) theResource;
|
||||
Long resourceId = theEntity.getId();
|
||||
if (theDeletedTimestampOrNull != null) {
|
||||
Query q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_DELETE");
|
||||
q.setParameter("res_id", resourceId);
|
||||
q.executeUpdate();
|
||||
} else {
|
||||
Query q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_SET_STATUS");
|
||||
q.setParameter("res_id", resourceId);
|
||||
q.setParameter("status", resource.getStatusElement().getValueAsEnum());
|
||||
if (q.executeUpdate() > 0) {
|
||||
ourLog.info("Updated subscription status for subscription {} to {}", resourceId, resource.getStatusElement().getValueAsEnum());
|
||||
} else {
|
||||
createSubscriptionTable(retVal, resource);
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validateResourceForStorage(Subscription theResource, ResourceTable theEntityToSave) {
|
||||
super.validateResourceForStorage(theResource, theEntityToSave);
|
||||
|
||||
String query = theResource.getCriteria();
|
||||
if (isBlank(query)) {
|
||||
throw new UnprocessableEntityException("Subscription.criteria must be populated");
|
||||
}
|
||||
|
||||
int sep = query.indexOf('?');
|
||||
if (sep <= 1) {
|
||||
throw new UnprocessableEntityException("Subscription.criteria must be in the form \"{Resource Type}?[params]\"");
|
||||
}
|
||||
|
||||
String resType = query.substring(0, sep);
|
||||
if (resType.contains("/")) {
|
||||
throw new UnprocessableEntityException("Subscription.criteria must be in the form \"{Resource Type}?[params]\"");
|
||||
}
|
||||
|
||||
RuntimeResourceDefinition resDef;
|
||||
try {
|
||||
resDef = getContext().getResourceDefinition(resType);
|
||||
} catch (DataFormatException e) {
|
||||
throw new UnprocessableEntityException("Subscription.criteria contains invalid/unsupported resource type: " + resType);
|
||||
}
|
||||
|
||||
IFhirResourceDao<? extends IBaseResource> dao = getDao(resDef.getImplementingClass());
|
||||
if (dao == null) {
|
||||
throw new UnprocessableEntityException("Subscription.criteria contains invalid/unsupported resource type: " + resDef);
|
||||
}
|
||||
|
||||
// SearchParameterMap parsedUrl = translateMatchUrl(query, resDef);
|
||||
|
||||
if (theResource.getChannel().getType() == null) {
|
||||
throw new UnprocessableEntityException("Subscription.channel.type must be populated on this server");
|
||||
}
|
||||
|
||||
SubscriptionStatusEnum status = theResource.getStatusElement().getValueAsEnum();
|
||||
Subscription existing = theEntityToSave.getEncoding() != null ? toResource(Subscription.class, theEntityToSave, false) : null;
|
||||
if (status == null) {
|
||||
// if (existing != null) {
|
||||
// status = existing.getStatusElement().getValueAsEnum();
|
||||
// theResource.setStatus(status);
|
||||
// } else {
|
||||
status = SubscriptionStatusEnum.REQUESTED;
|
||||
theResource.setStatus(status);
|
||||
// }
|
||||
} else {
|
||||
SubscriptionStatusEnum existingStatus = existing.getStatusElement().getValueAsEnum();
|
||||
if (existingStatus != status) {
|
||||
if (!theResource.getResourceMetadata().containsKey(ALLOW_STATUS_CHANGE)) {
|
||||
throw new UnprocessableEntityException("Subscription.status can not be changed from " + existingStatus + " to " + status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (theEntityToSave.getId() == null) {
|
||||
if (status != SubscriptionStatusEnum.REQUESTED) {
|
||||
throw new UnprocessableEntityException("Subscription.status must be " + SubscriptionStatusEnum.REQUESTED.getCode() + " on a newly created subscription");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -60,7 +60,7 @@ public class FhirResourceDaoValueSetDstu2 extends FhirResourceDaoDstu2<ValueSet>
|
|||
if (sourceEntity == null) {
|
||||
throw new ResourceNotFoundException(theId);
|
||||
}
|
||||
ValueSet source = (ValueSet) toResource(sourceEntity);
|
||||
ValueSet source = (ValueSet) toResource(sourceEntity, false);
|
||||
|
||||
/*
|
||||
* Add composed concepts
|
||||
|
|
|
@ -104,6 +104,7 @@ public class FhirSystemDaoDstu1 extends BaseHapiFhirSystemDao<List<IResource>> {
|
|||
OperationOutcome oo = new OperationOutcome();
|
||||
retVal.add(oo);
|
||||
|
||||
Date updateTime = new Date();
|
||||
for (int resourceIdx = 0; resourceIdx < theResources.size(); resourceIdx++) {
|
||||
IResource nextResource = theResources.get(resourceIdx);
|
||||
|
||||
|
@ -160,6 +161,8 @@ public class FhirSystemDaoDstu1 extends BaseHapiFhirSystemDao<List<IResource>> {
|
|||
if (entity == null) {
|
||||
nextResouceOperationOut = BundleEntryTransactionMethodEnum.POST;
|
||||
entity = toEntity(nextResource);
|
||||
entity.setUpdated(updateTime);
|
||||
entity.setPublished(updateTime);
|
||||
if (nextId.isEmpty() == false && "cid:".equals(nextId.getBaseUrl())) {
|
||||
ourLog.debug("Resource in transaction has ID[{}], will replace with server assigned ID", nextId.getIdPart());
|
||||
} else if (nextResouceOperationIn == BundleEntryTransactionMethodEnum.POST) {
|
||||
|
@ -170,7 +173,7 @@ public class FhirSystemDaoDstu1 extends BaseHapiFhirSystemDao<List<IResource>> {
|
|||
if (candidateMatches.size() == 1) {
|
||||
ourLog.debug("Resource with match URL [{}] already exists, will be NOOP", matchUrl);
|
||||
BaseHasResource existingEntity = loadFirstEntityFromCandidateMatches(candidateMatches);
|
||||
IResource existing = (IResource) toResource(existingEntity);
|
||||
IResource existing = (IResource) toResource(existingEntity, false);
|
||||
persistedResources.add(null);
|
||||
retVal.add(existing);
|
||||
continue;
|
||||
|
@ -262,11 +265,11 @@ public class FhirSystemDaoDstu1 extends BaseHapiFhirSystemDao<List<IResource>> {
|
|||
InstantDt deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get(resource);
|
||||
Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null;
|
||||
if (deletedInstantOrNull == null && ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(resource) == BundleEntryTransactionMethodEnum.DELETE) {
|
||||
deletedTimestampOrNull = new Date();
|
||||
deletedTimestampOrNull = updateTime;
|
||||
ResourceMetadataKeyEnum.DELETED_AT.put(resource, new InstantDt(deletedTimestampOrNull));
|
||||
}
|
||||
|
||||
updateEntity(resource, table, table.getId() != null, deletedTimestampOrNull);
|
||||
updateEntity(resource, table, table.getId() != null, deletedTimestampOrNull, updateTime);
|
||||
}
|
||||
|
||||
long delay = System.currentTimeMillis() - start;
|
||||
|
|
|
@ -19,7 +19,9 @@ package ca.uhn.fhir.jpa.dao;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
@ -64,6 +66,8 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
|||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
import ca.uhn.fhir.util.UrlUtil.UrlParts;
|
||||
|
||||
public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoDstu2.class);
|
||||
|
@ -92,10 +96,10 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
|
|||
resp.addEntry().setResource(ooResp);
|
||||
|
||||
/*
|
||||
* For batch, we handle each entry as a mini-transaction in its own
|
||||
* database transaction so that if one fails, it doesn't prevent others
|
||||
* For batch, we handle each entry as a mini-transaction in its own database transaction so that if one fails, it
|
||||
* doesn't prevent others
|
||||
*/
|
||||
|
||||
|
||||
for (final Entry nextRequestEntry : theRequest.getEntry()) {
|
||||
|
||||
TransactionCallback<Bundle> callback = new TransactionCallback<Bundle>() {
|
||||
|
@ -118,13 +122,13 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
|
|||
Entry subResponseEntry = nextResponseBundle.getEntry().get(0);
|
||||
resp.addEntry(subResponseEntry);
|
||||
/*
|
||||
* If the individual entry didn't have a resource in its response, bring the
|
||||
* sub-transaction's OperationOutcome across so the client can see it
|
||||
* If the individual entry didn't have a resource in its response, bring the sub-transaction's
|
||||
* OperationOutcome across so the client can see it
|
||||
*/
|
||||
if (subResponseEntry.getResource() == null) {
|
||||
subResponseEntry.setResource(nextResponseBundle.getEntry().get(0).getResource());
|
||||
}
|
||||
|
||||
|
||||
} catch (BaseServerResponseException e) {
|
||||
caughtEx = e;
|
||||
} catch (Throwable t) {
|
||||
|
@ -167,75 +171,6 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private UrlParts parseUrl(String theAction, String theUrl) {
|
||||
UrlParts retVal = new UrlParts();
|
||||
|
||||
//@formatter:off
|
||||
/*
|
||||
* We assume that the URL passed in is in one of the following forms:
|
||||
* [Resource Type]?[Search Params]
|
||||
* [Resource Type]/[Resource ID]
|
||||
* [Resource Type]/[Resource ID]/_history/[Version ID]
|
||||
*/
|
||||
//@formatter:on
|
||||
int nextStart = 0;
|
||||
boolean nextIsHistory = false;
|
||||
|
||||
for (int idx = 0; idx < theUrl.length(); idx++) {
|
||||
char nextChar = theUrl.charAt(idx);
|
||||
boolean atEnd = (idx + 1) == theUrl.length();
|
||||
if (nextChar == '?' || nextChar == '/' || atEnd) {
|
||||
int endIdx = atEnd ? idx + 1 : idx;
|
||||
String nextSubstring = theUrl.substring(nextStart, endIdx);
|
||||
if (retVal.getResourceType() == null) {
|
||||
retVal.setResourceType(nextSubstring);
|
||||
} else if (retVal.getResourceId() == null) {
|
||||
retVal.setResourceId(nextSubstring);
|
||||
} else if (nextIsHistory) {
|
||||
retVal.setVersionId(nextSubstring);
|
||||
} else {
|
||||
if (nextSubstring.equals(Constants.URL_TOKEN_HISTORY)) {
|
||||
nextIsHistory = true;
|
||||
} else {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theAction, theUrl);
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
}
|
||||
if (nextChar == '?') {
|
||||
if (theUrl.length() > idx + 1) {
|
||||
retVal.setParams(theUrl.substring(idx + 1, theUrl.length()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
nextStart = idx + 1;
|
||||
}
|
||||
}
|
||||
|
||||
RuntimeResourceDefinition resType;
|
||||
try {
|
||||
resType = getContext().getResourceDefinition(retVal.getResourceType());
|
||||
} catch (DataFormatException e) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theAction, theUrl);
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
IFhirResourceDao<? extends IBaseResource> dao = null;
|
||||
if (resType != null) {
|
||||
dao = getDao(resType.getImplementingClass());
|
||||
}
|
||||
if (dao == null) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theAction, theUrl);
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
retVal.setDao(dao);
|
||||
|
||||
if (retVal.getResourceId() == null && retVal.getParams() == null) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theAction, theUrl);
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRED)
|
||||
@Override
|
||||
public Bundle transaction(Bundle theRequest) {
|
||||
|
@ -265,6 +200,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
|
|||
ourLog.info("Beginning {} with {} resources", theActionName, theRequest.getEntry().size());
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
Date updateTime = new Date();
|
||||
|
||||
Set<IdDt> allIds = new LinkedHashSet<IdDt>();
|
||||
Map<IdDt, IdDt> idSubstitutions = new HashMap<IdDt, IdDt>();
|
||||
|
@ -275,11 +211,11 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
|
|||
// TODO: process verbs in the correct order
|
||||
|
||||
for (int i = 0; i < theRequest.getEntry().size(); i++) {
|
||||
|
||||
|
||||
if (i % 100 == 0) {
|
||||
ourLog.info("Processed {} entries out of {}", i, theRequest.getEntry().size());
|
||||
}
|
||||
|
||||
|
||||
Entry nextEntry = theRequest.getEntry().get(i);
|
||||
IResource res = nextEntry.getResource();
|
||||
IdDt nextResourceId = null;
|
||||
|
@ -330,11 +266,12 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
|
|||
// DELETE
|
||||
Entry newEntry = response.addEntry();
|
||||
String url = extractTransactionUrlOrThrowException(nextEntry, verb);
|
||||
UrlParts parts = parseUrl(verb.getCode(), url);
|
||||
UrlParts parts = UrlUtil.parseUrl(url);
|
||||
ca.uhn.fhir.jpa.dao.IFhirResourceDao<? extends IBaseResource> dao = toDao(parts, verb.getCode(), url);
|
||||
if (parts.getResourceId() != null) {
|
||||
parts.getDao().delete(new IdDt(parts.getResourceType(), parts.getResourceId()));
|
||||
dao.delete(new IdDt(parts.getResourceType(), parts.getResourceId()));
|
||||
} else {
|
||||
parts.getDao().deleteByUrl(parts.getResourceType() + '?' + parts.getParams());
|
||||
dao.deleteByUrl(parts.getResourceType() + '?' + parts.getParams());
|
||||
}
|
||||
|
||||
newEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_204_NO_CONTENT));
|
||||
|
@ -350,7 +287,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
|
|||
|
||||
String url = extractTransactionUrlOrThrowException(nextEntry, verb);
|
||||
|
||||
UrlParts parts = parseUrl(verb.getCode(), url);
|
||||
UrlParts parts = UrlUtil.parseUrl(url);
|
||||
if (isNotBlank(parts.getResourceId())) {
|
||||
res.setId(new IdDt(parts.getResourceType(), parts.getResourceId()));
|
||||
outcome = resourceDao.update(res, null, false);
|
||||
|
@ -365,10 +302,10 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
|
|||
case GET: {
|
||||
// SEARCH/READ/VREAD
|
||||
String url = extractTransactionUrlOrThrowException(nextEntry, verb);
|
||||
UrlParts parts = parseUrl(verb.getCode(), url);
|
||||
UrlParts parts = UrlUtil.parseUrl(url);
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
IFhirResourceDao resourceDao = parts.getDao();
|
||||
IFhirResourceDao dao = toDao(parts, verb.getCode(), url);
|
||||
|
||||
String ifNoneMatch = nextEntry.getRequest().getIfNoneMatch();
|
||||
if (isNotBlank(ifNoneMatch)) {
|
||||
|
@ -382,9 +319,9 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
|
|||
if (isNotBlank(ifNoneMatch)) {
|
||||
throw new InvalidRequestException("Unable to perform vread on '" + url + "' with ifNoneMatch also set. Do not include a version in the URL to perform a conditional read.");
|
||||
}
|
||||
found = (IResource) resourceDao.read(new IdDt(parts.getResourceType(), parts.getResourceId(), parts.getVersionId()));
|
||||
found = (IResource) dao.read(new IdDt(parts.getResourceType(), parts.getResourceId(), parts.getVersionId()));
|
||||
} else {
|
||||
found = (IResource) resourceDao.read(new IdDt(parts.getResourceType(), parts.getResourceId()));
|
||||
found = (IResource) dao.read(new IdDt(parts.getResourceType(), parts.getResourceId()));
|
||||
if (isNotBlank(ifNoneMatch) && ifNoneMatch.equals(found.getId().getVersionIdPart())) {
|
||||
notChanged = true;
|
||||
}
|
||||
|
@ -402,9 +339,9 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
|
|||
resp.setStatus(toStatusString(Constants.STATUS_HTTP_304_NOT_MODIFIED));
|
||||
}
|
||||
} else if (parts.getParams() != null) {
|
||||
RuntimeResourceDefinition def = getContext().getResourceDefinition(parts.getDao().getResourceType());
|
||||
RuntimeResourceDefinition def = getContext().getResourceDefinition(dao.getResourceType());
|
||||
SearchParameterMap params = translateMatchUrl(url, def);
|
||||
IBundleProvider bundle = parts.getDao().search(params);
|
||||
IBundleProvider bundle = dao.search(params);
|
||||
|
||||
Bundle searchBundle = new Bundle();
|
||||
searchBundle.setTotal(bundle.size());
|
||||
|
@ -453,7 +390,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
|
|||
|
||||
InstantDt deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get(nextResource);
|
||||
Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null;
|
||||
updateEntity(nextResource, nextOutcome.getEntity(), false, deletedTimestampOrNull, true, false);
|
||||
updateEntity(nextResource, nextOutcome.getEntity(), false, deletedTimestampOrNull, true, false, updateTime);
|
||||
}
|
||||
|
||||
myEntityManager.flush();
|
||||
|
@ -468,8 +405,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
|
|||
IFhirResourceDao<?> resourceDao = getDao(nextEntry.getResource().getClass());
|
||||
Set<Long> val = resourceDao.processMatchUrl(matchUrl);
|
||||
if (val.size() > 1) {
|
||||
throw new InvalidRequestException(
|
||||
"Unable to process " + theActionName + " - Request would cause multiple resources to match URL: \"" + matchUrl + "\". Does transaction request contain duplicates?");
|
||||
throw new InvalidRequestException("Unable to process " + theActionName + " - Request would cause multiple resources to match URL: \"" + matchUrl + "\". Does transaction request contain duplicates?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -488,13 +424,38 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
|
|||
|
||||
long delay = System.currentTimeMillis() - start;
|
||||
ourLog.info(theActionName + " completed in {}ms", new Object[] { delay });
|
||||
|
||||
|
||||
notifyWriteCompleted();
|
||||
|
||||
response.setType(BundleTypeEnum.TRANSACTION_RESPONSE);
|
||||
return response;
|
||||
}
|
||||
|
||||
private ca.uhn.fhir.jpa.dao.IFhirResourceDao<? extends IBaseResource> toDao(UrlParts theParts, String theVerb, String theUrl) {
|
||||
RuntimeResourceDefinition resType;
|
||||
try {
|
||||
resType = getContext().getResourceDefinition(theParts.getResourceType());
|
||||
} catch (DataFormatException e) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
IFhirResourceDao<? extends IBaseResource> dao = null;
|
||||
if (resType != null) {
|
||||
dao = getDao(resType.getImplementingClass());
|
||||
}
|
||||
if (dao == null) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
|
||||
if (theParts.getResourceId() == null && theParts.getParams() == null) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
|
||||
return dao;
|
||||
}
|
||||
|
||||
private IFhirResourceDao<?> getDaoOrThrowException(Class<? extends IResource> theClass) {
|
||||
IFhirResourceDao<? extends IResource> retVal = getDao(theClass);
|
||||
if (retVal == null) {
|
||||
|
@ -503,15 +464,15 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private static void handleTransactionCreateOrUpdateOutcome(Map<IdDt, IdDt> idSubstitutions, Map<IdDt, DaoMethodOutcome> idToPersistedOutcome, IdDt nextResourceId, DaoMethodOutcome outcome,
|
||||
Entry newEntry, String theResourceType, IResource theRes) {
|
||||
private static void handleTransactionCreateOrUpdateOutcome(Map<IdDt, IdDt> idSubstitutions, Map<IdDt, DaoMethodOutcome> idToPersistedOutcome, IdDt nextResourceId, DaoMethodOutcome outcome, Entry newEntry, String theResourceType, IResource theRes) {
|
||||
IdDt newId = (IdDt) outcome.getId().toUnqualifiedVersionless();
|
||||
IdDt resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless();
|
||||
if (newId.equals(resourceId) == false) {
|
||||
idSubstitutions.put(resourceId, newId);
|
||||
if (isPlaceholder(resourceId)) {
|
||||
/*
|
||||
* The correct way for substitution IDs to be is to be with no resource type, but we'll accept the qualified kind too just to be lenient.
|
||||
* The correct way for substitution IDs to be is to be with no resource type, but we'll accept the qualified
|
||||
* kind too just to be lenient.
|
||||
*/
|
||||
idSubstitutions.put(new IdDt(theResourceType + '/' + resourceId.getValue()), newId);
|
||||
}
|
||||
|
@ -538,52 +499,4 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
|
|||
return Integer.toString(theStatusCode) + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode));
|
||||
}
|
||||
|
||||
private static class UrlParts {
|
||||
private IFhirResourceDao<? extends IBaseResource> myDao;
|
||||
private String myParams;
|
||||
private String myResourceId;
|
||||
private String myResourceType;
|
||||
private String myVersionId;
|
||||
|
||||
public IFhirResourceDao<? extends IBaseResource> getDao() {
|
||||
return myDao;
|
||||
}
|
||||
|
||||
public String getParams() {
|
||||
return myParams;
|
||||
}
|
||||
|
||||
public String getResourceId() {
|
||||
return myResourceId;
|
||||
}
|
||||
|
||||
public String getResourceType() {
|
||||
return myResourceType;
|
||||
}
|
||||
|
||||
public String getVersionId() {
|
||||
return myVersionId;
|
||||
}
|
||||
|
||||
public void setDao(IFhirResourceDao<? extends IBaseResource> theDao) {
|
||||
myDao = theDao;
|
||||
}
|
||||
|
||||
public void setParams(String theParams) {
|
||||
myParams = theParams;
|
||||
}
|
||||
|
||||
public void setResourceId(String theResourceId) {
|
||||
myResourceId = theResourceId;
|
||||
}
|
||||
|
||||
public void setResourceType(String theResourceType) {
|
||||
myResourceType = theResourceType;
|
||||
}
|
||||
|
||||
public void setVersionId(String theVersionId) {
|
||||
myVersionId = theVersionId;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package ca.uhn.fhir.jpa.dao;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2015 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||
|
||||
public interface IFhirResourceDaoSubscription<T extends IBaseResource> extends IFhirResourceDao<T> {
|
||||
|
||||
void pollForNewUndeliveredResources();
|
||||
|
||||
void setSubscriptionStatus(Long theResourceId, SubscriptionStatusEnum theStatus);
|
||||
|
||||
SubscriptionTable getSubscriptionByResourceId(long theSubscriptionResourceId);
|
||||
|
||||
}
|
|
@ -98,7 +98,11 @@ public abstract class BaseHasResource {
|
|||
public abstract IdDt getIdDt();
|
||||
|
||||
public InstantDt getPublished() {
|
||||
return new InstantDt(myPublished);
|
||||
if (myPublished != null) {
|
||||
return new InstantDt(myPublished);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getResource() {
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package ca.uhn.fhir.jpa.entity;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.SequenceGenerator;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "HFJ_SUBSCRIPTION_CAND_RES")
|
||||
public class SubscriptionCandidateResource {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE)
|
||||
@SequenceGenerator(name = "SEQ_SUBSCRIPTION_CAND_ID", sequenceName = "SEQ_SUBSCRIPTION_CAND_ID")
|
||||
@Column(name = "PID", insertable = false, updatable = false)
|
||||
private Long myId;
|
||||
|
||||
@ManyToOne(fetch = FetchType.EAGER)
|
||||
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID")
|
||||
private ResourceTable myResource;
|
||||
|
||||
@Column(name = "RES_VERSION", nullable = false)
|
||||
private long myResourceVersion;
|
||||
|
||||
public ResourceTable getResource() {
|
||||
return myResource;
|
||||
}
|
||||
|
||||
public long getResourceVersion() {
|
||||
return myResourceVersion;
|
||||
}
|
||||
|
||||
public void setResource(ResourceTable theResource) {
|
||||
myResource = theResource;
|
||||
}
|
||||
|
||||
public void setResourceVersion(long theResourceVersion) {
|
||||
myResourceVersion = theResourceVersion;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package ca.uhn.fhir.jpa.entity;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.SequenceGenerator;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
|
||||
@Entity
|
||||
@Table(name = "HFJ_SUBSCRIPTION_FLAG_RES")
|
||||
public class SubscriptionFlaggedResource {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE)
|
||||
@SequenceGenerator(name = "SEQ_SUBSCRIPTION_FLAG_ID", sequenceName = "SEQ_SUBSCRIPTION_FLAG_ID")
|
||||
@Column(name = "PID", insertable = false, updatable = false)
|
||||
private Long myId;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
@Column(name = "CREATED", nullable = false)
|
||||
private Date myCreated;
|
||||
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package ca.uhn.fhir.jpa.entity;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.ForeignKey;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.OneToOne;
|
||||
import javax.persistence.SequenceGenerator;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||
|
||||
//@formatter:off
|
||||
@Entity
|
||||
@Table(name = "HFJ_SUBSCRIPTION", uniqueConstraints= {
|
||||
@UniqueConstraint(name="IDX_SUBS_RESID", columnNames= { "RES_ID" }),
|
||||
@UniqueConstraint(name="IDX_SUBS_NEXTCHECK", columnNames= { "SUBSCRIPTION_STATUS", "NEXT_CHECK" })
|
||||
})
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="Q_HFJ_SUBSCRIPTION_SET_STATUS", query="UPDATE SubscriptionTable t SET t.myStatus = :status WHERE t.myResId = :res_id"),
|
||||
@NamedQuery(name="Q_HFJ_SUBSCRIPTION_NEXT_CHECK", query="SELECT t FROM SubscriptionTable t WHERE t.myStatus = :status AND t.myNextCheck <= :next_check"),
|
||||
@NamedQuery(name="Q_HFJ_SUBSCRIPTION_GET_BY_RES", query="SELECT t FROM SubscriptionTable t WHERE t.myResId = :res_id"),
|
||||
@NamedQuery(name="Q_HFJ_SUBSCRIPTION_DELETE", query="DELETE FROM SubscriptionTable t WHERE t.myResId = :res_id"),
|
||||
})
|
||||
//@formatter:on
|
||||
public class SubscriptionTable {
|
||||
|
||||
@Column(name = "CHECK_INTERVAL", nullable = false)
|
||||
private long myCheckInterval;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE)
|
||||
@SequenceGenerator(name = "SEQ_SUBSCRIPTION_ID", sequenceName = "SEQ_SUBSCRIPTION_ID")
|
||||
@Column(name = "PID", insertable = false, updatable = false)
|
||||
private Long myId;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
@Column(name = "NEXT_CHECK", nullable = false)
|
||||
private Date myNextCheck;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
@Column(name = "NEXT_CHECK_SINCE", nullable = false)
|
||||
private Date myNextCheckSince;
|
||||
|
||||
@Column(name = "RES_ID", insertable = false, updatable = false)
|
||||
private Long myResId;
|
||||
|
||||
@Column(name = "SUBSCRIPTION_STATUS", nullable = false, length = 20)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private SubscriptionStatusEnum myStatus;
|
||||
|
||||
@OneToOne()
|
||||
@JoinColumn(name = "RES_ID", insertable = true, updatable = false, referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_SUBSCRIPTION_RESOURCE_ID") )
|
||||
private ResourceTable mySubscriptionResource;
|
||||
|
||||
public long getCheckInterval() {
|
||||
return myCheckInterval;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return myId;
|
||||
}
|
||||
|
||||
public Date getNextCheck() {
|
||||
return myNextCheck;
|
||||
}
|
||||
|
||||
public Date getNextCheckSince() {
|
||||
return myNextCheckSince;
|
||||
}
|
||||
|
||||
public SubscriptionStatusEnum getStatus() {
|
||||
return myStatus;
|
||||
}
|
||||
|
||||
public ResourceTable getSubscriptionResource() {
|
||||
return mySubscriptionResource;
|
||||
}
|
||||
|
||||
public void setCheckInterval(long theCheckInterval) {
|
||||
myCheckInterval = theCheckInterval;
|
||||
}
|
||||
|
||||
public void setNextCheck(Date theNextCheck) {
|
||||
myNextCheck = theNextCheck;
|
||||
}
|
||||
|
||||
public void setNextCheckSince(Date theNextCheckSince) {
|
||||
myNextCheckSince = theNextCheckSince;
|
||||
}
|
||||
|
||||
public void setStatus(SubscriptionStatusEnum theStatus) {
|
||||
myStatus = theStatus;
|
||||
}
|
||||
|
||||
public void setSubscriptionResource(ResourceTable theSubscriptionResource) {
|
||||
mySubscriptionResource = theSubscriptionResource;
|
||||
}
|
||||
|
||||
}
|
|
@ -17,7 +17,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.transaction.TransactionConfiguration;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -38,6 +37,9 @@ import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri;
|
|||
import ca.uhn.fhir.jpa.entity.ResourceLink;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTag;
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionCandidateResource;
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource;
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
|
||||
import ca.uhn.fhir.jpa.entity.TagDefinition;
|
||||
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||
|
@ -55,6 +57,7 @@ import ca.uhn.fhir.model.dstu2.resource.Practitioner;
|
|||
import ca.uhn.fhir.model.dstu2.resource.Questionnaire;
|
||||
import ca.uhn.fhir.model.dstu2.resource.QuestionnaireResponse;
|
||||
import ca.uhn.fhir.model.dstu2.resource.StructureDefinition;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Substance;
|
||||
import ca.uhn.fhir.model.dstu2.resource.ValueSet;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
|
@ -66,7 +69,6 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
|||
@ContextConfiguration(locations={
|
||||
"classpath:hapi-fhir-server-resourceproviders-dstu2.xml",
|
||||
"classpath:fhir-jpabase-spring-test-config.xml"})
|
||||
@TransactionConfiguration(defaultRollback=false)
|
||||
//@formatter:on
|
||||
public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
||||
|
||||
|
@ -74,9 +76,6 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
|||
@Qualifier("myConceptMapDaoDstu2")
|
||||
protected IFhirResourceDao<ConceptMap> myConceptMapDao;
|
||||
@Autowired
|
||||
@Qualifier("mySubstanceDaoDstu2")
|
||||
protected IFhirResourceDao<Substance> mySubstanceDao;
|
||||
@Autowired
|
||||
protected DaoConfig myDaoConfig;
|
||||
@Autowired
|
||||
@Qualifier("myDeviceDaoDstu2")
|
||||
|
@ -126,6 +125,12 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
|||
@Qualifier("myStructureDefinitionDaoDstu2")
|
||||
protected IFhirResourceDao<StructureDefinition> myStructureDefinitionDao;
|
||||
@Autowired
|
||||
@Qualifier("mySubscriptionDaoDstu2")
|
||||
protected IFhirResourceDaoSubscription<Subscription> mySubscriptionDao;
|
||||
@Autowired
|
||||
@Qualifier("mySubstanceDaoDstu2")
|
||||
protected IFhirResourceDao<Substance> mySubstanceDao;
|
||||
@Autowired
|
||||
@Qualifier("mySystemDaoDstu2")
|
||||
protected IFhirSystemDao<Bundle> mySystemDao;
|
||||
@Autowired
|
||||
|
@ -160,6 +165,13 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
|||
return newJsonParser.parseResource(type, string);
|
||||
}
|
||||
|
||||
public TransactionTemplate newTxTemplate() {
|
||||
TransactionTemplate retVal = new TransactionTemplate(myTxManager);
|
||||
retVal.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
|
||||
retVal.afterPropertiesSet();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public static void purgeDatabase(final EntityManager entityManager, PlatformTransactionManager theTxManager) {
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(theTxManager);
|
||||
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
|
||||
|
@ -174,6 +186,8 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
|||
txTemplate.execute(new TransactionCallback<Void>() {
|
||||
@Override
|
||||
public Void doInTransaction(TransactionStatus theStatus) {
|
||||
entityManager.createQuery("DELETE from " + SubscriptionCandidateResource.class.getSimpleName() + " d").executeUpdate();
|
||||
entityManager.createQuery("DELETE from " + SubscriptionFlaggedResource.class.getSimpleName() + " d").executeUpdate();
|
||||
entityManager.createQuery("DELETE from " + ForcedId.class.getSimpleName() + " d").executeUpdate();
|
||||
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamDate.class.getSimpleName() + " d").executeUpdate();
|
||||
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamNumber.class.getSimpleName() + " d").executeUpdate();
|
||||
|
@ -189,6 +203,7 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
|||
txTemplate.execute(new TransactionCallback<Void>() {
|
||||
@Override
|
||||
public Void doInTransaction(TransactionStatus theStatus) {
|
||||
entityManager.createQuery("DELETE from " + SubscriptionTable.class.getSimpleName() + " d").executeUpdate();
|
||||
entityManager.createQuery("DELETE from " + ResourceHistoryTag.class.getSimpleName() + " d").executeUpdate();
|
||||
entityManager.createQuery("DELETE from " + ResourceTag.class.getSimpleName() + " d").executeUpdate();
|
||||
entityManager.createQuery("DELETE from " + TagDefinition.class.getSimpleName() + " d").executeUpdate();
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
package ca.uhn.fhir.jpa.dao;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.TypedQuery;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.ObservationStatusEnum;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
|
||||
public class FhirResourceDaoDstu2SubscriptionTest extends BaseJpaDstu2Test {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu2SubscriptionTest.class);
|
||||
|
||||
@Test
|
||||
public void testCreateSubscriptionInvalidCriteria() {
|
||||
Subscription subs = new Subscription();
|
||||
subs.setCriteria("Observation");
|
||||
try {
|
||||
mySubscriptionDao.create(subs);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), containsString("Subscription.criteria must be in the form \"{Resource Type}?[params]\""));
|
||||
}
|
||||
|
||||
subs = new Subscription();
|
||||
subs.setCriteria("http://foo.com/Observation?AAA=BBB");
|
||||
try {
|
||||
mySubscriptionDao.create(subs);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), containsString("Subscription.criteria must be in the form \"{Resource Type}?[params]\""));
|
||||
}
|
||||
|
||||
subs = new Subscription();
|
||||
subs.setCriteria("ObservationZZZZ?a=b");
|
||||
try {
|
||||
mySubscriptionDao.create(subs);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), containsString("Subscription.criteria contains invalid/unsupported resource type: ObservationZZZZ"));
|
||||
}
|
||||
|
||||
subs = new Subscription();
|
||||
subs.setCriteria("Observation?identifier=123");
|
||||
try {
|
||||
mySubscriptionDao.create(subs);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), containsString("Subscription.channel.type must be populated on this server"));
|
||||
}
|
||||
|
||||
subs = new Subscription();
|
||||
subs.setCriteria("Observation?identifier=123");
|
||||
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
|
||||
assertTrue(mySubscriptionDao.create(subs).getId().hasIdPart());
|
||||
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeEnableSubscription() {
|
||||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionResourcesAppear() {
|
||||
String methodName = "testSubscriptionResourcesAppear";
|
||||
Patient p = new Patient();
|
||||
p.addName().addFamily(methodName);
|
||||
IIdType pId = myPatientDao.create(p).getId().toUnqualifiedVersionless();
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getSubject().setReference(pId);
|
||||
obs.setStatus(ObservationStatusEnum.FINAL);
|
||||
IIdType beforeId = myObservationDao.create(obs).getId().toUnqualifiedVersionless();
|
||||
|
||||
Subscription subs = new Subscription();
|
||||
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
|
||||
subs.setCriteria("Observation?subject=Patient/123");
|
||||
IIdType id = mySubscriptionDao.create(subs).getId().toUnqualifiedVersionless();
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReference(pId);
|
||||
obs.setStatus(ObservationStatusEnum.FINAL);
|
||||
IIdType afterId1 = myObservationDao.create(obs).getId().toUnqualifiedVersionless();
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReference(pId);
|
||||
obs.setStatus(ObservationStatusEnum.FINAL);
|
||||
IIdType afterId2 = myObservationDao.create(obs).getId().toUnqualifiedVersionless();
|
||||
|
||||
mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateSubscription() {
|
||||
Subscription subs = new Subscription();
|
||||
subs.setCriteria("Observation?subject=Patient/123");
|
||||
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
|
||||
|
||||
IIdType id = mySubscriptionDao.create(subs).getId().toUnqualifiedVersionless();
|
||||
|
||||
TypedQuery<SubscriptionTable> q = myEntityManager.createQuery("SELECT t from SubscriptionTable t WHERE t.mySubscriptionResource.myId = :id", SubscriptionTable.class);
|
||||
q.setParameter("id", id.getIdPartAsLong());
|
||||
final SubscriptionTable table = q.getSingleResult();
|
||||
|
||||
assertNotNull(table);
|
||||
assertNotNull(table.getNextCheck());
|
||||
assertEquals(table.getNextCheck(), table.getSubscriptionResource().getPublished().getValue());
|
||||
assertEquals(SubscriptionStatusEnum.REQUESTED, myEntityManager.find(SubscriptionTable.class, table.getId()).getStatus());
|
||||
assertEquals(SubscriptionStatusEnum.REQUESTED, mySubscriptionDao.read(id).getStatusElement().getValueAsEnum());
|
||||
|
||||
mySubscriptionDao.setSubscriptionStatus(id.getIdPartAsLong(), SubscriptionStatusEnum.ACTIVE);
|
||||
|
||||
assertEquals(SubscriptionStatusEnum.ACTIVE, myEntityManager.find(SubscriptionTable.class, table.getId()).getStatus());
|
||||
assertEquals(SubscriptionStatusEnum.ACTIVE, mySubscriptionDao.read(id).getStatusElement().getValueAsEnum());
|
||||
|
||||
mySubscriptionDao.delete(id);
|
||||
|
||||
assertNull(myEntityManager.find(SubscriptionTable.class, table.getId()));
|
||||
|
||||
/*
|
||||
* Re-create again
|
||||
*/
|
||||
|
||||
subs = new Subscription();
|
||||
subs.setCriteria("Observation?subject=Patient/123");
|
||||
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
|
||||
subs.setId(id);
|
||||
mySubscriptionDao.update(subs);
|
||||
|
||||
assertEquals(SubscriptionStatusEnum.REQUESTED, myEntityManager.createQuery("SELECT t FROM SubscriptionTable t WHERE t.myResId = " + id.getIdPart(), SubscriptionTable.class).getSingleResult().getStatus());
|
||||
assertEquals(SubscriptionStatusEnum.REQUESTED, mySubscriptionDao.read(id).getStatusElement().getValueAsEnum());
|
||||
}
|
||||
|
||||
}
|
|
@ -77,6 +77,7 @@ import ca.uhn.fhir.model.primitive.InstantDt;
|
|||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.model.primitive.UriDt;
|
||||
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
|
||||
import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.SortOrderEnum;
|
||||
|
@ -966,8 +967,13 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
|
|||
assertEquals(id.withVersion("1"), entries.get(2).getIdElement());
|
||||
|
||||
assertNull(ResourceMetadataKeyEnum.DELETED_AT.get((IResource) entries.get(0)));
|
||||
assertEquals(BundleEntryTransactionMethodEnum.PUT, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get((IResource) entries.get(0)));
|
||||
|
||||
assertNotNull(ResourceMetadataKeyEnum.DELETED_AT.get((IResource) entries.get(1)));
|
||||
assertEquals(BundleEntryTransactionMethodEnum.DELETE, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get((IResource) entries.get(1)));
|
||||
|
||||
assertNull(ResourceMetadataKeyEnum.DELETED_AT.get((IResource) entries.get(2)));
|
||||
assertEquals(BundleEntryTransactionMethodEnum.POST, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get((IResource) entries.get(2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -21,6 +21,9 @@
|
|||
<class>ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.ResourceLink</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.ResourceTag</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.SubscriptionTable</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.SubscriptionCandidateResource</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.TagDefinition</class>
|
||||
|
||||
<exclude-unlisted-classes>false</exclude-unlisted-classes>
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
<class>ca.uhn.fhir.jpa.entity.ResourceLink</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.ResourceTable</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.ResourceTag</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.SubscriptionTable</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.SubscriptionCandidateResource</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.TagDefinition</class>
|
||||
|
||||
<exclude-unlisted-classes>true</exclude-unlisted-classes>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
and other properties supported by BasicDataSource.
|
||||
-->
|
||||
<bean id="myPersistenceDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
|
||||
<property name="url" value="jdbc:derby:directory:jpaserver_derby_files;create=true" />
|
||||
<property name="url" value="jdbc:derby:directory:target/jpaserver_derby_files;create=true" />
|
||||
<property name="driverClassName" value="org.apache.derby.jdbc.EmbeddedDriver"></property>
|
||||
<property name="username" value=""/>
|
||||
<property name="password" value=""/>
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
<class>ca.uhn.fhir.jpa.entity.ResourceLink</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.ResourceTable</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.ResourceTag</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.SubscriptionTable</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.SubscriptionCandidateResource</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.TagDefinition</class>
|
||||
|
||||
<exclude-unlisted-classes>true</exclude-unlisted-classes>
|
||||
|
|
|
@ -16,7 +16,10 @@
|
|||
<class>ca.uhn.fhir.jpa.entity.ResourceLink</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.ResourceTable</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.ResourceTag</class>
|
||||
|
||||
<class>ca.uhn.fhir.jpa.entity.SubscriptionTable</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.SubscriptionCandidateResource</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource</class>
|
||||
|
||||
<exclude-unlisted-classes>true</exclude-unlisted-classes>
|
||||
<properties>
|
||||
<property name="hibernate.connection.url" value="jdbc:hsqldb:mem:unit-testing-jpa" />
|
||||
|
|
|
@ -104,12 +104,10 @@
|
|||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>${spring_version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>${spring_version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>xml-apis</artifactId>
|
||||
|
@ -120,53 +118,44 @@
|
|||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
<version>${spring_version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-tx</artifactId>
|
||||
<version>${spring_version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
<version>${spring_version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>${spring_version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlets</artifactId>
|
||||
<version>${jetty_version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
<version>${jetty_version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>${jetty_version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
<version>${jetty_version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
<version>${jetty_version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
|
|
@ -21,6 +21,9 @@
|
|||
<class>ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.ResourceLink</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.ResourceTag</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.SubscriptionTable</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.SubscriptionCandidateResource</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource</class>
|
||||
<class>ca.uhn.fhir.jpa.entity.TagDefinition</class>
|
||||
|
||||
<exclude-unlisted-classes>false</exclude-unlisted-classes>
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
#foreach ( $res in $resources )
|
||||
<bean id="my${res.name}Dao${versionCapitalized}"
|
||||
## Some resource types have customized DAOs for resource specific functionality
|
||||
#if ( ${versionCapitalized} == 'Dstu2' && ( ${res.name} == 'Bundle' || ${res.name} == 'QuestionnaireResponse' || ${res.name} == 'ValueSet'))
|
||||
#if ( ${versionCapitalized} == 'Dstu2' && ( ${res.name} == 'Bundle' || ${res.name} == 'Subscription' || ${res.name} == 'QuestionnaireResponse' || ${res.name} == 'ValueSet'))
|
||||
class="ca.uhn.fhir.jpa.dao.FhirResourceDao${res.name}${versionCapitalized}">
|
||||
#else
|
||||
class="ca.uhn.fhir.jpa.dao.FhirResourceDao${versionCapitalized}">
|
||||
|
|
5
pom.xml
5
pom.xml
|
@ -523,6 +523,11 @@
|
|||
<artifactId>spring-web</artifactId>
|
||||
<version>${spring_version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>${spring_version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.thymeleaf</groupId>
|
||||
<artifactId>thymeleaf</artifactId>
|
||||
|
|
|
@ -26,6 +26,10 @@
|
|||
JPA server did not correctly index search parameters of type "reference" where the
|
||||
path had multiple entries (i.e. "Resource.path1 | Resource.path2")
|
||||
</action>
|
||||
<action type="fix">
|
||||
JPA server _history operations (server, type, instance) not correctly set the
|
||||
Bundle.entry.request.method to POST or PUT for create and updates of the resource.
|
||||
</action>
|
||||
</release>
|
||||
<release version="1.2" date="2015-09-18">
|
||||
<action type="add">
|
||||
|
|
Loading…
Reference in New Issue