Improve efficiency of JPA queries with _lastUpdated

This commit is contained in:
jamesagnew 2015-10-12 09:32:25 -04:00
parent 8c0b665565
commit b827823004
7 changed files with 393 additions and 326 deletions

View File

@ -58,20 +58,6 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
setRangeFromDatesInclusive(theLowerBound, theUpperBound);
}
/**
* Constructor which takes two Dates representing the lower and upper bounds of the range (inclusive on both ends)
*
* @param theLowerBound
* A qualified date param representing the lower date bound (optionally may include time), e.g.
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
* @param theUpperBound
* A qualified date param representing the upper date bound (optionally may include time), e.g.
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
*/
public DateRangeParam(DateTimeDt theLowerBound, DateTimeDt theUpperBound) {
setRangeFromDatesInclusive(theLowerBound, theUpperBound);
}
/**
* Sets the range from a single date param. If theDateParam has no qualifier, treats it as the lower and upper bound
* (e.g. 2011-01-02 would match any time on that day). If theDateParam has a qualifier, treats it as either the
@ -106,6 +92,20 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
validateAndThrowDataFormatExceptionIfInvalid();
}
/**
* Constructor which takes two Dates representing the lower and upper bounds of the range (inclusive on both ends)
*
* @param theLowerBound
* A qualified date param representing the lower date bound (optionally may include time), e.g.
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
* @param theUpperBound
* A qualified date param representing the upper date bound (optionally may include time), e.g.
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
*/
public DateRangeParam(DateTimeDt theLowerBound, DateTimeDt theUpperBound) {
setRangeFromDatesInclusive(theLowerBound, theUpperBound);
}
/**
* Constructor which takes two strings representing the lower and upper bounds of the range (inclusive on both ends)
*
@ -120,6 +120,39 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
setRangeFromDatesInclusive(theLowerBound, theUpperBound);
}
private void addParam(DateParam theParsed) throws InvalidRequestException {
if (theParsed.getComparator() == null) {
if (myLowerBound != null || myUpperBound != null) {
throw new InvalidRequestException("Can not have multiple date range parameters for the same param without a qualifier");
}
myLowerBound = new DateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, theParsed.getValueAsString());
myUpperBound = new DateParam(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, theParsed.getValueAsString());
} else {
switch (theParsed.getComparator()) {
case GREATERTHAN:
case GREATERTHAN_OR_EQUALS:
if (myLowerBound != null) {
throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify a lower bound");
}
myLowerBound = theParsed;
break;
case LESSTHAN:
case LESSTHAN_OR_EQUALS:
if (myUpperBound != null) {
throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify an upper bound");
}
myUpperBound = theParsed;
break;
default:
throw new InvalidRequestException("Unknown comparator: " + theParsed.getComparator());
}
}
}
public DateParam getLowerBound() {
return myLowerBound;
}
@ -182,6 +215,18 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
return retVal;
}
private boolean haveLowerBound() {
return myLowerBound != null && myLowerBound.isEmpty() == false;
}
private boolean haveUpperBound() {
return myUpperBound != null && myUpperBound.isEmpty() == false;
}
public boolean isEmpty() {
return (getLowerBoundAsInstant() == null) && (getUpperBoundAsInstant() == null);
}
public void setLowerBound(DateParam theLowerBound) {
myLowerBound = theLowerBound;
validateAndThrowDataFormatExceptionIfInvalid();
@ -275,37 +320,32 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
}
private void addParam(DateParam theParsed) throws InvalidRequestException {
if (theParsed.getComparator() == null) {
if (myLowerBound != null || myUpperBound != null) {
throw new InvalidRequestException("Can not have multiple date range parameters for the same param without a qualifier");
@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append(getClass().getSimpleName());
b.append("[");
if (haveLowerBound()) {
if (myLowerBound.getComparator() != null) {
b.append(myLowerBound.getComparator().getCode());
}
myLowerBound = new DateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, theParsed.getValueAsString());
myUpperBound = new DateParam(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, theParsed.getValueAsString());
} else {
switch (theParsed.getComparator()) {
case GREATERTHAN:
case GREATERTHAN_OR_EQUALS:
if (myLowerBound != null) {
throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify a lower bound");
}
myLowerBound = theParsed;
break;
case LESSTHAN:
case LESSTHAN_OR_EQUALS:
if (myUpperBound != null) {
throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify an upper bound");
}
myUpperBound = theParsed;
break;
default:
throw new InvalidRequestException("Unknown comparator: " + theParsed.getComparator());
}
b.append(myLowerBound.getValueAsString());
}
if (haveUpperBound()) {
if(haveLowerBound()) {
b.append(" ");
}
if (myUpperBound.getComparator() != null) {
b.append(myUpperBound.getComparator().getCode());
}
b.append(myUpperBound.getValueAsString());
} else {
if (!haveLowerBound()) {
b.append("empty");
}
}
b.append("]");
return b.toString();
}
private void validateAndThrowDataFormatExceptionIfInvalid() {
@ -354,40 +394,4 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
}
private boolean haveUpperBound() {
return myUpperBound != null && myUpperBound.isEmpty() == false;
}
private boolean haveLowerBound() {
return myLowerBound != null && myLowerBound.isEmpty() == false;
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append(getClass().getSimpleName());
b.append("[");
if (haveLowerBound()) {
if (myLowerBound.getComparator() != null) {
b.append(myLowerBound.getComparator().getCode());
}
b.append(myLowerBound.getValueAsString());
}
if (haveUpperBound()) {
if(haveLowerBound()) {
b.append(" ");
}
if (myUpperBound.getComparator() != null) {
b.append(myUpperBound.getComparator().getCode());
}
b.append(myUpperBound.getValueAsString());
} else {
if (!haveLowerBound()) {
b.append("empty");
}
}
b.append("]");
return b.toString();
}
}

View File

@ -772,7 +772,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
}
IFhirResourceDao<R> dao = getDao(theResourceType);
Set<Long> ids = dao.searchForIdsWithAndOr(paramMap, new HashSet<Long>());
Set<Long> ids = dao.searchForIdsWithAndOr(paramMap, new HashSet<Long>(), paramMap.getLastUpdated());
return ids;
}

View File

@ -228,7 +228,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
codePredicates.add(p);
}
Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0]));
Predicate masterCodePredicate = builder.or(toArray(codePredicates));
Predicate type = builder.equal(from.get("myResourceType"), myResourceName);
Predicate name = builder.equal(from.get("myParamName"), theParamName);
@ -243,7 +243,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
return new HashSet<Long>(q.getResultList());
}
private Set<Long> addPredicateId(Set<Long> theExistingPids, Set<Long> thePids) {
private Set<Long> addPredicateId(Set<Long> theExistingPids, Set<Long> thePids, DateRangeParam theLastUpdated) {
if (thePids == null || thePids.isEmpty()) {
return Collections.emptySet();
}
@ -253,10 +253,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
Root<ResourceTable> from = cq.from(ResourceTable.class);
cq.select(from.get("myId").as(Long.class));
Predicate typePredicate = builder.equal(from.get("myResourceType"), myResourceName);
Predicate idPrecidate = from.get("myId").in(thePids);
cq.where(builder.and(typePredicate, idPrecidate));
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(from.get("myId").in(thePids));
predicates.addAll(createLastUpdatedPredicates(theLastUpdated, builder, from));
cq.where(toArray(predicates));
TypedQuery<Long> q = myEntityManager.createQuery(cq);
HashSet<Long> found = new HashSet<Long>(q.getResultList());
@ -272,7 +274,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
// IQueryParameterType> theList) {
// }
private Set<Long> addPredicateLanguage(Set<Long> thePids, List<List<? extends IQueryParameterType>> theList) {
private Set<Long> addPredicateLanguage(Set<Long> thePids, List<List<? extends IQueryParameterType>> theList, DateRangeParam theLastUpdated) {
Set<Long> retVal = thePids;
if (theList == null || theList.isEmpty()) {
return retVal;
@ -301,17 +303,18 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
return retVal;
}
Predicate typePredicate = builder.equal(from.get("myResourceType"), myResourceName);
Predicate langPredicate = from.get("myLanguage").as(String.class).in(values);
Predicate masterCodePredicate = builder.and(typePredicate, langPredicate);
Predicate notDeletedPredicate = builder.isNull(from.get("myDeleted"));
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(from.get("myLanguage").as(String.class).in(values));
if (retVal.size() > 0) {
Predicate inPids = (from.get("myId").in(retVal));
cq.where(builder.and(masterCodePredicate, inPids, notDeletedPredicate));
} else {
cq.where(builder.and(masterCodePredicate, notDeletedPredicate));
}
predicates.add(inPids);
}
predicates.add(builder.isNull(from.get("myDeleted")));
cq.where(toArray(predicates));
TypedQuery<Long> q = myEntityManager.createQuery(cq);
retVal = new HashSet<Long>(q.getResultList());
@ -413,7 +416,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0]));
Predicate masterCodePredicate = builder.or(toArray(codePredicates));
Predicate type = builder.equal(from.get("myResourceType"), myResourceName);
Predicate name = builder.equal(from.get("myParamName"), theParamName);
@ -592,7 +595,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
}
Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0]));
Predicate masterCodePredicate = builder.or(toArray(codePredicates));
Predicate type = builder.equal(from.get("myResourceType"), myResourceName);
Predicate name = builder.equal(from.get("myParamName"), theParamName);
@ -746,7 +749,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0]));
Predicate masterCodePredicate = builder.or(toArray(codePredicates));
Predicate type = createResourceLinkPathPredicate(theParamName, builder, from);
if (pidsToRetain.size() > 0) {
@ -785,7 +788,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
codePredicates.add(singleCode);
}
Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0]));
Predicate masterCodePredicate = builder.or(toArray(codePredicates));
Predicate type = builder.equal(from.get("myResourceType"), myResourceName);
Predicate name = builder.equal(from.get("myParamName"), theParamName);
@ -800,7 +803,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
return new HashSet<Long>(q.getResultList());
}
private Set<Long> addPredicateTag(Set<Long> thePids, List<List<? extends IQueryParameterType>> theList, String theParamName) {
private Set<Long> addPredicateTag(Set<Long> thePids, List<List<? extends IQueryParameterType>> theList, String theParamName, DateRangeParam theLastUpdated) {
Set<Long> pids = thePids;
if (theList == null || theList.isEmpty()) {
return pids;
@ -874,14 +877,17 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
if (orPredicates.isEmpty() == false) {
andPredicates.add(builder.or(orPredicates.toArray(new Predicate[0])));
andPredicates.add(builder.or(toArray(orPredicates)));
}
From<ResourceTag, ResourceTable> defJoin = from.join("myResource");
Predicate notDeletedPredicatePrediate = builder.isNull(defJoin.get("myDeleted"));
andPredicates.add(notDeletedPredicatePrediate);
Predicate masterCodePredicate = builder.and(andPredicates.toArray(new Predicate[0]));
if (theLastUpdated != null) {
andPredicates.addAll(createLastUpdatedPredicates(theLastUpdated, builder, defJoin));
}
Predicate masterCodePredicate = builder.and(toArray(andPredicates));
if (pids.size() > 0) {
Predicate inPids = (from.get("myResourceId").in(pids));
@ -928,7 +934,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
codePredicates.add(singleCode);
}
Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0]));
Predicate masterCodePredicate = builder.or(toArray(codePredicates));
Predicate type = builder.equal(from.get("myResourceType"), myResourceName);
Predicate name = builder.equal(from.get("myParamName"), theParamName);
@ -943,6 +949,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
return new HashSet<Long>(q.getResultList());
}
private static Predicate[] toArray(List<Predicate> thePredicates) {
return thePredicates.toArray(new Predicate[thePredicates.size()]);
}
private Set<Long> addPredicateUri(String theParamName, Set<Long> thePids, List<? extends IQueryParameterType> theList) {
if (theList == null || theList.isEmpty()) {
return thePids;
@ -981,7 +991,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0]));
Predicate masterCodePredicate = builder.or(toArray(codePredicates));
Predicate type = builder.equal(from.get("myResourceType"), myResourceName);
Predicate name = builder.equal(from.get("myParamName"), theParamName);
@ -1227,7 +1237,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
} else {
singleCodePredicates.add(theBuilder.isNull(theFrom.get("myValue")));
}
Predicate singleCode = theBuilder.and(singleCodePredicates.toArray(new Predicate[0]));
Predicate singleCode = theBuilder.and(toArray(singleCodePredicates));
return singleCode;
}
@ -1662,87 +1672,87 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
}
@Override
public IBundleProvider everything(IIdType theId) {
Search search = new Search();
search.setUuid(UUID.randomUUID().toString());
search.setCreated(new Date());
myEntityManager.persist(search);
List<SearchResult> results = new ArrayList<SearchResult>();
if (theId != null) {
Long pid = translateForcedIdToPid(theId);
ResourceTable entity = myEntityManager.find(ResourceTable.class, pid);
validateGivenIdIsAppropriateToRetrieveResource(theId, entity);
SearchResult res = new SearchResult(search);
res.setResourcePid(pid);
results.add(res);
} else {
TypedQuery<Tuple> query = createSearchAllByTypeQuery();
for (Tuple next : query.getResultList()) {
SearchResult res = new SearchResult(search);
res.setResourcePid(next.get(0, Long.class));
results.add(res);
}
}
int totalCount = results.size();
mySearchResultDao.save(results);
mySearchResultDao.flush();
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
// Load _revincludes
CriteriaQuery<Long> cq = builder.createQuery(Long.class);
Root<ResourceLink> from = cq.from(ResourceLink.class);
cq.select(from.get("mySourceResourcePid").as(Long.class));
Subquery<Long> pidsSubquery = cq.subquery(Long.class);
Root<SearchResult> pidsSubqueryFrom = pidsSubquery.from(SearchResult.class);
pidsSubquery.select(pidsSubqueryFrom.get("myResourcePid").as(Long.class));
pidsSubquery.where(pidsSubqueryFrom.get("mySearch").in(search));
cq.where(from.get("myTargetResourceId").in(pidsSubquery));
TypedQuery<Long> query = myEntityManager.createQuery(cq);
results = new ArrayList<SearchResult>();
for (Long next : query.getResultList()) {
SearchResult res = new SearchResult(search);
res.setResourcePid(next);
results.add(res);
}
// Save _revincludes
totalCount += results.size();
mySearchResultDao.save(results);
mySearchResultDao.flush();
final int finalTotalCount = totalCount;
return new IBundleProvider() {
@Override
public int size() {
return finalTotalCount;
}
@Override
public Integer preferredPageSize() {
return null;
}
@Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
// TODO Auto-generated method stub
return null;
}
@Override
public InstantDt getPublished() {
// TODO Auto-generated method stub
return null;
}
};
}
// @Override
// public IBundleProvider everything(IIdType theId) {
// Search search = new Search();
// search.setUuid(UUID.randomUUID().toString());
// search.setCreated(new Date());
// myEntityManager.persist(search);
//
// List<SearchResult> results = new ArrayList<SearchResult>();
// if (theId != null) {
// Long pid = translateForcedIdToPid(theId);
// ResourceTable entity = myEntityManager.find(ResourceTable.class, pid);
// validateGivenIdIsAppropriateToRetrieveResource(theId, entity);
// SearchResult res = new SearchResult(search);
// res.setResourcePid(pid);
// results.add(res);
// } else {
// TypedQuery<Tuple> query = createSearchAllByTypeQuery();
// for (Tuple next : query.getResultList()) {
// SearchResult res = new SearchResult(search);
// res.setResourcePid(next.get(0, Long.class));
// results.add(res);
// }
// }
//
// int totalCount = results.size();
// mySearchResultDao.save(results);
// mySearchResultDao.flush();
//
// CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
//
// // Load _revincludes
// CriteriaQuery<Long> cq = builder.createQuery(Long.class);
// Root<ResourceLink> from = cq.from(ResourceLink.class);
// cq.select(from.get("mySourceResourcePid").as(Long.class));
//
// Subquery<Long> pidsSubquery = cq.subquery(Long.class);
// Root<SearchResult> pidsSubqueryFrom = pidsSubquery.from(SearchResult.class);
// pidsSubquery.select(pidsSubqueryFrom.get("myResourcePid").as(Long.class));
// pidsSubquery.where(pidsSubqueryFrom.get("mySearch").in(search));
//
// cq.where(from.get("myTargetResourceId").in(pidsSubquery));
// TypedQuery<Long> query = myEntityManager.createQuery(cq);
//
// results = new ArrayList<SearchResult>();
// for (Long next : query.getResultList()) {
// SearchResult res = new SearchResult(search);
// res.setResourcePid(next);
// results.add(res);
// }
//
// // Save _revincludes
// totalCount += results.size();
// mySearchResultDao.save(results);
// mySearchResultDao.flush();
//
// final int finalTotalCount = totalCount;
// return new IBundleProvider() {
//
// @Override
// public int size() {
// return finalTotalCount;
// }
//
// @Override
// public Integer preferredPageSize() {
// return null;
// }
//
// @Override
// public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
// // TODO Auto-generated method stub
// return null;
// }
//
// @Override
// public InstantDt getPublished() {
// // TODO Auto-generated method stub
// return null;
// }
// };
// }
/**
* THIS SHOULD RETURN HASHSET and not jsut Set because we add to it later (so it can't be Collections.emptySet())
@ -2150,6 +2160,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
StopWatch w = new StopWatch();
final InstantDt now = InstantDt.withCurrentTime();
DateRangeParam lu = theParams.getLastUpdated();
if (lu != null && lu.isEmpty()) {
lu = null;
}
Collection<Long> loadPids;
if (theParams.getEverythingMode() != null) {
@ -2163,7 +2178,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.isNull(from.get("myDeleted")));
cq.where(builder.and(predicates.toArray(new Predicate[predicates.size()])));
cq.where(builder.and(toArray(predicates)));
Join<Object, Object> join = from.join("myIncomingResourceLinks", JoinType.LEFT);
cq.multiselect(from.get("myId").as(Long.class), join.get("mySourceResourcePid").as(Long.class));
@ -2181,7 +2196,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
} else if (theParams.isEmpty()) {
loadPids = new HashSet<Long>();
TypedQuery<Tuple> query = createSearchAllByTypeQuery();
TypedQuery<Tuple> query = createSearchAllByTypeQuery(lu);
lu = null;
for (Tuple next : query.getResultList()) {
loadPids.add(next.get(0, Long.class));
}
@ -2205,7 +2221,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
if (theParams.isEmpty()) {
loadPids = searchResultPids;
} else {
loadPids = searchForIdsWithAndOr(theParams, searchResultPids);
loadPids = searchForIdsWithAndOr(theParams, searchResultPids, lu);
}
if (loadPids.isEmpty()) {
return new SimpleBundleProvider();
@ -2222,9 +2238,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
// }
// Handle _lastUpdated
final DateRangeParam lu = theParams.getLastUpdated();
if (lu != null && (lu.getLowerBoundAsInstant() != null || lu.getUpperBoundAsInstant() != null)) {
if (lu != null) {
List<Long> resultList = filterResourceIdsByLastUpdated(loadPids, lu);
loadPids.clear();
for (Long next : resultList) {
@ -2271,7 +2285,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
// Load includes
pidsSubList = new ArrayList<Long>(pidsSubList);
revIncludedPids.addAll(loadReverseIncludes(pidsSubList, theParams.getIncludes(), false, null, lu));
revIncludedPids.addAll(loadReverseIncludes(pidsSubList, theParams.getIncludes(), false, null, theParams.getLastUpdated()));
// Execute the query and make sure we return distinct results
List<IBaseResource> resources = new ArrayList<IBaseResource>();
@ -2305,29 +2319,44 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
Root<ResourceTable> from = cq.from(ResourceTable.class);
cq.select(from.get("myId").as(Long.class));
Predicate predicateIds = (from.get("myId").in(thePids));
Predicate predicateLower = theLastUpdated.getLowerBoundAsInstant() != null ? builder.greaterThanOrEqualTo(from.<Date> get("myUpdated"), theLastUpdated.getLowerBoundAsInstant()) : null;
Predicate predicateUpper = theLastUpdated.getUpperBoundAsInstant() != null ? builder.lessThanOrEqualTo(from.<Date> get("myUpdated"), theLastUpdated.getUpperBoundAsInstant()) : null;
if (predicateLower != null && predicateUpper != null) {
cq.where(predicateIds, predicateLower, predicateUpper);
} else if (predicateLower != null) {
cq.where(predicateIds, predicateLower);
} else {
cq.where(predicateIds, predicateUpper);
}
List<Predicate> lastUpdatedPredicates = createLastUpdatedPredicates(theLastUpdated, builder, from);
lastUpdatedPredicates.add(0, from.get("myId").in(thePids));
cq.where(toArray(lastUpdatedPredicates));
TypedQuery<Long> query = myEntityManager.createQuery(cq);
List<Long> resultList = query.getResultList();
return resultList;
}
private TypedQuery<Tuple> createSearchAllByTypeQuery() {
private List<Predicate> createLastUpdatedPredicates(final DateRangeParam theLastUpdated, CriteriaBuilder builder, From<?, ResourceTable> from) {
List<Predicate> lastUpdatedPredicates = new ArrayList<Predicate>();
if (theLastUpdated != null) {
if (theLastUpdated.getLowerBoundAsInstant() != null) {
Predicate predicateLower = builder.greaterThanOrEqualTo(from.<Date> get("myUpdated"), theLastUpdated.getLowerBoundAsInstant());
lastUpdatedPredicates.add(predicateLower);
}
if (theLastUpdated.getUpperBoundAsInstant() != null) {
Predicate predicateUpper = builder.lessThanOrEqualTo(from.<Date> get("myUpdated"), theLastUpdated.getUpperBoundAsInstant());
lastUpdatedPredicates.add(predicateUpper);
}
}
return lastUpdatedPredicates;
}
private TypedQuery<Tuple> createSearchAllByTypeQuery(DateRangeParam theLastUpdated) {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = builder.createTupleQuery();
Root<ResourceTable> from = cq.from(ResourceTable.class);
cq.multiselect(from.get("myId").as(Long.class));
Predicate typeEquals = builder.equal(from.get("myResourceType"), myResourceName);
Predicate notDeleted = builder.isNull(from.get("myDeleted"));
cq.where(builder.and(typeEquals, notDeleted));
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.isNull(from.get("myDeleted")));
if (theLastUpdated != null) {
predicates.addAll(createLastUpdatedPredicates(theLastUpdated, builder, from));
}
cq.where(toArray(predicates));
TypedQuery<Tuple> query = myEntityManager.createQuery(cq);
return query;
@ -2348,7 +2377,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
Collection<Long> originalPids = theLoadPids;
LinkedHashSet<Long> loadPids = new LinkedHashSet<Long>();
cq.multiselect(from.get("myId").as(Long.class));
cq.where(predicates.toArray(new Predicate[0]));
cq.where(toArray(predicates));
cq.orderBy(orders);
TypedQuery<Tuple> query = myEntityManager.createQuery(cq);
@ -2398,7 +2427,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
for (Entry<String, IQueryParameterType> nextEntry : theParams.entrySet()) {
map.add(nextEntry.getKey(), (nextEntry.getValue()));
}
return searchForIdsWithAndOr(map, null);
return searchForIdsWithAndOr(map, null, null);
}
@Override
@ -2407,7 +2436,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
@Override
public Set<Long> searchForIdsWithAndOr(SearchParameterMap theParams, Collection<Long> theInitialPids) {
public Set<Long> searchForIdsWithAndOr(SearchParameterMap theParams, Collection<Long> theInitialPids, DateRangeParam theLastUpdated) {
SearchParameterMap params = theParams;
if (params == null) {
params = new SearchParameterMap();
@ -2451,7 +2480,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
}
pids = addPredicateId(pids, joinPids);
pids = addPredicateId(pids, joinPids, theLastUpdated);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
@ -2466,11 +2495,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
} else if (nextParamName.equals(BaseResource.SP_RES_LANGUAGE)) {
pids = addPredicateLanguage(pids, nextParamEntry.getValue());
pids = addPredicateLanguage(pids, nextParamEntry.getValue(), theLastUpdated);
} else if (nextParamName.equals(Constants.PARAM_TAG) || nextParamName.equals(Constants.PARAM_PROFILE) || nextParamName.equals(Constants.PARAM_SECURITY)) {
pids = addPredicateTag(pids, nextParamEntry.getValue(), nextParamName);
pids = addPredicateTag(pids, nextParamEntry.getValue(), nextParamName, theLastUpdated);
} else {

View File

@ -28,7 +28,6 @@ import java.util.Date;
import java.util.List;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -77,6 +76,9 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
@Autowired
private ISubscriptionTableDao mySubscriptionTableDao;
@Autowired
private PlatformTransactionManager myTxManager;
private void createSubscriptionTable(ResourceTable theEntity, Subscription theSubscription) {
SubscriptionTable subscriptionEntity = new SubscriptionTable();
subscriptionEntity.setCreated(new Date());
@ -87,15 +89,32 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
myEntityManager.persist(subscriptionEntity);
}
@Autowired
private PlatformTransactionManager myTxManager;
@Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public synchronized void pollForNewUndeliveredResourcesScheduler() {
pollForNewUndeliveredResources();
@Override
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId) {
ResourceTable entity = readEntityLatestVersion(theId);
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
if (table == null) {
return null;
}
return table.getId();
}
@Override
public synchronized List<IBaseResource> getUndeliveredResourcesAndPurge(Long theSubscriptionPid) {
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
Page<SubscriptionFlaggedResource> flaggedResources = mySubscriptionFlaggedResourceDataDao.findAllBySubscriptionId(theSubscriptionPid, new PageRequest(0, 100));
for (SubscriptionFlaggedResource nextFlaggedResource : flaggedResources) {
retVal.add(toResource(nextFlaggedResource.getResource(), false));
}
mySubscriptionFlaggedResourceDataDao.delete(flaggedResources);
mySubscriptionFlaggedResourceDataDao.flush();
mySubscriptionTableDao.updateLastClientPoll(new Date());
return retVal;
}
@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public synchronized int pollForNewUndeliveredResources() {
@ -189,6 +208,12 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
return results.size();
}
@Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public synchronized void pollForNewUndeliveredResourcesScheduler() {
pollForNewUndeliveredResources();
}
@Override
protected void postPersist(ResourceTable theEntity, Subscription theSubscription) {
super.postPersist(theEntity, theSubscription);
@ -196,105 +221,6 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
createSubscriptionTable(theEntity, theSubscription);
}
@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) {
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt());
if (subscriptionId != null) {
mySubscriptionFlaggedResourceDataDao.deleteAllForSubscription(subscriptionId);
mySubscriptionTableDao.deleteAllForSubscription(subscriptionId);
}
} 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);
RuntimeResourceDefinition resDef = validateCriteriaAndReturnResourceDefinition(theResource);
IFhirResourceDao<? extends IBaseResource> dao = getDao(resDef.getImplementingClass());
if (dao == null) {
throw new UnprocessableEntityException("Subscription.criteria contains invalid/unsupported resource type: " + resDef);
}
if (theResource.getChannel().getType() == null) {
throw new UnprocessableEntityException("Subscription.channel.type must be populated on this server");
}
SubscriptionStatusEnum status = theResource.getStatusElement().getValueAsEnum();
if (status == null) {
throw new UnprocessableEntityException("Subscription.status must be populated on this server");
}
}
private RuntimeResourceDefinition validateCriteriaAndReturnResourceDefinition(Subscription theResource) {
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);
}
return resDef;
}
@Override
public synchronized List<IBaseResource> getUndeliveredResourcesAndPurge(Long theSubscriptionPid) {
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
Page<SubscriptionFlaggedResource> flaggedResources = mySubscriptionFlaggedResourceDataDao.findAllBySubscriptionId(theSubscriptionPid, new PageRequest(0, 100));
for (SubscriptionFlaggedResource nextFlaggedResource : flaggedResources) {
retVal.add(toResource(nextFlaggedResource.getResource(), false));
}
mySubscriptionFlaggedResourceDataDao.delete(flaggedResources);
mySubscriptionFlaggedResourceDataDao.flush();
mySubscriptionTableDao.updateLastClientPoll(new Date());
return retVal;
}
@Override
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId) {
ResourceTable entity = readEntityLatestVersion(theId);
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
if (table == null) {
return null;
}
return table.getId();
}
@Scheduled(fixedDelay = DateUtils.MILLIS_PER_MINUTE)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
@ -323,4 +249,77 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
}
}
@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) {
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt());
if (subscriptionId != null) {
mySubscriptionFlaggedResourceDataDao.deleteAllForSubscription(subscriptionId);
mySubscriptionTableDao.deleteAllForSubscription(subscriptionId);
}
} 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;
}
private RuntimeResourceDefinition validateCriteriaAndReturnResourceDefinition(Subscription theResource) {
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);
}
return resDef;
}
@Override
protected void validateResourceForStorage(Subscription theResource, ResourceTable theEntityToSave) {
super.validateResourceForStorage(theResource, theEntityToSave);
RuntimeResourceDefinition resDef = validateCriteriaAndReturnResourceDefinition(theResource);
IFhirResourceDao<? extends IBaseResource> dao = getDao(resDef.getImplementingClass());
if (dao == null) {
throw new UnprocessableEntityException("Subscription.criteria contains invalid/unsupported resource type: " + resDef);
}
if (theResource.getChannel().getType() == null) {
throw new UnprocessableEntityException("Subscription.channel.type must be populated on this server");
}
SubscriptionStatusEnum status = theResource.getStatusElement().getValueAsEnum();
if (status == null) {
throw new UnprocessableEntityException("Subscription.status must be populated on this server");
}
}
}

View File

@ -36,6 +36,7 @@ import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
@ -123,7 +124,7 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
Set<Long> searchForIds(String theParameterName, IQueryParameterType theValue);
Set<Long> searchForIdsWithAndOr(SearchParameterMap theParams, Collection<Long> theInitialPids);
Set<Long> searchForIdsWithAndOr(SearchParameterMap theParams, Collection<Long> theInitialPids, DateRangeParam theLastUpdated);
DaoMethodOutcome update(T theResource);
@ -146,9 +147,9 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
*/
DaoMethodOutcome deleteByUrl(String theUrl, boolean theTransaction);
/**
* Invoke the everything operation
*/
IBundleProvider everything(IIdType theId);
// /**
// * Invoke the everything operation
// */
// IBundleProvider everything(IIdType theId);
}

