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,10 +106,10 @@ public class BaseConfig implements SchedulingConfigurer {
}
@Bean(autowire=Autowire.BY_TYPE)
public ITerminologySvc terminologyService() {
return new TerminologySvcImpl();
public StaleSearchDeletingSvc staleSearchDeletingSvc() {
return new StaleSearchDeletingSvc();
}
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler retVal = new ThreadPoolTaskScheduler();
@ -110,14 +117,11 @@ 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
// public void wireResourceDaos() {
// Map<String, IDao> daoBeans = myAppCtx.getBeansOfType(IDao.class);
@ -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,21 +40,37 @@ public class DaoConfig {
private boolean myAllowInlineMatchUrlReferences = false;
private boolean myAllowMultipleDelete;
private int myHardSearchLimit = 1000;
private int myHardTagListLimit = 1000;
private int myIncludeLimit = 2000;
// ***
// 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;
// ***
// update setter javadoc if default changes
// ***
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

@ -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)
private String myUuid;
@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;
}
@ -74,17 +134,53 @@ public class Search implements Serializable {
public String getUuid() {
return myUuid;
}
public void setCreated(Date theCreated) {
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;
}
public void setUuid(String theUuid) {
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,22 +32,27 @@ 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 {
private static final long serialVersionUID = 1L;
@GeneratedValue(strategy = GenerationType.AUTO, generator="SEQ_SEARCH_RES")
@SequenceGenerator(name="SEQ_SEARCH_RES", sequenceName="SEQ_SEARCH_RES")
@Id
@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;
@ -58,14 +63,17 @@ public class SearchResult implements Serializable {
@ManyToOne
@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
*/
public SearchResult() {
// nothing
}
/**
* 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">