Improve indexing on JPA server in anticipation of a new indexing

strategy in HAPI FHIR 3.5.0
This commit is contained in:
James Agnew 2018-05-23 08:09:57 -04:00
parent e58779d484
commit 2f2900e837
40 changed files with 611 additions and 175 deletions

View File

@ -58,6 +58,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu3 {
return retVal; return retVal;
} }
@Override
@Bean() @Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();

View File

@ -70,6 +70,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu3 {
return FhirServerConfigCommon.getDataSource(env); return FhirServerConfigCommon.getDataSource(env);
} }
@Override
@Bean() @Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
return FhirServerConfigCommon.getEntityManagerFactory(env, dataSource(), fhirContextDstu3()); return FhirServerConfigCommon.getEntityManagerFactory(env, dataSource(), fhirContextDstu3());

View File

@ -73,6 +73,7 @@ public class FhirServerConfigDstu2 extends BaseJavaConfigDstu2 {
return FhirServerConfigCommon.getDataSource(env); return FhirServerConfigCommon.getDataSource(env);
} }
@Override
@Bean() @Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
return FhirServerConfigCommon.getEntityManagerFactory(env, dataSource(), fhirContextDstu2()); return FhirServerConfigCommon.getEntityManagerFactory(env, dataSource(), fhirContextDstu2());

View File

