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;
}
@Override
@Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();

View File

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

View File

@ -73,6 +73,7 @@ public class FhirServerConfigDstu2 extends BaseJavaConfigDstu2 {
return FhirServerConfigCommon.getDataSource(env);
}
@Override
@Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
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 HEADER_X_CACHE = "X-Cache";
public static final String HEADER_X_SECURITY_CONTEXT = "X-Security-Context";
public static final String POWERED_BY_HEADER = "X-Powered-By";
static {
CHARSET_UTF8 = Charset.forName(CHARSET_NAME_UTF8);

View File

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

View File

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

View File

@ -66,6 +66,7 @@ public class FhirServerConfigR4 extends BaseJavaConfigR4 {
return retVal;
}
@Override
@Bean()
public LocalContainerEntityManagerFactoryBean 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.resthook.SubscriptionRestHookInterceptor;
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.springframework.beans.factory.annotation.Autowire;
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.config.ScheduledTaskRegistrar;
import javax.annotation.Nonnull;
import javax.annotation.Resource;
@Configuration
@ -63,14 +66,13 @@ public abstract class BaseConfig implements SchedulingConfigurer {
private ApplicationContext myAppCtx;
@Override
public void configureTasks(ScheduledTaskRegistrar theTaskRegistrar) {
public void configureTasks(@Nonnull ScheduledTaskRegistrar theTaskRegistrar) {
theTaskRegistrar.setTaskScheduler(taskScheduler());
}
@Bean(autowire = Autowire.BY_TYPE)
public DatabaseBackedPagingProvider databaseBackedPagingProvider() {
DatabaseBackedPagingProvider retVal = new DatabaseBackedPagingProvider();
return retVal;
return new DatabaseBackedPagingProvider();
}
/**
@ -96,6 +98,11 @@ public abstract class BaseConfig implements SchedulingConfigurer {
return new HibernateJpaDialect();
}
@Bean
private IReindexController reindexController() {
return new ReindexController();
}
@Bean()
public ScheduledExecutorFactoryBean scheduledExecutorService() {
ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean();
@ -167,5 +174,4 @@ public abstract class BaseConfig implements SchedulingConfigurer {
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.ExpungeOptions;
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.xmlpatch.XmlPatchUtils;
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.IServerOperationInterceptor;
import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.*;
import org.apache.commons.lang3.Validate;
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.TransactionTemplate;
import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import javax.persistence.NoResultException;
import javax.persistence.TypedQuery;
@ -88,6 +89,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
private String mySecondaryPrimaryKeyParamName;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@Autowired
private ReindexController myReindexController;
@Override
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);
}
//@formatter:off
for (BaseTag next : new ArrayList<>(entity.getTags())) {
if (ObjectUtil.equals(next.getTag().getTagType(), theTagType) &&
ObjectUtil.equals(next.getTag().getSystem(), theScheme) &&
@ -105,7 +107,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return;
}
}
//@formatter:on
entity.setHasTags(true);
@ -459,7 +460,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return outcome;
}
private <MT extends IBaseMetaType> void doMetaAdd(MT theMetaAdd, BaseHasResource entity) {
List<TagDefinition> tags = toTagList(theMetaAdd);
@ -538,7 +538,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return doExpunge(getResourceName(), null, null, theExpungeOptions);
}
@Override
public TagList getAllResourceTags(RequestDetails theRequestDetails) {
// Notify interceptors
@ -630,7 +629,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
Integer updatedCount = txTemplate.execute(new TransactionCallback<Integer>() {
@Override
public @NonNull
Integer doInTransaction(TransactionStatus theStatus) {
Integer doInTransaction(@Nonnull TransactionStatus theStatus) {
return myResourceTableDao.markResourcesOfTypeAsRequiringReindexing(resourceType);
}
});
@ -640,6 +639,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
mySearchParamRegistry.requestRefresh();
myReindexController.requestReindex();
}
@Override

View File

@ -52,32 +52,6 @@ public class FhirResourceDaoSearchParameterDstu2 extends FhirResourceDaoDstu2<Se
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
protected void postPersist(ResourceTable theEntity, SearchParameter 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> {
void performReindexingPass();
}

View File

@ -1,26 +1,16 @@
package ca.uhn.fhir.jpa.dao.dstu3;
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.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4;
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.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 static org.apache.commons.lang3.StringUtils.isBlank;
/*
* #%L
* 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> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSearchParameterDstu3.class);
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@ -57,31 +45,6 @@ public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3<Se
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
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.rest.server.exceptions.UnprocessableEntityException;
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.r4.model.*;
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;
@ -51,31 +47,6 @@ public class FhirResourceDaoSearchParameterR4 extends FhirResourceDaoR4<SearchPa
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
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.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.Field;
@ -30,6 +36,10 @@ import java.util.Date;
@MappedSuperclass
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;
@ -68,14 +78,23 @@ public abstract class BaseResourceIndexedSearchParam implements Serializable {
}
public void setParamName(String theName) {
clearHashes();
myParamName = theName;
}
/**
* Subclasses may override
*/
protected void clearHashes() {
// nothing
}
public ResourceTable getResource() {
return myResource;
}
public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
clearHashes();
myResource = theResource;
myResourceType = theResource.getResourceType();
return this;
@ -107,4 +126,23 @@ public abstract class BaseResourceIndexedSearchParam implements Serializable {
}
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
@Column(name = "PID")
private Long myId;
@ManyToOne
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_IDXCMPSTRUNIQ_RES_ID"))
private ResourceTable myResource;

View File

@ -29,7 +29,6 @@ import org.hibernate.search.annotations.Field;
import javax.persistence.*;
//@formatter:off
@Embeddable
@Entity
@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_RESID", columnList = "RES_ID")
})
//@formatter:on
public class ResourceIndexedSearchParamCoords extends BaseResourceIndexedSearchParam {
public static final int MAX_LENGTH = 100;

View File

@ -33,6 +33,7 @@ import org.hibernate.search.annotations.NumericField;
import javax.persistence.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
//@formatter:off
@Embeddable
@ -64,6 +65,16 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_QUANTITY")
@Column(name = "SP_ID")
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() {
// nothing
@ -76,6 +87,20 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
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
public boolean equals(Object theObj) {
if (this == theObj) {
@ -94,9 +119,29 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
b.append(getSystem(), obj.getSystem());
b.append(getUnits(), obj.getUnits());
b.append(getValue(), obj.getValue());
b.append(getHashUnitsAndValPrefix(), obj.getHashUnitsAndValPrefix());
b.append(getHashValPrefix(), obj.getHashValPrefix());
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
protected Long getId() {
return myId;
@ -107,6 +152,7 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
}
public void setSystem(String theSystem) {
clearHashes();
mySystem = theSystem;
}
@ -115,6 +161,7 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
}
public void setUnits(String theUnits) {
clearHashes();
myUnits = theUnits;
}
@ -123,6 +170,7 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
}
public void setValue(BigDecimal theValue) {
clearHashes();
myValue = theValue;
}
@ -134,6 +182,8 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
b.append(getSystem());
b.append(getUnits());
b.append(getValue());
b.append(getHashUnitsAndValPrefix());
b.append(getHashValPrefix());
return b.toHashCode();
}
@ -153,4 +203,8 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
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.Index;
import static org.apache.commons.lang3.StringUtils.left;
//@formatter:off
@Embeddable
@Entity
@ -91,9 +93,8 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
* 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 HASH_PREFIX_LENGTH = 1;
private static final long serialVersionUID = 1L;
@Id
@SequenceGenerator(name = "SEQ_SPIDX_STRING", sequenceName = "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)
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() {
super();
@ -128,6 +139,20 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
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
public boolean equals(Object theObj) {
if (this == theObj) {
@ -144,9 +169,29 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
b.append(getParamName(), obj.getParamName());
b.append(getResource(), obj.getResource());
b.append(getValueExact(), obj.getValueExact());
b.append(getHashNormalizedPrefix(), obj.getHashNormalizedPrefix());
b.append(getHashExact(), obj.getHashExact());
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
protected Long getId() {
return myId;
@ -180,6 +225,8 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
b.append(getParamName());
b.append(getResource());
b.append(getValueExact());
b.append(getHashNormalizedPrefix());
b.append(getHashExact());
return b.toHashCode();
}

View File

@ -31,7 +31,6 @@ import org.hibernate.search.annotations.Field;
import javax.persistence.*;
//@formatter:off
@Embeddable
@Entity
@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_RESID", columnList = "RES_ID")
})
//@formatter:on
public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchParam {
public static final int MAX_LENGTH = 200;
private static final long serialVersionUID = 1L;
@Field()
@Column(name = "SP_SYSTEM", nullable = true, length = MAX_LENGTH)
public String mySystem;
@ -57,16 +56,58 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_TOKEN")
@Column(name = "SP_ID")
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() {
super();
}
/**
* Constructor
*/
public ResourceIndexedSearchParamToken(String theName, String theSystem, String theValue) {
super();
setParamName(theName);
setSystem(theSystem);
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
public boolean equals(Object theObj) {
if (this == theObj) {
@ -84,9 +125,40 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
b.append(getResource(), obj.getResource());
b.append(getSystem(), obj.getSystem());
b.append(getValue(), obj.getValue());
b.append(getHashSystem(), obj.getHashSystem());
b.append(getHashSystemAndValue(), obj.getHashSystemAndValue());
b.append(getHashValue(), obj.getHashValue());
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
protected Long getId() {
return myId;
@ -97,6 +169,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
}
public void setSystem(String theSystem) {
clearHashes();
mySystem = StringUtils.defaultIfBlank(theSystem, null);
}
@ -105,6 +178,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
}
public void setValue(String theValue) {
clearHashes();
myValue = StringUtils.defaultIfBlank(theValue, null);
}
@ -115,9 +189,13 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
b.append(getResource());
b.append(getSystem());
b.append(getValue());
b.append(getHashSystem());
b.append(getHashSystemAndValue());
b.append(getHashValue());
return b.toHashCode();
}
@Override
public IQueryParameterType toQueryParameterType() {
return new TokenParam(getSystem(), getValue());

View File

@ -30,7 +30,6 @@ import org.hibernate.search.annotations.Field;
import javax.persistence.*;
//@formatter:off
@Embeddable
@Entity
@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_COORDS", columnList = "RES_ID")
})
//@formatter:on
public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchParam {
/*
@ -56,15 +54,38 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_URI")
@Column(name = "SP_ID")
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() {
}
/**
* Constructor
*/
public ResourceIndexedSearchParamUri(String theName, String theUri) {
setParamName(theName);
setUri(theUri);
}
@PrePersist
public void calculateHashes() {
if (myHashUri == null) {
setHashUri(hash(getResourceType(), getParamName(), getUri()));
}
}
@Override
protected void clearHashes() {
myHashUri = null;
}
@Override
public boolean equals(Object theObj) {
if (this == theObj) {
@ -81,9 +102,19 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
b.append(getParamName(), obj.getParamName());
b.append(getResource(), obj.getResource());
b.append(getUri(), obj.getUri());
b.append(getHashUri(), obj.getHashUri());
return b.isEquals();
}
public Long getHashUri() {
calculateHashes();
return myHashUri;
}
public void setHashUri(Long theHashUri) {
myHashUri = theHashUri;
}
@Override
protected Long getId() {
return myId;
@ -103,6 +134,7 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
b.append(getParamName());
b.append(getResource());
b.append(getUri());
b.append(getHashUri());
return b.toHashCode();
}

View File

@ -434,8 +434,9 @@ public class ResourceTable extends BaseHasResource implements Serializable {
return myResourceType;
}
public void setResourceType(String theResourceType) {
public ResourceTable setResourceType(String theResourceType) {
myResourceType = theResourceType;
return this;
}
@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;
}
@Override
@Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -86,6 +86,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
return retVal;
}
@Override
@Bean()
public LocalContainerEntityManagerFactoryBean 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
*/
private String myServerVersion = VersionUtil.getVersion();
private String myServerVersion = createPoweredByHeaderProductVersion();
private boolean myStarted;
private Map<String, IResourceProvider> myTypeToProvider = new HashMap<>();
private boolean myUncompressIncomingContents = true;
@ -159,7 +159,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
*/
public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
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) {
@ -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());
}
/**
* 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() {
StringBuilder b = new StringBuilder();
b.append(createPoweredByHeaderProductName());
b.append(" ");
b.append(VersionUtil.getVersion());
b.append(" REST Server (");
b.append(createPoweredByHeaderProductVersion());
b.append(" ");
b.append(createPoweredByHeaderComponentName());
b.append(" (");
List<String> poweredByAttributes = createPoweredByAttributes();
for (ListIterator<String> iter = poweredByAttributes.listIterator(); iter.hasNext(); ) {
@ -232,10 +241,33 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
return b.toString();
}
/**
* Subclasses my override
*
* @see #createPoweredByHeader()
*/
protected String createPoweredByHeaderComponentName() {
return "REST Server";
}
/**
* Subclasses my override
*
* @see #createPoweredByHeader()
*/
protected String createPoweredByHeaderProductName() {
return "HAPI FHIR";
}
/**
* Subclasses my override
*
* @see #createPoweredByHeader()
*/
protected String createPoweredByHeaderProductVersion() {
return VersionUtil.getVersion();
}
@Override
public void destroy() {
if (getResourceProviders() != null) {
@ -539,10 +571,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
*
* @param theList The list of interceptors (may be null)
*/
public void setInterceptors(IServerInterceptor... theList) {
public void setInterceptors(List<IServerInterceptor> theList) {
myInterceptors.clear();
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)
*/
public void setPlainProviders(Collection<Object> theProviders) {
myPlainProviders.clear();
if (theProviders != null) {
myPlainProviders.addAll(theProviders);
}
public void setPlainProviders(Object... theProv) {
setPlainProviders(Arrays.asList(theProv));
}
/**
@ -606,10 +635,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/**
* Sets the resource providers for this server
*/
public void setResourceProviders(Collection<IResourceProvider> theResourceProviders) {
public void setResourceProviders(IResourceProvider... theResourceProviders) {
myResourceProviders.clear();
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
*
* @param theRequest
*/
public String getServerBaseForRequest(ServletRequestDetails theRequest) {
String fhirServerBase;
@ -694,9 +721,9 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
// passing the server into the constructor. Having that sort
// of cross linkage causes reference cycles in Spring wiring
try {
Method setRestfulServer = theServerConformanceProvider.getClass().getMethod("setRestfulServer", new Class[] {RestfulServer.class});
Method setRestfulServer = theServerConformanceProvider.getClass().getMethod("setRestfulServer", RestfulServer.class);
if (setRestfulServer != null) {
setRestfulServer.invoke(theServerConformanceProvider, new Object[] {this});
setRestfulServer.invoke(theServerConformanceProvider, this);
}
} catch (Exception e) {
ourLog.warn("Error calling IServerConformanceProvider.setRestfulServer", e);
@ -1011,9 +1038,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
invokeInitialize(iResourceProvider);
}
}
if (confProvider != null) {
invokeInitialize(confProvider);
}
invokeInitialize(confProvider);
if (getPlainProviders() != null) {
for (Object next : getPlainProviders()) {
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}
*/
public void registerProviders(Collection<? extends Object> providers) {
public void registerProviders(Collection<?> providers) {
myProviderRegistrationMutex.lock();
try {
if (!myStarted) {
@ -1323,9 +1349,9 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/*
* Inner method to actually register providers
*/
protected void registerProviders(Collection<? extends Object> providers, boolean inInit) {
List<IResourceProvider> newResourceProviders = new ArrayList<IResourceProvider>();
List<Object> newPlainProviders = new ArrayList<Object>();
protected void registerProviders(Collection<?> providers, boolean inInit) {
List<IResourceProvider> newResourceProviders = new ArrayList<>();
List<Object> newPlainProviders = new ArrayList<>();
ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
if (providers != null) {
@ -1392,7 +1418,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
ourLog.info("Removing RESTful methods for: {}", theProvider.getClass());
Class<?> clazz = theProvider.getClass();
Class<?> supertype = clazz.getSuperclass();
Collection<String> resourceNames = new ArrayList<String>();
Collection<String> resourceNames = new ArrayList<>();
while (!Object.class.equals(supertype)) {
removeResourceMethods(theProvider, supertype, resourceNames);
supertype = supertype.getSuperclass();
@ -1468,12 +1494,18 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
case OPTIONS:
doOptions(theReq, theResp);
break;
case PATCH:
break;
case POST:
doPost(theReq, theResp);
break;
case PUT:
doPut(theReq, theResp);
break;
case TRACE:
case TRACK:
case HEAD:
case CONNECT:
default:
handleRequest(method, theReq, theResp);
break;
@ -1485,10 +1517,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
*
* @param theList The list of interceptors (may be null)
*/
public void setInterceptors(List<IServerInterceptor> theList) {
public void setInterceptors(IServerInterceptor... theList) {
myInterceptors.clear();
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)
*/
public void setPlainProviders(Object... theProv) {
setPlainProviders(Arrays.asList(theProv));
public void setPlainProviders(Collection<Object> theProviders) {
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
*/
public void setResourceProviders(IResourceProvider... theResourceProviders) {
public void setResourceProviders(Collection<IResourceProvider> theResourceProviders) {
myResourceProviders.clear();
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)
*
* @param provider
* @throws Exception
*/
public void unregisterProvider(Object provider) throws Exception {
public void unregisterProvider(Object provider) {
if (provider != null) {
Collection<Object> providerList = new ArrayList<Object>(1);
Collection<Object> providerList = new ArrayList<>(1);
providerList.add(provider);
unregisterProviders(providerList);
}
@ -1553,11 +1585,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/**
* 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());
if (providers != null) {
for (Object provider : providers) {

View File

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

View File

@ -17,6 +17,20 @@
</ul>
]]>
</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">
R4 structures have been updated to the latest definitions
(SVN 13732)
@ -226,6 +240,11 @@
An issue in the narrative generator template for the CodeableConcept
datatype was corrected. Thanks to @RuthAlk for the pull request!
</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 version="3.3.0" date="2018-03-29">
<action type="add">