Subscription refactoring

This commit is contained in:
James 2017-09-21 08:33:20 -04:00
parent 5f89f6ed16
commit be07ebc4ef
17 changed files with 209 additions and 88 deletions

View File

@ -131,6 +131,12 @@
<artifactId>javassist</artifactId>
</dependency>
<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.phloc</groupId>
<artifactId>phloc-schematron</artifactId>

View File

@ -55,7 +55,7 @@ public class DaoConfig {
* @see #setMaximumSearchResultCountInTransaction(Integer)
*/
private static final Integer DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION = null;
private IndexEnabledEnum myIndexMissingFieldsEnabled = IndexEnabledEnum.ENABLED;
private IndexEnabledEnum myIndexMissingFieldsEnabled = IndexEnabledEnum.DISABLED;
/**
* update setter javadoc if default changes
*/
@ -107,6 +107,7 @@ public class DaoConfig {
private Set<String> myTreatBaseUrlsAsLocal = new HashSet<String>();
private Set<String> myTreatReferencesAsLogical = new HashSet<String>(DEFAULT_LOGICAL_BASE_URLS);
private boolean myAutoCreatePlaceholderReferenceTargets;
/**
* Constructor
*/
@ -284,7 +285,7 @@ public class DaoConfig {
}
/**
* If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#ENABLED})
* If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#DISABLED})
* the server will not create search indexes for search parameters with no values in resources.
* <p>
* Disabling this feature means that the <code>:missing</code> search modifier will not be
@ -292,13 +293,17 @@ public class DaoConfig {
* database) may be much faster on servers which have lots of search parameters and need
* to write quickly.
* </p>
* <p>
* This feature may be enabled on servers where supporting the use of the :missing parameter is
* of higher importance than raw write performance
* </p>
*/
public IndexEnabledEnum getIndexMissingFields() {
return myIndexMissingFieldsEnabled;
}
/**
* If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#ENABLED})
* If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#DISABLED})
* the server will not create search indexes for search parameters with no values in resources.
* <p>
* Disabling this feature means that the <code>:missing</code> search modifier will not be
@ -306,6 +311,10 @@ public class DaoConfig {
* database) may be much faster on servers which have lots of search parameters and need
* to write quickly.
* </p>
* <p>
* This feature may be enabled on servers where supporting the use of the :missing parameter is
* of higher importance than raw write performance
* </p>
*/
public void setIndexMissingFields(IndexEnabledEnum theIndexMissingFields) {
Validate.notNull(theIndexMissingFields, "theIndexMissingFields must not be null");

View File

@ -21,8 +21,10 @@ package ca.uhn.fhir.jpa.subscription;
*/
import ca.uhn.fhir.context.FhirContext;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -37,30 +39,33 @@ import java.util.List;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class CanonicalSubscription implements Serializable {
private static final long serialVersionUID = 1L;
@SerializedName("id")
@JsonProperty("id")
private String myIdElement;
@SerializedName("criteria")
@JsonProperty("criteria")
private String myCriteriaString;
@SerializedName("endpointUrl")
@JsonProperty("endpointUrl")
private String myEndpointUrl;
@SerializedName("payload")
@JsonProperty("payload")
private String myPayloadString;
@SerializedName("headers")
@JsonProperty("headers")
private List<String> myHeaders;
@SerializedName("channelType")
@JsonProperty("channelType")
private Subscription.SubscriptionChannelType myChannelType;
@SerializedName("status")
@JsonProperty("status")
private Subscription.SubscriptionStatus myStatus;
@JsonIgnore
private transient IBaseResource myBackingSubscription;
@SerializedName("backingSubscription")
@JsonProperty("backingSubscription")
private String myBackingSubscriptionString;
@SerializedName("triggerDefinition")
@JsonProperty("triggerDefinition")
private CanonicalEventDefinition myTrigger;
@SerializedName("emailDetails")
@JsonProperty("emailDetails")
private EmailDetails myEmailDetails;
/**
@ -91,18 +96,6 @@ public class CanonicalSubscription implements Serializable {
return myBackingSubscription;
}
String getIdElementString() {
return myIdElement;
}
public void setBackingSubscription(FhirContext theCtx, IBaseResource theBackingSubscription) {
myBackingSubscription = theBackingSubscription;
myBackingSubscriptionString = null;
if (myBackingSubscription != null) {
myBackingSubscriptionString = theCtx.newJsonParser().encodeResourceToString(myBackingSubscription);
}
}
public Subscription.SubscriptionChannelType getChannelType() {
return myChannelType;
}
@ -138,12 +131,10 @@ public class CanonicalSubscription implements Serializable {
return myHeaders;
}
public void setHeaders(List<? extends IPrimitiveType<String>> theHeader) {
public void setHeaders(String theHeaders) {
myHeaders = new ArrayList<>();
for (IPrimitiveType<String> next : theHeader) {
if (isNotBlank(next.getValueAsString())) {
myHeaders.add(next.getValueAsString());
}
if (isNotBlank(theHeaders)) {
myHeaders.add(theHeaders);
}
}
@ -155,6 +146,10 @@ public class CanonicalSubscription implements Serializable {
return retVal;
}
String getIdElementString() {
return myIdElement;
}
public String getPayloadString() {
return myPayloadString;
}
@ -186,10 +181,20 @@ public class CanonicalSubscription implements Serializable {
.toHashCode();
}
public void setHeaders(String theHeaders) {
public void setBackingSubscription(FhirContext theCtx, IBaseResource theBackingSubscription) {
myBackingSubscription = theBackingSubscription;
myBackingSubscriptionString = null;
if (myBackingSubscription != null) {
myBackingSubscriptionString = theCtx.newJsonParser().encodeResourceToString(myBackingSubscription);
}
}
public void setHeaders(List<? extends IPrimitiveType<String>> theHeader) {
myHeaders = new ArrayList<>();
if (isNotBlank(theHeaders)) {
myHeaders.add(theHeaders);
for (IPrimitiveType<String> next : theHeader) {
if (isNotBlank(next.getValueAsString())) {
myHeaders.add(next.getValueAsString());
}
}
}
@ -200,12 +205,14 @@ public class CanonicalSubscription implements Serializable {
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public static class EmailDetails {
@SerializedName("from")
@JsonProperty("from")
private String myFrom;
@SerializedName("subjectTemplate")
@JsonProperty("subjectTemplate")
private String mySubjectTemplate;
@SerializedName("bodyTemplate")
@JsonProperty("bodyTemplate")
private String myBodyTemplate;
public String getBodyTemplate() {
@ -233,6 +240,8 @@ public class CanonicalSubscription implements Serializable {
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public static class CanonicalEventDefinition {
public CanonicalEventDefinition(EventDefinition theDef) {

View File

@ -22,21 +22,30 @@ package ca.uhn.fhir.jpa.subscription;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import com.fasterxml.jackson.annotation.*;
import com.google.gson.Gson;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import java.io.Serializable;
public class ResourceDeliveryMessage implements Serializable {
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class ResourceDeliveryMessage {
private static final long serialVersionUID = 1L;
@JsonIgnore
private transient CanonicalSubscription mySubscription;
@JsonProperty("subscription")
private String mySubscriptionString;
@JsonIgnore
private transient IBaseResource myPayload;
@JsonProperty("payload")
private String myPayoadString;
@JsonProperty("payloadId")
private String myPayloadId;
@JsonProperty("operationType")
private RestOperationTypeEnum myOperationType;
public RestOperationTypeEnum getOperationType() {

View File

@ -22,18 +22,28 @@ package ca.uhn.fhir.jpa.subscription;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import java.io.Serializable;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class ResourceModifiedMessage implements Serializable {
private static final long serialVersionUID = 1L;
@JsonProperty("resourceId")
private String myId;
@JsonProperty("operationType")
private RestOperationTypeEnum myOperationType;
@JsonProperty("newPayload")
private String myNewPayloadEncoded;
@JsonIgnore
private transient IBaseResource myNewPayload;
public IIdType getId(FhirContext theCtx) {

View File

@ -27,14 +27,13 @@ import static org.junit.Assert.fail;
public class TestDstu3Config extends BaseJavaConfigDstu3 {
static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TestDstu3Config.class);
private Exception myLastStackTrace;
@Bean()
public DaoConfig daoConfig() {
return new DaoConfig();
}
private Exception myLastStackTrace;
@Bean()
public DataSource dataSource() {
BasicDataSource retVal = new BasicDataSource() {
@ -48,10 +47,10 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
} catch (Exception e) {
ourLog.error("Exceeded maximum wait for connection", e);
logGetConnectionStackTrace();
if ("true".equals(System.getProperty("ci"))) {
// if ("true".equals(System.getProperty("ci"))) {
fail("Exceeded maximum wait for connection: "+ e.toString());
}
System.exit(1);
// }
// System.exit(1);
retVal = null;
}

View File

@ -43,7 +43,7 @@ import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= {TestDstu2Config.class})
@ContextConfiguration(classes = {TestDstu2Config.class})
public abstract class BaseJpaDstu2Test extends BaseJpaTest {
@Autowired
protected ApplicationContext myAppCtx;
@ -84,13 +84,11 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
@Qualifier("myLocationDaoDstu2")
protected IFhirResourceDao<Location> myLocationDao;
@Autowired
@Qualifier("myMediaDaoDstu2")
protected IFhirResourceDao<Media> myMediaDao;
@Autowired
@Qualifier("myMedicationAdministrationDaoDstu2")
protected IFhirResourceDao<MedicationAdministration> myMedicationAdministrationDao;
@Qualifier("myMediaDaoDstu2")
protected IFhirResourceDao<Media> myMediaDao;
@Autowired
@Qualifier("myMedicationAdministrationDaoDstu2")
protected IFhirResourceDao<MedicationAdministration> myMedicationAdministrationDao;
@Autowired
@Qualifier("myMedicationDaoDstu2")
protected IFhirResourceDao<Medication> myMedicationDao;
@ -158,6 +156,7 @@ protected IFhirResourceDao<MedicationAdministration> myMedicationAdministrationD
myInterceptor = mock(IServerInterceptor.class);
myDaoConfig.setInterceptors(myInterceptor);
}
@Before
@Transactional
public void beforeFlushFT() {
@ -167,6 +166,7 @@ protected IFhirResourceDao<MedicationAdministration> myMedicationAdministrationD
ftem.flushToIndexes();
myDaoConfig.setSchedulingDisabled(true);
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED);
}
@Before

View File

@ -118,8 +118,6 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
protected IFhirResourceDao<ImmunizationRecommendation> myImmunizationRecommendationDao;
protected IServerInterceptor myInterceptor;
@Autowired
private JpaValidationSupportChainDstu3 myJpaValidationSupportChainDstu3;
@Autowired
@Qualifier("myLocationDaoDstu3")
protected IFhirResourceDao<Location> myLocationDao;
@Autowired
@ -219,6 +217,8 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
@Autowired
@Qualifier("myValueSetDaoDstu3")
protected IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> myValueSetDao;
@Autowired
private JpaValidationSupportChainDstu3 myJpaValidationSupportChainDstu3;
@After()
public void afterCleanupDao() {
@ -250,6 +250,7 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
ftem.flushToIndexes();
myDaoConfig.setSchedulingDisabled(true);
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED);
}
@Before

View File

@ -258,6 +258,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
ftem.flushToIndexes();
myDaoConfig.setSchedulingDisabled(true);
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED);
}
@Before

View File

@ -22,7 +22,7 @@ public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test {
@Before
public void beforeResetMissing() {
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED);
}
@Test

View File

@ -35,6 +35,16 @@ public class CommonConfig {
return retVal;
}
/**
* This is a joke
*
* https://chat.fhir.org/#narrow/stream/implementers/topic/Unsupported.20search.20parameters
*/
@Bean
public IServerInterceptor holyFooCowInterceptor() {
return new HolyFooCowInterceptor();
}
/**
* Do some fancy logging to create a nice access log that has details about each incoming request.
*/
@ -47,16 +57,6 @@ public class CommonConfig {
return retVal;
}
/**
* This is a joke
*
* https://chat.fhir.org/#narrow/stream/implementers/topic/Unsupported.20search.20parameters
*/
@Bean
public IServerInterceptor holyFooCowInterceptor() {
return new HolyFooCowInterceptor();
}
public static boolean isLocalTestMode(){
return "true".equalsIgnoreCase(System.getProperty("testmode.local"));
}

View File

@ -66,6 +66,7 @@ public class TdlDstu2Config extends BaseJavaConfigDstu2 {
retVal.setAllowExternalReferences(true);
retVal.getTreatBaseUrlsAsLocal().add("http://fhirtest.uhn.ca/testDataLibraryDstu2");
retVal.getTreatBaseUrlsAsLocal().add("https://fhirtest.uhn.ca/testDataLibraryDstu2");
retVal.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED);
return retVal;
}

View File

@ -56,14 +56,10 @@ public class TdlDstu3Config extends BaseJavaConfigDstu3 {
retVal.setAllowExternalReferences(true);
retVal.getTreatBaseUrlsAsLocal().add("http://fhirtest.uhn.ca/testDataLibraryStu3");
retVal.getTreatBaseUrlsAsLocal().add("https://fhirtest.uhn.ca/testDataLibraryStu3");
retVal.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED);
return retVal;
}
@Bean
public IServerInterceptor securityInterceptor() {
return new TdlSecurityInterceptor();
}
@Bean(name = "myPersistenceDataSourceDstu3", destroyMethod = "close")
public DataSource dataSource() {
BasicDataSource retVal = new BasicDataSource();
@ -146,6 +142,11 @@ public class TdlDstu3Config extends BaseJavaConfigDstu3 {
return responseValidator;
}
@Bean
public IServerInterceptor securityInterceptor() {
return new TdlSecurityInterceptor();
}
@Bean(autowire = Autowire.BY_TYPE)
public IServerInterceptor subscriptionSecurityInterceptor() {
return new SubscriptionsRequireManualActivationInterceptorDstu3();

View File

@ -55,24 +55,10 @@ public class TestR4Config extends BaseJavaConfigR4 {
retVal.setAllowExternalReferences(true);
retVal.getTreatBaseUrlsAsLocal().add("http://fhirtest.uhn.ca/baseR4");
retVal.getTreatBaseUrlsAsLocal().add("https://fhirtest.uhn.ca/baseR4");
retVal.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED);
return retVal;
}
@Override
@Bean(autowire = Autowire.BY_TYPE)
public DatabaseBackedPagingProvider databaseBackedPagingProvider() {
DatabaseBackedPagingProvider retVal = super.databaseBackedPagingProvider();
retVal.setDefaultPageSize(20);
retVal.setMaximumPageSize(500);
return retVal;
}
@Bean
public IServerInterceptor securityInterceptor() {
return new PublicSecurityInterceptor();
}
@Bean(name = "myPersistenceDataSourceR4", destroyMethod = "close")
public DataSource dataSource() {
BasicDataSource retVal = new BasicDataSource();
@ -87,6 +73,15 @@ public class TestR4Config extends BaseJavaConfigR4 {
return retVal;
}
@Override
@Bean(autowire = Autowire.BY_TYPE)
public DatabaseBackedPagingProvider databaseBackedPagingProvider() {
DatabaseBackedPagingProvider retVal = super.databaseBackedPagingProvider();
retVal.setDefaultPageSize(20);
retVal.setMaximumPageSize(500);
return retVal;
}
@Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = new LocalContainerEntityManagerFactoryBean();
@ -135,6 +130,10 @@ public class TestR4Config extends BaseJavaConfigR4 {
return requestValidator;
}
@Bean
public IServerInterceptor securityInterceptor() {
return new PublicSecurityInterceptor();
}
@Bean()
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {

View File

@ -0,0 +1,37 @@
package ca.uhn.fhir.tinder;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.Set;
public class ExamineTestTrace {
private static final Logger ourLog = LoggerFactory.getLogger(ExamineTestTrace.class);
public static void main(String[] aaa) {
String input = "Running ca.uhn.fhir.jpa.config.IdentifierLengthTest\n" +
"Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.913 sec - in ca.uhn.fhir.jpa.subscription.r4.RestHookTestWithInterceptorRegisteredToDaoConfigR4Test";
Set<String> started = new HashSet<>();
Set<String> finished = new HashSet<>();
for (String next : input.split("\n")) {
if (next.startsWith("Running ")) {
started.add(next.substring("Running ".length()));
} else if (next.startsWith("Tests run: ")) {
finished.add(next.substring(next.indexOf(" - in ") + " - in ".length()));
} else {
throw new IllegalStateException();
}
}
ourLog.info("Started {}", started.size());
ourLog.info("Finished {}", finished.size());
ourLog.info(CollectionUtils.disjunction(started, finished).toString());
}
}

32
pom.xml
View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.sonatype.oss</groupId>
@ -422,6 +422,36 @@
<artifactId>commonmark</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>

View File

@ -384,6 +384,15 @@
<![CDATA[<code>:missing</code>]]> modifiers, which
increases write performance since fewer indexes are written
</action>
<action type="add">
A new JPA configuration option has been added to the DaoConfig which allows
support for the <![CDATA[<code>:missing</code>]]> search parameter modifier
to be enabled or disabled, and sets the default to DISABLED.
<![CDATA[<br/><br/>]]>
Support for this parameter causes many more index rows to be inserted in the database,
which has a significant impact on write performance. A future HAPI update may allow these
rows to be written asynchronously in order to improve this.
</action>
</release>
<release version="2.5" date="2017-06-08">
<action type="fix">