View File

@ -399,6 +399,7 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test {
patient.addIdentifier().setSystem("urn:system").setValue("001");
id1 = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
}
long betweenTime = System.currentTimeMillis();
IIdType id2;
{
Patient patient = new Patient();
@ -418,6 +419,13 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test {
params.add("_id", new StringOrListParam().addOr(new StringParam(id1.getIdPart())).addOr(new StringParam("999999999999")));
assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1));
// With lastupdated
params = new SearchParameterMap();
params.add("_id", new StringOrListParam().addOr(new StringParam(id1.getIdPart())).addOr(new StringParam(id2.getIdPart())));
params.setLastUpdated(new DateRangeParam(new Date(betweenTime), null));
assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id2));
}
@Test
@ -610,6 +618,9 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test {
patient.addName().addFamily("testSearchLanguageParam").addGiven("Joe");
id1 = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
}
Date betweenTime = new Date();
IIdType id2;
{
Patient patient = new Patient();
@ -623,6 +634,12 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test {
params.add(BaseResource.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("en_US")));
assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1, id2));
}
{
SearchParameterMap params = new SearchParameterMap();
params.add(BaseResource.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("en_US")));
params.setLastUpdated(new DateRangeParam(betweenTime, null));
assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id2));
}
{
SearchParameterMap params = new SearchParameterMap();
params.add(BaseResource.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ")));
@ -1943,6 +1960,9 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test {
ResourceMetadataKeyEnum.TAG_LIST.put(org, tagList);
tag1id = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
}
Date betweenDate = new Date();
IIdType tag2id;
{
Organization org = new Organization();
@ -1978,6 +1998,17 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test {
List<IIdType> patients = toUnqualifiedVersionlessIds(myOrganizationDao.search(params));
assertThat(patients, containsInAnyOrder(tag1id, tag2id));
}
{
// Or tags with lastupdated
SearchParameterMap params = new SearchParameterMap();
TokenOrListParam orListParam = new TokenOrListParam();
orListParam.add(new TokenParam("urn:taglist", methodName + "1a"));
orListParam.add(new TokenParam("urn:taglist", methodName + "2a"));
params.add("_tag", orListParam);
params.setLastUpdated(new DateRangeParam(betweenDate, null));
List<IIdType> patients = toUnqualifiedVersionlessIds(myOrganizationDao.search(params));
assertThat(patients, containsInAnyOrder(tag2id));
}
// TODO: get multiple/AND working
{
// And tags

View File

@ -162,6 +162,9 @@
meaning that the same operation can also be invoked
at the type level.
</action>
<action type="add">
Make JPA search queries with _lastUpdated parameter a bit more efficient
</action>
</release>
<release version="1.2" date="2015-09-18">
<action type="add">