@ -188,6 +188,7 @@ public class Constants {
public static final String PARAM_GRAPHQL_QUERY = "query"; public static final String PARAM_GRAPHQL_QUERY = "query";
public static final String HEADER_X_CACHE = "X-Cache"; public static final String HEADER_X_CACHE = "X-Cache";
public static final String HEADER_X_SECURITY_CONTEXT = "X-Security-Context"; public static final String HEADER_X_SECURITY_CONTEXT = "X-Security-Context";
public static final String POWERED_BY_HEADER = "X-Powered-By";
static { static {
CHARSET_UTF8 = Charset.forName(CHARSET_NAME_UTF8); CHARSET_UTF8 = Charset.forName(CHARSET_NAME_UTF8);

View File

@ -63,6 +63,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu2 {
@Qualifier("jpaProperties") @Qualifier("jpaProperties")
private Properties myJpaProperties; private Properties myJpaProperties;
@Override
@Bean() @Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();

View File

@ -66,6 +66,7 @@ public class FhirServerConfigDstu3 extends BaseJavaConfigDstu3 {
return retVal; return retVal;
} }
@Override
@Bean() @Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();

View File

@ -66,6 +66,7 @@ public class FhirServerConfigR4 extends BaseJavaConfigR4 {
return retVal; return retVal;
} }
@Override
@Bean() @Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();

View File

@ -28,6 +28,8 @@ import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl;
import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor; import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor;
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor; import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor; import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
import ca.uhn.fhir.jpa.util.IReindexController;
import ca.uhn.fhir.jpa.util.ReindexController;
import org.hibernate.jpa.HibernatePersistenceProvider; import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -48,6 +50,7 @@ import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
import org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean; import org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean;
import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import javax.annotation.Nonnull;
import javax.annotation.Resource; import javax.annotation.Resource;
@Configuration @Configuration
@ -63,14 +66,13 @@ public abstract class BaseConfig implements SchedulingConfigurer {
private ApplicationContext myAppCtx; private ApplicationContext myAppCtx;
@Override @Override
public void configureTasks(ScheduledTaskRegistrar theTaskRegistrar) { public void configureTasks(@Nonnull ScheduledTaskRegistrar theTaskRegistrar) {
theTaskRegistrar.setTaskScheduler(taskScheduler()); theTaskRegistrar.setTaskScheduler(taskScheduler());
} }
@Bean(autowire = Autowire.BY_TYPE) @Bean(autowire = Autowire.BY_TYPE)
public DatabaseBackedPagingProvider databaseBackedPagingProvider() { public DatabaseBackedPagingProvider databaseBackedPagingProvider() {
DatabaseBackedPagingProvider retVal = new DatabaseBackedPagingProvider(); return new DatabaseBackedPagingProvider();
return retVal;
} }
/** /**
@ -96,6 +98,11 @@ public abstract class BaseConfig implements SchedulingConfigurer {
return new HibernateJpaDialect(); return new HibernateJpaDialect();
} }
@Bean
private IReindexController reindexController() {
return new ReindexController();
}
@Bean() @Bean()
public ScheduledExecutorFactoryBean scheduledExecutorService() { public ScheduledExecutorFactoryBean scheduledExecutorService() {
ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean(); ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean();
@ -167,5 +174,4 @@ public abstract class BaseConfig implements SchedulingConfigurer {
return new PropertySourcesPlaceholderConfigurer(); return new PropertySourcesPlaceholderConfigurer();
} }
} }

View File

@ -32,6 +32,7 @@ import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.jpa.util.ExpungeOptions; import ca.uhn.fhir.jpa.util.ExpungeOptions;
import ca.uhn.fhir.jpa.util.ExpungeOutcome; import ca.uhn.fhir.jpa.util.ExpungeOutcome;
import ca.uhn.fhir.jpa.util.ReindexController;
import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils; import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils;
import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils; import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils;
import ca.uhn.fhir.model.api.*; import ca.uhn.fhir.model.api.*;
@ -46,7 +47,6 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
import ca.uhn.fhir.rest.server.method.SearchMethodBinding; import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.*; import ca.uhn.fhir.util.*;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.instance.model.api.*;
@ -61,6 +61,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.persistence.NoResultException; import javax.persistence.NoResultException;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
@ -88,6 +89,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
private String mySecondaryPrimaryKeyParamName; private String mySecondaryPrimaryKeyParamName;
@Autowired @Autowired
private ISearchParamRegistry mySearchParamRegistry; private ISearchParamRegistry mySearchParamRegistry;
@Autowired
private ReindexController myReindexController;
@Override @Override
public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) { public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
@ -97,7 +100,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
throw new ResourceNotFoundException(theId); throw new ResourceNotFoundException(theId);
} }
//@formatter:off
for (BaseTag next : new ArrayList<>(entity.getTags())) { for (BaseTag next : new ArrayList<>(entity.getTags())) {
if (ObjectUtil.equals(next.getTag().getTagType(), theTagType) && if (ObjectUtil.equals(next.getTag().getTagType(), theTagType) &&
ObjectUtil.equals(next.getTag().getSystem(), theScheme) && ObjectUtil.equals(next.getTag().getSystem(), theScheme) &&
@ -105,7 +107,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return; return;
} }
} }
//@formatter:on
entity.setHasTags(true); entity.setHasTags(true);
@ -459,7 +460,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return outcome; return outcome;
} }
private <MT extends IBaseMetaType> void doMetaAdd(MT theMetaAdd, BaseHasResource entity) { private <MT extends IBaseMetaType> void doMetaAdd(MT theMetaAdd, BaseHasResource entity) {
List<TagDefinition> tags = toTagList(theMetaAdd); List<TagDefinition> tags = toTagList(theMetaAdd);
@ -538,7 +538,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return doExpunge(getResourceName(), null, null, theExpungeOptions); return doExpunge(getResourceName(), null, null, theExpungeOptions);
} }
@Override @Override
public TagList getAllResourceTags(RequestDetails theRequestDetails) { public TagList getAllResourceTags(RequestDetails theRequestDetails) {
// Notify interceptors // Notify interceptors
@ -630,7 +629,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
Integer updatedCount = txTemplate.execute(new TransactionCallback<Integer>() { Integer updatedCount = txTemplate.execute(new TransactionCallback<Integer>() {
@Override @Override
public @NonNull public @NonNull
Integer doInTransaction(TransactionStatus theStatus) { Integer doInTransaction(@Nonnull TransactionStatus theStatus) {
return myResourceTableDao.markResourcesOfTypeAsRequiringReindexing(resourceType); return myResourceTableDao.markResourcesOfTypeAsRequiringReindexing(resourceType);
} }
}); });
@ -640,6 +639,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
mySearchParamRegistry.requestRefresh(); mySearchParamRegistry.requestRefresh();
myReindexController.requestReindex();
} }
@Override @Override

View File

@ -52,32 +52,6 @@ public class FhirResourceDaoSearchParameterDstu2 extends FhirResourceDaoDstu2<Se
markResourcesMatchingExpressionAsNeedingReindexing(reindex, expression); markResourcesMatchingExpressionAsNeedingReindexing(reindex, expression);
} }
/**
* This method is called once per minute to perform any required re-indexing. During most passes this will
* just check and find that there are no resources requiring re-indexing. In that case the method just returns
* immediately. If the search finds that some resources require reindexing, the system will do a bunch of
* reindexing and then return.
*/
@Override
@Scheduled(fixedDelay = DateUtils.MILLIS_PER_MINUTE)
@Transactional(propagation = Propagation.NEVER)
public void performReindexingPass() {
if (getConfig().isSchedulingDisabled()) {
return;
}
Integer count = mySystemDao.performReindexingPass(100);
for (int i = 0; i < 50 && count != null && count != 0; i++) {
count = mySystemDao.performReindexingPass(100);
try {
Thread.sleep(DateUtils.MILLIS_PER_SECOND);
} catch (InterruptedException e) {
break;
}
}
}
@Override @Override
protected void postPersist(ResourceTable theEntity, SearchParameter theResource) { protected void postPersist(ResourceTable theEntity, SearchParameter theResource) {
super.postPersist(theEntity, theResource); super.postPersist(theEntity, theResource);

View File

@ -24,6 +24,4 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
public interface IFhirResourceDaoSearchParameter<T extends IBaseResource> extends IFhirResourceDao<T> { public interface IFhirResourceDaoSearchParameter<T extends IBaseResource> extends IFhirResourceDao<T> {
void performReindexingPass();
} }

View File

@ -1,26 +1,16 @@
package ca.uhn.fhir.jpa.dao.dstu3; package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.BaseSearchParamExtractor;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSearchParameter; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSearchParameter;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry; import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4; import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4;
import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.ElementUtil;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.*;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
import static org.apache.commons.lang3.StringUtils.isBlank;
/* /*
* #%L * #%L
* HAPI FHIR JPA Server * HAPI FHIR JPA Server
@ -43,8 +33,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3<SearchParameter> implements IFhirResourceDaoSearchParameter<SearchParameter> { public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3<SearchParameter> implements IFhirResourceDaoSearchParameter<SearchParameter> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSearchParameterDstu3.class);
@Autowired @Autowired
private ISearchParamRegistry mySearchParamRegistry; private ISearchParamRegistry mySearchParamRegistry;
@ -57,31 +45,6 @@ public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3<Se
markResourcesMatchingExpressionAsNeedingReindexing(reindex, expression); markResourcesMatchingExpressionAsNeedingReindexing(reindex, expression);
} }
/**
* This method is called once per minute to perform any required re-indexing. During most passes this will
* just check and find that there are no resources requiring re-indexing. In that case the method just returns
* immediately. If the search finds that some resources require reindexing, the system will do multiple
* reindexing passes and then return.
*/
@Override
@Scheduled(fixedDelay = DateUtils.MILLIS_PER_MINUTE)
@Transactional(propagation = Propagation.NEVER)
public void performReindexingPass() {
if (getConfig().isSchedulingDisabled()) {
return;
}
Integer count = mySystemDao.performReindexingPass(100);
for (int i = 0; i < 50 && count != null && count != 0; i++) {
count = mySystemDao.performReindexingPass(100);
try {
Thread.sleep(DateUtils.MILLIS_PER_SECOND);
} catch (InterruptedException e) {
break;
}
}
}
@Override @Override
protected void postPersist(ResourceTable theEntity, SearchParameter theResource) { protected void postPersist(ResourceTable theEntity, SearchParameter theResource) {

View File

@ -8,13 +8,9 @@ import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.ElementUtil; import ca.uhn.fhir.util.ElementUtil;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.*;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
@ -51,31 +47,6 @@ public class FhirResourceDaoSearchParameterR4 extends FhirResourceDaoR4<SearchPa
markResourcesMatchingExpressionAsNeedingReindexing(reindex, expression); markResourcesMatchingExpressionAsNeedingReindexing(reindex, expression);
} }
/**
* This method is called once per minute to perform any required re-indexing. During most passes this will
* just check and find that there are no resources requiring re-indexing. In that case the method just returns
* immediately. If the search finds that some resources require reindexing, the system will do multiple
* reindexing passes and then return.
*/
@Override
@Scheduled(fixedDelay = DateUtils.MILLIS_PER_MINUTE)
@Transactional(propagation = Propagation.NEVER)
public void performReindexingPass() {
if (getConfig().isSchedulingDisabled()) {
return;
}
Integer count = mySystemDao.performReindexingPass(100);
for (int i = 0; i < 50 && count != null && count != 0; i++) {
count = mySystemDao.performReindexingPass(100);
try {
Thread.sleep(DateUtils.MILLIS_PER_SECOND);
} catch (InterruptedException e) {
break;
}
}
}
@Override @Override
protected void postPersist(ResourceTable theEntity, SearchParameter theResource) { protected void postPersist(ResourceTable theEntity, SearchParameter theResource) {

View File

@ -21,6 +21,12 @@ package ca.uhn.fhir.jpa.entity;
*/ */
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import org.hibernate.search.annotations.ContainedIn; import org.hibernate.search.annotations.ContainedIn;
import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.Field;
@ -30,6 +36,10 @@ import java.util.Date;
@MappedSuperclass @MappedSuperclass
public abstract class BaseResourceIndexedSearchParam implements Serializable { public abstract class BaseResourceIndexedSearchParam implements Serializable {
/** Don't change this without careful consideration. You will break existing hashes! */
private static final HashFunction HASH_FUNCTION = Hashing.murmur3_128(0);
/** Don't make this public 'cause nobody better touch it! */
private static final byte[] DELIMITER_BYTES = "|".getBytes(Charsets.UTF_8);
static final int MAX_SP_NAME = 100; static final int MAX_SP_NAME = 100;
@ -68,14 +78,23 @@ public abstract class BaseResourceIndexedSearchParam implements Serializable {
} }
public void setParamName(String theName) { public void setParamName(String theName) {
clearHashes();
myParamName = theName; myParamName = theName;
} }
/**
* Subclasses may override
*/
protected void clearHashes() {
// nothing
}
public ResourceTable getResource() { public ResourceTable getResource() {
return myResource; return myResource;
} }
public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) { public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
clearHashes();
myResource = theResource; myResource = theResource;
myResourceType = theResource.getResourceType(); myResourceType = theResource.getResourceType();
return this; return this;
@ -107,4 +126,23 @@ public abstract class BaseResourceIndexedSearchParam implements Serializable {
} }
public abstract IQueryParameterType toQueryParameterType(); public abstract IQueryParameterType toQueryParameterType();
/**
* Applies a fast and consistent hashing algorithm to a set of strings
*/
static long hash(String... theValues) {
Hasher hasher = HASH_FUNCTION.newHasher();
for (String next : theValues) {
next = UrlUtil.escapeUrlParam(next);
byte[] bytes = next.getBytes(Charsets.UTF_8);
hasher.putBytes(bytes);
hasher.putBytes(DELIMITER_BYTES);
}
HashCode hashCode = hasher.hash();
return hashCode.asLong();
}
} }

View File

@ -41,7 +41,6 @@ public class ResourceIndexedCompositeStringUnique implements Comparable<Resource
@Id @Id
@Column(name = "PID") @Column(name = "PID")
private Long myId; private Long myId;
@ManyToOne @ManyToOne
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_IDXCMPSTRUNIQ_RES_ID")) @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_IDXCMPSTRUNIQ_RES_ID"))
private ResourceTable myResource; private ResourceTable myResource;

View File

@ -29,7 +29,6 @@ import org.hibernate.search.annotations.Field;
import javax.persistence.*; import javax.persistence.*;
//@formatter:off
@Embeddable @Embeddable
@Entity @Entity
@Table(name = "HFJ_SPIDX_COORDS", indexes = { @Table(name = "HFJ_SPIDX_COORDS", indexes = {
@ -37,7 +36,6 @@ import javax.persistence.*;
@Index(name = "IDX_SP_COORDS_UPDATED", columnList = "SP_UPDATED"), @Index(name = "IDX_SP_COORDS_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_COORDS_RESID", columnList = "RES_ID") @Index(name = "IDX_SP_COORDS_RESID", columnList = "RES_ID")
}) })
//@formatter:on
public class ResourceIndexedSearchParamCoords extends BaseResourceIndexedSearchParam { public class ResourceIndexedSearchParamCoords extends BaseResourceIndexedSearchParam {
public static final int MAX_LENGTH = 100; public static final int MAX_LENGTH = 100;

View File

@ -33,6 +33,7 @@ import org.hibernate.search.annotations.NumericField;
import javax.persistence.*; import javax.persistence.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode;
//@formatter:off //@formatter:off
@Embeddable @Embeddable
@ -64,6 +65,16 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_QUANTITY") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_QUANTITY")
@Column(name = "SP_ID") @Column(name = "SP_ID")
private Long myId; private Long myId;
/**
* @since 3.4.0 - At some point this should be made not-null
*/
@Column(name = "HASH_UNITS_AND_VALPREFIX", nullable = true)
private Long myHashUnitsAndValPrefix;
/**
* @since 3.4.0 - At some point this should be made not-null
*/
@Column(name = "HASH_VALPREFIX", nullable = true)
private Long myHashValPrefix;
public ResourceIndexedSearchParamQuantity() { public ResourceIndexedSearchParamQuantity() {
// nothing // nothing
@ -76,6 +87,20 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
setUnits(theUnits); setUnits(theUnits);
} }
@PrePersist
public void calculateHashes() {
if (myHashUnitsAndValPrefix == null) {
setHashUnitsAndValPrefix(hash(getResourceType(), getParamName(), getSystem(), getUnits(), toTruncatedString(getValue())));
setHashValPrefix(hash(getResourceType(), getParamName(), toTruncatedString(getValue())));
}
}
@Override
protected void clearHashes() {
myHashUnitsAndValPrefix = null;
myHashValPrefix = null;
}
@Override @Override
public boolean equals(Object theObj) { public boolean equals(Object theObj) {
if (this == theObj) { if (this == theObj) {
@ -94,9 +119,29 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
b.append(getSystem(), obj.getSystem()); b.append(getSystem(), obj.getSystem());
b.append(getUnits(), obj.getUnits()); b.append(getUnits(), obj.getUnits());
b.append(getValue(), obj.getValue()); b.append(getValue(), obj.getValue());
b.append(getHashUnitsAndValPrefix(), obj.getHashUnitsAndValPrefix());
b.append(getHashValPrefix(), obj.getHashValPrefix());
return b.isEquals(); return b.isEquals();
} }
public Long getHashUnitsAndValPrefix() {
calculateHashes();
return myHashUnitsAndValPrefix;
}
public void setHashUnitsAndValPrefix(Long theHashUnitsAndValPrefix) {
myHashUnitsAndValPrefix = theHashUnitsAndValPrefix;
}
public Long getHashValPrefix() {
calculateHashes();
return myHashValPrefix;
}
public void setHashValPrefix(Long theHashValPrefix) {
myHashValPrefix = theHashValPrefix;
}
@Override @Override
protected Long getId() { protected Long getId() {
return myId; return myId;
@ -107,6 +152,7 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
} }
public void setSystem(String theSystem) { public void setSystem(String theSystem) {
clearHashes();
mySystem = theSystem; mySystem = theSystem;
} }
@ -115,6 +161,7 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
} }
public void setUnits(String theUnits) { public void setUnits(String theUnits) {
clearHashes();
myUnits = theUnits; myUnits = theUnits;
} }
@ -123,6 +170,7 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
} }
public void setValue(BigDecimal theValue) { public void setValue(BigDecimal theValue) {
clearHashes();
myValue = theValue; myValue = theValue;
} }
@ -134,6 +182,8 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
b.append(getSystem()); b.append(getSystem());
b.append(getUnits()); b.append(getUnits());
b.append(getValue()); b.append(getValue());
b.append(getHashUnitsAndValPrefix());
b.append(getHashValPrefix());
return b.toHashCode(); return b.toHashCode();
} }
@ -153,4 +203,8 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
return b.build(); return b.build();
} }
private static String toTruncatedString(BigDecimal theValue) {
return theValue.setScale(0, RoundingMode.FLOOR).toPlainString();
}
} }

