Page JPA search results to the database

This commit is contained in:
James Agnew 2016-03-07 07:23:44 -05:00
parent 8de41ca8b4
commit 3bfcb57783
35 changed files with 1258 additions and 489 deletions

View File

@ -38,6 +38,8 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvc;
import ca.uhn.fhir.jpa.term.ITerminologySvc;
import ca.uhn.fhir.jpa.term.TerminologySvcImpl;
@ -62,6 +64,11 @@ public class BaseConfig implements SchedulingConfigurer {
theTaskRegistrar.setTaskScheduler(taskScheduler());
}
@Bean(autowire = Autowire.BY_TYPE)
public DatabaseBackedPagingProvider databaseBackedPagingProvider() {
return new DatabaseBackedPagingProvider(10);
}
@Bean(name = "myFhirContextDstu1")
@Lazy
public FhirContext fhirContextDstu1() {
@ -99,8 +106,8 @@ public class BaseConfig implements SchedulingConfigurer {
}
@Bean(autowire=Autowire.BY_TYPE)
public ITerminologySvc terminologyService() {
return new TerminologySvcImpl();
public StaleSearchDeletingSvc staleSearchDeletingSvc() {
return new StaleSearchDeletingSvc();
}
@Bean
@ -110,12 +117,9 @@ public class BaseConfig implements SchedulingConfigurer {
return retVal;
}
/**
* This lets the "@Value" fields reference properties from the properties file
*/
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
@Bean(autowire = Autowire.BY_TYPE)
public ITerminologySvc terminologyService() {
return new TerminologySvcImpl();
}
// @PostConstruct
@ -127,4 +131,12 @@ public class BaseConfig implements SchedulingConfigurer {
// }
// }
/**
* This lets the "@Value" fields reference properties from the properties file
*/
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}

View File

@ -27,9 +27,9 @@ import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.FhirSearchDao;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.ISearchDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
@Configuration
@ -42,6 +42,18 @@ public class BaseDstu2Config extends BaseConfig {
return fhirContextDstu2();
}
@Bean(name = "myJpaValidationSupportDstu2", autowire = Autowire.BY_NAME)
public ca.uhn.fhir.jpa.dao.IJpaValidationSupportDstu2 jpaValidationSupportDstu2() {
ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2 retVal = new ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2();
return retVal;
}
@Bean(autowire = Autowire.BY_TYPE)
public IFulltextSearchSvc searchDao() {
FulltextSearchSvcImpl searchDao = new FulltextSearchSvcImpl();
return searchDao;
}
@Bean(name = "mySystemDaoDstu2", autowire = Autowire.BY_NAME)
public IFhirSystemDao<ca.uhn.fhir.model.dstu2.resource.Bundle, MetaDt> systemDaoDstu2() {
ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu2 retVal = new ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu2();
@ -54,17 +66,4 @@ public class BaseDstu2Config extends BaseConfig {
retVal.setDao(systemDaoDstu2());
return retVal;
}
@Bean(name = "myJpaValidationSupportDstu2", autowire = Autowire.BY_NAME)
public ca.uhn.fhir.jpa.dao.IJpaValidationSupportDstu2 jpaValidationSupportDstu2() {
ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2 retVal = new ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2();
return retVal;
}
@Bean(autowire = Autowire.BY_TYPE)
public ISearchDao searchDao() {
FhirSearchDao searchDao = new FhirSearchDao();
return searchDao;
}
}

View File

@ -34,9 +34,9 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.dao.FhirSearchDao;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.ISearchDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
import ca.uhn.fhir.validation.IValidatorModule;
@ -50,6 +50,35 @@ public class BaseDstu3Config extends BaseConfig {
return fhirContextDstu3();
}
@Bean(name="myInstanceValidatorDstu3")
@Lazy
public IValidatorModule instanceValidatorDstu3() {
FhirInstanceValidator val = new FhirInstanceValidator();
val.setBestPracticeWarningLevel(BestPracticeWarningLevel.Warning);
val.setValidationSupport(validationSupportChainDstu3());
return val;
}
@Bean(name = "myJpaValidationSupportDstu3", autowire = Autowire.BY_NAME)
public ca.uhn.fhir.jpa.dao.dstu3.IJpaValidationSupportDstu3 jpaValidationSupportDstu3() {
ca.uhn.fhir.jpa.dao.dstu3.JpaValidationSupportDstu3 retVal = new ca.uhn.fhir.jpa.dao.dstu3.JpaValidationSupportDstu3();
return retVal;
}
@Bean(name="myQuestionnaireResponseValidatorDstu3")
@Lazy
public IValidatorModule questionnaireResponseValidatorDstu3() {
FhirQuestionnaireResponseValidator module = new FhirQuestionnaireResponseValidator();
module.setValidationSupport(validationSupportChainDstu3());
return module;
}
@Bean(autowire = Autowire.BY_TYPE)
public IFulltextSearchSvc searchDaoDstu3() {
FulltextSearchSvcImpl searchDao = new FulltextSearchSvcImpl();
return searchDao;
}
@Bean(name = "mySystemDaoDstu3", autowire = Autowire.BY_NAME)
public IFhirSystemDao<org.hl7.fhir.dstu3.model.Bundle, org.hl7.fhir.dstu3.model.Meta> systemDaoDstu3() {
ca.uhn.fhir.jpa.dao.dstu3.FhirSystemDaoDstu3 retVal = new ca.uhn.fhir.jpa.dao.dstu3.FhirSystemDaoDstu3();
@ -63,35 +92,6 @@ public class BaseDstu3Config extends BaseConfig {
return retVal;
}
@Bean(name = "myJpaValidationSupportDstu3", autowire = Autowire.BY_NAME)
public ca.uhn.fhir.jpa.dao.dstu3.IJpaValidationSupportDstu3 jpaValidationSupportDstu3() {
ca.uhn.fhir.jpa.dao.dstu3.JpaValidationSupportDstu3 retVal = new ca.uhn.fhir.jpa.dao.dstu3.JpaValidationSupportDstu3();
return retVal;
}
@Bean(autowire = Autowire.BY_TYPE)
public ISearchDao searchDaoDstu3() {
FhirSearchDao searchDao = new FhirSearchDao();
return searchDao;
}
@Bean(name="myInstanceValidatorDstu3")
@Lazy
public IValidatorModule instanceValidatorDstu3() {
FhirInstanceValidator val = new FhirInstanceValidator();
val.setBestPracticeWarningLevel(BestPracticeWarningLevel.Warning);
val.setValidationSupport(validationSupportChainDstu3());
return val;
}
@Bean(name="myQuestionnaireResponseValidatorDstu3")
@Lazy
public IValidatorModule questionnaireResponseValidatorDstu3() {
FhirQuestionnaireResponseValidator module = new FhirQuestionnaireResponseValidator();
module.setValidationSupport(validationSupportChainDstu3());
return module;
}
@Bean(autowire=Autowire.BY_NAME, name="myJpaValidationSupportChainDstu3")
public IValidationSupport validationSupportChainDstu3() {
return new JpaValidationSupportChainDstu3();

View File

@ -518,7 +518,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
}
@SuppressWarnings("unchecked")
protected <R extends IBaseResource> IFhirResourceDao<R> getDao(Class<R> theType) {
public <R extends IBaseResource> IFhirResourceDao<R> getDao(Class<R> theType) {
if (myResourceTypeToDao == null) {
myResourceTypeToDao = new HashMap<Class<? extends IBaseResource>, IFhirResourceDao<?>>();
for (IFhirResourceDao<?> next : myResourceDaos) {
@ -959,6 +959,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType);
SearchParameterMap paramMap = translateMatchUrl(theMatchUrl, resourceDef);
paramMap.setPersistResults(false);
if (paramMap.isEmpty()) {
throw new InvalidRequestException("Invalid match URL[" + theMatchUrl + "] - URL has no search parameters");

View File

@ -105,7 +105,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
private Class<T> myResourceType;
@Autowired(required = false)
protected ISearchDao mySearchDao;
protected IFulltextSearchSvc mySearchDao;
@Autowired()
protected ISearchResultDao mySearchResultDao;
@Autowired()

View File

@ -40,6 +40,11 @@ public class DaoConfig {
private boolean myAllowInlineMatchUrlReferences = false;
private boolean myAllowMultipleDelete;
// ***
// update setter javadoc if default changes
// ***
private long myExpireSearchResultsAfterMillis = DateUtils.MILLIS_PER_HOUR;
private int myHardSearchLimit = 1000;
private int myHardTagListLimit = 1000;
private int myIncludeLimit = 2000;
@ -50,11 +55,22 @@ public class DaoConfig {
private boolean myIndexContainedResources = true;
private List<IServerInterceptor> myInterceptors;
private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC;
private boolean mySchedulingDisabled;
private boolean mySubscriptionEnabled;
private long mySubscriptionPollDelay = 1000;
private Long mySubscriptionPurgeInactiveAfterMillis;
/**
* Search results are stored in the database so that they can be paged through. After this
* number of milliseconds, they will be deleted from the database. Defaults to 1 hour.
*
* @since 1.5
*/
public long getExpireSearchResultsAfterMillis() {
return myExpireSearchResultsAfterMillis;
}
/**
* See {@link #setIncludeLimit(int)}
*/
@ -78,7 +94,6 @@ public class DaoConfig {
}
return myInterceptors;
}
public ResourceEncodingEnum getResourceEncoding() {
return myResourceEncoding;
}
@ -139,6 +154,16 @@ public class DaoConfig {
myAllowMultipleDelete = theAllowMultipleDelete;
}
/**
* Search results are stored in the database so that they can be paged through. After this
* number of milliseconds, they will be deleted from the database. Defaults to 1 hour.
*
* @since 1.5
*/
public void setExpireSearchResultsAfterMillis(long theExpireSearchResultsAfterMillis) {
myExpireSearchResultsAfterMillis = theExpireSearchResultsAfterMillis;
}
public void setHardSearchLimit(int theHardSearchLimit) {
myHardSearchLimit = theHardSearchLimit;
}

View File

@ -61,8 +61,8 @@ import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class FhirSearchDao extends BaseHapiFhirDao<IBaseResource> implements ISearchDao {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSearchDao.class);
public class FulltextSearchSvcImpl extends BaseHapiFhirDao<IBaseResource> implements IFulltextSearchSvc {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FulltextSearchSvcImpl.class);
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
private EntityManager myEntityManager;

View File

@ -23,6 +23,8 @@ package ca.uhn.fhir.jpa.dao;
import java.util.Date;
import java.util.Map;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.IBundleProvider;
@ -41,6 +43,8 @@ public interface IFhirSystemDao<T, MT> extends IDao {
TagList getAllTags(RequestDetails theRequestDetails);
public <R extends IBaseResource> IFhirResourceDao<R> getDao(Class<R> theType);
Map<String, Long> getResourceCounts();
IBundleProvider history(Date theDate, RequestDetails theRequestDetails);

View File

@ -22,9 +22,9 @@ package ca.uhn.fhir.jpa.dao;
import java.util.List;
import ca.uhn.fhir.jpa.dao.FhirSearchDao.Suggestion;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
public interface ISearchDao {
public interface IFulltextSearchSvc {
List<Suggestion> suggestKeywords(String theContext, String theSearchParam, String theText);

View File

@ -42,6 +42,7 @@ import java.util.Set;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
@ -61,6 +62,9 @@ import org.apache.commons.lang3.StringUtils;
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 org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
@ -87,6 +91,8 @@ 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.Search;
import ca.uhn.fhir.jpa.entity.SearchInclude;
import ca.uhn.fhir.jpa.entity.SearchResult;
import ca.uhn.fhir.jpa.entity.TagDefinition;
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.util.StopWatch;
@ -129,20 +135,18 @@ public class SearchBuilder {
private BaseHapiFhirDao<?> myCallingDao;
private FhirContext myContext;
private EntityManager myEntityManager;
private SearchParameterMap myParams;
private Collection<Long> myPids;
private PlatformTransactionManager myPlatformTransactionManager;
private IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
private String myResourceName;
private Class<? extends IBaseResource> myResourceType;
private ISearchDao mySearchDao;
private ISearchResultDao mySearchResultDao;
private IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
private IFulltextSearchSvc mySearchDao;
private Search mySearchEntity;
private ISearchResultDao mySearchResultDao;
private boolean myHaveFlushedSearch;
private SearchParameterMap myParams;
public SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, PlatformTransactionManager thePlatformTransactionManager, ISearchDao theSearchDao, ISearchResultDao theSearchResultDao, BaseHapiFhirDao theDao, IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao) {
public SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, PlatformTransactionManager thePlatformTransactionManager, IFulltextSearchSvc theSearchDao,
ISearchResultDao theSearchResultDao, BaseHapiFhirDao theDao, IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao) {
myContext = theFhirContext;
myEntityManager = theEntityManager;
myPlatformTransactionManager = thePlatformTransactionManager;
@ -177,11 +181,11 @@ public class SearchBuilder {
IQueryParameterType rightValue = cp.getRightValue();
predicates.add(createCompositeParamPart(builder, from, right, rightValue));
doCreateIdPredicate(predicates, from.get("myId").as(Long.class));
doCreateIdPredicate(builder, cq, predicates, from.get("myId").as(Long.class));
cq.where(builder.and(toArray(predicates)));
TypedQuery<Long> q = myEntityManager.createQuery(cq);
doSetPids(new HashSet<Long>(q.getResultList()));
doSetPids(q.getResultList());
}
@ -213,13 +217,13 @@ public class SearchBuilder {
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.equal(from.get("myParamName"), theParamName));
doCreateIdPredicate(predicates, from.get("myResourcePid").as(Long.class));
doCreateIdPredicate(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
predicates.add(masterCodePredicate);
cq.where(builder.and(toArray(predicates)));
TypedQuery<Long> q = myEntityManager.createQuery(cq);
doSetPids(new HashSet<Long>(q.getResultList()));
doSetPids(q.getResultList());
}
private void addPredicateId(Set<Long> thePids, DateRangeParam theLastUpdated) {
@ -235,20 +239,13 @@ public class SearchBuilder {
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(from.get("myId").in(thePids));
doCreateIdPredicate(predicates, from.get("myId").as(Long.class));
doCreateIdPredicate(builder, cq, predicates, from.get("myId").as(Long.class));
predicates.addAll(createLastUpdatedPredicates(theLastUpdated, builder, from));
cq.where(toArray(predicates));
TypedQuery<Long> q = myEntityManager.createQuery(cq);
HashSet<Long> found = new HashSet<Long>(q.getResultList());
doSetPids(found);
}
private void doCreateIdPredicate(List<Predicate> thePredicates, Expression<Long> theExpression) {
if (myPids != null) {
thePredicates.add(theExpression.in(myPids));
}
doSetPids(q.getResultList());
}
private void addPredicateLanguage(List<List<? extends IQueryParameterType>> theList, DateRangeParam theLastUpdated) {
@ -279,15 +276,14 @@ public class SearchBuilder {
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(from.get("myLanguage").as(String.class).in(values));
doCreateIdPredicate(predicates, from.get("myId").as(Long.class));
doCreateIdPredicate(builder, cq, predicates, from.get("myId").as(Long.class));
predicates.add(builder.isNull(from.get("myDeleted")));
cq.where(toArray(predicates));
TypedQuery<Long> q = myEntityManager.createQuery(cq);
HashSet<Long> pids = new HashSet<Long>(q.getResultList());
doSetPids(pids);
doSetPids(q.getResultList());
if (doHaveNoResults()) {
return;
}
@ -296,7 +292,8 @@ public class SearchBuilder {
return;
}
private boolean addPredicateMissingFalseIfPresent(CriteriaBuilder theBuilder, String theParamName, Root<? extends BaseResourceIndexedSearchParam> from, List<Predicate> codePredicates, IQueryParameterType nextOr) {
private boolean addPredicateMissingFalseIfPresent(CriteriaBuilder theBuilder, String theParamName, Root<? extends BaseResourceIndexedSearchParam> from, List<Predicate> codePredicates,
IQueryParameterType nextOr) {
boolean missingFalse = false;
if (nextOr.getMissing() != null) {
if (nextOr.getMissing().booleanValue() == true) {
@ -310,7 +307,8 @@ public class SearchBuilder {
return missingFalse;
}
private boolean addPredicateMissingFalseIfPresentForResourceLink(CriteriaBuilder theBuilder, String theParamName, Root<? extends ResourceLink> from, List<Predicate> codePredicates, IQueryParameterType nextOr) {
private boolean addPredicateMissingFalseIfPresentForResourceLink(CriteriaBuilder theBuilder, String theParamName, Root<? extends ResourceLink> from, List<Predicate> codePredicates,
IQueryParameterType nextOr) {
boolean missingFalse = false;
if (nextOr.getMissing() != null) {
if (nextOr.getMissing().booleanValue() == true) {
@ -370,12 +368,12 @@ public class SearchBuilder {
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.equal(from.get("myParamName"), theParamName));
predicates.add(builder.or(toArray(codePredicates)));
doCreateIdPredicate(predicates, from.get("myResourcePid").as(Long.class));
doCreateIdPredicate(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
cq.where(builder.and(toArray(predicates)));
TypedQuery<Long> q = myEntityManager.createQuery(cq);
doSetPids(new HashSet<Long>(q.getResultList()));
doSetPids(q.getResultList());
}
private void addPredicateParamMissing(String joinName, String theParamName, Class<? extends BaseResourceIndexedSearchParam> theParamTable) {
@ -395,17 +393,14 @@ public class SearchBuilder {
predicates.add(builder.not(builder.in(from.get("myId")).value(subQ)));
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.isNull(from.get("myDeleted")));
doCreateIdPredicate(predicates, from.get("myId").as(Long.class));
doCreateIdPredicate(builder, cq, predicates, from.get("myId").as(Long.class));
cq.where(builder.and(toArray(predicates)));
ourLog.info("Adding :missing qualifier for parameter '{}'", theParamName);
TypedQuery<Long> q = myEntityManager.createQuery(cq);
List<Long> resultList = q.getResultList();
HashSet<Long> retVal = new HashSet<Long>(resultList);
doSetPids(retVal);
doSetPids(q.getResultList());
}
private void addPredicateParamMissingResourceLink(String joinName, String theParamName) {
@ -423,7 +418,7 @@ public class SearchBuilder {
subQ.where(path);
List<Predicate> predicates = new ArrayList<Predicate>();
doCreateIdPredicate(predicates, from.get("myId").as(Long.class));
doCreateIdPredicate(builder, cq, predicates, from.get("myId").as(Long.class));
predicates.add(builder.not(builder.in(from.get("myId")).value(subQ)));
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
@ -511,7 +506,7 @@ public class SearchBuilder {
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.equal(from.get("myParamName"), theParamName));
predicates.add(builder.or(toArray(codePredicates)));
doCreateIdPredicate(predicates, from.get("myResourcePid").as(Long.class));
doCreateIdPredicate(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
cq.where(builder.and(toArray(predicates)));
@ -657,7 +652,7 @@ public class SearchBuilder {
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(createResourceLinkPathPredicate(theParamName, builder, from));
predicates.add(builder.or(toArray(codePredicates)));
doCreateIdPredicate(predicates, from.get("mySourceResourcePid").as(Long.class));
doCreateIdPredicate(builder, cq, predicates, from.get("mySourceResourcePid").as(Long.class));
cq.where(builder.and(toArray(predicates)));
@ -691,7 +686,7 @@ public class SearchBuilder {
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.equal(from.get("myParamName"), theParamName));
predicates.add(builder.or(toArray(codePredicates)));
doCreateIdPredicate(predicates, from.get("myResourcePid").as(Long.class));
doCreateIdPredicate(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
cq.where(builder.and(toArray(predicates)));
@ -778,7 +773,7 @@ public class SearchBuilder {
andPredicates.addAll(createLastUpdatedPredicates(theLastUpdated, builder, defJoin));
}
doCreateIdPredicate(andPredicates, from.get("myResourceId").as(Long.class));
doCreateIdPredicate(builder, cq, andPredicates, from.get("myResourceId").as(Long.class));
Predicate masterCodePredicate = builder.and(toArray(andPredicates));
cq.where(masterCodePredicate);
@ -828,7 +823,7 @@ public class SearchBuilder {
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.equal(from.get("myParamName"), theParamName));
predicates.add(builder.or(toArray(codePredicates)));
doCreateIdPredicate(predicates, from.get("myResourcePid").as(Long.class));
doCreateIdPredicate(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
cq.where(builder.and(toArray(predicates)));
@ -868,17 +863,12 @@ public class SearchBuilder {
if (param.getQualifier() == UriParamQualifierEnum.ABOVE) {
/*
* :above is an inefficient query- It means that the user is supplying a more specific URL
* (say http://example.com/foo/bar/baz) and that we should match on any URLs that are
* less specific but otherwise the same. For example http://example.com and http://example.com/foo
* would both match.
* :above is an inefficient query- It means that the user is supplying a more specific URL (say http://example.com/foo/bar/baz) and that we should match on any URLs that are less
* specific but otherwise the same. For example http://example.com and http://example.com/foo would both match.
*
* We do this by querying the DB for all candidate URIs and then manually checking
* each one. This isn't very efficient, but this is also probably not a very common
* type of query to do.
* We do this by querying the DB for all candidate URIs and then manually checking each one. This isn't very efficient, but this is also probably not a very common type of query to do.
*
* If we ever need to make this more efficient, lucene could certainly be used
* as an optimization.
* If we ever need to make this more efficient, lucene could certainly be used as an optimization.
*/
ourLog.info("Searching for candidate URI:above parameters for Resource[{}] param[{}]", myResourceName, theParamName);
Collection<String> candidates = myResourceIndexedSearchParamUriDao.findAllByResourceTypeAndParamName(myResourceName, theParamName);
@ -918,7 +908,7 @@ public class SearchBuilder {
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.equal(from.get("myParamName"), theParamName));
predicates.add(builder.or(toArray(codePredicates)));
doCreateIdPredicate(predicates, from.get("myResourcePid").as(Long.class));
doCreateIdPredicate(builder, cq, predicates, from.get("myResourcePid").as(Long.class));
cq.where(builder.and(toArray(predicates)));
@ -953,7 +943,7 @@ public class SearchBuilder {
return retVal;
}
private List<Predicate> createLastUpdatedPredicates(final DateRangeParam theLastUpdated, CriteriaBuilder builder, From<?, ResourceTable> from) {
private static List<Predicate> createLastUpdatedPredicates(final DateRangeParam theLastUpdated, CriteriaBuilder builder, From<?, ResourceTable> from) {
List<Predicate> lastUpdatedPredicates = new ArrayList<Predicate>();
if (theLastUpdated != null) {
if (theLastUpdated.getLowerBoundAsInstant() != null) {
@ -1023,7 +1013,8 @@ public class SearchBuilder {
}
}
private Predicate createPredicateNumeric(CriteriaBuilder builder, IQueryParameterType params, ParamPrefixEnum cmpValue, BigDecimal valueValue, final Expression<BigDecimal> path, String invalidMessageName, String theValueString) {
private Predicate createPredicateNumeric(CriteriaBuilder builder, IQueryParameterType params, ParamPrefixEnum cmpValue, BigDecimal valueValue, final Expression<BigDecimal> path,
String invalidMessageName, String theValueString) {
Predicate num;
switch (cmpValue) {
case GREATERTHAN:
@ -1065,7 +1056,8 @@ public class SearchBuilder {
return num;
}
private Predicate createPredicateString(IQueryParameterType theParameter, String theParamName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> theFrom) {
private Predicate createPredicateString(IQueryParameterType theParameter, String theParamName, CriteriaBuilder theBuilder,
From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> theFrom) {
String rawSearchTerm;
if (theParameter instanceof TokenParam) {
TokenParam id = (TokenParam) theParameter;
@ -1084,7 +1076,8 @@ public class SearchBuilder {
}
if (rawSearchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
throw new InvalidRequestException("Parameter[" + theParamName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm);
throw new InvalidRequestException("Parameter[" + theParamName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed ("
+ ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm);
}
String likeExpression = BaseHapiFhirDao.normalizeString(rawSearchTerm);
@ -1098,11 +1091,8 @@ public class SearchBuilder {
return singleCode;
}
private static String createLeftMatchLikeExpression(String likeExpression) {
return likeExpression.replace("%", "[%]") + "%";
}
private Predicate createPredicateToken(IQueryParameterType theParameter, String theParamName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> theFrom) {
private Predicate createPredicateToken(IQueryParameterType theParameter, String theParamName, CriteriaBuilder theBuilder,
From<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> theFrom) {
String code;
String system;
if (theParameter instanceof TokenParam) {
@ -1122,10 +1112,12 @@ public class SearchBuilder {
}
if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
throw new InvalidRequestException("Parameter[" + theParamName + "] has system (" + system.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + system);
throw new InvalidRequestException(
"Parameter[" + theParamName + "] has system (" + system.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + system);
}
if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
throw new InvalidRequestException("Parameter[" + theParamName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + code);
throw new InvalidRequestException(
"Parameter[" + theParamName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + code);
}
ArrayList<Predicate> singleCodePredicates = (new ArrayList<Predicate>());
@ -1153,11 +1145,11 @@ public class SearchBuilder {
return type;
}
private TypedQuery<Tuple> createSearchAllByTypeQuery(DateRangeParam theLastUpdated) {
private TypedQuery<Long> createSearchAllByTypeQuery(DateRangeParam theLastUpdated) {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = builder.createTupleQuery();
CriteriaQuery<Long> cq = builder.createQuery(Long.class);
Root<ResourceTable> from = cq.from(ResourceTable.class);
cq.multiselect(from.get("myId").as(Long.class));
cq.select(from.get("myId").as(Long.class));
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.isNull(from.get("myDeleted")));
@ -1168,7 +1160,7 @@ public class SearchBuilder {
cq.where(toArray(predicates));
TypedQuery<Tuple> query = myEntityManager.createQuery(cq);
TypedQuery<Long> query = myEntityManager.createQuery(cq);
return query;
}
@ -1267,6 +1259,112 @@ public class SearchBuilder {
createSort(theBuilder, theFrom, theSort.getChain(), theOrders, thePredicates);
}
private void doCreateIdPredicate(CriteriaBuilder builder, CriteriaQuery<?> cq, List<Predicate> thePredicates, Expression<Long> theExpression) {
if (myParams.isPersistResults()) {
if (mySearchEntity.getTotalCount() > -1) {
Subquery<Long> subQ = cq.subquery(Long.class);
Root<SearchResult> subQfrom = subQ.from(SearchResult.class);
subQ.select(subQfrom.get("myResourcePid").as(Long.class));
Predicate subQname = builder.equal(subQfrom.get("mySearch"), mySearchEntity);
subQ.where(subQname);
thePredicates.add(theExpression.in(subQ));
}
} else {
if (myPids != null) {
thePredicates.add(theExpression.in(myPids));
}
}
}
public Set<Long> doGetPids() {
if (myParams.isPersistResults()) {
HashSet<Long> retVal = new HashSet<Long>();
for (SearchResult next : mySearchResultDao.findWithSearchUuid(mySearchEntity)) {
retVal.add(next.getResourcePid());
}
return retVal;
} else {
return new HashSet<Long>(myPids);
}
}
private boolean doHaveNoResults() {
if (myParams.isPersistResults()) {
return mySearchEntity.getTotalCount() == 0;
} else {
return myPids != null && myPids.isEmpty();
}
}
private void doInitializeSearch() {
if (mySearchEntity == null) {
mySearchEntity = new Search();
mySearchEntity.setUuid(UUID.randomUUID().toString());
mySearchEntity.setCreated(new Date());
mySearchEntity.setTotalCount(-1);
mySearchEntity.setPreferredPageSize(myParams.getCount());
mySearchEntity.setEverythingMode(myParams.getEverythingMode());
mySearchEntity.setLastUpdated(myParams.getLastUpdated());
for (Include next : myParams.getIncludes()) {
mySearchEntity.getIncludes().add(new SearchInclude(mySearchEntity, next.getValue(), false, next.isRecurse()));
}
for (Include next : myParams.getRevIncludes()) {
mySearchEntity.getIncludes().add(new SearchInclude(mySearchEntity, next.getValue(), true, next.isRecurse()));
}
if (myParams.isPersistResults()) {
myEntityManager.persist(mySearchEntity);
for (SearchInclude next : mySearchEntity.getIncludes()) {
myEntityManager.persist(next);
}
}
}
}
private IBundleProvider doReturnProvider() {
if (myParams.isPersistResults()) {
return new BundleProviderPersisted(mySearchEntity.getUuid(), myPlatformTransactionManager, mySearchResultDao, myEntityManager, myContext, myCallingDao);
} else {
if (myPids == null) {
return new SimpleBundleProvider();
} else {
return new BundleProviderInMemory(myPids);
}
}
}
private void doSetPids(Collection<Long> thePids) {
if (myParams.isPersistResults()) {
mySearchResultDao.deleteForSearch(mySearchEntity.getId());
mySearchResultDao.flush();
LinkedHashSet<SearchResult> results = new LinkedHashSet<SearchResult>();
int index = 0;
for (Long next : thePids) {
SearchResult nextResult = new SearchResult(mySearchEntity);
nextResult.setResourcePid(next);
nextResult.setOrder(index);
results.add(nextResult);
index++;
}
mySearchResultDao.save(results);
mySearchEntity.setTotalCount(results.size());
mySearchEntity = myEntityManager.merge(mySearchEntity);
myEntityManager.flush();
} else {
myPids = thePids;
}
}
private void filterResourceIdsByLastUpdated(final DateRangeParam theLastUpdated) {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = builder.createQuery(Long.class);
@ -1274,7 +1372,7 @@ public class SearchBuilder {
cq.select(from.get("myId").as(Long.class));
List<Predicate> lastUpdatedPredicates = createLastUpdatedPredicates(theLastUpdated, builder, from);
doCreateIdPredicate(lastUpdatedPredicates, from.get("myId").as(Long.class));
doCreateIdPredicate(builder, cq, lastUpdatedPredicates, from.get("myId").as(Long.class));
cq.where(SearchBuilder.toArray(lastUpdatedPredicates));
TypedQuery<Long> query = myEntityManager.createQuery(cq);
@ -1283,8 +1381,8 @@ public class SearchBuilder {
doSetPids(resultList);
}
private List<Long> filterResourceIdsByLastUpdated(final DateRangeParam theLastUpdated, Collection<Long> thePids) {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
private static List<Long> filterResourceIdsByLastUpdated(EntityManager theEntityManager, final DateRangeParam theLastUpdated, Collection<Long> thePids) {
CriteriaBuilder builder = theEntityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = builder.createQuery(Long.class);
Root<ResourceTable> from = cq.from(ResourceTable.class);
cq.select(from.get("myId").as(Long.class));
@ -1293,13 +1391,22 @@ public class SearchBuilder {
lastUpdatedPredicates.add(from.get("myId").as(Long.class).in(thePids));
cq.where(SearchBuilder.toArray(lastUpdatedPredicates));
TypedQuery<Long> query = myEntityManager.createQuery(cq);
TypedQuery<Long> query = theEntityManager.createQuery(cq);
List<Long> resultList = query.getResultList();
return resultList;
}
private void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation) {
EntityManager entityManager = myEntityManager;
FhirContext context = myContext;
BaseHapiFhirDao<?> dao = myCallingDao;
loadResourcesByPid(theIncludePids, theResourceListToPopulate, theRevIncludedPids, theForHistoryOperation, entityManager, context, dao);
}
private static void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation,
EntityManager entityManager, FhirContext context, IDao theDao) {
if (theIncludePids.isEmpty()) {
return;
}
@ -1310,15 +1417,15 @@ public class SearchBuilder {
theResourceListToPopulate.add(null);
}
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<ResourceTable> cq = builder.createQuery(ResourceTable.class);
Root<ResourceTable> from = cq.from(ResourceTable.class);
cq.where(from.get("myId").in(theIncludePids));
TypedQuery<ResourceTable> q = myEntityManager.createQuery(cq);
TypedQuery<ResourceTable> q = entityManager.createQuery(cq);
for (ResourceTable next : q.getResultList()) {
Class<? extends IBaseResource> resourceType = myContext.getResourceDefinition(next.getResourceType()).getImplementingClass();
IBaseResource resource = (IBaseResource) myCallingDao.toResource(resourceType, next, theForHistoryOperation);
Class<? extends IBaseResource> resourceType = context.getResourceDefinition(next.getResourceType()).getImplementingClass();
IBaseResource resource = (IBaseResource) theDao.toResource(resourceType, next, theForHistoryOperation);
Integer index = position.get(next.getId());
if (index == null) {
ourLog.warn("Got back unexpected resource PID {}", next.getId());
@ -1348,7 +1455,8 @@ public class SearchBuilder {
*
* @param theLastUpdated
*/
private HashSet<Long> loadReverseIncludes(Collection<Long> theMatches, Set<Include> theRevIncludes, boolean theReverseMode, DateRangeParam theLastUpdated) {
private static HashSet<Long> loadReverseIncludes(FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes, boolean theReverseMode,
DateRangeParam theLastUpdated) {
if (theMatches.size() == 0) {
return new HashSet<Long>();
}
@ -1382,7 +1490,7 @@ public class SearchBuilder {
if (matchAll) {
String sql;
sql = "SELECT r FROM ResourceLink r WHERE r." + searchFieldName + " IN (:target_pids)";
TypedQuery<ResourceLink> q = myEntityManager.createQuery(sql, ResourceLink.class);
TypedQuery<ResourceLink> q = theEntityManager.createQuery(sql, ResourceLink.class);
q.setParameter("target_pids", nextRoundMatches);
List<ResourceLink> results = q.getResultList();
for (ResourceLink resourceLink : results) {
@ -1401,14 +1509,14 @@ public class SearchBuilder {
} else {
List<String> paths;
if (myContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
paths = Collections.singletonList(nextInclude.getValue());
} else {
String resType = nextInclude.getParamType();
if (isBlank(resType)) {
continue;
}
RuntimeResourceDefinition def = myContext.getResourceDefinition(resType);
RuntimeResourceDefinition def = theContext.getResourceDefinition(resType);
if (def == null) {
ourLog.warn("Unknown resource type in include/revinclude=" + nextInclude.getValue());
continue;
@ -1432,7 +1540,7 @@ public class SearchBuilder {
} else {
sql = "SELECT r FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchFieldName + " IN (:target_pids)";
}
TypedQuery<ResourceLink> q = myEntityManager.createQuery(sql, ResourceLink.class);
TypedQuery<ResourceLink> q = theEntityManager.createQuery(sql, ResourceLink.class);
q.setParameter("src_path", nextPath);
q.setParameter("target_pids", nextRoundMatches);
if (targetResourceType != null) {
@ -1451,7 +1559,7 @@ public class SearchBuilder {
}
if (theLastUpdated != null && (theLastUpdated.getLowerBoundAsInstant() != null || theLastUpdated.getUpperBoundAsInstant() != null)) {
pidsToInclude = new HashSet<Long>(filterResourceIdsByLastUpdated(theLastUpdated, pidsToInclude));
pidsToInclude = new HashSet<Long>(filterResourceIdsByLastUpdated(theEntityManager, theLastUpdated, pidsToInclude));
}
for (Long next : pidsToInclude) {
if (original.contains(next) == false && allAdded.contains(next) == false) {
@ -1480,14 +1588,14 @@ public class SearchBuilder {
CriteriaQuery<Tuple> cq = builder.createTupleQuery();
Root<ResourceTable> from = cq.from(ResourceTable.class);
doCreateIdPredicate(predicates, from.get("myId").as(Long.class));
doCreateIdPredicate(builder, cq, predicates, from.get("myId").as(Long.class));
createSort(builder, from, theParams.getSort(), orders, predicates);
if (orders.size() > 0) {
// TODO: why do we need the existing list for this join to work?
Collection<Long> originalPids = myPids;
Collection<Long> originalPids = doGetPids();
LinkedHashSet<Long> loadPids = new LinkedHashSet<Long>();
cq.multiselect(from.get("myId").as(Long.class));
@ -1528,7 +1636,7 @@ public class SearchBuilder {
lu = null;
}
// Collection<Long> loadPids;
// Collection<Long> loadPids;
if (theParams.getEverythingMode() != null) {
Long pid = null;
@ -1575,18 +1683,8 @@ public class SearchBuilder {
} else if (theParams.isEmpty()) {
TypedQuery<Tuple> query = createSearchAllByTypeQuery(lu);
lu = null;
List<Tuple> resultList = query.getResultList();
if (resultList.isEmpty()) {
return doReturnProvider();
}
Set<Long> loadPids = new HashSet<Long>();
for (Tuple next : resultList) {
loadPids.add(next.get(0, Long.class));
}
doSetPids(loadPids);
TypedQuery<Long> query = createSearchAllByTypeQuery(lu);
doSetPids(query.getResultList());
} else {
@ -1634,7 +1732,6 @@ public class SearchBuilder {
}
}
// Handle sorting if any was provided
processSort(theParams);
@ -1642,101 +1739,14 @@ public class SearchBuilder {
return doReturnProvider();
}
private IBundleProvider doReturnProvider() {
if (myPids == null) {
return new SimpleBundleProvider();
} else {
final ArrayList<Long> pids;
if (!(myPids instanceof List)) {
pids = new ArrayList<Long>(myPids);
} else {
pids = (ArrayList<Long>) myPids;
}
// // Load _revinclude resources
// final Set<Long> revIncludedPids;
// if (myParams.getEverythingMode() == null) {
// if (myParams.getRevIncludes() != null && myParams.getRevIncludes().isEmpty() == false) {
// revIncludedPids = loadReverseIncludes(pids, myParams.getRevIncludes(), true, myParams.getLastUpdated());
// } else {
// revIncludedPids = new HashSet<Long>();
// }
// } else {
// revIncludedPids = new HashSet<Long>();
// }
return new IBundleProvider() {
@Override
public InstantDt getPublished() {
return mySearchStarted;
}
@Override
public List<IBaseResource> getResources(final int theFromIndex, final int theToIndex) {
TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager);
return template.execute(new TransactionCallback<List<IBaseResource>>() {
@Override
public List<IBaseResource> doInTransaction(TransactionStatus theStatus) {
List<Long> pidsSubList = pids.subList(theFromIndex, theToIndex);
// Load includes
pidsSubList = new ArrayList<Long>(pidsSubList);
Set<Long> revIncludedPids = new HashSet<Long>();
if (myParams.getEverythingMode() == null) {
revIncludedPids.addAll(loadReverseIncludes(pidsSubList, myParams.getRevIncludes(), true, myParams.getLastUpdated()));
}
revIncludedPids.addAll(loadReverseIncludes(pidsSubList, myParams.getIncludes(), false, myParams.getLastUpdated()));
// Execute the query and make sure we return distinct results
List<IBaseResource> resources = new ArrayList<IBaseResource>();
loadResourcesByPid(pidsSubList, resources, revIncludedPids, false);
return resources;
}
});
}
@Override
public Integer preferredPageSize() {
return myParams.getCount();
}
@Override
public int size() {
return pids.size();
}
};
}
}
private Collection<Long> myPids;
private InstantDt mySearchStarted;
private void doSetPids(Collection<Long> thePids) {
myPids = thePids;
}
private void doInitializeSearch() {
assert mySearchEntity == null;
mySearchStarted = InstantDt.withCurrentTime();
// mySearchEntity = new Search();
// mySearchEntity.setUuid(UUID.randomUUID().toString());
// myEntityManager.persist(mySearchEntity);
//
// myHaveFlushedSearch = false;
}
public void searchForIdsWithAndOr(SearchParameterMap theParams, DateRangeParam theLastUpdated) {
SearchParameterMap params = theParams;
if (params == null) {
params = new SearchParameterMap();
}
myParams = theParams;
doInitializeSearch();
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceType);
@ -1868,10 +1878,6 @@ public class SearchBuilder {
}
private boolean doHaveNoResults() {
return myPids != null && myPids.isEmpty();
}
public void setType(Class<? extends IBaseResource> theResourceType, String theResourceName) {
myResourceType = theResourceType;
myResourceName = theResourceName;
@ -1931,9 +1937,8 @@ public class SearchBuilder {
}
/**
* Figures out the tolerance for a search. For example, if the user is searching for
* <code>4.00</code>, this method returns <code>0.005</code> because we shold actually
* match values which are <code>4 (+/-) 0.005</code> according to the FHIR specs.
* Figures out the tolerance for a search. For example, if the user is searching for <code>4.00</code>, this method returns <code>0.005</code> because we shold actually match values which are
* <code>4 (+/-) 0.005</code> according to the FHIR specs.
*/
static BigDecimal calculateFuzzAmount(ParamPrefixEnum cmpValue, BigDecimal theValue) {
if (cmpValue == ParamPrefixEnum.APPROXIMATE) {
@ -1952,12 +1957,181 @@ public class SearchBuilder {
}
}
private static String createLeftMatchLikeExpression(String likeExpression) {
return likeExpression.replace("%", "[%]") + "%";
}
static Predicate[] toArray(List<Predicate> thePredicates) {
return thePredicates.toArray(new Predicate[thePredicates.size()]);
}
public Set<Long> doGetPids() {
return new HashSet<Long>(myPids);
private final class BundleProviderInMemory implements IBundleProvider {
private final ArrayList<Long> myPids;
private BundleProviderInMemory(Collection<Long> thePids) {
final ArrayList<Long> pids;
if (!(thePids instanceof List)) {
pids = new ArrayList<Long>(thePids);
} else {
pids = (ArrayList<Long>) thePids;
}
myPids = pids;
}
@Override
public InstantDt getPublished() {
return new InstantDt(mySearchEntity.getCreated());
}
@Override
public List<IBaseResource> getResources(final int theFromIndex, final int theToIndex) {
TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager);
return template.execute(new TransactionCallback<List<IBaseResource>>() {
@Override
public List<IBaseResource> doInTransaction(TransactionStatus theStatus) {
List<Long> pidsSubList = myPids.subList(theFromIndex, theToIndex);
// Load includes
pidsSubList = new ArrayList<Long>(pidsSubList);
Set<Long> revIncludedPids = new HashSet<Long>();
if (myParams.getEverythingMode() == null) {
revIncludedPids.addAll(loadReverseIncludes(myContext, myEntityManager, pidsSubList, myParams.getRevIncludes(), true, myParams.getLastUpdated()));
}
revIncludedPids.addAll(loadReverseIncludes(myContext, myEntityManager, pidsSubList, myParams.getIncludes(), false, myParams.getLastUpdated()));
// Execute the query and make sure we return distinct results
List<IBaseResource> resources = new ArrayList<IBaseResource>();
loadResourcesByPid(pidsSubList, resources, revIncludedPids, false);
return resources;
}
});
}
@Override
public Integer preferredPageSize() {
return myParams.getCount();
}
@Override
public int size() {
return myPids.size();
}
}
public final static class BundleProviderPersisted implements IBundleProvider {
private String myUuid;
private PlatformTransactionManager myPlatformTransactionManager;
private ISearchResultDao mySearchResultDao;
private Search mySearchEntity;
private EntityManager myEntityManager;
private FhirContext myContext;
private IDao myDao;
public BundleProviderPersisted(String theSearchUuid, PlatformTransactionManager thePlatformTransactionManager, ISearchResultDao theSearchResultDao, EntityManager theEntityManager,
FhirContext theContext, IDao theDao) {
myUuid = theSearchUuid;
myPlatformTransactionManager = thePlatformTransactionManager;
mySearchResultDao = theSearchResultDao;
myEntityManager = theEntityManager;
myContext = theContext;
myDao = theDao;
}
@Override
public InstantDt getPublished() {
ensureSearchEntityLoaded();
return new InstantDt(mySearchEntity.getCreated());
}
@Override
public List<IBaseResource> getResources(final int theFromIndex, final int theToIndex) {
TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager);
return template.execute(new TransactionCallback<List<IBaseResource>>() {
@Override
public List<IBaseResource> doInTransaction(TransactionStatus theStatus) {
ensureSearchEntityLoaded();
int pageSize = theToIndex - theFromIndex;
if (pageSize < 1) {
return Collections.emptyList();
}
int pageIndex = theFromIndex / pageSize;
Pageable page = new PageRequest(pageIndex, pageSize);
Page<SearchResult> search = mySearchResultDao.findWithSearchUuid(mySearchEntity, page);
List<Long> pidsSubList = new ArrayList<Long>();
for (SearchResult next : search) {
pidsSubList.add(next.getResourcePid());
}
// Load includes
pidsSubList = new ArrayList<Long>(pidsSubList);
Set<Long> revIncludedPids = new HashSet<Long>();
if (mySearchEntity.getEverythingMode() == null) {
revIncludedPids.addAll(loadReverseIncludes(myContext, myEntityManager, pidsSubList, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated()));
}
revIncludedPids.addAll(loadReverseIncludes(myContext, myEntityManager, pidsSubList, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated()));
// Execute the query and make sure we return distinct results
List<IBaseResource> resources = new ArrayList<IBaseResource>();
loadResourcesByPid(pidsSubList, resources, revIncludedPids, false, myEntityManager, myContext, myDao);
return resources;
}
});
}
@Override
public Integer preferredPageSize() {
ensureSearchEntityLoaded();
return mySearchEntity.getPreferredPageSize();
}
@Override
public int size() {
ensureSearchEntityLoaded();
return mySearchEntity.getTotalCount();
}
public String getSearchUuid() {
return myUuid;
}
/**
* Returns false if the entity can't be found
*/
public boolean ensureSearchEntityLoaded() {
if (mySearchEntity == null) {
TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager);
template.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
return template.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus theStatus) {
TypedQuery<Search> q = myEntityManager.createQuery("SELECT s FROM Search s WHERE s.myUuid = :uuid", Search.class);
q.setParameter("uuid", myUuid);
try {
mySearchEntity = q.getSingleResult();
// Ensure includes are loaded
mySearchEntity.getIncludes().size();
return true;
} catch (NoResultException e) {
return false;
}
}
});
}
return true;
}
}
}

View File

@ -46,6 +46,7 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
private EverythingModeEnum myEverythingMode = null;
private Set<Include> myIncludes;
private DateRangeParam myLastUpdated;
private boolean myPersistResults = true;
private RequestDetails myRequestDetails;
private Set<Include> myRevIncludes;
private SortSpec mySort;
@ -133,6 +134,10 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
return mySort;
}
public boolean isPersistResults() {
return myPersistResults;
}
public void setCount(Integer theCount) {
myCount = theCount;
}
@ -149,6 +154,13 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
myLastUpdated = theLastUpdated;
}
/**
* Should results be persisted into a table for paging
*/
public void setPersistResults(boolean thePersistResults) {
myPersistResults = thePersistResults;
}
public void setRequestDetails(RequestDetails theRequestDetails) {
myRequestDetails = theRequestDetails;
}
@ -174,12 +186,13 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
}
public enum EverythingModeEnum {
/*
* Don't reorder! We rely on the ordinals
*/
ENCOUNTER_INSTANCE(false, true, true),
ENCOUNTER_TYPE(false, true, false),
PATIENT_INSTANCE(true, false, true),
//@formatter:off
PATIENT_TYPE(true, false, false);
//@formatter:on
private final boolean myEncounter;

View File

@ -0,0 +1,37 @@
package ca.uhn.fhir.jpa.dao.data;
import java.util.Collection;
import java.util.Date;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2016 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.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import ca.uhn.fhir.jpa.entity.Search;
public interface ISearchDao extends JpaRepository<Search, Long> {
@Query("SELECT s FROM Search s WHERE s.myCreated < :cutoff")
public Collection<Search> findWhereCreatedBefore(@Param("cutoff") Date theCutoff);
}

View File

@ -0,0 +1,35 @@
package ca.uhn.fhir.jpa.dao.data;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2016 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.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import ca.uhn.fhir.jpa.entity.SearchInclude;
public interface ISearchIncludeDao extends JpaRepository<SearchInclude, Long> {
@Modifying
@Query(value="DELETE FROM SearchInclude r WHERE r.mySearchPid = :search")
void deleteForSearch(@Param("search") Long theSearchPid);
}

View File

@ -1,5 +1,10 @@
package ca.uhn.fhir.jpa.dao.data;
import java.util.Collection;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
/*
* #%L
* HAPI FHIR JPA Server
@ -21,9 +26,22 @@ package ca.uhn.fhir.jpa.dao.data;
*/
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchResult;
public interface ISearchResultDao extends JpaRepository<SearchResult, Long> {
// nothing
@Query(value="SELECT r FROM SearchResult r WHERE r.mySearch = :search")
Collection<SearchResult> findWithSearchUuid(@Param("search") Search theSearch);
@Query(value="SELECT r FROM SearchResult r WHERE r.mySearch = :search ORDER BY r.myOrder ASC")
Page<SearchResult> findWithSearchUuid(@Param("search") Search theSearch, Pageable thePage);
@Modifying
@Query(value="DELETE FROM SearchResult r WHERE r.mySearchPid = :search")
void deleteForSearch(@Param("search") Long theSearchPid);
}

View File

@ -31,7 +31,9 @@ import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
@ -66,6 +68,13 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
@OneToMany(mappedBy = "myResourceHistory", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Collection<ResourceHistoryTag> myTags;
/**
* This field is only populated if this specific histor entry corresponds to
* the most recent version of a resource
*/
@OneToOne(fetch=FetchType.LAZY, optional=true, mappedBy="myHistory")
private ResourceTable myCorrespondsToVersion;
public void addTag(ResourceHistoryTag theTag) {
for (ResourceHistoryTag next : getTags()) {
if (next.getTag().equals(theTag)) {

View File

@ -35,7 +35,9 @@ import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
@ -150,6 +152,10 @@ public class ResourceTable extends BaseHasResource implements Serializable {
@Column(name = "SP_HAS_LINKS")
private boolean myHasLinks;
@OneToOne(fetch=FetchType.LAZY, optional=true)
@JoinColumn(name="HISTORY_VERSION_PID", referencedColumnName="PID", nullable=true)
private ResourceHistoryTable myHistory;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "RES_ID")

View File

@ -21,20 +21,32 @@ package ca.uhn.fhir.jpa.entity;
*/
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.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;
import ca.uhn.fhir.jpa.dao.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.param.DateRangeParam;
//@formatter:off
@Entity
@Table(name = "HFJ_SEARCH", uniqueConstraints= {
@ -48,25 +60,73 @@ public class Search implements Serializable {
private static final long serialVersionUID = 1L;
@Temporal(TemporalType.TIMESTAMP)
@Column(name="CREATED", nullable=false)
@Column(name="CREATED", nullable=false, updatable=false)
private Date myCreated;
@Enumerated(EnumType.ORDINAL)
@Column(name="EVERYTHING_MODE", nullable=true)
private EverythingModeEnum myEverythingMode;
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator="SEQ_SEARCH")
@SequenceGenerator(name="SEQ_SEARCH", sequenceName="SEQ_SEARCH")
@Column(name = "PID")
private Long myId;
@OneToMany(mappedBy="mySearch")
private Collection<SearchInclude> myIncludes;
@Temporal(TemporalType.TIMESTAMP)
@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)
private Date myLastUpdatedLow;
@Column(name="PREFERRED_PAGE_SIZE", nullable=true)
private Integer myPreferredPageSize;
@OneToMany(mappedBy="mySearch")
private Collection<SearchResult> myResults;
@Column(name="TOTAL_COUNT")
private int myTotalCount;
@Id
@Column(name="SEARCH_UUID", length=40, nullable=false)
@Column(name="SEARCH_UUID", length=40, nullable=false, updatable=false)
private String myUuid;
public Date getCreated() {
return myCreated;
}
public EverythingModeEnum getEverythingMode() {
return myEverythingMode;
}
public Long getId() {
return myId;
}
public Collection<SearchInclude> getIncludes() {
if (myIncludes == null) {
myIncludes = new ArrayList<SearchInclude>();
}
return myIncludes;
}
public DateRangeParam getLastUpdated() {
if (myLastUpdatedLow == null && myLastUpdatedHigh == null) {
return null;
} else {
return new DateRangeParam(myLastUpdatedLow, myLastUpdatedHigh);
}
}
public Integer getPreferredPageSize() {
return myPreferredPageSize;
}
public int getTotalCount() {
return myTotalCount;
}
@ -79,6 +139,24 @@ public class Search implements Serializable {
myCreated = theCreated;
}
public void setEverythingMode(EverythingModeEnum theEverythingMode) {
myEverythingMode = theEverythingMode;
}
public void setLastUpdated(DateRangeParam theLastUpdated) {
if (theLastUpdated == null) {
myLastUpdatedLow = null;
myLastUpdatedHigh = null;
} else {
myLastUpdatedLow = theLastUpdated.getLowerBoundAsInstant();
myLastUpdatedHigh = theLastUpdated.getUpperBoundAsInstant();
}
}
public void setPreferredPageSize(Integer thePreferredPageSize) {
myPreferredPageSize = thePreferredPageSize;
}
public void setTotalCount(int theTotalCount) {
myTotalCount = theTotalCount;
}
@ -87,4 +165,22 @@ public class Search implements Serializable {
myUuid = theUuid;
}
private Set<Include> toIncList(boolean theWantReverse) {
HashSet<Include> retVal = new HashSet<Include>();
for (SearchInclude next : getIncludes()) {
if (theWantReverse == next.isReverse()) {
retVal.add(new Include(next.getInclude(), next.isRecurse()));
}
}
return Collections.unmodifiableSet(retVal);
}
public Set<Include> toIncludesList() {
return toIncList(false);
}
public Set<Include> toRevIncludesList() {
return toIncList(true);
}
}

View File

@ -0,0 +1,111 @@
package ca.uhn.fhir.jpa.entity;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2016 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 java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
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;
//@formatter:off
@Entity
@Table(name = "HFJ_SEARCH_INCLUDE")
//@formatter:on
public class SearchInclude implements Serializable {
private static final long serialVersionUID = 1L;
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SEARCH_INC")
@SequenceGenerator(name = "SEQ_SEARCH_INC", sequenceName = "SEQ_SEARCH_INC")
@Id
@Column(name = "PID")
private Long myId;
@Column(name = "REVINCLUDE", insertable = true, updatable = false, nullable = false)
private boolean myReverse;
public boolean isReverse() {
return myReverse;
}
@Column(name = "SEARCH_INCLUDE", length = 200, insertable = true, updatable = false, nullable = false)
private String myInclude;
@ManyToOne
@JoinColumn(name = "SEARCH_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_SEARCHINC_SEARCH"), insertable = true, updatable = false, nullable = false)
private Search mySearch;
@Column(name="SEARCH_PID", insertable=false, updatable=false, nullable=false)
private Long mySearchPid;
@Column(name = "INC_RECURSE", insertable = true, updatable = false, nullable = false)
private boolean myRecurse;
/**
* Constructor
*/
public SearchInclude() {
// nothing
}
/**
* Constructor
*/
public SearchInclude(Search theSearch, String theInclude, boolean theReverse, boolean theRecurse) {
mySearch = theSearch;
myInclude = theInclude;
myReverse = theReverse;
myRecurse = theRecurse;
}
@Override
public boolean equals(Object theObj) {
if (!(theObj instanceof SearchInclude)) {
return false;
}
if (myId == null) {
return false;
}
return myId.equals(((SearchInclude) theObj).myId);
}
public String getInclude() {
return myInclude;
}
@Override
public int hashCode() {
return myId == null ? 0 : myId.hashCode();
}
public boolean isRecurse() {
return myRecurse;
}
}

View File

@ -32,10 +32,12 @@ import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
//@formatter:off
@Entity
@Table(name = "HFJ_SEARCH_RESULT", uniqueConstraints= {
@UniqueConstraint(name="IDX_SEARCHRES_ORDER", columnNames= {"SEARCH_PID", "SEARCH_ORDER"})
})
//@formatter:on
public class SearchResult implements Serializable {
@ -48,6 +50,9 @@ public class SearchResult implements Serializable {
@Column(name = "PID")
private Long myId;
@Column(name="SEARCH_ORDER", nullable=false)
private int myOrder;
@ManyToOne
@JoinColumn(name="RESOURCE_PID", referencedColumnName="RES_ID", foreignKey=@ForeignKey(name="FK_SEARCHRES_RES"), insertable=false, updatable=false, nullable=false)
private ResourceTable myResource;
@ -59,6 +64,9 @@ public class SearchResult implements Serializable {
@JoinColumn(name="SEARCH_PID", referencedColumnName="PID", foreignKey=@ForeignKey(name="FK_SEARCHRES_SEARCH"))
private Search mySearch;
@Column(name="SEARCH_PID", insertable=false, updatable=false, nullable=false)
private Long mySearchPid;
/**
* Constructor
*/
@ -73,6 +81,31 @@ public class SearchResult implements Serializable {
mySearch = theSearch;
}
@Override
public boolean equals(Object theObj) {
if (!(theObj instanceof SearchResult)) {
return false;
}
return myResourcePid.equals(((SearchResult)theObj).myResourcePid);
}
public int getOrder() {
return myOrder;
}
public Long getResourcePid() {
return myResourcePid;
}
@Override
public int hashCode() {
return myResourcePid.hashCode();
}
public void setOrder(int theOrder) {
myOrder = theOrder;
}
public void setResourcePid(Long theResourcePid) {
myResourcePid = theResourcePid;
}

View File

@ -31,9 +31,9 @@ import java.util.TreeMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import ca.uhn.fhir.jpa.dao.FhirSearchDao.Suggestion;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.ISearchDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
@ -57,7 +57,7 @@ public class JpaSystemProviderDstu2 extends BaseJpaSystemProvider<Bundle, MetaDt
private IFhirSystemDao<Bundle, MetaDt> mySystemDao;
@Autowired
private ISearchDao mySearchDao;
private IFulltextSearchSvc mySearchDao;
//@formatter:off
// This is generated by hand:

View File

@ -38,10 +38,10 @@ import org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import ca.uhn.fhir.jpa.dao.FhirSearchDao.Suggestion;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.ISearchDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.rest.annotation.Operation;
@ -59,7 +59,7 @@ public class JpaSystemProviderDstu3 extends BaseJpaSystemProvider<Bundle, Meta>
private IFhirSystemDao<Bundle, Meta> mySystemDao;
@Autowired
private ISearchDao mySearchDao;
private IFulltextSearchSvc mySearchDao;
//@formatter:off
// This is generated by hand:

View File

@ -0,0 +1,57 @@
package ca.uhn.fhir.jpa.search;
import javax.persistence.EntityManager;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IDao;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchBuilder.BundleProviderPersisted;
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.IBundleProvider;
public class DatabaseBackedPagingProvider extends FifoMemoryPagingProvider {
@Autowired
private PlatformTransactionManager thePlatformTransactionManager;
@Autowired
private ISearchResultDao theSearchResultDao;
@Autowired
private EntityManager theEntityManager;
@Autowired
private FhirContext theContext;
@Autowired
private IFhirSystemDao<?, ?> theDao;
public DatabaseBackedPagingProvider(int theSize) {
super(theSize);
}
@Override
public synchronized IBundleProvider retrieveResultList(String theId) {
IBundleProvider retVal = super.retrieveResultList(theId);
if (retVal == null) {
BundleProviderPersisted provider = new SearchBuilder.BundleProviderPersisted(theId, thePlatformTransactionManager, theSearchResultDao, theEntityManager,
theContext, theDao);
if (!provider.ensureSearchEntityLoaded()) {
return null;
}
return provider;
}
return retVal;
}
@Override
public synchronized String storeResultList(IBundleProvider theList) {
if (theList instanceof SearchBuilder.BundleProviderPersisted) {
return ((BundleProviderPersisted)theList).getSearchUuid();
}
return super.storeResultList(theList);
}
}

View File

@ -0,0 +1,69 @@
package ca.uhn.fhir.jpa.search;
import java.util.Collection;
import java.util.Date;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao;
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
import ca.uhn.fhir.jpa.entity.Search;
/**
* Deletes old searches
*/
public class StaleSearchDeletingSvc {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StaleSearchDeletingSvc.class);
@Autowired
private ISearchDao mySearchDao;
@Autowired
private DaoConfig myDaoConfig;
@Autowired
private ISearchResultDao mySearchResultDao;
@Autowired
private ISearchIncludeDao mySearchIncludeDao;
@Autowired
private PlatformTransactionManager myTransactionManager;
@Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public synchronized void pollForStaleSearches() {
Date cutoff = new Date(System.currentTimeMillis() - myDaoConfig.getExpireSearchResultsAfterMillis());
ourLog.debug("Searching for searches which are before {}", cutoff);
Collection<Search> toDelete = mySearchDao.findWhereCreatedBefore(cutoff);
if (toDelete.isEmpty()) {
return;
}
TransactionTemplate tt = new TransactionTemplate(myTransactionManager);
for (final Search next : toDelete) {
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus theArg0) {
Search searchToDelete = mySearchDao.findOne(next.getId());
ourLog.info("Expiring stale search {} / {}", searchToDelete.getId(), searchToDelete.getUuid());
mySearchIncludeDao.deleteForSearch(searchToDelete.getId());
mySearchResultDao.deleteForSearch(searchToDelete.getId());
mySearchDao.delete(searchToDelete);
}
});
}
}
}

View File

@ -8,14 +8,42 @@ import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import org.apache.commons.io.IOUtils;
import org.hibernate.internal.SessionFactoryRegistry;
import org.hibernate.search.jpa.Search;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass;
import org.junit.Before;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.jpa.entity.ForcedId;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTag;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken;
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.SearchInclude;
import ca.uhn.fhir.jpa.entity.SearchResult;
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.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
@ -114,4 +142,59 @@ public class BaseJpaTest {
return bundleStr;
}
public static void purgeDatabase(final EntityManager entityManager, PlatformTransactionManager theTxManager) {
TransactionTemplate txTemplate = new TransactionTemplate(theTxManager);
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
txTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus theStatus) {
entityManager.createQuery("UPDATE " + ResourceHistoryTable.class.getSimpleName() + " d SET d.myForcedId = null").executeUpdate();
entityManager.createQuery("UPDATE " + ResourceTable.class.getSimpleName() + " d SET d.myForcedId = null").executeUpdate();
return null;
}
});
txTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus theStatus) {
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();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamQuantity.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamString.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamToken.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamUri.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamCoords.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceLink.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + SearchResult.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + SearchInclude.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + TermConceptParentChildLink.class.getSimpleName() + " d").executeUpdate();
return null;
}
});
txTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus theStatus) {
entityManager.createQuery("DELETE from " + TermConcept.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + TermCodeSystemVersion.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + TermCodeSystem.class.getSimpleName() + " d").executeUpdate();
return null;
}
});
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();
entityManager.createQuery("DELETE from " + ResourceHistoryTable.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceTable.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + Search.class.getSimpleName() + " d").executeUpdate();
return null;
}
});
}
}

View File

@ -20,9 +20,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.context.FhirContext;
@ -34,23 +32,9 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.ISearchDao;
import ca.uhn.fhir.jpa.entity.ForcedId;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTag;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken;
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.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.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
@ -89,7 +73,7 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
@Autowired
protected ApplicationContext myAppCtx;
@Autowired
protected ISearchDao mySearchDao;
protected IFulltextSearchSvc mySearchDao;
@Autowired
@Qualifier("myConceptMapDaoDstu2")
protected IFhirResourceDao<ConceptMap> myConceptMapDao;
@ -221,45 +205,4 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
return retVal;
}
public static void purgeDatabase(final EntityManager entityManager, PlatformTransactionManager theTxManager) {
TransactionTemplate txTemplate = new TransactionTemplate(theTxManager);
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
txTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus theStatus) {
entityManager.createQuery("UPDATE " + ResourceHistoryTable.class.getSimpleName() + " d SET d.myForcedId = null").executeUpdate();
entityManager.createQuery("UPDATE " + ResourceTable.class.getSimpleName() + " d SET d.myForcedId = null").executeUpdate();
return null;
}
});
txTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus theStatus) {
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();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamQuantity.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamString.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamToken.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamUri.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamCoords.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceLink.class.getSimpleName() + " d").executeUpdate();
return null;
}
});
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();
entityManager.createQuery("DELETE from " + ResourceHistoryTable.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceTable.class.getSimpleName() + " d").executeUpdate();
return null;
}
});
}
}

View File

@ -16,7 +16,7 @@ import javax.servlet.http.HttpServletRequest;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.Test;
import ca.uhn.fhir.jpa.dao.FhirSearchDao.Suggestion;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.model.dstu2.resource.Device;
import ca.uhn.fhir.model.dstu2.resource.Media;

View File

@ -9,7 +9,7 @@ import java.util.List;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import ca.uhn.fhir.jpa.dao.ISearchDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.model.dstu2.resource.Organization;
import ca.uhn.fhir.model.dstu2.resource.Patient;
@ -22,7 +22,7 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
public class FhirSearchDaoDstu2Test extends BaseJpaDstu2Test {
@Autowired
private ISearchDao mySearchDao;
private IFulltextSearchSvc mySearchDao;
@Test
public void testContentSearch() {

View File

@ -48,9 +48,7 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.context.FhirContext;
@ -62,30 +60,13 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.ISearchDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest;
import ca.uhn.fhir.jpa.entity.ForcedId;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTag;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken;
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.SubscriptionFlaggedResource;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.jpa.entity.TagDefinition;
import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvc;
import ca.uhn.fhir.jpa.term.ITerminologySvc;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.method.MethodUtil;
@ -96,29 +77,14 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
@ContextConfiguration(classes= {TestDstu3Config.class})
//@formatter:on
public abstract class BaseJpaDstu3Test extends BaseJpaTest {
@Autowired
protected IResourceTableDao myResourceTableDao;
@Autowired
protected ITerminologySvc myTermSvc;
@Autowired
@Qualifier("myJpaValidationSupportChainDstu3")
protected IValidationSupport myValidationSupport;
@Autowired
protected ApplicationContext myAppCtx;
@Autowired
protected ISearchDao mySearchDao;
@Autowired
@Qualifier("myConceptMapDaoDstu3")
protected IFhirResourceDao<ConceptMap> myConceptMapDao;
@Autowired
@Qualifier("myCodeSystemDaoDstu3")
protected IFhirResourceDao<CodeSystem> myCodeSystemDao;
@Autowired
@Qualifier("myMedicationDaoDstu3")
protected IFhirResourceDao<Medication> myMedicationDao;
@Autowired
@Qualifier("myMedicationOrderDaoDstu3")
protected IFhirResourceDao<MedicationOrder> myMedicationOrderDao;
@Qualifier("myConceptMapDaoDstu3")
protected IFhirResourceDao<ConceptMap> myConceptMapDao;
@Autowired
protected DaoConfig myDaoConfig;
@Autowired
@ -133,11 +99,9 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
@Autowired
@Qualifier("myEncounterDaoDstu3")
protected IFhirResourceDao<Encounter> myEncounterDao;
// @PersistenceContext()
// @PersistenceContext()
@Autowired
protected EntityManager myEntityManager;
@Autowired
@Qualifier("myFhirContextDstu3")
protected FhirContext myFhirCtx;
@ -149,6 +113,20 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
@Qualifier("myLocationDaoDstu3")
protected IFhirResourceDao<Location> myLocationDao;
@Autowired
@Qualifier("myMediaDaoDstu3")
protected IFhirResourceDao<Media> myMediaDao;
@Autowired
@Qualifier("myMedicationDaoDstu3")
protected IFhirResourceDao<Medication> myMedicationDao;
@Autowired
@Qualifier("myMedicationOrderDaoDstu3")
protected IFhirResourceDao<MedicationOrder> myMedicationOrderDao;
@Autowired
@Qualifier("myNamingSystemDaoDstu3")
protected IFhirResourceDao<NamingSystem> myNamingSystemDao;
@Autowired
@Qualifier("myObservationDaoDstu3")
protected IFhirResourceDao<Observation> myObservationDao;
@Autowired
@ -158,12 +136,6 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
@Qualifier("myPatientDaoDstu3")
protected IFhirResourceDaoPatient<Patient> myPatientDao;
@Autowired
@Qualifier("myNamingSystemDaoDstu3")
protected IFhirResourceDao<NamingSystem> myNamingSystemDao;
@Autowired
@Qualifier("myMediaDaoDstu3")
protected IFhirResourceDao<Media> myMediaDao;
@Autowired
@Qualifier("myPractitionerDaoDstu3")
protected IFhirResourceDao<Practitioner> myPractitionerDao;
@Autowired
@ -176,6 +148,12 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
@Qualifier("myResourceProvidersDstu3")
protected Object myResourceProviders;
@Autowired
protected IResourceTableDao myResourceTableDao;
@Autowired
protected IFulltextSearchSvc mySearchDao;
@Autowired
protected StaleSearchDeletingSvc myStaleSearchDeletingSvc;
@Autowired
@Qualifier("myStructureDefinitionDaoDstu3")
protected IFhirResourceDao<StructureDefinition> myStructureDefinitionDao;
@Autowired
@ -191,8 +169,13 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
@Qualifier("mySystemProviderDstu3")
protected JpaSystemProviderDstu3 mySystemProvider;
@Autowired
protected ITerminologySvc myTermSvc;
@Autowired
protected PlatformTransactionManager myTxManager;
@Autowired
@Qualifier("myJpaValidationSupportChainDstu3")
protected IValidationSupport myValidationSupport;
@Autowired
@Qualifier("myValueSetDaoDstu3")
protected IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> myValueSetDao;
@ -213,14 +196,6 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
myDaoConfig.setSchedulingDisabled(true);
}
@Before
public void beforeResetConfig() {
myDaoConfig.setHardSearchLimit(1000);
myDaoConfig.setHardTagListLimit(1000);
myDaoConfig.setIncludeLimit(2000);
}
@Before
@Transactional()
public void beforePurgeDatabase() {
@ -228,6 +203,14 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
purgeDatabase(entityManager, myTxManager);
}
@Before
public void beforeResetConfig() {
myDaoConfig.setHardSearchLimit(1000);
myDaoConfig.setHardTagListLimit(1000);
myDaoConfig.setIncludeLimit(2000);
}
protected <T extends IBaseResource> T loadResourceFromClasspath(Class<T> type, String resourceName) throws IOException {
InputStream stream = FhirResourceDaoDstu2SearchNoFtTest.class.getResourceAsStream(resourceName);
if (stream == null) {
@ -245,55 +228,4 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
return retVal;
}
public static void purgeDatabase(final EntityManager entityManager, PlatformTransactionManager theTxManager) {
TransactionTemplate txTemplate = new TransactionTemplate(theTxManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
txTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus theStatus) {
entityManager.createQuery("UPDATE " + ResourceHistoryTable.class.getSimpleName() + " d SET d.myForcedId = null").executeUpdate();
entityManager.createQuery("UPDATE " + ResourceTable.class.getSimpleName() + " d SET d.myForcedId = null").executeUpdate();
return null;
}
});
txTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus theStatus) {
entityManager.createQuery("DELETE from " + TermConceptParentChildLink.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + TermConcept.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + TermCodeSystemVersion.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + TermCodeSystem.class.getSimpleName() + " d").executeUpdate();
return null;
}
});
txTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus theStatus) {
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();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamQuantity.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamString.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamToken.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamUri.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamCoords.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceLink.class.getSimpleName() + " d").executeUpdate();
return null;
}
});
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();
entityManager.createQuery("DELETE from " + ResourceHistoryTable.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceTable.class.getSimpleName() + " d").executeUpdate();
return null;
}
});
}
}

View File

@ -25,7 +25,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.Ignore;
import org.junit.Test;
import ca.uhn.fhir.jpa.dao.FhirSearchDao.Suggestion;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringOrListParam;

View File

@ -11,7 +11,7 @@ import org.hl7.fhir.dstu3.model.Patient;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import ca.uhn.fhir.jpa.dao.ISearchDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringOrListParam;
@ -22,7 +22,7 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
public class FhirSearchDaoDstu3Test extends BaseJpaDstu3Test {
@Autowired
private ISearchDao mySearchDao;
private IFulltextSearchSvc mySearchDao;
@Test
public void testContentSearch() {

View File

@ -0,0 +1,22 @@
package ca.uhn.fhir.jpa.provider;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import ca.uhn.fhir.jpa.dao.SearchParameterMap.EverythingModeEnum;
public class SearchParameterMapTest {
/**
* {@link Search} uses these ordinals so they shouldn't get out of order
*/
@Test
public void testEverythingOrdinals() {
assertEquals(0, EverythingModeEnum.ENCOUNTER_INSTANCE.ordinal());
assertEquals(1, EverythingModeEnum.ENCOUNTER_TYPE.ordinal());
assertEquals(2, EverythingModeEnum.PATIENT_INSTANCE.ordinal());
assertEquals(3, EverythingModeEnum.PATIENT_TYPE.ordinal());
}
}

View File

@ -29,6 +29,7 @@ import org.springframework.web.servlet.DispatcherServlet;
import ca.uhn.fhir.jpa.config.dstu3.WebsocketDstu3Config;
import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
@ -78,7 +79,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
}
@After
public void after() {
public void after() throws Exception {
myFhirCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE);
}
@ -105,7 +106,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
confProvider.setImplementationDescription("THIS IS THE DESC");
ourRestServer.setServerConformanceProvider(confProvider);
ourRestServer.setPagingProvider(new FifoMemoryPagingProvider(10));
ourRestServer.setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
Server server = new Server(ourPort);

View File

@ -0,0 +1,78 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import static org.hamcrest.Matchers.blankOrNullString;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Bundle.BundleLinkComponent;
import org.hl7.fhir.dstu3.model.Patient;
import org.junit.Test;
import ca.uhn.fhir.rest.gclient.IQuery;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
public class StaleSearchDeletingSvcDstu3Test extends BaseResourceProviderDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StaleSearchDeletingSvcDstu3Test.class);
@Override
public void after() throws Exception {
super.after();
myDaoConfig.setExpireSearchResultsAfterMillis(DateUtils.MILLIS_PER_HOUR);
}
@Test
public void testEverythingInstanceWithContentFilter() throws Exception {
for (int i = 0; i < 20; i++) {
Patient pt1 = new Patient();
pt1.addName().addFamily("Everything").addGiven("Arthur");
myPatientDao.create(pt1, new ServletRequestDetails()).getId().toUnqualifiedVersionless();
}
//@formatter:off
IQuery<Bundle> search = ourClient
.search()
.forResource(Patient.class)
.where(Patient.NAME.matches().value("Everything"))
.returnBundle(Bundle.class);
//@formatter:on
Bundle resp1 = search.execute();
for (int i = 0; i < 20; i++) {
search.execute();
}
BundleLinkComponent nextLink = resp1.getLink("next");
assertNotNull(nextLink);
String nextLinkUrl = nextLink.getUrl();
assertThat(nextLinkUrl, not(blankOrNullString()));
Bundle resp2 = ourClient.search().byUrl(nextLinkUrl).returnBundle(Bundle.class).execute();
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp2));
myStaleSearchDeletingSvc.pollForStaleSearches();
ourClient.search().byUrl(nextLinkUrl).returnBundle(Bundle.class).execute();
Thread.sleep(20);
myDaoConfig.setExpireSearchResultsAfterMillis(10);
myStaleSearchDeletingSvc.pollForStaleSearches();
try {
ourClient.search().byUrl(nextLinkUrl).returnBundle(Bundle.class).execute();
fail();
} catch (ResourceGoneException e) {
assertThat(e.getMessage(), containsString("does not exist and may have expired"));
}
}
}

View File

@ -19,6 +19,7 @@ import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu1;
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
@ -127,15 +128,17 @@ public class JpaServerDemo extends RestfulServer {
ctx.setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
/*
* Default to XML and pretty printing
* Default to JSON and pretty printing
*/
setDefaultPrettyPrint(true);
setDefaultResponseEncoding(EncodingEnum.JSON);
/*
* This is a simple paging strategy that keeps the last 10 searches in memory
* -- New in HAPI FHIR 1.5 --
* This configures the server to page search results to and from
* the database
*/
setPagingProvider(new FifoMemoryPagingProvider(10));
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
/*
* Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)

View File

@ -183,6 +183,14 @@
longer includes _revinclude matches in the Bundle.total count, or the
page size limit.
</action>
<action type="add">
JPA server now persists search results to the database in a new table where they
can be temporaily preserved. This makes the JPA server much more scalable, since it
no longer needs to store large lists of pages in memory between search invocations.
<![CDATA[<br/><br/>]]>
Old searches are deleted after an hour by default, but this can be changed
via a setting in the DaoConfig.
</action>
</release>
<release version="1.4" date="2016-02-04">
<action type="add">