Merge branch 'master' of github.com:jamesagnew/hapi-fhir

This commit is contained in:
jamesagnew 2018-08-12 18:40:08 -04:00
commit 1a5ba99e2d
11 changed files with 488 additions and 327 deletions

View File

@ -25,9 +25,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* 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.
@ -36,6 +36,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* #L%
*/
@SuppressWarnings("UnusedReturnValue")
public class DateRangeParam implements IQueryParameterAnd<DateParam> {
private static final long serialVersionUID = 1L;
@ -208,6 +209,52 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
return this;
}
/**
* Sets the lower bound using a string that is compliant with
* FHIR dateTime format (ISO-8601).
* <p>
* This lower bound is assumed to have a <code>ge</code>
* (greater than or equals) modifier.
* </p>
*/
public DateRangeParam setLowerBound(String theLowerBound) {
setLowerBound(new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound));
return this;
}
/**
* Sets the lower bound to be greaterthan or equal to the given date
*/
public DateRangeParam setLowerBoundInclusive(Date theLowerBound) {
validateAndSet(new DateParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, theLowerBound), myUpperBound);
return this;
}
/**
* Sets the upper bound to be greaterthan or equal to the given date
*/
public DateRangeParam setUpperBoundInclusive(Date theUpperBound) {
validateAndSet(myLowerBound, new DateParam(ParamPrefixEnum.LESSTHAN_OR_EQUALS, theUpperBound));
return this;
}
/**
* Sets the lower bound to be greaterthan to the given date
*/
public DateRangeParam setLowerBoundExclusive(Date theLowerBound) {
validateAndSet(new DateParam(ParamPrefixEnum.GREATERTHAN, theLowerBound), myUpperBound);
return this;
}
/**
* Sets the upper bound to be greaterthan to the given date
*/
public DateRangeParam setUpperBoundExclusive(Date theUpperBound) {
validateAndSet(myLowerBound, new DateParam(ParamPrefixEnum.LESSTHAN, theUpperBound));
return this;
}
public Date getLowerBoundAsInstant() {
if (myLowerBound == null) {
return null;
@ -237,6 +284,19 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
return myUpperBound;
}
/**
* Sets the upper bound using a string that is compliant with
* FHIR dateTime format (ISO-8601).
* <p>
* This upper bound is assumed to have a <code>le</code>
* (less than or equals) modifier.
* </p>
*/
public DateRangeParam setUpperBound(String theUpperBound) {
setUpperBound(new DateParam(LESSTHAN_OR_EQUALS, theUpperBound));
return this;
}
public DateRangeParam setUpperBound(DateParam theUpperBound) {
validateAndSet(myLowerBound, theUpperBound);
return this;
@ -298,19 +358,6 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
return (getLowerBoundAsInstant() == null) && (getUpperBoundAsInstant() == null);
}
/**
* Sets the lower bound using a string that is compliant with
* FHIR dateTime format (ISO-8601).
* <p>
* This lower bound is assumed to have a <code>ge</code>
* (greater than or equals) modifier.
* </p>
*/
public DateRangeParam setLowerBound(String theLowerBound) {
setLowerBound(new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound));
return this;
}
/**
* Sets the range from a pair of dates, inclusive on both ends
*
@ -394,19 +441,6 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
validateAndSet(lowerBound, upperBound);
}
/**
* Sets the upper bound using a string that is compliant with
* FHIR dateTime format (ISO-8601).
* <p>
* This upper bound is assumed to have a <code>le</code>
* (less than or equals) modifier.
* </p>
*/
public DateRangeParam setUpperBound(String theUpperBound) {
setUpperBound(new DateParam(LESSTHAN_OR_EQUALS, theUpperBound));
return this;
}
@Override
public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, List<QualifiedParamList> theParameters)
throws InvalidRequestException {
@ -512,4 +546,5 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
myLowerBound = lowerBound;
myUpperBound = upperBound;
}
}

View File

