Start working on JPA subscriptions

This commit is contained in:
jamesagnew 2015-09-21 09:08:19 -04:00
parent 8fee057de3
commit 04c2cce13f
29 changed files with 988 additions and 287 deletions

View File

@ -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
@ -159,4 +162,91 @@ public class UrlUtil {
}
}
//@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;
}
}
}

View File

@ -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>

View File

@ -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,8 +978,22 @@ 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()));
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());
@ -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);
@ -1182,7 +1194,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
} 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) {

View File

@ -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();

View File

@ -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()));

View File

@ -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 setInterceptors(IServerInterceptor... theInterceptor) {
if (theInterceptor == null || theInterceptor.length==0){
setInterceptors(new ArrayList<IServerInterceptor>());
} else {
setInterceptors(Arrays.asList(theInterceptor));
}
public void setResourceEncoding(ResourceEncodingEnum theResourceEncoding) {
myResourceEncoding = theResourceEncoding;
}
/**
@ -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;
}
}

View File

@ -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;
}

View File

@ -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");
}
}
}
}

View File

@ -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

View File

@ -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;

View File

@ -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,8 +96,8 @@ 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()) {
@ -118,8 +122,8 @@ 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());
@ -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>();
@ -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?");
}
}
}
@ -495,6 +431,31 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
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;
}
}
}

View File

@ -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);
}

View File

@ -98,7 +98,11 @@ public abstract class BaseHasResource {
public abstract IdDt getIdDt();
public InstantDt getPublished() {
if (myPublished != null) {
return new InstantDt(myPublished);
} else {
return null;
}
}
public byte[] getResource() {

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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());
}
}

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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=""/>

View File

@ -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>

View File

@ -16,6 +16,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>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>

View File

@ -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>

View File

@ -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>

View File

@ -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}">

View File

@ -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>

View File

@ -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">