View File

@ -32,6 +32,8 @@ import org.hibernate.search.annotations.*;
import javax.persistence.*; import javax.persistence.*;
import javax.persistence.Index; import javax.persistence.Index;
import static org.apache.commons.lang3.StringUtils.left;
//@formatter:off //@formatter:off
@Embeddable @Embeddable
@Entity @Entity
@ -91,9 +93,8 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
* Note that MYSQL chokes on unique indexes for lengths > 255 so be careful here * Note that MYSQL chokes on unique indexes for lengths > 255 so be careful here
*/ */
public static final int MAX_LENGTH = 200; public static final int MAX_LENGTH = 200;
public static final int HASH_PREFIX_LENGTH = 1;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Id @Id
@SequenceGenerator(name = "SEQ_SPIDX_STRING", sequenceName = "SEQ_SPIDX_STRING") @SequenceGenerator(name = "SEQ_SPIDX_STRING", sequenceName = "SEQ_SPIDX_STRING")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_STRING") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_STRING")
@ -116,6 +117,16 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
@Column(name = "SP_VALUE_NORMALIZED", length = MAX_LENGTH, nullable = true) @Column(name = "SP_VALUE_NORMALIZED", length = MAX_LENGTH, nullable = true)
private String myValueNormalized; private String myValueNormalized;
/**
* @since 3.4.0 - At some point this should be made not-null
*/
@Column(name = "HASH_NORM_PREFIX", nullable = true)
private Long myHashNormalizedPrefix;
/**
* @since 3.4.0 - At some point this should be made not-null
*/
@Column(name = "HASH_EXACT", nullable = true)
private Long myHashExact;
public ResourceIndexedSearchParamString() { public ResourceIndexedSearchParamString() {
super(); super();
@ -128,6 +139,20 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
setValueExact(theValueExact); setValueExact(theValueExact);
} }
@PrePersist
public void calculateHashes() {
if (myHashNormalizedPrefix == null) {
setHashNormalizedPrefix(hash(getResourceType(), getParamName(), left(getValueNormalized(), HASH_PREFIX_LENGTH)));
setHashExact(hash(getResourceType(), getParamName(), getValueExact()));
}
}
@Override
protected void clearHashes() {
myHashNormalizedPrefix = null;
myHashExact = null;
}
@Override @Override
public boolean equals(Object theObj) { public boolean equals(Object theObj) {
if (this == theObj) { if (this == theObj) {
@ -144,9 +169,29 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
b.append(getParamName(), obj.getParamName()); b.append(getParamName(), obj.getParamName());
b.append(getResource(), obj.getResource()); b.append(getResource(), obj.getResource());
b.append(getValueExact(), obj.getValueExact()); b.append(getValueExact(), obj.getValueExact());
b.append(getHashNormalizedPrefix(), obj.getHashNormalizedPrefix());
b.append(getHashExact(), obj.getHashExact());
return b.isEquals(); return b.isEquals();
} }
public Long getHashExact() {
calculateHashes();
return myHashExact;
}
public void setHashExact(Long theHashExact) {
myHashExact = theHashExact;
}
public Long getHashNormalizedPrefix() {
calculateHashes();
return myHashNormalizedPrefix;
}
public void setHashNormalizedPrefix(Long theHashNormalizedPrefix) {
myHashNormalizedPrefix = theHashNormalizedPrefix;
}
@Override @Override
protected Long getId() { protected Long getId() {
return myId; return myId;
@ -180,6 +225,8 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
b.append(getParamName()); b.append(getParamName());
b.append(getResource()); b.append(getResource());
b.append(getValueExact()); b.append(getValueExact());
b.append(getHashNormalizedPrefix());
b.append(getHashExact());
return b.toHashCode(); return b.toHashCode();
} }

View File

@ -31,7 +31,6 @@ import org.hibernate.search.annotations.Field;
import javax.persistence.*; import javax.persistence.*;
//@formatter:off
@Embeddable @Embeddable
@Entity @Entity
@Table(name = "HFJ_SPIDX_TOKEN", indexes = { @Table(name = "HFJ_SPIDX_TOKEN", indexes = {
@ -40,12 +39,12 @@ import javax.persistence.*;
@Index(name = "IDX_SP_TOKEN_UPDATED", columnList = "SP_UPDATED"), @Index(name = "IDX_SP_TOKEN_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_TOKEN_RESID", columnList = "RES_ID") @Index(name = "IDX_SP_TOKEN_RESID", columnList = "RES_ID")
}) })
//@formatter:on
public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchParam { public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchParam {
public static final int MAX_LENGTH = 200; public static final int MAX_LENGTH = 200;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Field() @Field()
@Column(name = "SP_SYSTEM", nullable = true, length = MAX_LENGTH) @Column(name = "SP_SYSTEM", nullable = true, length = MAX_LENGTH)
public String mySystem; public String mySystem;
@ -57,16 +56,58 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_TOKEN") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_TOKEN")
@Column(name = "SP_ID") @Column(name = "SP_ID")
private Long myId; private Long myId;
/**
* @since 3.4.0 - At some point this should be made not-null
*/
@Column(name = "HASH_SYS", nullable = true)
private Long myHashSystem;
/**
* @since 3.4.0 - At some point this should be made not-null
*/
@Column(name = "HASH_SYS_AND_VALUE", nullable = true)
private Long myHashSystemAndValue;
/**
* @since 3.4.0 - At some point this should be made not-null
*/
@Column(name = "HASH_VALUE", nullable = true)
private Long myHashValue;
/**
* Constructor
*/
public ResourceIndexedSearchParamToken() { public ResourceIndexedSearchParamToken() {
super();
} }
/**
* Constructor
*/
public ResourceIndexedSearchParamToken(String theName, String theSystem, String theValue) { public ResourceIndexedSearchParamToken(String theName, String theSystem, String theValue) {
super();
setParamName(theName); setParamName(theName);
setSystem(theSystem); setSystem(theSystem);
setValue(theValue); setValue(theValue);
} }
@PrePersist
public void calculateHashes() {
if (myHashSystem == null) {
setHashSystem(hash(getResourceType(), getParamName(), getSystem()));
setHashSystemAndValue(hash(getResourceType(), getParamName(), getSystem(), getValue()));
setHashValue(hash(getResourceType(), getParamName(), getValue()));
}
}
@Override
protected void clearHashes() {
myHashSystem = null;
myHashSystemAndValue = null;
myHashValue = null;
}
@Override @Override
public boolean equals(Object theObj) { public boolean equals(Object theObj) {
if (this == theObj) { if (this == theObj) {
@ -84,9 +125,40 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
b.append(getResource(), obj.getResource()); b.append(getResource(), obj.getResource());
b.append(getSystem(), obj.getSystem()); b.append(getSystem(), obj.getSystem());
b.append(getValue(), obj.getValue()); b.append(getValue(), obj.getValue());
b.append(getHashSystem(), obj.getHashSystem());
b.append(getHashSystemAndValue(), obj.getHashSystemAndValue());
b.append(getHashValue(), obj.getHashValue());
return b.isEquals(); return b.isEquals();
} }
public Long getHashSystem() {
calculateHashes();
return myHashSystem;
}
public void setHashSystem(Long theHashSystem) {
myHashSystem = theHashSystem;
}
public Long getHashSystemAndValue() {
calculateHashes();
return myHashSystemAndValue;
}
public void setHashSystemAndValue(Long theHashSystemAndValue) {
calculateHashes();
myHashSystemAndValue = theHashSystemAndValue;
}
public Long getHashValue() {
calculateHashes();
return myHashValue;
}
public void setHashValue(Long theHashValue) {
myHashValue = theHashValue;
}
@Override @Override
protected Long getId() { protected Long getId() {
return myId; return myId;
@ -97,6 +169,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
} }
public void setSystem(String theSystem) { public void setSystem(String theSystem) {
clearHashes();
mySystem = StringUtils.defaultIfBlank(theSystem, null); mySystem = StringUtils.defaultIfBlank(theSystem, null);
} }
@ -105,6 +178,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
} }
public void setValue(String theValue) { public void setValue(String theValue) {
clearHashes();
myValue = StringUtils.defaultIfBlank(theValue, null); myValue = StringUtils.defaultIfBlank(theValue, null);
} }
@ -115,9 +189,13 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
b.append(getResource()); b.append(getResource());
b.append(getSystem()); b.append(getSystem());
b.append(getValue()); b.append(getValue());
b.append(getHashSystem());
b.append(getHashSystemAndValue());
b.append(getHashValue());
return b.toHashCode(); return b.toHashCode();
} }
@Override @Override
public IQueryParameterType toQueryParameterType() { public IQueryParameterType toQueryParameterType() {
return new TokenParam(getSystem(), getValue()); return new TokenParam(getSystem(), getValue());

View File

@ -30,7 +30,6 @@ import org.hibernate.search.annotations.Field;
import javax.persistence.*; import javax.persistence.*;
//@formatter:off
@Embeddable @Embeddable
@Entity @Entity
@Table(name = "HFJ_SPIDX_URI", indexes = { @Table(name = "HFJ_SPIDX_URI", indexes = {
@ -39,7 +38,6 @@ import javax.persistence.*;
@Index(name = "IDX_SP_URI_UPDATED", columnList = "SP_UPDATED"), @Index(name = "IDX_SP_URI_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_URI_COORDS", columnList = "RES_ID") @Index(name = "IDX_SP_URI_COORDS", columnList = "RES_ID")
}) })
//@formatter:on
public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchParam { public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchParam {
/* /*
@ -56,15 +54,38 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_URI") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_URI")
@Column(name = "SP_ID") @Column(name = "SP_ID")
private Long myId; private Long myId;
/**
* @since 3.4.0 - At some point this should be made not-null
*/
@Column(name = "HASH_URI", nullable = true)
private Long myHashUri;
/**
* Constructor
*/
public ResourceIndexedSearchParamUri() { public ResourceIndexedSearchParamUri() {
} }
/**
* Constructor
*/
public ResourceIndexedSearchParamUri(String theName, String theUri) { public ResourceIndexedSearchParamUri(String theName, String theUri) {
setParamName(theName); setParamName(theName);
setUri(theUri); setUri(theUri);
} }
@PrePersist
public void calculateHashes() {
if (myHashUri == null) {
setHashUri(hash(getResourceType(), getParamName(), getUri()));
}
}
@Override
protected void clearHashes() {
myHashUri = null;
}
@Override @Override
public boolean equals(Object theObj) { public boolean equals(Object theObj) {
if (this == theObj) { if (this == theObj) {
@ -81,9 +102,19 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
b.append(getParamName(), obj.getParamName()); b.append(getParamName(), obj.getParamName());
b.append(getResource(), obj.getResource()); b.append(getResource(), obj.getResource());
b.append(getUri(), obj.getUri()); b.append(getUri(), obj.getUri());
b.append(getHashUri(), obj.getHashUri());
return b.isEquals(); return b.isEquals();
} }
public Long getHashUri() {
calculateHashes();
return myHashUri;
}
public void setHashUri(Long theHashUri) {
myHashUri = theHashUri;
}
@Override @Override
protected Long getId() { protected Long getId() {
return myId; return myId;
@ -103,6 +134,7 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
b.append(getParamName()); b.append(getParamName());
b.append(getResource()); b.append(getResource());
b.append(getUri()); b.append(getUri());
b.append(getHashUri());
return b.toHashCode(); return b.toHashCode();
} }

View File

@ -434,8 +434,9 @@ public class ResourceTable extends BaseHasResource implements Serializable {
return myResourceType; return myResourceType;
} }
public void setResourceType(String theResourceType) { public ResourceTable setResourceType(String theResourceType) {
myResourceType = theResourceType; myResourceType = theResourceType;
return this;
} }
@Override @Override

View File

@ -0,0 +1,5 @@
package ca.uhn.fhir.jpa.util;
public interface IReindexController {
void requestReindex();
}

View File

@ -0,0 +1,90 @@
package ca.uhn.fhir.jpa.util;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.Semaphore;
public class ReindexController implements IReindexController {
private static final Logger ourLog = LoggerFactory.getLogger(ReindexController.class);
private final Semaphore myReindexingLock = new Semaphore(1);
@Autowired
private DaoConfig myDaoConfig;
@Autowired
private IFhirSystemDao<?, ?> mySystemDao;
private Long myDontReindexUntil;
/**
* This method is called once per minute to perform any required re-indexing.
* <p>
* If nothing if found that requires reindexing, the query will not fire again for
* a longer amount of time.
* <p>
* During most passes this will just check and find that there are no resources
* requiring re-indexing. In that case the method just returns immediately.
* If the search finds that some resources require reindexing, the system will
* do a bunch of reindexing and then return.
*/
@Scheduled(fixedDelay = DateUtils.MILLIS_PER_MINUTE)
@Transactional(propagation = Propagation.NEVER)
public void performReindexingPass() {
if (myDaoConfig.isSchedulingDisabled()) {
return;
}
synchronized (this) {
if (myDontReindexUntil == null && myDontReindexUntil > System.currentTimeMillis()) {
return;
}
}
if (!myReindexingLock.tryAcquire()) {
ourLog.trace("Not going to reindex in parallel threads");
return;
}
Integer count;
try {
count = mySystemDao.performReindexingPass(100);
for (int i = 0; i < 50 && count != null && count != 0; i++) {
count = mySystemDao.performReindexingPass(100);
try {
Thread.sleep(DateUtils.MILLIS_PER_SECOND);
} catch (InterruptedException e) {
break;
}
}
} finally {
myReindexingLock.release();
}
synchronized (this) {
if (count == null) {
myDontReindexUntil = System.currentTimeMillis() + DateUtils.MILLIS_PER_HOUR;
} else {
myDontReindexUntil = null;
}
}
}
/**
* Calling this will cause a reindex loop to be triggered sooner that it would otherwise
*/
@Override
public void requestReindex() {
synchronized (this) {
myDontReindexUntil = null;
}
}
}

View File

@ -133,6 +133,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
return retVal; return retVal;
} }
@Override
@Bean() @Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();

View File

@ -115,6 +115,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
return dataSource; return dataSource;
} }
@Override
@Bean() @Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();

View File

@ -0,0 +1,38 @@
package ca.uhn.fhir.jpa.entity;
import org.junit.Test;
import java.math.BigDecimal;
import static org.junit.Assert.*;
public class ResourceIndexedSearchParamQuantityTest {
private ResourceIndexedSearchParamQuantity createParam(String theParamName, String theValue, String theSystem, String theUnits) {
ResourceIndexedSearchParamQuantity token = new ResourceIndexedSearchParamQuantity(theParamName, new BigDecimal(theValue), theSystem, theUnits);
token.setResource(new ResourceTable().setResourceType("Patient"));
return token;
}
@Test
public void testHashFunctions() {
ResourceIndexedSearchParamQuantity token = createParam("NAME", "123.001", "value", "VALUE");
// Make sure our hashing function gives consistent results
assertEquals(945335027461836896L, token.getHashUnitsAndValPrefix().longValue());
assertEquals(5549105497508660145L, token.getHashValPrefix().longValue());
}
@Test
public void testValueTrimming() {
assertEquals(7265149425397186226L, createParam("NAME", "401.001", "value", "VALUE").getHashUnitsAndValPrefix().longValue());
assertEquals(7265149425397186226L, createParam("NAME", "401.99999", "value", "VALUE").getHashUnitsAndValPrefix().longValue());
assertEquals(7265149425397186226L, createParam("NAME", "401", "value", "VALUE").getHashUnitsAndValPrefix().longValue());
// Should be different
assertEquals(-8387917096585386046L, createParam("NAME", "400.9999999", "value", "VALUE").getHashUnitsAndValPrefix().longValue());
// Should be different
assertEquals(8819656626732693650L, createParam("NAME", "402.000000", "value", "VALUE").getHashUnitsAndValPrefix().longValue());
}
}

View File

@ -0,0 +1,31 @@
package ca.uhn.fhir.jpa.entity;
import org.junit.Test;
import static org.junit.Assert.*;
public class ResourceIndexedSearchParamStringTest {
@Test
public void testHashFunctions() {
ResourceIndexedSearchParamString token = new ResourceIndexedSearchParamString("NAME", "value", "VALUE");
token.setResource(new ResourceTable().setResourceType("Patient"));
// Make sure our hashing function gives consistent results
assertEquals(6598082761639188617L, token.getHashNormalizedPrefix().longValue());
assertEquals(-1970227166134682431L, token.getHashExact().longValue());
}
@Test
public void testHashFunctionsPrefixOnly() {
ResourceIndexedSearchParamString token = new ResourceIndexedSearchParamString("NAME", "vZZZZZZZZZZZZZZZZ", "VZZZZZZzzzZzzzZ");
token.setResource(new ResourceTable().setResourceType("Patient"));
// Should be the same as in testHashFunctions()
assertEquals(6598082761639188617L, token.getHashNormalizedPrefix().longValue());
// Should be different from testHashFunctions()
assertEquals(-1970227166134682431L, token.getHashExact().longValue());
}
}

View File

@ -0,0 +1,31 @@
package ca.uhn.fhir.jpa.entity;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ResourceIndexedSearchParamTokenTest {
@Test
public void testHashFunctions() {
ResourceIndexedSearchParamToken token = new ResourceIndexedSearchParamToken("NAME", "SYSTEM", "VALUE");
token.setResource(new ResourceTable().setResourceType("Patient"));
// Make sure our hashing function gives consistent results
assertEquals(-8558989679010582575L, token.getHashSystem().longValue());
assertEquals(-8644532105141886455L, token.getHashSystemAndValue().longValue());
assertEquals(-1970227166134682431L, token.getHashValue().longValue());
}
@Test
public void testHashFunctionsWithOverlapNames() {
ResourceIndexedSearchParamToken token = new ResourceIndexedSearchParamToken("NAME", "SYSTEM", "VALUE");
token.setResource(new ResourceTable().setResourceType("Patient"));
// Make sure our hashing function gives consistent results
assertEquals(-8558989679010582575L, token.getHashSystem().longValue());
assertEquals(-8644532105141886455L, token.getHashSystemAndValue().longValue());
assertEquals(-1970227166134682431L, token.getHashValue().longValue());
}
}

View File

@ -0,0 +1,19 @@
package ca.uhn.fhir.jpa.entity;
import org.junit.Test;
import static org.junit.Assert.*;
public class ResourceIndexedSearchParamUriTest {
@Test
public void testHashFunctions() {
ResourceIndexedSearchParamUri token = new ResourceIndexedSearchParamUri("NAME", "http://example.com");
token.setResource(new ResourceTable().setResourceType("Patient"));
// Make sure our hashing function gives consistent results
assertEquals(-6132951326739875838L, token.getHashUri().longValue());
}
}

View File

@ -57,6 +57,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu3 {
return retVal; return retVal;
} }
@Override
@Bean() @Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();

View File

@ -58,6 +58,7 @@ public class FhirServerConfigDstu2 extends BaseJavaConfigDstu2 {
return retVal; return retVal;
} }
@Override
@Bean() @Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();