@ -38,8 +38,8 @@ public interface ISearchBuilder {
void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation, EntityManager theEntityManager,
FhirContext theContext, IDao theDao);
Set<Long> loadReverseIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes, boolean theReverseMode,
DateRangeParam theLastUpdated);
Set<Long> loadIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes, boolean theReverseMode,
DateRangeParam theLastUpdated);
/**
* How many results may be fetched at once

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao;
* 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.
@ -69,6 +69,7 @@ import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nonnull;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
@ -114,7 +115,7 @@ public class SearchBuilder implements ISearchBuilder {
protected IResourceTagDao myResourceTagDao;
protected IResourceSearchViewDao myResourceSearchViewDao;
/**
* Constructor
*/
@ -1603,17 +1604,17 @@ public class SearchBuilder implements ISearchBuilder {
// -- get the resource from the searchView
Collection<ResourceSearchView> resourceSearchViewList = myResourceSearchViewDao.findByResourceIds(pids);
//-- preload all tags with tag definition if any
Map<Long, Collection<ResourceTag>> tagMap = getResourceTagMap(resourceSearchViewList);
Long resourceId = null;
for (ResourceSearchView next : resourceSearchViewList) {
Class<? extends IBaseResource> resourceType = context.getResourceDefinition(next.getResourceType()).getImplementingClass();
resourceId = next.getId();
IBaseResource resource = theDao.toResource(resourceType, next, tagMap.get(resourceId), theForHistoryOperation);
if (resource == null) {
ourLog.warn("Unable to find resource {}/{}/_history/{} in database", next.getResourceType(), next.getIdDt().getIdPart(), next.getVersion());
@ -1643,30 +1644,30 @@ public class SearchBuilder implements ISearchBuilder {
}
}
private Map<Long, Collection<ResourceTag>> getResourceTagMap(Collection<ResourceSearchView> theResourceSearchViewList) {
private Map<Long, Collection<ResourceTag>> getResourceTagMap(Collection<ResourceSearchView> theResourceSearchViewList) {
List<Long> idList = new ArrayList<Long>(theResourceSearchViewList.size());
//-- find all resource has tags
for (ResourceSearchView resource: theResourceSearchViewList) {
for (ResourceSearchView resource: theResourceSearchViewList) {
if (resource.isHasTags())
idList.add(resource.getId());
}
Map<Long, Collection<ResourceTag>> tagMap = new HashMap<>();
//-- no tags
if (idList.size() == 0)
return tagMap;
//-- get all tags for the idList
Collection<ResourceTag> tagList = myResourceTagDao.findByResourceIds(idList);
//-- build the map, key = resourceId, value = list of ResourceTag
Long resourceId;
Collection<ResourceTag> tagCol;
for (ResourceTag tag : tagList) {
resourceId = tag.getResourceId();
tagCol = tagMap.get(resourceId);
if (tagCol == null) {
@ -1678,7 +1679,7 @@ public class SearchBuilder implements ISearchBuilder {
}
}
return tagMap;
return tagMap;
}
@Override
@ -1720,8 +1721,8 @@ public class SearchBuilder implements ISearchBuilder {
* THIS SHOULD RETURN HASHSET and not just Set because we add to it later (so it can't be Collections.emptySet())
*/
@Override
public HashSet<Long> loadReverseIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes,
boolean theReverseMode, DateRangeParam theLastUpdated) {
public HashSet<Long> loadIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes,
boolean theReverseMode, DateRangeParam theLastUpdated) {
if (theMatches.size() == 0) {
return new HashSet<>();
}
@ -1828,8 +1829,10 @@ public class SearchBuilder implements ISearchBuilder {
}
}
if (theLastUpdated != null && (theLastUpdated.getLowerBoundAsInstant() != null || theLastUpdated.getUpperBoundAsInstant() != null)) {
pidsToInclude = new HashSet<>(filterResourceIdsByLastUpdated(theEntityManager, theLastUpdated, pidsToInclude));
if (theReverseMode) {
if (theLastUpdated != null && (theLastUpdated.getLowerBoundAsInstant() != null || theLastUpdated.getUpperBoundAsInstant() != null)) {
pidsToInclude = new HashSet<>(filterResourceIdsByLastUpdated(theEntityManager, theLastUpdated, pidsToInclude));
}
}
for (Long next : pidsToInclude) {
if (original.contains(next) == false && allAdded.contains(next) == false) {
@ -1841,19 +1844,15 @@ public class SearchBuilder implements ISearchBuilder {
nextRoundMatches = pidsToInclude;
} while (includes.size() > 0 && nextRoundMatches.size() > 0 && addedSomeThisRound);
ourLog.info("Loaded {} {} in {} rounds and {} ms", new Object[] {allAdded.size(), theReverseMode ? "_revincludes" : "_includes", roundCounts, w.getMillisAndRestart()});
ourLog.info("Loaded {} {} in {} rounds and {} ms", allAdded.size(), theReverseMode ? "_revincludes" : "_includes", roundCounts, w.getMillisAndRestart());
return allAdded;
}
private void searchForIdsWithAndOr(SearchParameterMap theParams) {
SearchParameterMap params = theParams;
if (params == null) {
params = new SearchParameterMap();
}
private void searchForIdsWithAndOr(@Nonnull SearchParameterMap theParams) {
myParams = theParams;
for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : params.entrySet()) {
for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : myParams.entrySet()) {
String nextParamName = nextParamEntry.getKey();
List<List<? extends IQueryParameterType>> andOrParams = nextParamEntry.getValue();
searchForIdsWithAndOr(myResourceName, nextParamName, andOrParams);
@ -2182,7 +2181,7 @@ public class SearchBuilder implements ISearchBuilder {
myCurrentOffset = end;
Collection<Long> pidsToScan = myCurrentPids.subList(start, end);
Set<Include> includes = Collections.singleton(new Include("*", true));
Set<Long> newPids = loadReverseIncludes(myCallingDao, myContext, myEntityManager, pidsToScan, includes, false, myParams.getLastUpdated());
Set<Long> newPids = loadIncludes(myCallingDao, myContext, myEntityManager, pidsToScan, includes, false, myParams.getLastUpdated());
myCurrentIterator = newPids.iterator();
}

View File

@ -1,5 +1,24 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.model.api.IQueryParameterOr;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.util.ObjectUtil;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
@ -11,9 +30,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* 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.
@ -21,20 +40,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* limitations under the License.
* #L%
*/
import java.util.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.util.ObjectUtil;
import ca.uhn.fhir.util.UrlUtil;
public class SearchParameterMap extends LinkedHashMap<String, List<List<? extends IQueryParameterType>>> {
@ -48,7 +53,7 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
private Integer myLoadSynchronousUpTo;
private Set<Include> myRevIncludes;
private SortSpec mySort;
/**
* Constructor
*/
@ -130,7 +135,7 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
private void addUrlIncludeParams(StringBuilder b, String paramName, Set<Include> theList) {
ArrayList<Include> list = new ArrayList<Include>(theList);
Collections.sort(list, new IncludeComparator());
for (Include nextInclude : list) {
addUrlParamSeparator(b);
@ -158,10 +163,18 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
return myCount;
}
public void setCount(Integer theCount) {
myCount = theCount;
}
public EverythingModeEnum getEverythingMode() {
return myEverythingMode;
}
public void setEverythingMode(EverythingModeEnum theConsolidateMatches) {
myEverythingMode = theConsolidateMatches;
}
public Set<Include> getIncludes() {
if (myIncludes == null) {
myIncludes = new HashSet<Include>();
@ -169,6 +182,10 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
return myIncludes;
}
public void setIncludes(Set<Include> theIncludes) {
myIncludes = theIncludes;
}
/**
* Returns null if there is no last updated value
*/
@ -181,6 +198,10 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
return myLastUpdated;
}
public void setLastUpdated(DateRangeParam theLastUpdated) {
myLastUpdated = theLastUpdated;
}
/**
* Returns null if there is no last updated value, and removes the lastupdated
* value from this map
@ -199,6 +220,19 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
return myLoadSynchronousUpTo;
}
/**
* If set, tells the server to load these results synchronously, and not to load
* more than X results. Note that setting this to a value will also set
* {@link #setLoadSynchronous(boolean)} to true
*/
public SearchParameterMap setLoadSynchronousUpTo(Integer theLoadSynchronousUpTo) {
myLoadSynchronousUpTo = theLoadSynchronousUpTo;
if (myLoadSynchronousUpTo != null) {
setLoadSynchronous(true);
}
return this;
}
public Set<Include> getRevIncludes() {
if (myRevIncludes == null) {
myRevIncludes = new HashSet<>();
@ -206,10 +240,18 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
return myRevIncludes;
}
public void setRevIncludes(Set<Include> theRevIncludes) {
myRevIncludes = theRevIncludes;
}
public SortSpec getSort() {
return mySort;
}
public void setSort(SortSpec theSort) {
mySort = theSort;
}
/**
* This will only return true if all parameters have no modifier of any kind
*/
@ -234,22 +276,6 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
return myLoadSynchronous;
}
public void setCount(Integer theCount) {
myCount = theCount;
}
public void setEverythingMode(EverythingModeEnum theConsolidateMatches) {
myEverythingMode = theConsolidateMatches;
}
public void setIncludes(Set<Include> theIncludes) {
myIncludes = theIncludes;
}
public void setLastUpdated(DateRangeParam theLastUpdated) {
myLastUpdated = theLastUpdated;
}
/**
* If set, tells the server to load these results synchronously, and not to load
* more than X results
@ -259,27 +285,6 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
return this;
}
/**
* If set, tells the server to load these results synchronously, and not to load
* more than X results. Note that setting this to a value will also set
* {@link #setLoadSynchronous(boolean)} to true
*/
public SearchParameterMap setLoadSynchronousUpTo(Integer theLoadSynchronousUpTo) {
myLoadSynchronousUpTo = theLoadSynchronousUpTo;
if (myLoadSynchronousUpTo != null) {
setLoadSynchronous(true);
}
return this;
}
public void setRevIncludes(Set<Include> theRevIncludes) {
myRevIncludes = theRevIncludes;
}
public void setSort(SortSpec theSort) {
mySort = theSort;
}
public String toNormalizedQueryString(FhirContext theCtx) {
StringBuilder b = new StringBuilder();
@ -298,7 +303,7 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
nextValuesOrsOut.add(nextValueOrIn);
}
}
Collections.sort(nextValuesOrsOut, new QueryParameterTypeComparator(theCtx));
if (nextValuesOrsOut.size() > 0) {
@ -308,7 +313,7 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
} // for AND
Collections.sort(nextValuesAndsOut, new QueryParameterOrComparator(theCtx));
for (List<IQueryParameterType> nextValuesAnd : nextValuesAndsOut) {
addUrlParamSeparator(b);
IQueryParameterType firstValue = nextValuesAnd.get(0);
@ -319,18 +324,18 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
b.append('=');
if (firstValue.getMissing()) {
b.append(Constants.PARAMQUALIFIER_MISSING_TRUE);
}else {
} else {
b.append(Constants.PARAMQUALIFIER_MISSING_FALSE);
}
continue;
}
if (isNotBlank(firstValue.getQueryParameterQualifier())){
if (isNotBlank(firstValue.getQueryParameterQualifier())) {
b.append(firstValue.getQueryParameterQualifier());
}
b.append('=');
for (int i = 0; i < nextValuesAnd.size(); i++) {
IQueryParameterType nextValueOr = nextValuesAnd.get(i);
if (i > 0) {
@ -341,13 +346,13 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
b.append(UrlUtil.escapeUrlParam(valueAsQueryToken));
}
}
} // for keys
SortSpec sort = getSort();
boolean first = true;
while (sort != null) {
if (isNotBlank(sort.getParamName())) {
if (first) {
addUrlParamSeparator(b);
@ -362,32 +367,32 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
}
b.append(sort.getParamName());
}
Validate.isTrue(sort != sort.getChain()); // just in case, shouldn't happen
sort = sort.getChain();
}
addUrlIncludeParams(b, Constants.PARAM_INCLUDE, getIncludes());
addUrlIncludeParams(b, Constants.PARAM_REVINCLUDE, getRevIncludes());
if (getLastUpdated() != null) {
DateParam lb = getLastUpdated().getLowerBound();
addLastUpdateParam(b, lb);
DateParam ub = getLastUpdated().getUpperBound();
addLastUpdateParam(b, ub);
}
if (getCount() != null) {
addUrlParamSeparator(b);
b.append(Constants.PARAM_COUNT);
b.append('=');
b.append(getCount());
}
if (b.length() == 0) {
b.append('?');
}
return b.toString();
}
@ -439,7 +444,10 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
/*
* Don't reorder! We rely on the ordinals
*/
ENCOUNTER_INSTANCE(false, true, true), ENCOUNTER_TYPE(false, true, false), PATIENT_INSTANCE(true, false, true), PATIENT_TYPE(true, false, false);
ENCOUNTER_INSTANCE(false, true, true),
ENCOUNTER_TYPE(false, true, false),
PATIENT_INSTANCE(true, false, true),
PATIENT_TYPE(true, false, false);
private final boolean myEncounter;
@ -447,7 +455,7 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
private final boolean myPatient;
private EverythingModeEnum(boolean thePatient, boolean theEncounter, boolean theInstance) {
EverythingModeEnum(boolean thePatient, boolean theEncounter, boolean theInstance) {
assert thePatient ^ theEncounter;
myPatient = thePatient;
myEncounter = theEncounter;

View File

@ -1,5 +1,13 @@
package ca.uhn.fhir.jpa.entity;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.param.DateRangeParam;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.left;
/*
@ -11,9 +19,9 @@ import static org.apache.commons.lang3.StringUtils.left;
* 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.
@ -22,106 +30,88 @@ import static org.apache.commons.lang3.StringUtils.left;
* #L%
*/
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import org.hibernate.annotations.Fetch;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.param.DateRangeParam;
@Entity
@Table(name = "HFJ_SEARCH", uniqueConstraints= {
@UniqueConstraint(name="IDX_SEARCH_UUID", columnNames="SEARCH_UUID")
}, indexes= {
@Index(name="IDX_SEARCH_LASTRETURNED", columnList="SEARCH_LAST_RETURNED"),
@Index(name="IDX_SEARCH_RESTYPE_HASHS", columnList="RESOURCE_TYPE,SEARCH_QUERY_STRING_HASH,CREATED")
@Table(name = "HFJ_SEARCH", uniqueConstraints = {
@UniqueConstraint(name = "IDX_SEARCH_UUID", columnNames = "SEARCH_UUID")
}, indexes = {
@Index(name = "IDX_SEARCH_LASTRETURNED", columnList = "SEARCH_LAST_RETURNED"),
@Index(name = "IDX_SEARCH_RESTYPE_HASHS", columnList = "RESOURCE_TYPE,SEARCH_QUERY_STRING_HASH,CREATED")
})
public class Search implements Serializable {
private static final int MAX_SEARCH_QUERY_STRING = 10000;
private static final int UUID_COLUMN_LENGTH = 36;
private static final int FAILURE_MESSAGE_LENGTH = 500;
private static final long serialVersionUID = 1L;
public static final int MAX_SEARCH_QUERY_STRING = 10000;
public static final int UUID_COLUMN_LENGTH = 36;
@Temporal(TemporalType.TIMESTAMP)
@Column(name="CREATED", nullable=false, updatable=false)
@Column(name = "CREATED", nullable = false, updatable = false)
private Date myCreated;
@Column(name="FAILURE_CODE", nullable=true)
@Column(name = "FAILURE_CODE", nullable = true)
private Integer myFailureCode;
@Column(name="FAILURE_MESSAGE", length=FAILURE_MESSAGE_LENGTH, nullable=true)
@Column(name = "FAILURE_MESSAGE", length = FAILURE_MESSAGE_LENGTH, nullable = true)
private String myFailureMessage;
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator="SEQ_SEARCH")
@SequenceGenerator(name="SEQ_SEARCH", sequenceName="SEQ_SEARCH")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SEARCH")
@SequenceGenerator(name = "SEQ_SEARCH", sequenceName = "SEQ_SEARCH")
@Column(name = "PID")
private Long myId;
@OneToMany(mappedBy="mySearch")
@OneToMany(mappedBy = "mySearch")
private Collection<SearchInclude> myIncludes;
@Temporal(TemporalType.TIMESTAMP)
@Column(name="LAST_UPDATED_HIGH", nullable=true, insertable=true, updatable=false)
@Column(name = "LAST_UPDATED_HIGH", nullable = true, insertable = true, updatable = false)
private Date myLastUpdatedHigh;
@Temporal(TemporalType.TIMESTAMP)
@Column(name="LAST_UPDATED_LOW", nullable=true, insertable=true, updatable=false)
@Column(name = "LAST_UPDATED_LOW", nullable = true, insertable = true, updatable = false)
private Date myLastUpdatedLow;
@Column(name="NUM_FOUND", nullable=false)
@Column(name = "NUM_FOUND", nullable = false)
private int myNumFound;
@Column(name="PREFERRED_PAGE_SIZE", nullable=true)
@Column(name = "PREFERRED_PAGE_SIZE", nullable = true)
private Integer myPreferredPageSize;
@Column(name="RESOURCE_ID", nullable=true)
@Column(name = "RESOURCE_ID", nullable = true)
private Long myResourceId;
@Column(name="RESOURCE_TYPE", length=200, nullable=true)
@Column(name = "RESOURCE_TYPE", length = 200, nullable = true)
private String myResourceType;
@OneToMany(mappedBy="mySearch")
@OneToMany(mappedBy = "mySearch")
private Collection<SearchResult> myResults;
@NotNull
@Temporal(TemporalType.TIMESTAMP)
@Column(name="SEARCH_LAST_RETURNED", nullable=false, updatable=false)
@Column(name = "SEARCH_LAST_RETURNED", nullable = false, updatable = false)
private Date mySearchLastReturned;
@Lob()
@Basic(fetch=FetchType.LAZY)
@Column(name="SEARCH_QUERY_STRING", nullable=true, updatable=false, length = MAX_SEARCH_QUERY_STRING)
@Basic(fetch = FetchType.LAZY)
@Column(name = "SEARCH_QUERY_STRING", nullable = true, updatable = false, length = MAX_SEARCH_QUERY_STRING)
private String mySearchQueryString;
@Column(name="SEARCH_QUERY_STRING_HASH", nullable=true, updatable=false)
@Column(name = "SEARCH_QUERY_STRING_HASH", nullable = true, updatable = false)
private Integer mySearchQueryStringHash;
@Enumerated(EnumType.ORDINAL)
@Column(name="SEARCH_TYPE", nullable=false)
@Column(name = "SEARCH_TYPE", nullable = false)
private SearchTypeEnum mySearchType;
@Enumerated(EnumType.STRING)
@Column(name="SEARCH_STATUS", nullable=false, length=10)
@Column(name = "SEARCH_STATUS", nullable = false, length = 10)
private SearchStatusEnum myStatus;
@Column(name="TOTAL_COUNT", nullable=true)
@Column(name = "TOTAL_COUNT", nullable = true)
private Integer myTotalCount;
@Column(name="SEARCH_UUID", length= UUID_COLUMN_LENGTH, nullable=false, updatable=false)
@Column(name = "SEARCH_UUID", length = UUID_COLUMN_LENGTH, nullable = false, updatable = false)
private String myUuid;
/**
* Constructor
*/
@ -133,21 +123,33 @@ public class Search implements Serializable {
return myCreated;
}
public void setCreated(Date theCreated) {
myCreated = theCreated;
}
public Integer getFailureCode() {
return myFailureCode;
}
public void setFailureCode(Integer theFailureCode) {
myFailureCode = theFailureCode;
}
public String getFailureMessage() {
return myFailureMessage;
}
public void setFailureMessage(String theFailureMessage) {
myFailureMessage = left(theFailureMessage, FAILURE_MESSAGE_LENGTH);
}
public Long getId() {
return myId;
}
public Collection<SearchInclude> getIncludes() {
if (myIncludes == null) {
myIncludes = new ArrayList<SearchInclude>();
myIncludes = new ArrayList<>();
}
return myIncludes;
}
@ -160,6 +162,16 @@ public class Search implements Serializable {
}
}
public void setLastUpdated(DateRangeParam theLastUpdated) {
if (theLastUpdated == null) {
myLastUpdatedLow = null;
myLastUpdatedHigh = null;
} else {
myLastUpdatedLow = theLastUpdated.getLowerBoundAsInstant();
myLastUpdatedHigh = theLastUpdated.getUpperBoundAsInstant();
}
}
public Date getLastUpdatedHigh() {
return myLastUpdatedHigh;
}
@ -172,90 +184,46 @@ public class Search implements Serializable {
return myNumFound;
}
public void setNumFound(int theNumFound) {
myNumFound = theNumFound;
}
public Integer getPreferredPageSize() {
return myPreferredPageSize;
}
public void setPreferredPageSize(Integer thePreferredPageSize) {
myPreferredPageSize = thePreferredPageSize;
}
public Long getResourceId() {
return myResourceId;
}
public String getResourceType() {
return myResourceType;
}
public Date getSearchLastReturned() {
return mySearchLastReturned;
}
public String getSearchQueryString() {
return mySearchQueryString;
}
public SearchTypeEnum getSearchType() {
return mySearchType;
}
public SearchStatusEnum getStatus() {
return myStatus;
}
public Integer getTotalCount() {
return myTotalCount;
}
public String getUuid() {
return myUuid;
}
public void setCreated(Date theCreated) {
myCreated = theCreated;
}
public void setFailureCode(Integer theFailureCode) {
myFailureCode = theFailureCode;
}
public void setFailureMessage(String theFailureMessage) {
myFailureMessage = left(theFailureMessage, FAILURE_MESSAGE_LENGTH);
}
public void setLastUpdated(Date theLowerBound, Date theUpperBound) {
myLastUpdatedLow = theLowerBound;
myLastUpdatedHigh = theUpperBound;
}
public void setLastUpdated(DateRangeParam theLastUpdated) {
if (theLastUpdated == null) {
myLastUpdatedLow = null;
myLastUpdatedHigh = null;
} else {
myLastUpdatedLow = theLastUpdated.getLowerBoundAsInstant();
myLastUpdatedHigh = theLastUpdated.getUpperBoundAsInstant();
}
}
public void setNumFound(int theNumFound) {
myNumFound = theNumFound;
}
public void setPreferredPageSize(Integer thePreferredPageSize) {
myPreferredPageSize = thePreferredPageSize;
}
public void setResourceId(Long theResourceId) {
myResourceId = theResourceId;
}
public String getResourceType() {
return myResourceType;
}
public void setResourceType(String theResourceType) {
myResourceType = theResourceType;
}
public Date getSearchLastReturned() {
return mySearchLastReturned;
}
public void setSearchLastReturned(Date theDate) {
mySearchLastReturned = theDate;
}
public String getSearchQueryString() {
return mySearchQueryString;
}
public void setSearchQueryString(String theSearchQueryString) {
if (theSearchQueryString != null && theSearchQueryString.length() > MAX_SEARCH_QUERY_STRING) {
mySearchQueryString = null;
@ -264,26 +232,47 @@ public class Search implements Serializable {
}
}
public void setSearchQueryStringHash(Integer theSearchQueryStringHash) {
mySearchQueryStringHash = theSearchQueryStringHash;
public SearchTypeEnum getSearchType() {
return mySearchType;
}
public void setSearchType(SearchTypeEnum theSearchType) {
mySearchType = theSearchType;
}
public SearchStatusEnum getStatus() {
return myStatus;
}
public void setStatus(SearchStatusEnum theStatus) {
myStatus = theStatus;
}
public Integer getTotalCount() {
return myTotalCount;
}
public void setTotalCount(Integer theTotalCount) {
myTotalCount = theTotalCount;
}
public String getUuid() {
return myUuid;
}
public void setUuid(String theUuid) {
myUuid = theUuid;
}
public void setLastUpdated(Date theLowerBound, Date theUpperBound) {
myLastUpdatedLow = theLowerBound;
myLastUpdatedHigh = theUpperBound;
}
public void setSearchQueryStringHash(Integer theSearchQueryStringHash) {
mySearchQueryStringHash = theSearchQueryStringHash;
}
private Set<Include> toIncList(boolean theWantReverse) {
HashSet<Include> retVal = new HashSet<Include>();
for (SearchInclude next : getIncludes()) {
@ -301,5 +290,8 @@ public class Search implements Serializable {
public Set<Include> toRevIncludesList() {
return toIncList(true);
}
public void addInclude(SearchInclude theInclude) {
getIncludes().add(theInclude);
}
}

View File

@ -32,6 +32,7 @@ import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
@ -134,13 +135,8 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
final List<Long> pidsSubList = mySearchCoordinatorSvc.getResources(myUuid, theFromIndex, theToIndex);
TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager);
template.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
return template.execute(new TransactionCallback<List<IBaseResource>>() {
@Override
public List<IBaseResource> doInTransaction(TransactionStatus theStatus) {
return toResourceList(sb, pidsSubList);
}
});
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
return template.execute(theStatus -> toResourceList(sb, pidsSubList));
}
private void ensureDependenciesInjected() {
@ -273,8 +269,8 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
List<IBaseResource> toResourceList(ISearchBuilder sb, List<Long> pidsSubList) {
Set<Long> includedPids = new HashSet<>();
if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) {
includedPids.addAll(sb.loadReverseIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated()));
includedPids.addAll(sb.loadReverseIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated()));
includedPids.addAll(sb.loadIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated()));
includedPids.addAll(sb.loadIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated()));
}
// Execute the query and make sure we return distinct results

View File

@ -258,8 +258,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
* individually for pages as we return them to clients
*/
final Set<Long> includedPids = new HashSet<Long>();
includedPids.addAll(sb.loadReverseIncludes(theCallingDao, myContext, myEntityManager, pids, theParams.getRevIncludes(), true, theParams.getLastUpdated()));
includedPids.addAll(sb.loadReverseIncludes(theCallingDao, myContext, myEntityManager, pids, theParams.getIncludes(), false, theParams.getLastUpdated()));
includedPids.addAll(sb.loadIncludes(theCallingDao, myContext, myEntityManager, pids, theParams.getRevIncludes(), true, theParams.getLastUpdated()));
includedPids.addAll(sb.loadIncludes(theCallingDao, myContext, myEntityManager, pids, theParams.getIncludes(), false, theParams.getLastUpdated()));
List<IBaseResource> resources = new ArrayList<IBaseResource>();
sb.loadResourcesByPid(pids, resources, includedPids, false, myEntityManager, myContext, theCallingDao);
@ -336,10 +336,10 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
search.setSearchQueryStringHash(queryString.hashCode());
for (Include next : theParams.getIncludes()) {
search.getIncludes().add(new SearchInclude(search, next.getValue(), false, next.isRecurse()));
search.addInclude(new SearchInclude(search, next.getValue(), false, next.isRecurse()));
}
for (Include next : theParams.getRevIncludes()) {
search.getIncludes().add(new SearchInclude(search, next.getValue(), true, next.isRecurse()));
search.addInclude(new SearchInclude(search, next.getValue(), true, next.isRecurse()));
}
SearchTask task = new SearchTask(search, theCallingDao, theParams, theResourceType, searchUuid);

View File

@ -47,7 +47,7 @@ import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "Duplicates"})
public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SearchNoFtTest.class);
@ -145,6 +145,124 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
assertThat(ids, empty());
}
/**
* See #1053
*/
@Test
public void testLastUpdateShouldntApplyToIncludes() {
SearchParameterMap map;
List<String> ids;
Date beforeAll = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
Organization org = new Organization();
org.setName("O1");
org.setId("O1");
myOrganizationDao.update(org);
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
Date beforePatient = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
Patient p = new Patient();
p.setId("P1");
p.setActive(true);
p.setManagingOrganization(new Reference("Organization/O1"));
myPatientDao.update(p);
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
Date afterAll = new Date();
// Search with between date (should still return Organization even though
// it was created before that date, since it's an include)
map = new SearchParameterMap();
map.setLastUpdated(new DateRangeParam().setLowerBoundInclusive(beforePatient));
map.addInclude(Patient.INCLUDE_ORGANIZATION);
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(ids, contains("Patient/P1", "Organization/O1"));
// Search before everything
map = new SearchParameterMap();
map.setLastUpdated(new DateRangeParam().setLowerBoundInclusive(beforeAll));
map.addInclude(Patient.INCLUDE_ORGANIZATION);
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(ids, contains("Patient/P1", "Organization/O1"));
// Search after everything
map = new SearchParameterMap();
map.setLastUpdated(new DateRangeParam().setLowerBoundInclusive(afterAll));
map.addInclude(Patient.INCLUDE_ORGANIZATION);
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(ids, empty());
}
/**
* See #1053
*
* Note that I don't know that _lastUpdate actually should apply to reverse includes. The
* spec doesn't say one way or ther other, but it seems like sensible behaviour to me.
*
* Definitely the $everything operation depends on this behaviour, so if we change it
* we need to account for the everything operation...
*/
@Test
public void testLastUpdateShouldApplyToReverseIncludes() {
SearchParameterMap map;
List<String> ids;
// This gets updated in a sec..
Organization org = new Organization();
org.setActive(false);
org.setId("O1");
myOrganizationDao.update(org);
Date beforeAll = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
Patient p = new Patient();
p.setId("P1");
p.setActive(true);
p.setManagingOrganization(new Reference("Organization/O1"));
myPatientDao.update(p);
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
Date beforeOrg = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
org = new Organization();
org.setActive(true);
org.setId("O1");
myOrganizationDao.update(org);
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
Date afterAll = new Date();
// Everything should come back
map = new SearchParameterMap();
map.setLastUpdated(new DateRangeParam().setLowerBoundInclusive(beforeAll));
map.addRevInclude(Patient.INCLUDE_ORGANIZATION);
ids = toUnqualifiedVersionlessIdValues(myOrganizationDao.search(map));
assertThat(ids, contains("Organization/O1", "Patient/P1"));
// Search before everything
map = new SearchParameterMap();
map.setLastUpdated(new DateRangeParam().setLowerBoundInclusive(beforeOrg));
map.addInclude(Patient.INCLUDE_ORGANIZATION);
ids = toUnqualifiedVersionlessIdValues(myOrganizationDao.search(map));
assertThat(ids, contains("Organization/O1"));
// Search after everything
map = new SearchParameterMap();
map.setLastUpdated(new DateRangeParam().setLowerBoundInclusive(afterAll));
map.addInclude(Patient.INCLUDE_ORGANIZATION);
ids = toUnqualifiedVersionlessIdValues(myOrganizationDao.search(map));
assertThat(ids, empty());
}
@Test
public void testEverythingTimings() {
String methodName = "testEverythingTimings";

View File

@ -1503,7 +1503,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
ourLog.info(output);
List<IIdType> ids = toUnqualifiedVersionlessIds(myFhirCtx.newXmlParser().parseResource(Bundle.class, output));
ourLog.info(ids.toString());
assertThat(ids, containsInAnyOrder(pId, cId));
assertThat(ids, containsInAnyOrder(pId, cId, oId));
} finally {
response.close();
}
@ -1518,7 +1518,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
ourLog.info(output);
List<IIdType> ids = toUnqualifiedVersionlessIds(myFhirCtx.newXmlParser().parseResource(Bundle.class, output));
ourLog.info(ids.toString());
assertThat(ids, containsInAnyOrder(pId, cId));
assertThat(ids, containsInAnyOrder(pId, cId, oId));
} finally {
response.close();
}

View File

@ -1,31 +1,5 @@
package ca.uhn.fhir.rest.param;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.APPROXIMATE;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.EQUAL;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.GREATERTHAN;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.GREATERTHAN_OR_EQUALS;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.LESSTHAN;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.LESSTHAN_OR_EQUALS;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.NOT_EQUAL;
import static com.google.common.collect.Lists.newArrayList;
import static java.lang.System.currentTimeMillis;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import com.google.common.testing.EqualsTester;
import org.junit.AfterClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.primitive.DateTimeDt;
@ -33,18 +7,57 @@ import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.api.QualifiedParamList;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.testing.EqualsTester;
import org.junit.AfterClass;
import org.junit.Test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.*;
import static java.lang.System.currentTimeMillis;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class DateRangeParamTest {
private static FhirContext ourCtx = FhirContext.forDstu3();
private static final SimpleDateFormat ourFmt;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DateRangeParamTest.class);
private static FhirContext ourCtx = FhirContext.forDstu3();
static {
ourFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSS");
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
private static DateRangeParam create(String theLower, String theUpper) throws InvalidRequestException {
DateRangeParam p = new DateRangeParam();
List<QualifiedParamList> tokens = new ArrayList<QualifiedParamList>();
tokens.add(QualifiedParamList.singleton(null, theLower));
if (theUpper != null) {
tokens.add(QualifiedParamList.singleton(null, theUpper));
}
p.setValuesAsQueryTokens(ourCtx, null, tokens);
return p;
}
public static Date parse(String theString) throws ParseException {
return ourFmt.parse(theString);
}
public static Date parseM1(String theString) throws ParseException {
return new Date(ourFmt.parse(theString).getTime() - 1L);
}
private DateRangeParam create(String theString) {
return new DateRangeParam(new DateParam(theString));
}
@ -85,7 +98,7 @@ public class DateRangeParamTest {
assertEquals(parseM1("2011-01-02 00:00:00.0000"), create(">=2011-01-01", "<2011-01-02").getUpperBoundAsInstant());
assertEquals(parse("2011-01-02 00:00:00.0000"), create(">2011-01-01", "<=2011-01-02").getLowerBoundAsInstant());
assertEquals(parseM1("2011-01-03 00:00:00.0000"), create(">2011-01-01", "<=2011-01-02").getUpperBoundAsInstant());
assertEquals(parse("2011-01-01 00:00:00.0000"), create("ge2011-01-01", "lt2011-01-02").getLowerBoundAsInstant());
assertEquals(parseM1("2011-01-02 00:00:00.0000"), create("ge2011-01-01", "lt2011-01-02").getUpperBoundAsInstant());
assertEquals(parse("2011-01-02 00:00:00.0000"), create("gt2011-01-01", "le2011-01-02").getLowerBoundAsInstant());
@ -119,6 +132,26 @@ public class DateRangeParamTest {
assertEquals(parseM1("2011-01-02 00:00:00.0000"), create("2011-01-01").getUpperBoundAsInstant());
}
@Test
public void testSetBoundsWithDatesInclusive() {
DateRangeParam range = new DateRangeParam();
range.setLowerBoundInclusive(new Date());
range.setUpperBoundInclusive(new Date());
assertEquals(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, range.getLowerBound().getPrefix());
assertEquals(ParamPrefixEnum.LESSTHAN_OR_EQUALS, range.getUpperBound().getPrefix());
}
@Test
public void testSetBoundsWithDatesExclusive() {
DateRangeParam range = new DateRangeParam();
range.setLowerBoundExclusive(new Date());
range.setUpperBoundExclusive(new Date());
assertEquals(ParamPrefixEnum.GREATERTHAN, range.getLowerBound().getPrefix());
assertEquals(ParamPrefixEnum.LESSTHAN, range.getUpperBound().getPrefix());
}
@Test
public void testOrList() {
assertNotNull(new DateOrListParam().newInstance());
@ -195,46 +228,22 @@ public class DateRangeParamTest {
assertEquals(parseM1("2014-01-01 00:00:00.0000"), create("gt2011", "le2013").getUpperBoundAsInstant());
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Test()
public void testEqualsAndHashCode() {
Date lowerBound = new Date(currentTimeMillis());
Date upperBound = new Date(lowerBound.getTime() + SECONDS.toMillis(1));
new EqualsTester()
.addEqualityGroup(new DateRangeParam(),
new DateRangeParam((Date) null, (Date) null))
new DateRangeParam((Date) null, (Date) null))
.addEqualityGroup(new DateRangeParam(lowerBound, upperBound),
new DateRangeParam(new DateParam(GREATERTHAN_OR_EQUALS, lowerBound), new DateParam(LESSTHAN_OR_EQUALS, upperBound)))
new DateRangeParam(new DateParam(GREATERTHAN_OR_EQUALS, lowerBound), new DateParam(LESSTHAN_OR_EQUALS, upperBound)))
.addEqualityGroup(new DateRangeParam(new DateParam(EQUAL, lowerBound)),
new DateRangeParam(new DateParam(null, lowerBound)),
new DateRangeParam(new DateParam(EQUAL, lowerBound), new DateParam(EQUAL, lowerBound)))
new DateRangeParam(new DateParam(null, lowerBound)),
new DateRangeParam(new DateParam(EQUAL, lowerBound), new DateParam(EQUAL, lowerBound)))
.addEqualityGroup(new DateRangeParam(lowerBound, null),
new DateRangeParam(new DateParam(GREATERTHAN_OR_EQUALS, lowerBound), null))
new DateRangeParam(new DateParam(GREATERTHAN_OR_EQUALS, lowerBound), null))
.addEqualityGroup(new DateRangeParam(null, upperBound),
new DateRangeParam(null, new DateParam(LESSTHAN_OR_EQUALS, upperBound)))
new DateRangeParam(null, new DateParam(LESSTHAN_OR_EQUALS, upperBound)))
.testEquals();
}
private static DateRangeParam create(String theLower, String theUpper) throws InvalidRequestException {
DateRangeParam p = new DateRangeParam();
List<QualifiedParamList> tokens = new ArrayList<QualifiedParamList>();
tokens.add(QualifiedParamList.singleton(null, theLower));
if (theUpper != null) {
tokens.add(QualifiedParamList.singleton(null, theUpper));
}
p.setValuesAsQueryTokens(ourCtx, null, tokens);
return p;
}
public static Date parse(String theString) throws ParseException {
return ourFmt.parse(theString);
}
public static Date parseM1(String theString) throws ParseException {
return new Date(ourFmt.parse(theString).getTime() - 1L);
}
}

View File

@ -249,6 +249,10 @@
queue (only the ID), which should reduce the memory/disk footprint of the queue
when it grows long.
</action>
<action type="fix" issue="1053">
A bug was fixed in JPA server searches: When performing a search with a _lastUpdate
filter, the filter was applied to any _include values, which it should not have been.
Thanks to Deepak Garg for reporting!
<action type="add">
When performing a ConceptMap/$translate operation with reverse="true" in the arguments,
the equivalency flag is now set on the response just as it is for a non-reverse lookup.