View File

@ -53,6 +53,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu3 {
return retVal; return retVal;
} }
@Override
@Bean() @Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();

View File

@ -90,6 +90,7 @@ public class TdlDstu2Config extends BaseJavaConfigDstu2 {
return retVal; return retVal;
} }
@Override
@Bean() @Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();

View File

@ -70,6 +70,7 @@ public class TdlDstu3Config extends BaseJavaConfigDstu3 {
return retVal; return retVal;
} }
@Override
@Bean() @Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();

View File

@ -92,6 +92,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
return retVal; return retVal;
} }
@Override
@Bean() @Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();

View File

@ -91,6 +91,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
return retVal; return retVal;
} }
@Override
@Bean() @Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();

View File

@ -86,6 +86,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
return retVal; return retVal;
} }
@Override
@Bean() @Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();

View File

@ -119,7 +119,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/** /**
* This is configurable but by default we just use HAPI version * This is configurable but by default we just use HAPI version
*/ */
private String myServerVersion = VersionUtil.getVersion(); private String myServerVersion = createPoweredByHeaderProductVersion();
private boolean myStarted; private boolean myStarted;
private Map<String, IResourceProvider> myTypeToProvider = new HashMap<>(); private Map<String, IResourceProvider> myTypeToProvider = new HashMap<>();
private boolean myUncompressIncomingContents = true; private boolean myUncompressIncomingContents = true;
@ -159,7 +159,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
*/ */
public void addHeadersToResponse(HttpServletResponse theHttpResponse) { public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
String b = createPoweredByHeader(); String b = createPoweredByHeader();
theHttpResponse.addHeader("X-Powered-By", b); theHttpResponse.addHeader(Constants.POWERED_BY_HEADER, b);
} }
private void addLocationHeader(RequestDetails theRequest, HttpServletResponse theResponse, MethodOutcome response, String headerLocation, String resourceName) { private void addLocationHeader(RequestDetails theRequest, HttpServletResponse theResponse, MethodOutcome response, String headerLocation, String resourceName) {
@ -213,12 +213,21 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
return Lists.newArrayList("FHIR Server", "FHIR " + myFhirContext.getVersion().getVersion().getFhirVersionString() + "/" + myFhirContext.getVersion().getVersion().name()); return Lists.newArrayList("FHIR Server", "FHIR " + myFhirContext.getVersion().getVersion().getFhirVersionString() + "/" + myFhirContext.getVersion().getVersion().name());
} }
/**
* Subclasses may override to provide their own powered by
* header. Note that if you want to be nice and still credit HAPI
* FHIR you could consider overriding
* {@link #createPoweredByAttributes()} instead and adding your own
* fragments to the list.
*/
protected String createPoweredByHeader() { protected String createPoweredByHeader() {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
b.append(createPoweredByHeaderProductName()); b.append(createPoweredByHeaderProductName());
b.append(" "); b.append(" ");
b.append(VersionUtil.getVersion()); b.append(createPoweredByHeaderProductVersion());
b.append(" REST Server ("); b.append(" ");
b.append(createPoweredByHeaderComponentName());
b.append(" (");
List<String> poweredByAttributes = createPoweredByAttributes(); List<String> poweredByAttributes = createPoweredByAttributes();
for (ListIterator<String> iter = poweredByAttributes.listIterator(); iter.hasNext(); ) { for (ListIterator<String> iter = poweredByAttributes.listIterator(); iter.hasNext(); ) {
@ -232,10 +241,33 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
return b.toString(); return b.toString();
} }
/**
* Subclasses my override
*
* @see #createPoweredByHeader()
*/
protected String createPoweredByHeaderComponentName() {
return "REST Server";
}
/**
* Subclasses my override
*
* @see #createPoweredByHeader()
*/
protected String createPoweredByHeaderProductName() { protected String createPoweredByHeaderProductName() {
return "HAPI FHIR"; return "HAPI FHIR";
} }
/**
* Subclasses my override
*
* @see #createPoweredByHeader()
*/
protected String createPoweredByHeaderProductVersion() {
return VersionUtil.getVersion();
}
@Override @Override
public void destroy() { public void destroy() {
if (getResourceProviders() != null) { if (getResourceProviders() != null) {
@ -539,10 +571,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
* *
* @param theList The list of interceptors (may be null) * @param theList The list of interceptors (may be null)
*/ */
public void setInterceptors(IServerInterceptor... theList) { public void setInterceptors(List<IServerInterceptor> theList) {
myInterceptors.clear(); myInterceptors.clear();
if (theList != null) { if (theList != null) {
myInterceptors.addAll(Arrays.asList(theList)); myInterceptors.addAll(theList);
} }
} }
@ -572,11 +604,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
* *
* @see #setResourceProviders(Collection) * @see #setResourceProviders(Collection)
*/ */
public void setPlainProviders(Collection<Object> theProviders) { public void setPlainProviders(Object... theProv) {
myPlainProviders.clear(); setPlainProviders(Arrays.asList(theProv));
if (theProviders != null) {
myPlainProviders.addAll(theProviders);
}
} }
/** /**
@ -606,10 +635,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/** /**
* Sets the resource providers for this server * Sets the resource providers for this server
*/ */
public void setResourceProviders(Collection<IResourceProvider> theResourceProviders) { public void setResourceProviders(IResourceProvider... theResourceProviders) {
myResourceProviders.clear(); myResourceProviders.clear();
if (theResourceProviders != null) { if (theResourceProviders != null) {
myResourceProviders.addAll(theResourceProviders); myResourceProviders.addAll(Arrays.asList(theResourceProviders));
} }
} }
@ -632,8 +661,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/** /**
* Returns the server base URL (with no trailing '/') for a given request * Returns the server base URL (with no trailing '/') for a given request
*
* @param theRequest
*/ */
public String getServerBaseForRequest(ServletRequestDetails theRequest) { public String getServerBaseForRequest(ServletRequestDetails theRequest) {
String fhirServerBase; String fhirServerBase;
@ -694,9 +721,9 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
// passing the server into the constructor. Having that sort // passing the server into the constructor. Having that sort
// of cross linkage causes reference cycles in Spring wiring // of cross linkage causes reference cycles in Spring wiring
try { try {
Method setRestfulServer = theServerConformanceProvider.getClass().getMethod("setRestfulServer", new Class[] {RestfulServer.class}); Method setRestfulServer = theServerConformanceProvider.getClass().getMethod("setRestfulServer", RestfulServer.class);
if (setRestfulServer != null) { if (setRestfulServer != null) {
setRestfulServer.invoke(theServerConformanceProvider, new Object[] {this}); setRestfulServer.invoke(theServerConformanceProvider, this);
} }
} catch (Exception e) { } catch (Exception e) {
ourLog.warn("Error calling IServerConformanceProvider.setRestfulServer", e); ourLog.warn("Error calling IServerConformanceProvider.setRestfulServer", e);
@ -1011,9 +1038,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
invokeInitialize(iResourceProvider); invokeInitialize(iResourceProvider);
} }
} }
if (confProvider != null) {
invokeInitialize(confProvider); invokeInitialize(confProvider);
}
if (getPlainProviders() != null) { if (getPlainProviders() != null) {
for (Object next : getPlainProviders()) { for (Object next : getPlainProviders()) {
invokeInitialize(next); invokeInitialize(next);
@ -1300,7 +1326,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
* *
* @param providers a {@code Collection} of providers. The parameter could be null or an empty {@code Collection} * @param providers a {@code Collection} of providers. The parameter could be null or an empty {@code Collection}
*/ */
public void registerProviders(Collection<? extends Object> providers) { public void registerProviders(Collection<?> providers) {
myProviderRegistrationMutex.lock(); myProviderRegistrationMutex.lock();
try { try {
if (!myStarted) { if (!myStarted) {
@ -1323,9 +1349,9 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/* /*
* Inner method to actually register providers * Inner method to actually register providers
*/ */
protected void registerProviders(Collection<? extends Object> providers, boolean inInit) { protected void registerProviders(Collection<?> providers, boolean inInit) {
List<IResourceProvider> newResourceProviders = new ArrayList<IResourceProvider>(); List<IResourceProvider> newResourceProviders = new ArrayList<>();
List<Object> newPlainProviders = new ArrayList<Object>(); List<Object> newPlainProviders = new ArrayList<>();
ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext()); ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
if (providers != null) { if (providers != null) {
@ -1392,7 +1418,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
ourLog.info("Removing RESTful methods for: {}", theProvider.getClass()); ourLog.info("Removing RESTful methods for: {}", theProvider.getClass());
Class<?> clazz = theProvider.getClass(); Class<?> clazz = theProvider.getClass();
Class<?> supertype = clazz.getSuperclass(); Class<?> supertype = clazz.getSuperclass();
Collection<String> resourceNames = new ArrayList<String>(); Collection<String> resourceNames = new ArrayList<>();
while (!Object.class.equals(supertype)) { while (!Object.class.equals(supertype)) {
removeResourceMethods(theProvider, supertype, resourceNames); removeResourceMethods(theProvider, supertype, resourceNames);
supertype = supertype.getSuperclass(); supertype = supertype.getSuperclass();
@ -1468,12 +1494,18 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
case OPTIONS: case OPTIONS:
doOptions(theReq, theResp); doOptions(theReq, theResp);
break; break;
case PATCH:
break;
case POST: case POST:
doPost(theReq, theResp); doPost(theReq, theResp);
break; break;
case PUT: case PUT:
doPut(theReq, theResp); doPut(theReq, theResp);
break; break;
case TRACE:
case TRACK:
case HEAD:
case CONNECT:
default: default:
handleRequest(method, theReq, theResp); handleRequest(method, theReq, theResp);
break; break;
@ -1485,10 +1517,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
* *
* @param theList The list of interceptors (may be null) * @param theList The list of interceptors (may be null)
*/ */
public void setInterceptors(List<IServerInterceptor> theList) { public void setInterceptors(IServerInterceptor... theList) {
myInterceptors.clear(); myInterceptors.clear();
if (theList != null) { if (theList != null) {
myInterceptors.addAll(theList); myInterceptors.addAll(Arrays.asList(theList));
} }
} }
@ -1497,8 +1529,11 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
* *
* @see #setResourceProviders(Collection) * @see #setResourceProviders(Collection)
*/ */
public void setPlainProviders(Object... theProv) { public void setPlainProviders(Collection<Object> theProviders) {
setPlainProviders(Arrays.asList(theProv)); myPlainProviders.clear();
if (theProviders != null) {
myPlainProviders.addAll(theProviders);
}
} }
/** /**
@ -1516,10 +1551,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/** /**
* Sets the resource providers for this server * Sets the resource providers for this server
*/ */
public void setResourceProviders(IResourceProvider... theResourceProviders) { public void setResourceProviders(Collection<IResourceProvider> theResourceProviders) {
myResourceProviders.clear(); myResourceProviders.clear();
if (theResourceProviders != null) { if (theResourceProviders != null) {
myResourceProviders.addAll(Arrays.asList(theResourceProviders)); myResourceProviders.addAll(theResourceProviders);
} }
} }
@ -1539,13 +1574,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/** /**
* Unregister one provider (either a Resource provider or a plain provider) * Unregister one provider (either a Resource provider or a plain provider)
*
* @param provider
* @throws Exception
*/ */
public void unregisterProvider(Object provider) throws Exception { public void unregisterProvider(Object provider) {
if (provider != null) { if (provider != null) {
Collection<Object> providerList = new ArrayList<Object>(1); Collection<Object> providerList = new ArrayList<>(1);
providerList.add(provider); providerList.add(provider);
unregisterProviders(providerList); unregisterProviders(providerList);
} }
@ -1553,11 +1585,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/** /**
* Unregister a {@code Collection} of providers * Unregister a {@code Collection} of providers
*
* @param providers
* @throws Exception
*/ */
public void unregisterProviders(Collection<? extends Object> providers) throws Exception { public void unregisterProviders(Collection<?> providers) {
ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext()); ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
if (providers != null) { if (providers != null) {
for (Object provider : providers) { for (Object provider : providers) {

View File

@ -128,9 +128,7 @@ public class SearchR4Test {
@Test @Test
public void testIncludeSingleParameter() throws Exception { public void testIncludeSingleParameter() throws Exception {
HttpGet httpGet; HttpGet httpGet;
String linkNext;
Bundle bundle; Bundle bundle;
String linkSelf;
// No include specified // No include specified
httpGet = new HttpGet("http://localhost:" + ourPort + "/MedicationRequest"); httpGet = new HttpGet("http://localhost:" + ourPort + "/MedicationRequest");

View File

@ -17,6 +17,20 @@
</ul> </ul>
]]> ]]>
</action> </action>
<action type="add">
Several enhancements have been made to the JPA server index
tables. These enhancements consist of new colums that will be
used in a future version of HAPI FHIR to significantly decrease
the amount of space required for indexes on token and string index
types.
<![CDATA[<br/><br/>]]>
These new columns are not yet used in HAPI FHIR 3.4.0 but will be
enabled in HAPI FHIR 3.5.0. Anyone upgrading to HAPI FHIR 3.4.0 (or above)
is recommended to invoke the following SQL statement on their
database in order to reindex all data in a background job:
<![CDATA[<br/>]]>
<![CDATA[<code>update HFJ_RESOURCE set SP_INDEX_STATUS = null;</code>]]>
</action>
<action type="add"> <action type="add">
R4 structures have been updated to the latest definitions R4 structures have been updated to the latest definitions
(SVN 13732) (SVN 13732)
@ -226,6 +240,11 @@
An issue in the narrative generator template for the CodeableConcept An issue in the narrative generator template for the CodeableConcept
datatype was corrected. Thanks to @RuthAlk for the pull request! datatype was corrected. Thanks to @RuthAlk for the pull request!
</action> </action>
<action type="add">
The JPA server automatic reindexing process has been tweaked so that it no
longer runs once per minute (this was a heavy strain on large databases)
but will instead run once an hour unless triggered for some reason.
</action>
</release> </release>
<release version="3.3.0" date="2018-03-29"> <release version="3.3.0" date="2018-03-29">
<action type="add"> <action type="add">