+ * Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
+ * {@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
+ * precision of {@link ca.uhn.fhir.model.api.TemporalPrecisionEnum#DAY}.
+ *
+ * For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
+ * integer representing the ordinal date {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
+ * and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
+ *
+ * Default is {@literal true} beginning in HAPI FHIR 4.3.
+ *
+ *
+ * @since 4.3
+ */
+ public boolean getUseOrdinalDatesForDayPrecisionSearches() {
+ return myUseOrdinalDatesForDayPrecisionSearches;
+ }
+
public enum StoreMetaSourceInformationEnum {
NONE(false, false),
SOURCE_URI(true, false),
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
index eebf0f2987f..fc182a9162b 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
@@ -140,7 +140,9 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
DateParam lowerBound = theRange.getLowerBound();
DateParam upperBound = theRange.getUpperBound();
- boolean isOrdinalComparison = isNullOrDayPrecision(lowerBound) && isNullOrDayPrecision(upperBound);
+ Integer lowerBoundAsOrdinal = theRange.getLowerBoundAsDateInteger();
+ Integer upperBoundAsOrdinal = theRange.getUpperBoundAsDateInteger();
+ boolean isOrdinalComparison = isNullOrDayPrecision(lowerBound) && isNullOrDayPrecision(upperBound) && myDaoConfig.getUseOrdinalDatesForDayPrecisionSearches();
Predicate lt = null;
Predicate gt = null;
Predicate lb = null;
@@ -152,7 +154,7 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
}
//im like 80% sure this should be ub and not lb, as it is an UPPER bound.
if (isOrdinalComparison) {
- lb = theBuilder.lessThan(theFrom.get("myValueLowDateOrdinal"), theRange.getLowerBoundAsDateInteger());
+ lb = theBuilder.lessThan(theFrom.get("myValueLowDateOrdinal"), lowerBoundAsOrdinal);
} else {
lb = theBuilder.lessThan(theFrom.get("myValueLow"), lowerBoundInstant);
}
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
index 6ea71e54b73..98778c257f2 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
@@ -64,10 +64,11 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks {
version.onTable("HFJ_RES_VER").modifyColumn("20200220.1", "RES_ID").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
//
- // Add support for integer comparisons during day-granularity date search.
+ // Add support for integer comparisons during day-precision date search.
Builder.BuilderWithTableName spidxDate = version.onTable("HFJ_SPIDX_DATE");
spidxDate.addColumn("20200225.1", "SP_VALUE_LOW_DATE_ORDINAL").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT);
spidxDate.addColumn("20200225.2", "SP_VALUE_HIGH_DATE_ORDINAL").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT);
+
spidxDate.addTask(new CalculateOrdinalDatesTask(VersionEnum.V4_3_0, "20200225.3")
.setColumnName("SP_VALUE_LOW_DATE_ORDINAL") //It doesn't matter which of the two we choose as they will both be null.
.addCalculator("SP_VALUE_LOW_DATE_ORDINAL", t -> ResourceIndexedSearchParamDate.calculateOrdinalValue(t.getDate("SP_VALUE_LOW")))
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
index c5ffe82375a..17d011d4e0c 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
@@ -111,6 +111,14 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
this.myValueLowDateOrdinal = generateOrdinalDateInteger(theLow);
}
+ public Integer getValueLowDateOrdinal() {
+ return myValueLowDateOrdinal;
+ }
+
+ public Integer getValueHighDateOrdinal() {
+ return myValueHighDateOrdinal;
+ }
+
@Override
@PrePersist
public void calculateHashes() {
From a1b6e37395746b006594ef144e2b33fab754feaf Mon Sep 17 00:00:00 2001
From: Gary Graham
Date: Tue, 25 Feb 2020 17:03:50 -0500
Subject: [PATCH 11/49] Fix bug with Period bounds not counting as first value
---
.../jpa/model/entity/ResourceIndexedSearchParamDate.java | 8 +++++---
.../searchparam/extractor/BaseSearchParamExtractor.java | 4 ++++
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
index 17d011d4e0c..d38f4dfe440 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
@@ -102,9 +102,11 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
this.myValueHighDateOrdinal = generateOrdinalDateInteger(theHigh);
}
private int generateOrdinalDateInteger(String theDateString){
- String t = theDateString.substring(0, theDateString.indexOf("T"));
- t = t.replace("-", "");
- return Integer.valueOf(t);
+ if (theDateString.contains("T")) {
+ theDateString = theDateString.substring(0, theDateString.indexOf("T"));
+ }
+ theDateString = theDateString.replace("-", "");
+ return Integer.valueOf(theDateString);
}
private void computeValueLowDateOrdinal(String theLow) {
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
index d3b42491c51..eb44a8a0a7a 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
@@ -642,6 +642,10 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
String endString = extractValueAsString(myPeriodEndValueChild, bounds.get());
dates.add(start);
dates.add(end);
+ //TODO Check if this logic is valid. Does the start of the first period indicate a lower bound??
+ if (firstValue == null) {
+ firstValue = extractValueAsString(myPeriodStartValueChild, bounds.get());
+ }
finalValue = endString;
}
}
From 1e833af5f046c464414821011f66921ceb5460cf Mon Sep 17 00:00:00 2001
From: Gary Graham
Date: Tue, 25 Feb 2020 17:06:38 -0500
Subject: [PATCH 12/49] Variable reuse
---
.../dao/predicate/PredicateBuilderDate.java | 20 +++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
index fc182a9162b..ae0e3df79ec 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
@@ -164,7 +164,7 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
}
//im like 80% sure this should be ub and not lb, as it is an UPPER bound.
if (isOrdinalComparison) {
- lb = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHighDateOrdinal"), theRange.getUpperBoundAsDateInteger());
+ lb = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHighDateOrdinal"), upperBoundAsOrdinal);
} else {
lb = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHigh"), upperBoundInstant);
}
@@ -173,7 +173,7 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
throw new InvalidRequestException("upperBound value not correctly specified for compare operation");
}
if (isOrdinalComparison) {
- lb = theBuilder.greaterThan(theFrom.get("myValueHighDateOrdinal"), theRange.getUpperBoundAsDateInteger());
+ lb = theBuilder.greaterThan(theFrom.get("myValueHighDateOrdinal"), upperBoundAsOrdinal);
} else {
lb = theBuilder.greaterThan(theFrom.get("myValueHigh"), upperBoundInstant);
}
@@ -182,7 +182,7 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
throw new InvalidRequestException("lowerBound value not correctly specified for compare operation");
}
if (isOrdinalComparison) {
- lb = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLowDateOrdinal"), theRange.getLowerBoundAsDateInteger());
+ lb = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLowDateOrdinal"), lowerBoundAsOrdinal);
} else {
lb = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLow"), lowerBoundInstant);
}
@@ -192,8 +192,8 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
throw new InvalidRequestException("lowerBound and/or upperBound value not correctly specified for compare operation");
}
if (isOrdinalComparison){
- lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLowDateOrdinal"), theRange.getLowerBoundAsDateInteger());
- gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHighDateOrdinal"), theRange.getUpperBoundAsDateInteger());
+ lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLowDateOrdinal"), lowerBoundAsOrdinal);
+ gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHighDateOrdinal"), upperBoundAsOrdinal);
} else {
lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLow"), lowerBoundInstant);
gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHigh"), upperBoundInstant);
@@ -203,8 +203,8 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
} else if ((operation == SearchFilterParser.CompareOperation.eq) || (operation == null)) {
if (lowerBoundInstant != null) {
if (isOrdinalComparison) {
- gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLowDateOrdinal"), theRange.getLowerBoundAsDateInteger());
- lt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHighDateOrdinal"), theRange.getLowerBoundAsDateInteger());
+ gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLowDateOrdinal"), lowerBoundAsOrdinal);
+ lt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHighDateOrdinal"), lowerBoundAsOrdinal);
//also try a strict equality here.
}
else {
@@ -220,8 +220,8 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
if (upperBoundInstant != null) {
if (isOrdinalComparison) {
- gt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLowDateOrdinal"), theRange.getUpperBoundAsDateInteger());
- lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHighDateOrdinal"), theRange.getUpperBoundAsDateInteger());
+ gt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLowDateOrdinal"), upperBoundAsOrdinal);
+ lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHighDateOrdinal"), upperBoundAsOrdinal);
} else {
gt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLow"), upperBoundInstant);
lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHigh"), upperBoundInstant);
@@ -238,7 +238,7 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
operation.name()));
}
if (isOrdinalComparison) {
- ourLog.trace("Ordinal date range is {} - {} ", theRange.getLowerBoundAsDateInteger(), theRange.getUpperBoundAsDateInteger());
+ ourLog.trace("Ordinal date range is {} - {} ", lowerBoundAsOrdinal, upperBoundAsOrdinal);
}
ourLog.trace("Date range is {} - {}", lowerBoundInstant, upperBoundInstant);
From 2204fbeabd84a75ffd9171daa6c3a7251bd3ee11 Mon Sep 17 00:00:00 2001
From: Gary Graham
Date: Tue, 25 Feb 2020 17:08:11 -0500
Subject: [PATCH 13/49] Adding some comments
---
.../uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
index ae0e3df79ec..a2e0aad60cc 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
@@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate;
* #L%
*/
+import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
@@ -142,7 +143,13 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
DateParam upperBound = theRange.getUpperBound();
Integer lowerBoundAsOrdinal = theRange.getLowerBoundAsDateInteger();
Integer upperBoundAsOrdinal = theRange.getUpperBoundAsDateInteger();
+
+ /**
+ * If all present search parameters are of DAY precision, and {@link DaoConfig#getUseOrdinalDatesForDayPrecisionSearches()} is true,
+ * then we attempt to use the ordinal field for date comparisons instead of the date field.
+ */
boolean isOrdinalComparison = isNullOrDayPrecision(lowerBound) && isNullOrDayPrecision(upperBound) && myDaoConfig.getUseOrdinalDatesForDayPrecisionSearches();
+
Predicate lt = null;
Predicate gt = null;
Predicate lb = null;
From 165f5fd808a4d6cf5907bf5d8d915629e7e11224 Mon Sep 17 00:00:00 2001
From: Gary Graham
Date: Wed, 26 Feb 2020 09:32:20 -0500
Subject: [PATCH 14/49] fix bugs in calculating ordinal date
---
.../main/java/ca/uhn/fhir/util/DateUtils.java | 8 ++++--
.../ResourceIndexedSearchParamDate.java | 16 +++++++++--
.../ResourceIndexedSearchParamDateTest.java | 27 ++++++++++---------
3 files changed, 34 insertions(+), 17 deletions(-)
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/DateUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/DateUtils.java
index f34a0ee12e5..d208e3fe599 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/DateUtils.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/DateUtils.java
@@ -171,15 +171,19 @@ public final class DateUtils {
return cal.getTime();
}
-
public static int convertDatetoDayInteger(final Date theDateValue) {
notNull(theDateValue, "Date value");
- Calendar cal = org.apache.commons.lang3.time.DateUtils.toCalendar(theDateValue);
SimpleDateFormat format = new SimpleDateFormat(PATTERN_INTEGER_DATE);
String theDateString = format.format(theDateValue);
return Integer.parseInt(theDateString);
}
+ public static String convertDateToIso8601String(final Date theDateValue){
+ notNull(theDateValue, "Date value");
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
+ return format.format(theDateValue);
+ }
+
/**
* Formats the given date according to the RFC 1123 pattern.
*
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
index d38f4dfe440..6a16c59b61e 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
@@ -26,6 +26,7 @@ import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.util.DateUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
@@ -34,6 +35,7 @@ import org.hibernate.search.annotations.Field;
import org.hl7.fhir.r4.model.DateTimeType;
import javax.persistence.*;
+import java.text.SimpleDateFormat;
import java.util.Date;
@Embeddable
@@ -93,13 +95,21 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
setParamName(theParamName);
setValueLow(theLow);
setValueHigh(theHigh);
+ if (theHigh != null && theHighString == null) {
+ theHighString = DateUtils.convertDateToIso8601String(theHigh);
+ }
+ if (theLow != null && theLowString == null) {
+ theLowString = DateUtils.convertDateToIso8601String(theLow);
+ }
computeValueHighDateOrdinal(theHighString);
computeValueLowDateOrdinal(theLowString);
myOriginalValue = theOriginalValue;
}
private void computeValueHighDateOrdinal(String theHigh) {
- this.myValueHighDateOrdinal = generateOrdinalDateInteger(theHigh);
+ if (!StringUtils.isBlank(theHigh)) {
+ this.myValueHighDateOrdinal = generateOrdinalDateInteger(theHigh);
+ }
}
private int generateOrdinalDateInteger(String theDateString){
if (theDateString.contains("T")) {
@@ -110,7 +120,9 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
}
private void computeValueLowDateOrdinal(String theLow) {
- this.myValueLowDateOrdinal = generateOrdinalDateInteger(theLow);
+ if (StringUtils.isNotBlank(theLow)) {
+ this.myValueLowDateOrdinal = generateOrdinalDateInteger(theLow);
+ }
}
public Integer getValueLowDateOrdinal() {
diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDateTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDateTest.java
index 407d05163f5..4ba8d5517e5 100644
--- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDateTest.java
+++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDateTest.java
@@ -4,6 +4,7 @@ import org.junit.Before;
import org.junit.Test;
import java.sql.Timestamp;
+import java.time.Instant;
import java.util.Calendar;
import java.util.Date;
@@ -45,8 +46,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsTrueForMatchingDates() {
- ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date1A.toString(), date2A, date2A.toString(), "SomeValue");
- ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1B, date1B.toString(), date2B, date2B.toString(), "SomeValue");
+ ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, null, date2A, null, "SomeValue");
+ ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1B, null, date2B, null, "SomeValue");
assertTrue(param.equals(param2));
assertTrue(param2.equals(param));
@@ -55,8 +56,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsTrueForMatchingTimeStampsThatMatch() {
- ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, timestamp1A.toString(), timestamp2A, timestamp2A.toString(), "SomeValue");
- ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1B, timestamp1B.toString(), timestamp2B, timestamp2B.toString(), "SomeValue");
+ ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, null, timestamp2A, null, "SomeValue");
+ ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1B, null, timestamp2B, null, "SomeValue");
assertTrue(param.equals(param2));
assertTrue(param2.equals(param));
@@ -67,8 +68,8 @@ public class ResourceIndexedSearchParamDateTest {
// other will be equivalent but will be a java.sql.Timestamp. Equals should work in both directions.
@Test
public void equalsIsTrueForMixedTimestampsAndDates() {
- ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date1A.toString(), date2A, date2A.toString(), "SomeValue");
- ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, timestamp1A.toString(), timestamp2A, timestamp2A.toString(), "SomeValue");
+ ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, null, date2A, null, "SomeValue");
+ ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, null, timestamp2A, null, "SomeValue");
assertTrue(param.equals(param2));
assertTrue(param2.equals(param));
@@ -77,8 +78,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsFalseForNonMatchingDates() {
- ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date1A.toString(), date2A, date2A.toString(), "SomeValue");
- ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date2A, date2A.toString(), date1A, date1A.toString(), "SomeValue");
+ ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, null, date2A, null, "SomeValue");
+ ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date2A, null, date1A, null, "SomeValue");
assertFalse(param.equals(param2));
assertFalse(param2.equals(param));
@@ -87,7 +88,7 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsFalseForNonMatchingDatesNullCase() {
- ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date1A.toString(), date2A, date2A.toString(), "SomeValue");
+ ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, null, date2A, null, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", null, null, null, null, "SomeValue");
assertFalse(param.equals(param2));
@@ -97,8 +98,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsFalseForNonMatchingTimeStamps() {
- ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, timestamp1A.toString(), timestamp2A, timestamp2A.toString(), "SomeValue");
- ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp2A, timestamp2A.toString(), timestamp1A, timestamp1A.toString(), "SomeValue");
+ ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, null, timestamp2A, null, "SomeValue");
+ ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp2A, null, timestamp1A, null, "SomeValue");
assertFalse(param.equals(param2));
assertFalse(param2.equals(param));
@@ -107,8 +108,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsFalseForMixedTimestampsAndDatesThatDoNotMatch() {
- ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date1A.toString(), date2A, date2A.toString(), "SomeValue");
- ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp2A, timestamp2A.toString(), timestamp1A, timestamp1A.toString(), "SomeValue");
+ ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, null, date2A, null, "SomeValue");
+ ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp2A, null, timestamp1A, null, "SomeValue");
assertFalse(param.equals(param2));
assertFalse(param2.equals(param));
From a97443b1ed075845b501a7f42354a1a941ec1e35 Mon Sep 17 00:00:00 2001
From: Gary Graham
Date: Wed, 26 Feb 2020 11:12:27 -0500
Subject: [PATCH 15/49] Fix test failures. One caused by invalid test. One
caused by granularity need LT not LTE
---
.../ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java | 4 ++--
.../fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java | 1 -
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
index 586e9692c95..dade7a7fbbe 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
@@ -222,8 +222,8 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
throw new InvalidRequestException("lowerBound and/or upperBound value not correctly specified for compare operation");
}
if (isOrdinalComparison){
- lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLowDateOrdinal"), lowerBoundAsOrdinal);
- gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHighDateOrdinal"), upperBoundAsOrdinal);
+ lt = theBuilder.lessThan(theFrom.get("myValueLowDateOrdinal"), lowerBoundAsOrdinal);
+ gt = theBuilder.greaterThan(theFrom.get("myValueHighDateOrdinal"), upperBoundAsOrdinal);
} else {
lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLow"), lowerBoundInstant);
gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHigh"), upperBoundInstant);
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java
index 9d1bbe51f52..143459c3f9b 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java
@@ -3282,7 +3282,6 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
public void testDateSearchParametersShouldBeTimezoneIndependent() {
createObservationWithEffective("NO1", "2011-01-02T23:00:00-11:30");
- createObservationWithEffective("NO2", "2011-01-03T00:00:00+01:00");
createObservationWithEffective("YES01", "2011-01-02T00:00:00-11:30");
createObservationWithEffective("YES02", "2011-01-02T00:00:00-10:00");
From 077f9df814640d37be47af9acfa5b9afbd846ccc Mon Sep 17 00:00:00 2001
From: Gary Graham
Date: Wed, 26 Feb 2020 12:04:33 -0500
Subject: [PATCH 16/49] I keep finding more tests
---
.../fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java | 3 ++-
.../module/matcher/InMemorySubscriptionMatcherR4Test.java | 4 ++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java
index 143459c3f9b..3e0d4d144d2 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java
@@ -3281,7 +3281,8 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
@Test
public void testDateSearchParametersShouldBeTimezoneIndependent() {
- createObservationWithEffective("NO1", "2011-01-02T23:00:00-11:30");
+ createObservationWithEffective("NO1", "2011-01-01T23:00:00-11:30");
+ createObservationWithEffective("NO2", "2011-01-03T23:00:00+01:30");
createObservationWithEffective("YES01", "2011-01-02T00:00:00-11:30");
createObservationWithEffective("YES02", "2011-01-02T00:00:00-10:00");
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java
index ee88580d9d6..77e2044500e 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java
@@ -884,10 +884,10 @@ public class InMemorySubscriptionMatcherR4Test {
public void testDateSearchParametersShouldBeTimezoneIndependent() {
List nlist = new ArrayList<>();
- nlist.add(createObservationWithEffective("NO1", "2011-01-02T23:00:00-11:30"));
- nlist.add(createObservationWithEffective("NO2", "2011-01-03T00:00:00+01:00"));
+ nlist.add(createObservationWithEffective("NO1", "2011-01-03T00:00:00+01:00"));
List ylist = new ArrayList<>();
+ ylist.add(createObservationWithEffective("YES00", "2011-01-02T23:00:00-11:30"));
ylist.add(createObservationWithEffective("YES01", "2011-01-02T00:00:00-11:30"));
ylist.add(createObservationWithEffective("YES02", "2011-01-02T00:00:00-10:00"));
ylist.add(createObservationWithEffective("YES03", "2011-01-02T00:00:00-09:00"));
From 2dc94de6bb13962c715e626f0b95c8ac41805b59 Mon Sep 17 00:00:00 2001
From: Gary Graham
Date: Wed, 26 Feb 2020 14:31:35 -0500
Subject: [PATCH 17/49] roll back change to inmemory matcher test
---
.../module/matcher/InMemorySubscriptionMatcherR4Test.java | 4 ++--
.../jpa/model/entity/ResourceIndexedSearchParamDate.java | 5 +++--
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java
index 77e2044500e..ee88580d9d6 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java
@@ -884,10 +884,10 @@ public class InMemorySubscriptionMatcherR4Test {
public void testDateSearchParametersShouldBeTimezoneIndependent() {
List nlist = new ArrayList<>();
- nlist.add(createObservationWithEffective("NO1", "2011-01-03T00:00:00+01:00"));
+ nlist.add(createObservationWithEffective("NO1", "2011-01-02T23:00:00-11:30"));
+ nlist.add(createObservationWithEffective("NO2", "2011-01-03T00:00:00+01:00"));
List ylist = new ArrayList<>();
- ylist.add(createObservationWithEffective("YES00", "2011-01-02T23:00:00-11:30"));
ylist.add(createObservationWithEffective("YES01", "2011-01-02T00:00:00-11:30"));
ylist.add(createObservationWithEffective("YES02", "2011-01-02T00:00:00-10:00"));
ylist.add(createObservationWithEffective("YES03", "2011-01-02T00:00:00-09:00"));
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
index 6a16c59b61e..abb132d093d 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
@@ -242,8 +242,9 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
if (!(theParam instanceof DateParam)) {
return false;
}
- DateParam date = (DateParam) theParam;
- DateRangeParam range = new DateRangeParam(date);
+ DateParam dateParam = (DateParam) theParam;
+ DateRangeParam range = new DateRangeParam(dateParam);
+
Date lowerBound = range.getLowerBoundAsInstant();
Date upperBound = range.getUpperBoundAsInstant();
From f86a4c9fa1cad9b1c3e2dd39ac400812ef3568f6 Mon Sep 17 00:00:00 2001
From: Gary Graham
Date: Wed, 26 Feb 2020 17:05:23 -0500
Subject: [PATCH 18/49] Add date ordinals to subscription matching checker
---
.../java/ca/uhn/fhir/jpa/dao/DaoConfig.java | 40 -----------------
.../dao/predicate/BasePredicateBuilder.java | 1 +
.../dao/predicate/PredicateBuilderDate.java | 2 +-
.../InMemorySubscriptionMatcherR4Test.java | 2 +-
.../BaseResourceIndexedSearchParam.java | 2 +-
.../fhir/jpa/model/entity/ModelConfig.java | 45 +++++++++++++++++--
.../ResourceIndexedSearchParamDate.java | 40 ++++++++++++++---
.../ResourceIndexedSearchParamNumber.java | 2 +-
.../ResourceIndexedSearchParamQuantity.java | 2 +-
.../ResourceIndexedSearchParamString.java | 2 +-
.../ResourceIndexedSearchParamToken.java | 2 +-
.../entity/ResourceIndexedSearchParamUri.java | 2 +-
.../ResourceIndexedSearchParams.java | 4 +-
.../matcher/InMemoryResourceMatcher.java | 6 +--
14 files changed, 91 insertions(+), 61 deletions(-)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
index 8d75b767a37..6d6262c1f1c 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
@@ -100,10 +100,6 @@ public class DaoConfig {
*/
private boolean myAllowInlineMatchUrlReferences = true;
private boolean myAllowMultipleDelete;
- /**
- * Update setter javadoc if default changes.
- */
- private boolean myUseOrdinalDatesForDayPrecisionSearches = true;
/**
* update setter javadoc if default changes
*/
@@ -1912,43 +1908,7 @@ public class DaoConfig {
setPreExpandValueSetsDefaultCount(Math.min(getPreExpandValueSetsDefaultCount(), getPreExpandValueSetsMaxCount()));
}
- /**
- *
- * Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
- * {@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
- * precision of {@link ca.uhn.fhir.model.api.TemporalPrecisionEnum#DAY}.
- *
- * For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
- * ordinal {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
- * and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
- *
- * Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
- * {@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
- * precision of {@link ca.uhn.fhir.model.api.TemporalPrecisionEnum#DAY}.
- *
- * For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
- * integer representing the ordinal date {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
- * and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
- *
- * Default is {@literal true} beginning in HAPI FHIR 4.3.
- *
- *
- * @since 4.3
- */
- public boolean getUseOrdinalDatesForDayPrecisionSearches() {
- return myUseOrdinalDatesForDayPrecisionSearches;
- }
public enum StoreMetaSourceInformationEnum {
NONE(false, false),
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java
index f5c6c0480ba..6c613e684bb 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java
@@ -50,6 +50,7 @@ abstract class BasePredicateBuilder {
@Autowired
DaoConfig myDaoConfig;
+
boolean myDontUseHashesForSearch;
final IDao myCallingDao;
final CriteriaBuilder myBuilder;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
index dade7a7fbbe..d8bfb3a7876 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
@@ -171,7 +171,7 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
* If all present search parameters are of DAY precision, and {@link DaoConfig#getUseOrdinalDatesForDayPrecisionSearches()} is true,
* then we attempt to use the ordinal field for date comparisons instead of the date field.
*/
- boolean isOrdinalComparison = isNullOrDayPrecision(lowerBound) && isNullOrDayPrecision(upperBound) && myDaoConfig.getUseOrdinalDatesForDayPrecisionSearches();
+ boolean isOrdinalComparison = isNullOrDayPrecision(lowerBound) && isNullOrDayPrecision(upperBound) && myDaoConfig.getModelConfig().getUseOrdinalDatesForDayPrecisionSearches();
Predicate lt = null;
Predicate gt = null;
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java
index ee88580d9d6..bb98b5de8d9 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java
@@ -884,10 +884,10 @@ public class InMemorySubscriptionMatcherR4Test {
public void testDateSearchParametersShouldBeTimezoneIndependent() {
List nlist = new ArrayList<>();
- nlist.add(createObservationWithEffective("NO1", "2011-01-02T23:00:00-11:30"));
nlist.add(createObservationWithEffective("NO2", "2011-01-03T00:00:00+01:00"));
List ylist = new ArrayList<>();
+ nlist.add(createObservationWithEffective("YES00", "2011-01-02T23:00:00-11:30"));
ylist.add(createObservationWithEffective("YES01", "2011-01-02T00:00:00-11:30"));
ylist.add(createObservationWithEffective("YES02", "2011-01-02T00:00:00-10:00"));
ylist.add(createObservationWithEffective("YES03", "2011-01-02T00:00:00-09:00"));
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java
index b891a3889b2..4393d0a5a29 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java
@@ -133,7 +133,7 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
public abstract IQueryParameterType toQueryParameterType();
- public boolean matches(IQueryParameterType theParam) {
+ public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
throw new UnsupportedOperationException("No parameter matcher for " + theParam);
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java
index 171c6d31866..1938f069ef6 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java
@@ -60,6 +60,10 @@ public class ModelConfig {
private String myEmailFromAddress = "noreply@unknown.com";
private boolean mySubscriptionMatchingEnabled = true;
private String myWebsocketContextPath = DEFAULT_WEBSOCKET_CONTEXT_PATH;
+ /**
+ * Update setter javadoc if default changes.
+ */
+ private boolean myUseOrdinalDatesForDayPrecisionSearches = true;
/**
* Constructor
@@ -257,7 +261,6 @@ public class ModelConfig {
myTreatReferencesAsLogical = new HashSet<>();
}
myTreatReferencesAsLogical.add(theTreatReferencesAsLogical);
-
}
/**
@@ -340,13 +343,12 @@ public class ModelConfig {
return mySubscriptionMatchingEnabled;
}
+
/**
* If set to true (default is true) the server will match incoming resources against active subscriptions
* and send them to the subscription channel. If set to false no matching or sending occurs.
* @since 3.7.0
*/
-
-
public void setSubscriptionMatchingEnabled(boolean theSubscriptionMatchingEnabled) {
mySubscriptionMatchingEnabled = theSubscriptionMatchingEnabled;
}
@@ -388,6 +390,43 @@ public class ModelConfig {
myWebsocketContextPath = theWebsocketContextPath;
}
+ /**
+ *
+ * Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
+ * {@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
+ * precision of {@link ca.uhn.fhir.model.api.TemporalPrecisionEnum#DAY}.
+ *
+ * For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
+ * ordinal {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
+ * and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
+ *
+ * Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
+ * {@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
+ * precision of {@link ca.uhn.fhir.model.api.TemporalPrecisionEnum#DAY}.
+ *
+ * For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
+ * integer representing the ordinal date {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
+ * and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
+ *
+ * Default is {@literal true} beginning in HAPI FHIR 4.3.
+ *
+ *
+ * @since 4.3
+ */
+ public boolean getUseOrdinalDatesForDayPrecisionSearches() {
+ return myUseOrdinalDatesForDayPrecisionSearches;
+ }
private static void validateTreatBaseUrlsAsLocal(String theUrl) {
Validate.notBlank(theUrl, "Base URL must not be null or empty");
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
index abb132d093d..9b2b4c93129 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
@@ -35,7 +35,6 @@ import org.hibernate.search.annotations.Field;
import org.hl7.fhir.r4.model.DateTimeType;
import javax.persistence.*;
-import java.text.SimpleDateFormat;
import java.util.Date;
@Embeddable
@@ -238,21 +237,33 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
}
@Override
- public boolean matches(IQueryParameterType theParam) {
+ public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
if (!(theParam instanceof DateParam)) {
return false;
}
DateParam dateParam = (DateParam) theParam;
DateRangeParam range = new DateRangeParam(dateParam);
+
+
+
+ boolean result;
+ if (theUseOrdinalDatesForDayComparison) {
+ result = matchesOrdinalDateBounds(range);
+ } else {
+ result = matchesDateBounds(range);
+ }
+
+ return result;
+ }
+
+ private boolean matchesDateBounds(DateRangeParam range) {
Date lowerBound = range.getLowerBoundAsInstant();
Date upperBound = range.getUpperBoundAsInstant();
-
if (lowerBound == null && upperBound == null) {
// should never happen
return false;
}
-
boolean result = true;
if (lowerBound != null) {
result &= (myValueLow.after(lowerBound) || myValueLow.equals(lowerBound));
@@ -265,8 +276,27 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
return result;
}
+ private boolean matchesOrdinalDateBounds(DateRangeParam range) {
+ boolean result = true;
+ Integer lowerBoundAsDateInteger = range.getLowerBoundAsDateInteger();
+ Integer upperBoundAsDateInteger = range.getUpperBoundAsDateInteger();
+ if (upperBoundAsDateInteger == null && lowerBoundAsDateInteger == null) {
+ return false;
+ }
+ if (lowerBoundAsDateInteger != null) {
+ result &= (myValueLowDateOrdinal.equals(lowerBoundAsDateInteger) || myValueLowDateOrdinal > lowerBoundAsDateInteger);
+ result &= (myValueHighDateOrdinal.equals(lowerBoundAsDateInteger) || myValueHighDateOrdinal > lowerBoundAsDateInteger);
+ }
+ if (upperBoundAsDateInteger != null) {
+ result &= (myValueHighDateOrdinal.equals(upperBoundAsDateInteger) || myValueHighDateOrdinal < upperBoundAsDateInteger);
+ result &= (myValueLowDateOrdinal.equals(upperBoundAsDateInteger) || myValueLowDateOrdinal < upperBoundAsDateInteger);
+ }
+ return result;
+ }
+
+
public static Long calculateOrdinalValue(Date theDate) {
return (long) DateUtils.convertDatetoDayInteger(theDate);
- };
+ }
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java
index f88e357ccd7..bb686e6cc5f 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java
@@ -158,7 +158,7 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
}
@Override
- public boolean matches(IQueryParameterType theParam) {
+ public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
if (!(theParam instanceof NumberParam)) {
return false;
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java
index 6107df7884d..8b9787a63db 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java
@@ -237,7 +237,7 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
}
@Override
- public boolean matches(IQueryParameterType theParam) {
+ public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
if (!(theParam instanceof QuantityParam)) {
return false;
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java
index 155bf7a8da3..714c2cd6f98 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java
@@ -314,7 +314,7 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
}
@Override
- public boolean matches(IQueryParameterType theParam) {
+ public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
if (!(theParam instanceof StringParam)) {
return false;
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java
index 44a14bf41ee..f18c2d53157 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java
@@ -239,7 +239,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
}
@Override
- public boolean matches(IQueryParameterType theParam) {
+ public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
if (!(theParam instanceof TokenParam)) {
return false;
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUri.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUri.java
index c827890d54b..6247dc668d1 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUri.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUri.java
@@ -188,7 +188,7 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
}
@Override
- public boolean matches(IQueryParameterType theParam) {
+ public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
if (!(theParam instanceof UriParam)) {
return false;
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java
index 73f0df466c1..9dff7395b62 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java
@@ -217,7 +217,7 @@ public final class ResourceIndexedSearchParams {
return myPopulatedResourceLinkParameters;
}
- public boolean matchParam(String theResourceName, String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam) {
+ public boolean matchParam(String theResourceName, String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
if (theParamDef == null) {
return false;
}
@@ -254,7 +254,7 @@ public final class ResourceIndexedSearchParams {
}
Predicate namedParamPredicate = param ->
param.getParamName().equalsIgnoreCase(theParamName) &&
- param.matches(theParam);
+ param.matches(theParam, theUseOrdinalDatesForDayComparison);
return resourceParams.stream().anyMatch(namedParamPredicate);
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java
index d83eb5048c9..2b52d347e91 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java
@@ -202,7 +202,7 @@ public class InMemoryResourceMatcher {
if (theSearchParams == null) {
return InMemoryMatchResult.successfulMatch();
} else {
- return InMemoryMatchResult.fromBoolean(theAndOrParams.stream().anyMatch(nextAnd -> matchParams(theResourceName, theParamName, theParamDef, nextAnd, theSearchParams)));
+ return InMemoryMatchResult.fromBoolean(theAndOrParams.stream().anyMatch(nextAnd -> matchParams(theResourceName, theParamName, theParamDef, nextAnd, theSearchParams, myModelConfig.getUseOrdinalDatesForDayPrecisionSearches())));
}
case COMPOSITE:
case HAS:
@@ -219,11 +219,11 @@ public class InMemoryResourceMatcher {
}
}
- private boolean matchParams(String theResourceName, String theParamName, RuntimeSearchParam paramDef, List extends IQueryParameterType> theNextAnd, ResourceIndexedSearchParams theSearchParams) {
+ private boolean matchParams(String theResourceName, String theParamName, RuntimeSearchParam paramDef, List extends IQueryParameterType> theNextAnd, ResourceIndexedSearchParams theSearchParams, boolean theUseOrdinalDatesForDayComparison) {
if (paramDef.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
stripBaseUrlsFromReferenceParams(theNextAnd);
}
- return theNextAnd.stream().anyMatch(token -> theSearchParams.matchParam(theResourceName, theParamName, paramDef, token));
+ return theNextAnd.stream().anyMatch(token -> theSearchParams.matchParam(theResourceName, theParamName, paramDef, token, theUseOrdinalDatesForDayComparison));
}
private void stripBaseUrlsFromReferenceParams(List extends IQueryParameterType> theNextAnd) {
From b1d7072c42d20e15d319675a923c51961b30e6c3 Mon Sep 17 00:00:00 2001
From: Gary Graham
Date: Wed, 26 Feb 2020 22:22:42 -0500
Subject: [PATCH 19/49] Add precision checker for ordinal date returning on
DateRangeParam
---
.../uhn/fhir/rest/param/DateRangeParam.java | 42 ++++++++++++++++++-
.../ResourceIndexedSearchParamDate.java | 2 +
.../InMemoryResourceMatcherR5Test.java | 4 +-
3 files changed, 44 insertions(+), 4 deletions(-)
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java
index d5f491a0f07..a948c247b97 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java
@@ -272,7 +272,27 @@ public class DateRangeParam implements IQueryParameterAnd {
if (myLowerBound == null || myLowerBound.getValue() == null) {
return null;
}
- return DateUtils.convertDatetoDayInteger(myLowerBound.getValue());
+ //TODO LOOK AT THE DATE VERSION, WHERE PRECISION OCCASIONALLY CHANGES THE STUPID THING. ESSENTIALLY ADD A SWITCH
+ int retVal = DateUtils.convertDatetoDayInteger(myLowerBound.getValue());
+
+ if (myLowerBound.getPrefix() != null) {
+ switch (myLowerBound.getPrefix()) {
+ case GREATERTHAN:
+ case STARTS_AFTER:
+ retVal += 1;
+ break;
+ case EQUAL:
+ case GREATERTHAN_OR_EQUALS:
+ break;
+ case LESSTHAN:
+ case APPROXIMATE:
+ case LESSTHAN_OR_EQUALS:
+ case ENDS_BEFORE:
+ case NOT_EQUAL:
+ throw new IllegalStateException("Invalid lower bound comparator: " + myLowerBound.getPrefix());
+ }
+ }
+ return retVal;
}
/**
@@ -284,7 +304,25 @@ public class DateRangeParam implements IQueryParameterAnd {
if (myUpperBound == null || myUpperBound.getValue() == null) {
return null;
}
- return DateUtils.convertDatetoDayInteger(myUpperBound.getValue());
+ int retVal = DateUtils.convertDatetoDayInteger(myUpperBound.getValue());
+ if (myUpperBound.getPrefix() != null) {
+ switch (myUpperBound.getPrefix()) {
+ case LESSTHAN:
+ case ENDS_BEFORE:
+ retVal -= 1;
+ break;
+ case EQUAL:
+ case LESSTHAN_OR_EQUALS:
+ break;
+ case GREATERTHAN_OR_EQUALS:
+ case GREATERTHAN:
+ case APPROXIMATE:
+ case NOT_EQUAL:
+ case STARTS_AFTER:
+ throw new IllegalStateException("Invalid upper bound comparator: " + myUpperBound.getPrefix());
+ }
+ }
+ return retVal;
}
public Date getLowerBoundAsInstant() {
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
index 9b2b4c93129..93e90200079 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
@@ -250,6 +250,7 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
boolean result;
if (theUseOrdinalDatesForDayComparison) {
result = matchesOrdinalDateBounds(range);
+ result = matchesDateBounds(range);
} else {
result = matchesDateBounds(range);
}
@@ -284,6 +285,7 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
return false;
}
if (lowerBoundAsDateInteger != null) {
+ //TODO as we run into equality issues
result &= (myValueLowDateOrdinal.equals(lowerBoundAsDateInteger) || myValueLowDateOrdinal > lowerBoundAsDateInteger);
result &= (myValueHighDateOrdinal.equals(lowerBoundAsDateInteger) || myValueHighDateOrdinal > lowerBoundAsDateInteger);
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5Test.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5Test.java
index bb111f87e86..daf4215ed68 100644
--- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5Test.java
+++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5Test.java
@@ -148,8 +148,8 @@ public class InMemoryResourceMatcherR5Test {
@Test
public void testDateSupportedOps() {
- testDateSupportedOp(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, true, true, false);
testDateSupportedOp(ParamPrefixEnum.GREATERTHAN, true, false, false);
+ testDateSupportedOp(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, true, true, false);
testDateSupportedOp(ParamPrefixEnum.EQUAL, false, true, false);
testDateSupportedOp(ParamPrefixEnum.LESSTHAN_OR_EQUALS, false, true, true);
testDateSupportedOp(ParamPrefixEnum.LESSTHAN, false, false, true);
@@ -165,7 +165,7 @@ public class InMemoryResourceMatcherR5Test {
{
InMemoryMatchResult result = myInMemoryResourceMatcher.match(equation + OBSERVATION_DATE, myObservation, mySearchParams);
assertTrue(result.getUnsupportedReason(), result.supported());
- assertEquals(result.matched(), theSame);
+ assertEquals(theSame, result.matched());
}
{
InMemoryMatchResult result = myInMemoryResourceMatcher.match(equation + LATE_DATE, myObservation, mySearchParams);
From 6123e4b189e450952043cf29c705ccda0ad1a12e Mon Sep 17 00:00:00 2001
From: Gary Graham
Date: Fri, 28 Feb 2020 16:31:16 -0500
Subject: [PATCH 20/49] rework based on PR comments
---
.../dao/predicate/PredicateBuilderDate.java | 71 +++++++------------
1 file changed, 27 insertions(+), 44 deletions(-)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
index d8bfb3a7876..fc509b95aa8 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
@@ -166,7 +166,8 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
DateParam upperBound = theRange.getUpperBound();
Integer lowerBoundAsOrdinal = theRange.getLowerBoundAsDateInteger();
Integer upperBoundAsOrdinal = theRange.getUpperBoundAsDateInteger();
-
+ Comparable genericLowerBound;
+ Comparable genericUpperBound;
/**
* If all present search parameters are of DAY precision, and {@link DaoConfig#getUseOrdinalDatesForDayPrecisionSearches()} is true,
* then we attempt to use the ordinal field for date comparisons instead of the date field.
@@ -177,70 +178,56 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
Predicate gt = null;
Predicate lb = null;
Predicate ub = null;
+ String lowValueField;
+ String highValueField;
+
+ if (isOrdinalComparison) {
+ lowValueField = "myValueLowDateOrdinal";
+ highValueField = "myValueHighDateOrdinal";
+ genericLowerBound = lowerBoundAsOrdinal;
+ genericUpperBound = upperBoundAsOrdinal;
+ } else {
+ lowValueField = "myValueLow";
+ highValueField = "myValueHigh";
+ genericLowerBound = lowerBoundInstant;
+ genericUpperBound = upperBoundInstant;
+ }
if (operation == SearchFilterParser.CompareOperation.lt) {
if (lowerBoundInstant == null) {
throw new InvalidRequestException("lowerBound value not correctly specified for compare operation");
}
//im like 80% sure this should be ub and not lb, as it is an UPPER bound.
- if (isOrdinalComparison) {
- lb = theBuilder.lessThan(theFrom.get("myValueLowDateOrdinal"), lowerBoundAsOrdinal);
- } else {
- lb = theBuilder.lessThan(theFrom.get("myValueLow"), lowerBoundInstant);
- }
+ lb = theBuilder.lessThan(theFrom.get(lowValueField), genericLowerBound);
} else if (operation == SearchFilterParser.CompareOperation.le) {
if (upperBoundInstant == null) {
throw new InvalidRequestException("upperBound value not correctly specified for compare operation");
}
//im like 80% sure this should be ub and not lb, as it is an UPPER bound.
- if (isOrdinalComparison) {
- lb = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHighDateOrdinal"), upperBoundAsOrdinal);
- } else {
- lb = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHigh"), upperBoundInstant);
- }
+ lb = theBuilder.lessThanOrEqualTo(theFrom.get(highValueField), genericUpperBound);
} else if (operation == SearchFilterParser.CompareOperation.gt) {
if (upperBoundInstant == null) {
throw new InvalidRequestException("upperBound value not correctly specified for compare operation");
}
- if (isOrdinalComparison) {
- lb = theBuilder.greaterThan(theFrom.get("myValueHighDateOrdinal"), upperBoundAsOrdinal);
- } else {
- lb = theBuilder.greaterThan(theFrom.get("myValueHigh"), upperBoundInstant);
- }
+ lb = theBuilder.greaterThan(theFrom.get(highValueField), genericUpperBound);
} else if (operation == SearchFilterParser.CompareOperation.ge) {
if (lowerBoundInstant == null) {
throw new InvalidRequestException("lowerBound value not correctly specified for compare operation");
}
- if (isOrdinalComparison) {
- lb = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLowDateOrdinal"), lowerBoundAsOrdinal);
- } else {
- lb = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLow"), lowerBoundInstant);
- }
+ lb = theBuilder.greaterThanOrEqualTo(theFrom.get(lowValueField), genericLowerBound);
} else if (operation == SearchFilterParser.CompareOperation.ne) {
if ((lowerBoundInstant == null) ||
(upperBoundInstant == null)) {
throw new InvalidRequestException("lowerBound and/or upperBound value not correctly specified for compare operation");
}
- if (isOrdinalComparison){
- lt = theBuilder.lessThan(theFrom.get("myValueLowDateOrdinal"), lowerBoundAsOrdinal);
- gt = theBuilder.greaterThan(theFrom.get("myValueHighDateOrdinal"), upperBoundAsOrdinal);
- } else {
- lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLow"), lowerBoundInstant);
- gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHigh"), upperBoundInstant);
- }
+ lt = theBuilder.lessThan(theFrom.get(lowValueField), genericLowerBound);
+ gt = theBuilder.greaterThan(theFrom.get(highValueField), genericUpperBound);
lb = theBuilder.or(lt,
gt);
} else if ((operation == SearchFilterParser.CompareOperation.eq) || (operation == null)) {
if (lowerBoundInstant != null) {
- if (isOrdinalComparison) {
- gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLowDateOrdinal"), lowerBoundAsOrdinal);
- lt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHighDateOrdinal"), lowerBoundAsOrdinal);
- //also try a strict equality here.
- }
- else {
- gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLow"), lowerBoundInstant);
- lt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHigh"), lowerBoundInstant);
- }
+ gt = theBuilder.greaterThanOrEqualTo(theFrom.get(lowValueField), genericLowerBound);
+ lt = theBuilder.greaterThanOrEqualTo(theFrom.get(highValueField), genericLowerBound);
if (lowerBound.getPrefix() == ParamPrefixEnum.STARTS_AFTER || lowerBound.getPrefix() == ParamPrefixEnum.EQUAL) {
lb = gt;
} else {
@@ -249,13 +236,9 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
}
if (upperBoundInstant != null) {
- if (isOrdinalComparison) {
- gt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLowDateOrdinal"), upperBoundAsOrdinal);
- lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHighDateOrdinal"), upperBoundAsOrdinal);
- } else {
- gt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLow"), upperBoundInstant);
- lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHigh"), upperBoundInstant);
- }
+ gt = theBuilder.lessThanOrEqualTo(theFrom.get(lowValueField), genericUpperBound);
+ lt = theBuilder.lessThanOrEqualTo(theFrom.get(highValueField), genericUpperBound);
+
if (theRange.getUpperBound().getPrefix() == ParamPrefixEnum.ENDS_BEFORE || theRange.getUpperBound().getPrefix() == ParamPrefixEnum.EQUAL) {
ub = lt;
From df7a9916c39d66422eb8a67f7d3fa4ebf4accaf3 Mon Sep 17 00:00:00 2001
From: Tadgh
Date: Tue, 3 Mar 2020 14:01:12 -0800
Subject: [PATCH 21/49] Swap from trace to info logging
---
hapi-fhir-jpaserver-base/src/test/resources/logback-test.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hapi-fhir-jpaserver-base/src/test/resources/logback-test.xml b/hapi-fhir-jpaserver-base/src/test/resources/logback-test.xml
index c8a33e5568b..6818eb0a0f3 100644
--- a/hapi-fhir-jpaserver-base/src/test/resources/logback-test.xml
+++ b/hapi-fhir-jpaserver-base/src/test/resources/logback-test.xml
@@ -27,7 +27,7 @@
-
+
From a5105439339d6cb94ed22308c97822932d20ad0e Mon Sep 17 00:00:00 2001
From: Tadgh
Date: Tue, 3 Mar 2020 14:03:55 -0800
Subject: [PATCH 22/49] Fix log conditional
---
.../ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
index fc509b95aa8..cb80ed64a5e 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java
@@ -252,8 +252,9 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
}
if (isOrdinalComparison) {
ourLog.trace("Ordinal date range is {} - {} ", lowerBoundAsOrdinal, upperBoundAsOrdinal);
+ } else {
+ ourLog.trace("Date range is {} - {}", lowerBoundInstant, upperBoundInstant);
}
- ourLog.trace("Date range is {} - {}", lowerBoundInstant, upperBoundInstant);
if (lb != null && ub != null) {
return (theBuilder.and(lb, ub));
From 8b223a1fd9d7a34f22fdb688472ae59db524e93e Mon Sep 17 00:00:00 2001
From: Tadgh
Date: Tue, 3 Mar 2020 16:07:25 -0800
Subject: [PATCH 23/49] Rework selection generation of calculator. Add null
check for empty dates
---
.../migrate/taskdef/BaseColumnCalculatorTask.java | 6 +++---
.../jpa/migrate/taskdef/BaseTableColumnTask.java | 14 +++++++++++++-
.../fhir/jpa/migrate/taskdef/BaseTableTask.java | 3 ++-
.../migrate/taskdef/CalculateOrdinalDatesTask.java | 4 +++-
.../entity/ResourceIndexedSearchParamDate.java | 3 +++
5 files changed, 24 insertions(+), 6 deletions(-)
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseColumnCalculatorTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseColumnCalculatorTask.java
index ddd5300bfcb..909d830bec3 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseColumnCalculatorTask.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseColumnCalculatorTask.java
@@ -20,7 +20,6 @@ package ca.uhn.fhir.jpa.migrate.taskdef;
* #L%
*/
-import ca.uhn.fhir.jpa.migrate.JdbcUtils;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.VersionEnum;
import com.google.common.collect.ForwardingMap;
@@ -77,8 +76,9 @@ public abstract class BaseColumnCalculatorTask extends BaseTableColumnTask {
JdbcTemplate jdbcTemplate = newJdbcTemplate();
jdbcTemplate.setMaxRows(100000);
- String sql = "SELECT * FROM " + getTableName() + " WHERE " + getColumnName() + " IS NULL";
- logInfo(ourLog, "Finding up to {} rows in {} that requires calculations", myBatchSize, getTableName());
+
+ String sql = "SELECT * FROM " + getTableName() + " WHERE " + getWhereClause();
+ logInfo(ourLog, "Finding up to {} rows in {} that requires calculations, using query: {}", myBatchSize, getTableName(), sql);
jdbcTemplate.query(sql, rch);
rch.done();
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTask.java
index e4e024ed1e1..dc38d081ad4 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTask.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTask.java
@@ -30,6 +30,8 @@ import java.util.Locale;
public abstract class BaseTableColumnTask> extends BaseTableTask {
private String myColumnName;
+ //If a concrete class decides to, they can define a custom WHERE clause for the task.
+ private String myWhereClause;
public BaseTableColumnTask(String theProductVersion, String theSchemaVersion) {
super(theProductVersion, theSchemaVersion);
@@ -41,11 +43,21 @@ public abstract class BaseTableColumnTask> extends Ba
return (T) this;
}
-
public String getColumnName() {
return myColumnName;
}
+ protected void setWhereClause(String theWhereClause) {
+ this.myWhereClause = theWhereClause;
+ }
+ protected String getWhereClause() {
+ if (myWhereClause == null) {
+ return getColumnName() + " IS NULL";
+ } else {
+ return myWhereClause;
+ }
+ }
+
@Override
public void validate() {
super.validate();
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableTask.java
index 3fce1b54fa3..73def6f5bc0 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableTask.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableTask.java
@@ -27,6 +27,8 @@ import org.apache.commons.lang3.builder.HashCodeBuilder;
public abstract class BaseTableTask> extends BaseTask {
private String myTableName;
+
+
public BaseTableTask(String theProductVersion, String theSchemaVersion) {
super(theProductVersion, theSchemaVersion);
}
@@ -34,7 +36,6 @@ public abstract class BaseTableTask> extends BaseTask
public String getTableName() {
return myTableName;
}
-
public T setTableName(String theTableName) {
Validate.notBlank(theTableName);
myTableName = theTableName;
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateOrdinalDatesTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateOrdinalDatesTask.java
index d0d16c1e8d4..29371cc1c6d 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateOrdinalDatesTask.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateOrdinalDatesTask.java
@@ -26,8 +26,10 @@ public class CalculateOrdinalDatesTask extends BaseColumnCalculatorTask {
public CalculateOrdinalDatesTask(VersionEnum theRelease, String theVersion) {
super(theRelease, theVersion);
- setDescription("Calculate SP_LOW_VALUE_DATE and SP_HIGH_VALUE_DATE based on existing SP_LOW and SP_HIGH date values in Date Search Params");
+ setDescription("Calculate SP_LOW_VALUE_DATE_ORDINAL and SP_HIGH_VALUE_DATE_ORDINAL based on existing SP_VALUE_LOW and SP_VALUE_HIGH date values in Date Search Params");
+ setWhereClause("SP_VALUE_LOW_DATE_ORDINAL IS NULL AND SP_VALUE_LOW IS NOT NULL) OR (SP_VALUE_HIGH_DATE_ORDINAL IS NULL AND SP_VALUE_HIGH IS NOT NULL");
}
+
@Override
protected boolean shouldSkipTask() {
return false; // TODO Is there a case where we should just not do this?
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
index 93e90200079..8ef1d524fe2 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
@@ -298,6 +298,9 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
public static Long calculateOrdinalValue(Date theDate) {
+ if (theDate == null) {
+ return null;
+ }
return (long) DateUtils.convertDatetoDayInteger(theDate);
}
From 14f671ec0bfc158a4dd3b5909983733dd15b744c Mon Sep 17 00:00:00 2001
From: Tadgh
Date: Tue, 3 Mar 2020 22:31:37 -0800
Subject: [PATCH 24/49] Fix SQL Typo
---
.../uhn/fhir/jpa/migrate/taskdef/CalculateOrdinalDatesTask.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateOrdinalDatesTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateOrdinalDatesTask.java
index 29371cc1c6d..22e1c0ec206 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateOrdinalDatesTask.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateOrdinalDatesTask.java
@@ -27,7 +27,7 @@ public class CalculateOrdinalDatesTask extends BaseColumnCalculatorTask {
public CalculateOrdinalDatesTask(VersionEnum theRelease, String theVersion) {
super(theRelease, theVersion);
setDescription("Calculate SP_LOW_VALUE_DATE_ORDINAL and SP_HIGH_VALUE_DATE_ORDINAL based on existing SP_VALUE_LOW and SP_VALUE_HIGH date values in Date Search Params");
- setWhereClause("SP_VALUE_LOW_DATE_ORDINAL IS NULL AND SP_VALUE_LOW IS NOT NULL) OR (SP_VALUE_HIGH_DATE_ORDINAL IS NULL AND SP_VALUE_HIGH IS NOT NULL");
+ setWhereClause("SP_VALUE_LOW_DATE_ORDINAL IS NULL AND SP_VALUE_LOW IS NOT NULL) OR (SP_VALUE_HIGH_DATE_ORDINAL IS NULL AND SP_VALUE_HIGH IS NOT NULL)");
}
@Override
From 598bf56e871604eec1cdcf841de19990e4706c1e Mon Sep 17 00:00:00 2001
From: Tadgh
Date: Wed, 4 Mar 2020 08:56:03 -0800
Subject: [PATCH 25/49] Broken SQL Statement
---
.../uhn/fhir/jpa/migrate/taskdef/CalculateOrdinalDatesTask.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateOrdinalDatesTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateOrdinalDatesTask.java
index 22e1c0ec206..78cbfda2474 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateOrdinalDatesTask.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateOrdinalDatesTask.java
@@ -27,7 +27,7 @@ public class CalculateOrdinalDatesTask extends BaseColumnCalculatorTask {
public CalculateOrdinalDatesTask(VersionEnum theRelease, String theVersion) {
super(theRelease, theVersion);
setDescription("Calculate SP_LOW_VALUE_DATE_ORDINAL and SP_HIGH_VALUE_DATE_ORDINAL based on existing SP_VALUE_LOW and SP_VALUE_HIGH date values in Date Search Params");
- setWhereClause("SP_VALUE_LOW_DATE_ORDINAL IS NULL AND SP_VALUE_LOW IS NOT NULL) OR (SP_VALUE_HIGH_DATE_ORDINAL IS NULL AND SP_VALUE_HIGH IS NOT NULL)");
+ setWhereClause("(SP_VALUE_LOW_DATE_ORDINAL IS NULL AND SP_VALUE_LOW IS NOT NULL) OR (SP_VALUE_HIGH_DATE_ORDINAL IS NULL AND SP_VALUE_HIGH IS NOT NULL)");
}
@Override
From 828ddd51854164d18b0f276af45cff0ac78330aa Mon Sep 17 00:00:00 2001
From: Tadgh
Date: Wed, 4 Mar 2020 12:14:55 -0800
Subject: [PATCH 26/49] Update docs
---
.../src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java | 1 -
.../main/resources/ca/uhn/hapi/fhir/docs/server_jpa/search.md | 4 ----
2 files changed, 5 deletions(-)
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java
index a948c247b97..609a49ebd06 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java
@@ -272,7 +272,6 @@ public class DateRangeParam implements IQueryParameterAnd {
if (myLowerBound == null || myLowerBound.getValue() == null) {
return null;
}
- //TODO LOOK AT THE DATE VERSION, WHERE PRECISION OCCASIONALLY CHANGES THE STUPID THING. ESSENTIALLY ADD A SWITCH
int retVal = DateUtils.convertDatetoDayInteger(myLowerBound.getValue());
if (myLowerBound.getPrefix() != null) {
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/search.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/search.md
index 26fe08dc30c..44f8fc19b8f 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/search.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/search.md
@@ -2,10 +2,6 @@
The HAPI FHIR JPA Server fully implements most [FHIR search](https://www.hl7.org/fhir/search.html) operations for most versions of FHIR. However, there are some known limitations of the current implementation. Here is a partial list of search functionality that is not currently supported in HAPI FHIR:
-### Date searches without timestamp
-
-Searching by date with no timestamp currently doesn't match all records it should. See [Issue 1499](https://github.com/jamesagnew/hapi-fhir/issues/1499).
-
### Chains within _has
Chains within _has are not currently supported for performance reasons. For example, this search is not currently supported
From ea817de68ac3799ebedc3b2c03dcc906a2b3b62f Mon Sep 17 00:00:00 2001
From: Thomas Papke
Date: Wed, 29 Apr 2020 15:01:39 +0200
Subject: [PATCH 27/49] Extend support for additional forwared headers
x-forwarded-port and (#1788)
x-forwarded-prefix
---
.../server/ApacheProxyAddressStrategy.java | 155 +++++++++++++-----
.../ApacheProxyAddressStrategyTest.java | 101 ++++++++++++
2 files changed, 214 insertions(+), 42 deletions(-)
create mode 100644 hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategyTest.java
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategy.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategy.java
index eb574295f62..a05a2a78526 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategy.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategy.java
@@ -1,5 +1,7 @@
package ca.uhn.fhir.rest.server;
+import java.net.URI;
+
/*
* #%L
* HAPI FHIR - Server Framework
@@ -20,68 +22,137 @@ package ca.uhn.fhir.rest.server;
* #L%
*/
+import java.util.Optional;
+
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.server.ServletServerHttpRequest;
+
+import static java.util.Optional.ofNullable;
+
+import ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy;
+
/**
- * Works like the normal {@link ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy} unless there's an x-forwarded-host present, in which case that's used in place of the server's address.
- *
- * If the Apache Http Server mod_proxy isn't configured to supply x-forwarded-proto, the factory method that you use to create the address strategy will determine the default. Note that
- * mod_proxy doesn't set this by default, but it can be configured via RequestHeader set X-Forwarded-Proto http (or https)
- *
- *
- * If you want to set the protocol based on something other than the constructor argument, you should be able to do so by overriding protocol.
- *
- *
- * Note that while this strategy was designed to work with Apache Http Server, and has been tested against it, it should work with any proxy server that sets x-forwarded-host
+ * Works like the normal
+ * {@link ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy} unless there's
+ * an x-forwarded-host present, in which case that's used in place of the
+ * server's address.
+ *
+ * If the Apache Http Server mod_proxy isn't configured to supply
+ * x-forwarded-proto, the factory method that you use to create the
+ * address strategy will determine the default. Note that mod_proxy
+ * doesn't set this by default, but it can be configured via
+ * RequestHeader set X-Forwarded-Proto http (or https)
+ *
+ *
+ * List of supported forward headers:
+ *
+ *
x-forwarded-host - original host requested by the client throw proxy
+ * server
+ *
x-forwarded-proto - original protocol (http, https) requested by the
+ * client
+ *
x-forwarded-port - original port request by the client, assume default
+ * port if not defined
+ *
x-forwarded-prefix - original server prefix / context path requested by
+ * the client
+ *
+ *
+ *
+ * If you want to set the protocol based on something other than the constructor
+ * argument, you should be able to do so by overriding protocol.
+ *
+ *
+ * Note that while this strategy was designed to work with Apache Http Server,
+ * and has been tested against it, it should work with any proxy server that
+ * sets x-forwarded-host
*
*
- * @author Created by Bill de Beaubien on 3/30/2015.
*/
public class ApacheProxyAddressStrategy extends IncomingRequestAddressStrategy {
- private boolean myUseHttps = false;
+ private static final String X_FORWARDED_PREFIX = "x-forwarded-prefix";
+ private static final String X_FORWARDED_PROTO = "x-forwarded-proto";
+ private static final String X_FORWARDED_HOST = "x-forwarded-host";
+ private static final String X_FORWARDED_PORT = "x-forwarded-port";
- protected ApacheProxyAddressStrategy(boolean theUseHttps) {
- myUseHttps = theUseHttps;
+ private static final Logger LOG = LoggerFactory
+ .getLogger(ApacheProxyAddressStrategy.class);
+
+ private final boolean useHttps;
+
+ /**
+ * @param useHttps
+ * Is used when the {@code x-forwarded-proto} is not set in the
+ * request.
+ */
+ public ApacheProxyAddressStrategy(boolean useHttps) {
+ this.useHttps = useHttps;
}
@Override
- public String determineServerBase(ServletContext theServletContext, HttpServletRequest theRequest) {
- String forwardedHost = getForwardedHost(theRequest);
- if (forwardedHost != null) {
- return forwardedServerBase(theServletContext, theRequest, forwardedHost);
- }
- return super.determineServerBase(theServletContext, theRequest);
+ public String determineServerBase(ServletContext servletContext,
+ HttpServletRequest request) {
+ String serverBase = super.determineServerBase(servletContext, request);
+ ServletServerHttpRequest requestWrapper = new ServletServerHttpRequest(
+ request);
+ HttpHeaders headers = requestWrapper.getHeaders();
+ Optional forwardedHost = headers
+ .getValuesAsList(X_FORWARDED_HOST).stream().findFirst();
+ return forwardedHost
+ .map(s -> forwardedServerBase(serverBase, headers, s))
+ .orElse(serverBase);
}
- public String forwardedServerBase(ServletContext theServletContext, HttpServletRequest theRequest, String theForwardedHost) {
- String serverBase = super.determineServerBase(theServletContext, theRequest);
- String host = theRequest.getHeader("host");
- if (host != null) {
- serverBase = serverBase.replace(host, theForwardedHost);
- serverBase = serverBase.substring(serverBase.indexOf("://"));
- return protocol(theRequest) + serverBase;
- }
- return serverBase;
+ private String forwardedServerBase(String originalServerBase,
+ HttpHeaders headers, String forwardedHost) {
+ Optional forwardedPrefix = getForwardedPrefix(headers);
+ LOG.debug("serverBase: {}, forwardedHost: {}, forwardedPrefix: {}",
+ originalServerBase, forwardedHost, forwardedPrefix);
+ LOG.debug("request header: {}", headers);
+
+ String host = protocol(headers) + "://" + forwardedHost;
+ String hostWithOptionalPort = port(headers).map(p -> (host + ":" + p))
+ .orElse(host);
+
+ String path = forwardedPrefix
+ .orElseGet(() -> pathFrom(originalServerBase));
+ return joinStringsWith(hostWithOptionalPort, path, "/");
}
- private String getForwardedHost(HttpServletRequest theRequest) {
- String forwardedHost = theRequest.getHeader("x-forwarded-host");
- if (forwardedHost != null) {
- int commaPos = forwardedHost.indexOf(',');
- if (commaPos >= 0) {
- forwardedHost = forwardedHost.substring(0, commaPos - 1);
- }
- }
- return forwardedHost;
+ private Optional port(HttpHeaders headers) {
+ return ofNullable(headers.getFirst(X_FORWARDED_PORT));
}
- protected String protocol(HttpServletRequest theRequest) {
- String protocol = theRequest.getHeader("x-forwarded-proto");
+ private String pathFrom(String serverBase) {
+ String serverBasePath = URI.create(serverBase).getPath();
+ return StringUtils.defaultIfBlank(serverBasePath, "");
+ }
+
+ private static String joinStringsWith(String left, String right,
+ String joiner) {
+ if (left.endsWith(joiner) && right.startsWith(joiner)) {
+ return left + right.substring(1);
+ } else if (left.endsWith(joiner) || right.startsWith(joiner)) {
+ return left + right;
+ } else {
+ return left + joiner + right;
+ }
+ }
+
+ private Optional getForwardedPrefix(HttpHeaders headers) {
+ return ofNullable(headers.getFirst(X_FORWARDED_PREFIX));
+ }
+
+ private String protocol(HttpHeaders headers) {
+ String protocol = headers.getFirst(X_FORWARDED_PROTO);
if (protocol != null) {
return protocol;
}
- return myUseHttps ? "https" : "http";
+ return useHttps ? "https" : "http";
}
/**
@@ -97,4 +168,4 @@ public class ApacheProxyAddressStrategy extends IncomingRequestAddressStrategy {
public static ApacheProxyAddressStrategy forHttps() {
return new ApacheProxyAddressStrategy(true);
}
-}
+}
\ No newline at end of file
diff --git a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategyTest.java b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategyTest.java
new file mode 100644
index 00000000000..8f5c2f3fc47
--- /dev/null
+++ b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategyTest.java
@@ -0,0 +1,101 @@
+package ca.uhn.fhir.rest.server;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+
+public class ApacheProxyAddressStrategyTest {
+
+ @Test
+ public void testWithoutForwarded() {
+ ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
+ true);
+ MockHttpServletRequest request = prepareRequest();
+ String serverBase = addressStrategy.determineServerBase(null, request);
+ assertEquals("https://localhost/imagingstudy/fhir", serverBase);
+ }
+
+ @Test
+ public void testWithForwardedHostWithoutForwardedProtoHttps() {
+ ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
+ true);
+ MockHttpServletRequest request = prepareRequest();
+ request.addHeader("X-Forwarded-Host", "my.example.host");
+ String serverBase = addressStrategy.determineServerBase(null, request);
+ assertEquals("https://my.example.host/imagingstudy/fhir", serverBase);
+ }
+
+ @Test
+ public void testWithForwardedHostWithoutForwardedProtoHttp() {
+ ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
+ false);
+ MockHttpServletRequest request = prepareRequest();
+ request.addHeader("X-Forwarded-Host", "my.example.host");
+ String serverBase = addressStrategy.determineServerBase(null, request);
+ assertEquals("http://my.example.host/imagingstudy/fhir", serverBase);
+ }
+
+ @Test
+ public void testWithForwarded() {
+ ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
+ true);
+ MockHttpServletRequest request = prepareRequest();
+ request.addHeader("X-Forwarded-Host", "my.example.host");
+ request.addHeader("X-Forwarded-Proto", "https");
+ request.addHeader("X-Forwarded-Prefix", "server-prefix/fhir");
+ String serverBase = addressStrategy.determineServerBase(null, request);
+ assertEquals("https://my.example.host/server-prefix/fhir", serverBase);
+ }
+
+ @Test
+ public void testWithForwardedWithHostPrefixWithSlash() {
+ ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
+ true);
+ MockHttpServletRequest request = prepareRequest();
+ request.addHeader("host", "localhost");
+
+ request.addHeader("X-Forwarded-Host", "my.example.host");
+ request.addHeader("X-Forwarded-Proto", "https");
+ request.addHeader("X-Forwarded-Prefix", "/server-prefix/fhir");
+ String serverBase = addressStrategy.determineServerBase(null, request);
+ assertEquals("https://my.example.host/server-prefix/fhir", serverBase);
+ }
+
+ @Test
+ public void testWithForwardedWithoutPrefix() {
+ ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
+ true);
+ MockHttpServletRequest request = prepareRequest();
+
+ request.addHeader("X-Forwarded-Host", "my.example.host");
+ request.addHeader("X-Forwarded-Proto", "https");
+ String serverBase = addressStrategy.determineServerBase(null, request);
+ assertEquals("https://my.example.host/imagingstudy/fhir", serverBase);
+ }
+
+ @Test
+ public void testWithForwardedHostAndPort() {
+ ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
+ true);
+ MockHttpServletRequest request = prepareRequest();
+
+ request.addHeader("X-Forwarded-Host", "my.example.host");
+ request.addHeader("X-Forwarded-Port", "345");
+ String serverBase = addressStrategy.determineServerBase(null, request);
+ assertEquals("https://my.example.host:345/imagingstudy/fhir",
+ serverBase);
+ }
+
+ private MockHttpServletRequest prepareRequest() {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setMethod("POST");
+ request.setScheme("https");
+ request.setServerPort(443);
+ request.setServletPath("/fhir");
+ request.setServerName("localhost");
+ request.setRequestURI("/imagingstudy/fhir/imagingstudy?_format=json");
+ request.setContextPath("/imagingstudy");
+ return request;
+ }
+}
\ No newline at end of file
From 885a18e0d8ce2ff80db2a5c2a3fbdc88003c1a24 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Wed, 29 Apr 2020 09:12:36 -0400
Subject: [PATCH 28/49] Credit for #1788
---
.../4_3_0/1788-improve-apacheproxyaddressstrategy.yaml | 6 ++++++
pom.xml | 5 +++++
2 files changed, 11 insertions(+)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1788-improve-apacheproxyaddressstrategy.yaml
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1788-improve-apacheproxyaddressstrategy.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1788-improve-apacheproxyaddressstrategy.yaml
new file mode 100644
index 00000000000..50ea2dd8c2a
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1788-improve-apacheproxyaddressstrategy.yaml
@@ -0,0 +1,6 @@
+---
+type: add
+issue: 1788
+title: "The ApacheProxyAddressStrategy has been improved to add support for additional proxy headers inclusing
+ `X-Forwarded-Host`, `X-Forwarded-Proto`, `X-Forwarded-Port`, and `X-Forwarded-Prefix`. Thanks to Thomas Papke
+ for the pull request!"
diff --git a/pom.xml b/pom.xml
index 44a275a5fbd..5bd3e7996b9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -611,6 +611,11 @@
mkucharekMaciej Kucharek
+
+ Thopap
+ Thomas Papke
+ InterComponentWare AG
+
From 0d8c42ee1a33cdbfc051e58986afb0735a98bed6 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Wed, 29 Apr 2020 10:13:55 -0400
Subject: [PATCH 29/49] Migrator tweaks
---
.../migrate/tasks/HapiFhirJpaMigrationTasks.java | 16 ++++++++--------
.../uhn/fhir/jpa/migrate/tasks/api/Builder.java | 3 ++-
2 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
index e553b6e05bc..30442fc385c 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
@@ -72,7 +72,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks {
// Drop unused column
version.onTable("HFJ_RESOURCE").dropIndex("20200419.1", "IDX_RES_PROFILE");
- version.onTable("HFJ_RESOURCE").dropColumn("20200419.2", "RES_PROFILE");
+ version.onTable("HFJ_RESOURCE").dropColumn("20200419.2", "RES_PROFILE").failureAllowed();
// Add Partitioning
Builder.BuilderAddTableByColumns partition = version.addTableByColumns("20200420.0", "HFJ_PARTITION", "PART_ID");
@@ -117,13 +117,13 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks {
version.onTable("HFJ_RES_PARAM_PRESENT").addColumn("20200420.34", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT);
version.onTable("HFJ_RES_PARAM_PRESENT").addColumn("20200420.35", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY);
- version.onTable("HFJ_SPIDX_STRING").modifyColumn("20200420.36", "SP_MISSING").nonNullable().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN);
- version.onTable("HFJ_SPIDX_COORDS").modifyColumn("20200420.37", "SP_MISSING").nonNullable().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN);
- version.onTable("HFJ_SPIDX_NUMBER").modifyColumn("20200420.38", "SP_MISSING").nonNullable().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN);
- version.onTable("HFJ_SPIDX_TOKEN").modifyColumn("20200420.39", "SP_MISSING").nonNullable().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN);
- version.onTable("HFJ_SPIDX_DATE").modifyColumn("20200420.40", "SP_MISSING").nonNullable().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN);
- version.onTable("HFJ_SPIDX_URI").modifyColumn("20200420.41", "SP_MISSING").nonNullable().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN);
- version.onTable("HFJ_SPIDX_QUANTITY").modifyColumn("20200420.42", "SP_MISSING").nonNullable().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN);
+ version.onTable("HFJ_SPIDX_STRING").modifyColumn("20200420.36", "SP_MISSING").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN);
+ version.onTable("HFJ_SPIDX_COORDS").modifyColumn("20200420.37", "SP_MISSING").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN);
+ version.onTable("HFJ_SPIDX_NUMBER").modifyColumn("20200420.38", "SP_MISSING").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN);
+ version.onTable("HFJ_SPIDX_TOKEN").modifyColumn("20200420.39", "SP_MISSING").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN);
+ version.onTable("HFJ_SPIDX_DATE").modifyColumn("20200420.40", "SP_MISSING").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN);
+ version.onTable("HFJ_SPIDX_URI").modifyColumn("20200420.41", "SP_MISSING").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN);
+ version.onTable("HFJ_SPIDX_QUANTITY").modifyColumn("20200420.42", "SP_MISSING").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN);
}
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java
index 82cbdf4e6c0..e247953b241 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java
@@ -208,12 +208,13 @@ public class Builder {
return new BuilderWithTableName.BuilderAddColumnWithName(myRelease, theVersion, theColumnName, this);
}
- public void dropColumn(String theVersion, String theColumnName) {
+ public BuilderCompleteTask dropColumn(String theVersion, String theColumnName) {
Validate.notBlank(theColumnName);
DropColumnTask task = new DropColumnTask(myRelease, theVersion);
task.setTableName(myTableName);
task.setColumnName(theColumnName);
addTask(task);
+ return new BuilderCompleteTask(task);
}
@Override
From e439891ea280137d23bc0f46b90237033ebb5130 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Wed, 29 Apr 2020 15:21:54 -0400
Subject: [PATCH 30/49] Add test
---
.../fhir/jpa/dao/TolerantJsonParserR4Test.java | 16 ++++++++++++++++
.../rest/server/ApacheProxyAddressStrategy.java | 2 +-
2 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/TolerantJsonParserR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/TolerantJsonParserR4Test.java
index 6b462d7db2f..dea758dc150 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/TolerantJsonParserR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/TolerantJsonParserR4Test.java
@@ -46,6 +46,22 @@ public class TolerantJsonParserR4Test {
assertEquals("0.5", obs.getValueQuantity().getValueElement().getValueAsString());
}
+ @Test
+ public void testParseInvalidNumeric_DoubleZeros() {
+ String input = "{\n" +
+ "\"resourceType\": \"Observation\",\n" +
+ "\"valueQuantity\": {\n" +
+ " \"value\": 00\n" +
+ " }\n" +
+ "}";
+
+
+ TolerantJsonParser parser = new TolerantJsonParser(myFhirContext, new LenientErrorHandler());
+ Observation obs = parser.parseResource(Observation.class, input);
+
+ assertEquals("0", obs.getValueQuantity().getValueElement().getValueAsString());
+ }
+
@Test
public void testParseInvalidNumeric2() {
String input = "{\n" +
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategy.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategy.java
index a05a2a78526..b6e2652543b 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategy.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategy.java
@@ -168,4 +168,4 @@ public class ApacheProxyAddressStrategy extends IncomingRequestAddressStrategy {
public static ApacheProxyAddressStrategy forHttps() {
return new ApacheProxyAddressStrategy(true);
}
-}
\ No newline at end of file
+}
From a5e1b3d159b545f5806913cca21ba69a1784afa7 Mon Sep 17 00:00:00 2001
From: Tadgh
Date: Wed, 29 Apr 2020 16:18:02 -0700
Subject: [PATCH 31/49] Minor changes for merge conflicts
---
.../migrate/taskdef/BaseColumnCalculatorTask.java | 9 +--------
.../jpa/migrate/taskdef/BaseTableColumnTask.java | 14 ++++++++++++--
.../migrate/tasks/HapiFhirJpaMigrationTasks.java | 10 +++++-----
3 files changed, 18 insertions(+), 15 deletions(-)
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseColumnCalculatorTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseColumnCalculatorTask.java
index 909d830bec3..447a95df39c 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseColumnCalculatorTask.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseColumnCalculatorTask.java
@@ -37,11 +37,10 @@ import java.util.*;
import java.util.concurrent.*;
import java.util.function.Function;
-public abstract class BaseColumnCalculatorTask extends BaseTableColumnTask {
+public abstract class BaseColumnCalculatorTask extends BaseTableColumnTask {
protected static final Logger ourLog = LoggerFactory.getLogger(BaseColumnCalculatorTask.class);
private int myBatchSize = 10000;
- private Map, Object>> myCalculators = new HashMap<>();
private ThreadPoolExecutor myExecutor;
public void setBatchSize(int theBatchSize) {
@@ -191,12 +190,6 @@ public abstract class BaseColumnCalculatorTask extends BaseTableColumnTask, Object> theConsumer) {
- Validate.isTrue(myCalculators.containsKey(theColumnName) == false);
- myCalculators.put(theColumnName, theConsumer);
- return this;
- }
-
private class MyRowCallbackHandler implements RowCallbackHandler {
private List> myRows = new ArrayList<>();
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTask.java
index 567ebf7e052..703e64c510b 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTask.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTask.java
@@ -25,13 +25,17 @@ import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.thymeleaf.util.StringUtils;
+import java.util.HashMap;
import java.util.Locale;
+import java.util.Map;
+import java.util.function.Function;
public abstract class BaseTableColumnTask extends BaseTableTask {
- private String myColumnName;
+ protected Map, Object>> myCalculators = new HashMap<>();
+ protected String myColumnName;
//If a concrete class decides to, they can define a custom WHERE clause for the task.
- private String myWhereClause;
+ protected String myWhereClause;
public BaseTableColumnTask(String theProductVersion, String theSchemaVersion) {
super(theProductVersion, theSchemaVersion);
@@ -75,4 +79,10 @@ public abstract class BaseTableColumnTask extends BaseTableTask {
super.generateHashCode(theBuilder);
theBuilder.append(myColumnName);
}
+
+ public BaseTableColumnTask addCalculator(String theColumnName, Function, Object> theConsumer) {
+ Validate.isTrue(myCalculators.containsKey(theColumnName) == false);
+ myCalculators.put(theColumnName, theConsumer);
+ return this;
+ }
}
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
index 7b8fdb0c097..7220bf0ced6 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
@@ -78,9 +78,9 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks {
spidxDate.addColumn("20200225.2", "SP_VALUE_HIGH_DATE_ORDINAL").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT);
spidxDate.addTask(new CalculateOrdinalDatesTask(VersionEnum.V4_3_0, "20200225.3")
- .setColumnName("SP_VALUE_LOW_DATE_ORDINAL") //It doesn't matter which of the two we choose as they will both be null.
.addCalculator("SP_VALUE_LOW_DATE_ORDINAL", t -> ResourceIndexedSearchParamDate.calculateOrdinalValue(t.getDate("SP_VALUE_LOW")))
.addCalculator("SP_VALUE_HIGH_DATE_ORDINAL", t -> ResourceIndexedSearchParamDate.calculateOrdinalValue(t.getDate("SP_VALUE_HIGH")))
+ .setColumnName("SP_VALUE_LOW_DATE_ORDINAL") //It doesn't matter which of the two we choose as they will both be null.
);
//
@@ -541,8 +541,8 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks {
.withColumns("HASH_IDENTITY", "SP_LATITUDE", "SP_LONGITUDE");
spidxCoords
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.5")
- .setColumnName("HASH_IDENTITY")
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME")))
+ .setColumnName("HASH_IDENTITY")
);
}
@@ -564,8 +564,8 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks {
.dropIndex("20180903.9", "IDX_SP_DATE");
spidxDate
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.10")
- .setColumnName("HASH_IDENTITY")
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME")))
+ .setColumnName("HASH_IDENTITY")
);
}
@@ -585,8 +585,8 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks {
.withColumns("HASH_IDENTITY", "SP_VALUE");
spidxNumber
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.14")
- .setColumnName("HASH_IDENTITY")
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME")))
+ .setColumnName("HASH_IDENTITY")
);
}
@@ -622,10 +622,10 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks {
.withColumns("HASH_IDENTITY_SYS_UNITS", "SP_VALUE");
spidxQuantity
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.22")
- .setColumnName("HASH_IDENTITY")
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("HASH_IDENTITY_AND_UNITS", t -> ResourceIndexedSearchParamQuantity.calculateHashUnits(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_UNITS")))
.addCalculator("HASH_IDENTITY_SYS_UNITS", t -> ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_SYSTEM"), t.getString("SP_UNITS")))
+ .setColumnName("HASH_IDENTITY")
);
}
From f94f2fde65dd04716252ea05472696da4c2e1eab Mon Sep 17 00:00:00 2001
From: Tadgh
Date: Thu, 30 Apr 2020 11:32:22 -0700
Subject: [PATCH 32/49] Add delegating setters and getters to DaoConfig (#1825)
---
.../ca/uhn/fhir/jpa/api/config/DaoConfig.java | 38 +++++++++++++++++++
.../fhir/jpa/model/entity/ModelConfig.java | 9 +++--
2 files changed, 43 insertions(+), 4 deletions(-)
diff --git a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java
index b79ca0c13d5..d2807f4cc35 100644
--- a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java
+++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java
@@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.api.config;
import ca.uhn.fhir.jpa.api.model.WarmCacheEntry;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
import com.google.common.annotations.VisibleForTesting;
@@ -932,6 +933,43 @@ public class DaoConfig {
myModelConfig.setAllowExternalReferences(theAllowExternalReferences);
}
+ /**
+ *
+ * Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
+ * {@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
+ * precision of {@link ca.uhn.fhir.model.api.TemporalPrecisionEnum#DAY}.
+ *
+ * For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
+ * ordinal {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
+ * and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
+ *
+ * Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
+ * {@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
+ * precision of {@link ca.uhn.fhir.model.api.TemporalPrecisionEnum#DAY}.
+ *
+ * For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
+ * integer representing the ordinal date {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
+ * and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
+ *
+ * Default is {@literal true} beginning in HAPI FHIR 5.0
+ *
+ *
+ * @since 5.0
+ */
+ public boolean getUseOrdinalDatesForDayPrecisionSearches() {
+ return myModelConfig.getUseOrdinalDatesForDayPrecisionSearches();
+ }
/**
* @see #setAllowInlineMatchUrlReferences(boolean)
*/
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java
index f7616f58f1b..7aa6deb8ced 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java
@@ -381,10 +381,10 @@ public class ModelConfig {
* ordinal {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
* and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
*
- * Default is {@literal true} beginning in HAPI FHIR 4.3.
+ * Default is {@literal true} beginning in HAPI FHIR 5.0
*
*
- * @since 4.3
+ * @since 5.0
*/
public void setUseOrdinalDatesForDayPrecisionSearches(boolean theUseOrdinalDates) {
myUseOrdinalDatesForDayPrecisionSearches = theUseOrdinalDates;
@@ -400,14 +400,15 @@ public class ModelConfig {
* integer representing the ordinal date {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
* and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
*
- * Default is {@literal true} beginning in HAPI FHIR 4.3.
+ * Default is {@literal true} beginning in HAPI FHIR 5.0
*
*
- * @since 4.3
+ * @since 5.0
*/
public boolean getUseOrdinalDatesForDayPrecisionSearches() {
return myUseOrdinalDatesForDayPrecisionSearches;
}
+
private static void validateTreatBaseUrlsAsLocal(String theUrl) {
Validate.notBlank(theUrl, "Base URL must not be null or empty");
From 3d5a8bb3f86c040f7864dc1365a675c638695eed Mon Sep 17 00:00:00 2001
From: James Agnew
Date: Thu, 30 Apr 2020 15:22:41 -0400
Subject: [PATCH 33/49] Add UCUM support (#1824)
* Add UCUM support
* Add changelog
* Some cleanup
* Test fix
* Add flywayDB callback
* Add hooks to schema migrator
---
.../java/ca/uhn/fhir/util/ClasspathUtil.java | 106 ++++++++++++++++++
.../fhir/validation/SchemaBaseValidator.java | 36 +-----
.../ca/uhn/fhir/util/ClasspathUtilTest.java | 61 ++++++++++
.../4_3_0/1824-add-ucum-support.yaml | 5 +
.../hapi/fhir/changelog/4_3_0/changes.yaml | 1 +
.../validation/validation_support_modules.md | 11 ++
.../ca/uhn/fhir/jpa/migrate/BaseMigrator.java | 15 +++
.../uhn/fhir/jpa/migrate/FlywayMigrator.java | 2 +
.../ca/uhn/fhir/jpa/migrate/Migrator.java | 2 +
.../uhn/fhir/jpa/migrate/SchemaMigrator.java | 12 +-
.../migrate/taskdef/InitializeSchemaTask.java | 3 +-
.../main/java/ca/uhn/fhir/test/BaseTest.java | 35 +-----
.../CommonCodeSystemsTerminologyService.java | 73 +++++++++++-
...oryTerminologyServerValidationSupport.java | 89 ++++++++++-----
.../validation/SchemaBaseValidatorTest.java | 2 +-
...mmonCodeSystemsTerminologyServiceTest.java | 68 +++++++++++
.../FhirInstanceValidatorR4Test.java | 77 ++++++++++++-
.../r4/observation-with-body-temp-ucum.json | 38 +++++++
pom.xml | 2 +-
19 files changed, 540 insertions(+), 98 deletions(-)
create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ClasspathUtil.java
create mode 100644 hapi-fhir-base/src/test/java/ca/uhn/fhir/util/ClasspathUtilTest.java
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1824-add-ucum-support.yaml
create mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java
create mode 100644 hapi-fhir-validation/src/test/resources/r4/observation-with-body-temp-ucum.json
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ClasspathUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ClasspathUtil.java
new file mode 100644
index 00000000000..5d3e661dce2
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ClasspathUtil.java
@@ -0,0 +1,106 @@
+package ca.uhn.fhir.util;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.rest.api.EncodingEnum;
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+import com.google.common.base.Charsets;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.input.BOMInputStream;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.function.Function;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * Use this API with caution, it may change!
+ */
+public class ClasspathUtil {
+
+ private static final Logger ourLog = LoggerFactory.getLogger(ClasspathUtil.class);
+
+ public static String loadResource(String theClasspath) {
+ Function streamTransform = t -> t;
+ return loadResource(theClasspath, streamTransform);
+ }
+
+ /**
+ * Load a classpath resource, throw an {@link InternalErrorException} if not found
+ */
+ @Nonnull
+ public static InputStream loadResourceAsStream(String theClasspath) {
+ InputStream retVal = ClasspathUtil.class.getResourceAsStream(theClasspath);
+ if (retVal == null) {
+ throw new InternalErrorException("Unable to find classpath resource: " + theClasspath);
+ }
+ return retVal;
+ }
+
+ /**
+ * Load a classpath resource, throw an {@link InternalErrorException} if not found
+ */
+ @Nonnull
+ public static String loadResource(String theClasspath, Function theStreamTransform) {
+ InputStream stream = ClasspathUtil.class.getResourceAsStream(theClasspath);
+ try {
+ if (stream == null) {
+ throw new IOException("Unable to find classpath resource: " + theClasspath);
+ }
+ try {
+ InputStream newStream = theStreamTransform.apply(stream);
+ return IOUtils.toString(newStream, Charsets.UTF_8);
+ } finally {
+ stream.close();
+ }
+ } catch (IOException e) {
+ throw new InternalErrorException(e);
+ }
+ }
+
+ @Nonnull
+ public static String loadCompressedResource(String theClasspath) {
+ Function streamTransform = t -> {
+ try {
+ return new GZIPInputStream(t);
+ } catch (IOException e) {
+ throw new InternalErrorException(e);
+ }
+ };
+ return loadResource(theClasspath, streamTransform);
+ }
+
+ @Nonnull
+ public static T loadResource(FhirContext theCtx, Class theType, String theClasspath) {
+ String raw = loadResource(theClasspath);
+ return EncodingEnum.detectEncodingNoDefault(raw).newParser(theCtx).parseResource(theType, raw);
+ }
+
+ public static void close(InputStream theInput) {
+ try {
+ if (theInput != null) {
+ theInput.close();
+ }
+ } catch (IOException e) {
+ ourLog.debug("Closing InputStream threw exception", e);
+ }
+ }
+
+ public static Function withBom() {
+ return t -> new BOMInputStream(t);
+ }
+
+ public static byte[] loadResourceAsByteArray(String theClasspath) {
+ InputStream stream = loadResourceAsStream(theClasspath);
+ try {
+ return IOUtils.toByteArray(stream);
+ } catch (IOException e) {
+ throw new InternalErrorException(e);
+ } finally {
+ close(stream);
+ }
+ }
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/SchemaBaseValidator.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/SchemaBaseValidator.java
index e2fe1cb2e97..cec0ff9194d 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/SchemaBaseValidator.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/SchemaBaseValidator.java
@@ -23,9 +23,7 @@ package ca.uhn.fhir.validation;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.EncodingEnum;
-import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.io.input.BOMInputStream;
+import ca.uhn.fhir.util.ClasspathUtil;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSResourceResolver;
@@ -41,10 +39,7 @@ import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.StringReader;
-import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -152,20 +147,9 @@ public class SchemaBaseValidator implements IValidatorModule {
Source loadXml(String theSchemaName) {
String pathToBase = myCtx.getVersion().getPathToSchemaDefinitions() + '/' + theSchemaName;
ourLog.debug("Going to load resource: {}", pathToBase);
- try (InputStream baseIs = FhirValidator.class.getResourceAsStream(pathToBase)) {
- if (baseIs == null) {
- throw new InternalErrorException("Schema not found. " + RESOURCES_JAR_NOTE);
- }
- try (BOMInputStream bomInputStream = new BOMInputStream(baseIs, false)) {
- try (InputStreamReader baseReader = new InputStreamReader(bomInputStream, StandardCharsets.UTF_8)) {
- // Buffer so that we can close the input stream
- String contents = IOUtils.toString(baseReader);
- return new StreamSource(new StringReader(contents), null);
- }
- }
- } catch (IOException e) {
- throw new InternalErrorException(e);
- }
+
+ String contents = ClasspathUtil.loadResource(pathToBase, ClasspathUtil.withBom());
+ return new StreamSource(new StringReader(contents), null);
}
@Override
@@ -188,16 +172,8 @@ public class SchemaBaseValidator implements IValidatorModule {
ourLog.debug("Loading referenced schema file: " + pathToBase);
- try (InputStream baseIs = FhirValidator.class.getResourceAsStream(pathToBase)) {
- if (baseIs == null) {
- throw new InternalErrorException("Schema file not found: " + pathToBase);
- }
-
- byte[] bytes = IOUtils.toByteArray(baseIs);
- input.setByteStream(new ByteArrayInputStream(bytes));
- } catch (IOException e) {
- throw new InternalErrorException(e);
- }
+ byte[] bytes = ClasspathUtil.loadResourceAsByteArray(pathToBase);
+ input.setByteStream(new ByteArrayInputStream(bytes));
return input;
}
diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/ClasspathUtilTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/ClasspathUtilTest.java
new file mode 100644
index 00000000000..c70687c910d
--- /dev/null
+++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/ClasspathUtilTest.java
@@ -0,0 +1,61 @@
+package ca.uhn.fhir.util;
+
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+
+public class ClasspathUtilTest {
+
+ @Test
+ public void testLoadResourceNotFound() {
+ try {
+ ClasspathUtil.loadResource("/FOOOOOO");
+ } catch (InternalErrorException e) {
+ assertEquals("Unable to find classpath resource: /FOOOOOO", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testLoadResourceAsStreamNotFound() {
+ try {
+ ClasspathUtil.loadResourceAsStream("/FOOOOOO");
+ } catch (InternalErrorException e) {
+ assertEquals("Unable to find classpath resource: /FOOOOOO", e.getMessage());
+ }
+ }
+
+ /**
+ * Should not throw any exception
+ */
+ @Test
+ public void testClose_Null() {
+ ClasspathUtil.close(null);
+ }
+
+ /**
+ * Should not throw any exception
+ */
+ @Test
+ public void testClose_Ok() {
+ ClasspathUtil.close(new ByteArrayInputStream(new byte[]{0,1,2}));
+ }
+
+
+ /**
+ * Should not throw any exception
+ */
+ @Test
+ public void testClose_ThrowException() throws IOException {
+ InputStream is = mock(InputStream.class);
+ doThrow(new IOException("FOO")).when(is).close();
+ ClasspathUtil.close(is);
+ }
+
+}
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1824-add-ucum-support.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1824-add-ucum-support.yaml
new file mode 100644
index 00000000000..70522664e71
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1824-add-ucum-support.yaml
@@ -0,0 +1,5 @@
+---
+type: add
+issue: 1824
+title: Native support for UCUM has been added to the validation stack, meaning that UCUM codes can be validated
+ at runtime without the need for any external validation.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
index bc38dd31845..4d12c1b1907 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
@@ -9,6 +9,7 @@
Spring Boot (Boot): 2.2.0.RELEASE -> 2.2.6.RELEASE
+
FlywayDB (JPA) 6.1.0 -> 6.4.1
"
- item:
issue: "1583"
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/validation_support_modules.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/validation_support_modules.md
index 5b559f560f9..13ad3028476 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/validation_support_modules.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/validation_support_modules.md
@@ -98,6 +98,17 @@ The following table lists vocabulary that is validated by this module:
added in the future, please get in touch if you would like to help.
+
+ Codes are validated using the UcumEssenceService provided by the UCUM Java library.
+
+
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/BaseMigrator.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/BaseMigrator.java
index b37a45f2735..d2f4332078f 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/BaseMigrator.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/BaseMigrator.java
@@ -21,9 +21,13 @@ package ca.uhn.fhir.jpa.migrate;
*/
import ca.uhn.fhir.jpa.migrate.taskdef.BaseTask;
+import org.apache.commons.lang3.Validate;
+import org.flywaydb.core.api.callback.Callback;
+import javax.annotation.Nonnull;
import javax.sql.DataSource;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -34,6 +38,17 @@ public abstract class BaseMigrator implements IMigrator {
private DriverTypeEnum myDriverType;
private DataSource myDataSource;
private List myExecutedStatements = new ArrayList<>();
+ private List myCallbacks = Collections.emptyList();
+
+ @Nonnull
+ public List getCallbacks() {
+ return myCallbacks;
+ }
+
+ public void setCallbacks(@Nonnull List theCallbacks) {
+ Validate.notNull(theCallbacks);
+ myCallbacks = theCallbacks;
+ }
public DataSource getDataSource() {
return myDataSource;
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/FlywayMigrator.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/FlywayMigrator.java
index 7373e1889ee..4b24a3a3c63 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/FlywayMigrator.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/FlywayMigrator.java
@@ -25,6 +25,7 @@ import ca.uhn.fhir.jpa.migrate.taskdef.InitializeSchemaTask;
import com.google.common.annotations.VisibleForTesting;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.MigrationInfoService;
+import org.flywaydb.core.api.callback.Callback;
import org.flywaydb.core.api.migration.JavaMigration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -79,6 +80,7 @@ public class FlywayMigrator extends BaseMigrator {
.baselineOnMigrate(true)
.outOfOrder(isOutOfOrderPermitted())
.javaMigrations(myTasks.toArray(new JavaMigration[0]))
+ .callbacks(getCallbacks().toArray(new Callback[0]))
.load();
for (FlywayMigration task : myTasks) {
task.setConnectionProperties(theConnectionProperties);
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/Migrator.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/Migrator.java
index 1688de89a5e..82eab50491d 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/Migrator.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/Migrator.java
@@ -132,4 +132,6 @@ public class Migrator {
public void setNoColumnShrink(boolean theNoColumnShrink) {
myNoColumnShrink = theNoColumnShrink;
}
+
+
}
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/SchemaMigrator.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/SchemaMigrator.java
index 3be8039ac3d..f4b98c3f615 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/SchemaMigrator.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/SchemaMigrator.java
@@ -24,20 +24,23 @@ import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.jpa.migrate.taskdef.BaseTask;
import org.flywaydb.core.api.MigrationInfo;
import org.flywaydb.core.api.MigrationInfoService;
+import org.flywaydb.core.api.callback.Callback;
import org.hibernate.cfg.AvailableSettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.util.Assert;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
public class SchemaMigrator {
- private static final Logger ourLog = LoggerFactory.getLogger(SchemaMigrator.class);
public static final String HAPI_FHIR_MIGRATION_TABLENAME = "FLY_HFJ_MIGRATION";
+ private static final Logger ourLog = LoggerFactory.getLogger(SchemaMigrator.class);
private final DataSource myDataSource;
private final boolean mySkipValidation;
private final String myMigrationTableName;
@@ -45,6 +48,7 @@ public class SchemaMigrator {
private boolean myDontUseFlyway;
private boolean myOutOfOrderPermitted;
private DriverTypeEnum myDriverType;
+ private List myCallbacks = Collections.emptyList();
/**
* Constructor
@@ -61,6 +65,11 @@ public class SchemaMigrator {
}
}
+ public void setCallbacks(List theCallbacks) {
+ Assert.notNull(theCallbacks);
+ myCallbacks = theCallbacks;
+ }
+
public void setDontUseFlyway(boolean theDontUseFlyway) {
myDontUseFlyway = theDontUseFlyway;
}
@@ -110,6 +119,7 @@ public class SchemaMigrator {
migrator.setOutOfOrderPermitted(myOutOfOrderPermitted);
}
migrator.addTasks(myMigrationTasks);
+ migrator.setCallbacks(myCallbacks);
return migrator;
}
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/InitializeSchemaTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/InitializeSchemaTask.java
index 3a2f28c6474..3664c4c271d 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/InitializeSchemaTask.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/InitializeSchemaTask.java
@@ -34,13 +34,14 @@ import java.util.Set;
public class InitializeSchemaTask extends BaseTask {
private static final Logger ourLog = LoggerFactory.getLogger(InitializeSchemaTask.class);
+ public static final String DESCRIPTION_PREFIX = "Initialize schema for ";
private final ISchemaInitializationProvider mySchemaInitializationProvider;
public InitializeSchemaTask(String theProductVersion, String theSchemaVersion, ISchemaInitializationProvider theSchemaInitializationProvider) {
super(theProductVersion, theSchemaVersion);
mySchemaInitializationProvider = theSchemaInitializationProvider;
- setDescription("Initialize schema for " + mySchemaInitializationProvider.getSchemaDescription());
+ setDescription(DESCRIPTION_PREFIX + mySchemaInitializationProvider.getSchemaDescription());
}
@Override
diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/BaseTest.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/BaseTest.java
index 0cbcf44f7b9..6072cd9445d 100644
--- a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/BaseTest.java
+++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/BaseTest.java
@@ -21,18 +21,12 @@ package ca.uhn.fhir.test;
*/
import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.rest.api.EncodingEnum;
-import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
-import com.google.common.base.Charsets;
-import org.apache.commons.io.IOUtils;
+import ca.uhn.fhir.util.ClasspathUtil;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.io.IOException;
-import java.io.InputStream;
-import java.util.function.Function;
-import java.util.zip.GZIPInputStream;
public class BaseTest {
@@ -41,35 +35,14 @@ public class BaseTest {
}
protected String loadResource(String theClasspath) throws IOException {
- Function streamTransform = t->t;
- return loadResource(theClasspath, streamTransform);
- }
-
- private String loadResource(String theClasspath, Function theStreamTransform) throws IOException {
- try (InputStream stream = BaseTest.class.getResourceAsStream(theClasspath)) {
- if (stream == null) {
- throw new IllegalArgumentException("Unable to find resource: " + theClasspath);
- }
-
- InputStream newStream = theStreamTransform.apply(stream);
-
- return IOUtils.toString(newStream, Charsets.UTF_8);
- }
+ return ClasspathUtil.loadResource(theClasspath);
}
protected String loadCompressedResource(String theClasspath) throws IOException {
- Function streamTransform = t-> {
- try {
- return new GZIPInputStream(t);
- } catch (IOException e) {
- throw new InternalErrorException(e);
- }
- };
- return loadResource(theClasspath, streamTransform);
+ return ClasspathUtil.loadCompressedResource(theClasspath);
}
protected T loadResource(FhirContext theCtx, Class theType, String theClasspath) throws IOException {
- String raw = loadResource(theClasspath);
- return EncodingEnum.detectEncodingNoDefault(raw).newParser(theCtx).parseResource(theType, raw);
+ return ClasspathUtil.loadResource(theCtx, theType, theClasspath);
}
}
diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java
index 5de2f64ed70..a007b2990fc 100644
--- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java
+++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java
@@ -1,13 +1,22 @@
package org.hl7.fhir.common.hapi.validation.support;
import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport;
+import ca.uhn.fhir.util.ClasspathUtil;
+import ca.uhn.fhir.util.FileUtil;
import org.apache.commons.lang3.Validate;
+import org.fhir.ucum.UcumEssenceService;
+import org.fhir.ucum.UcumException;
import org.hl7.fhir.dstu2.model.ValueSet;
import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -26,12 +35,13 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport {
public static final String MIMETYPES_VALUESET_URL = "http://hl7.org/fhir/ValueSet/mimetypes";
public static final String CURRENCIES_CODESYSTEM_URL = "urn:iso:std:iso:4217";
public static final String CURRENCIES_VALUESET_URL = "http://hl7.org/fhir/ValueSet/currencies";
+ public static final String UCUM_CODESYSTEM_URL = "http://unitsofmeasure.org";
private static final String USPS_CODESYSTEM_URL = "https://www.usps.com/";
private static final String USPS_VALUESET_URL = "http://hl7.org/fhir/us/core/ValueSet/us-core-usps-state";
+ private static final Logger ourLog = LoggerFactory.getLogger(CommonCodeSystemsTerminologyService.class);
+ public static final String UCUM_VALUESET_URL = "http://hl7.org/fhir/ValueSet/ucum-units";
private static Map USPS_CODES = Collections.unmodifiableMap(buildUspsCodes());
private static Map ISO_4217_CODES = Collections.unmodifiableMap(buildIso4217Codes());
-
-
private final FhirContext myFhirContext;
/**
@@ -71,8 +81,22 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport {
return new CodeValidationResult()
.setCode(theCode)
.setDisplay(theDisplay);
- }
+ case UCUM_VALUESET_URL: {
+ String system = theCodeSystem;
+ if (system == null && theOptions.isInferSystem()) {
+ system = UCUM_CODESYSTEM_URL;
+ }
+ LookupCodeResult lookupResult = lookupCode(theRootValidationSupport, system, theCode);
+ if (lookupResult != null) {
+ if (lookupResult.isFound()) {
+ return new CodeValidationResult()
+ .setCode(lookupResult.getSearchedForCode())
+ .setDisplay(lookupResult.getCodeDisplay());
+ }
+ }
+ }
+ }
if (handlerMap != null) {
String display = handlerMap.get(theCode);
@@ -92,6 +116,49 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport {
return null;
}
+ @Override
+ public LookupCodeResult lookupCode(IValidationSupport theRootValidationSupport, String theSystem, String theCode) {
+
+ if (UCUM_CODESYSTEM_URL.equals(theSystem) && theRootValidationSupport.getFhirContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
+
+ InputStream input = ClasspathUtil.loadResourceAsStream("/ucum-essence.xml");
+ try {
+ UcumEssenceService svc = new UcumEssenceService(input);
+ String outcome = svc.analyse(theCode);
+ if (outcome != null) {
+
+ LookupCodeResult retVal = new LookupCodeResult();
+ retVal.setSearchedForCode(theCode);
+ retVal.setSearchedForSystem(theSystem);
+ retVal.setFound(true);
+ retVal.setCodeDisplay(outcome);
+ return retVal;
+
+ }
+ } catch (UcumException e) {
+ ourLog.debug("Failed parse UCUM code: {}", theCode, e);
+ return null;
+ } finally {
+ ClasspathUtil.close(input);
+ }
+
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isCodeSystemSupported(IValidationSupport theRootValidationSupport, String theSystem) {
+
+ switch (theSystem) {
+ case UCUM_CODESYSTEM_URL:
+ return true;
+ }
+
+ return false;
+ }
+
+
public String getValueSetUrl(@Nonnull IBaseResource theValueSet) {
String url;
switch (getFhirContext().getVersion().getVersion()) {
diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java
index 0b608ede8b1..909e628e548 100644
--- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java
+++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java
@@ -22,6 +22,7 @@ import org.hl7.fhir.utilities.validation.ValidationMessage;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -53,7 +54,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
@Override
public ValueSetExpansionOutcome expandValueSet(IValidationSupport theRootValidationSupport, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) {
- org.hl7.fhir.r5.model.ValueSet expansionR5 = expandValueSetToCanonical(theRootValidationSupport, theValueSetToExpand);
+ org.hl7.fhir.r5.model.ValueSet expansionR5 = expandValueSetToCanonical(theRootValidationSupport, theValueSetToExpand, null, null);
if (expansionR5 == null) {
return null;
}
@@ -85,20 +86,20 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
return new ValueSetExpansionOutcome(expansion, null);
}
- private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical(IValidationSupport theRootValidationSupport, IBaseResource theValueSetToExpand) {
+ private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical(IValidationSupport theRootValidationSupport, IBaseResource theValueSetToExpand, @Nullable String theWantSystem, @Nullable String theWantCode) {
org.hl7.fhir.r5.model.ValueSet expansionR5;
switch (myCtx.getVersion().getVersion()) {
case DSTU2:
case DSTU2_HL7ORG: {
- expansionR5 = expandValueSetDstu2Hl7Org(theRootValidationSupport, (ValueSet) theValueSetToExpand);
+ expansionR5 = expandValueSetDstu2Hl7Org(theRootValidationSupport, (ValueSet) theValueSetToExpand, theWantSystem, theWantCode);
break;
}
case DSTU3: {
- expansionR5 = expandValueSetDstu3(theRootValidationSupport, (org.hl7.fhir.dstu3.model.ValueSet) theValueSetToExpand);
+ expansionR5 = expandValueSetDstu3(theRootValidationSupport, (org.hl7.fhir.dstu3.model.ValueSet) theValueSetToExpand, theWantSystem, theWantCode);
break;
}
case R4: {
- expansionR5 = expandValueSetR4(theRootValidationSupport, (org.hl7.fhir.r4.model.ValueSet) theValueSetToExpand);
+ expansionR5 = expandValueSetR4(theRootValidationSupport, (org.hl7.fhir.r4.model.ValueSet) theValueSetToExpand, theWantSystem, theWantCode);
break;
}
case R5: {
@@ -118,7 +119,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
@Override
public CodeValidationResult validateCodeInValueSet(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
- org.hl7.fhir.r5.model.ValueSet expansion = expandValueSetToCanonical(theRootValidationSupport, theValueSet);
+ org.hl7.fhir.r5.model.ValueSet expansion = expandValueSetToCanonical(theRootValidationSupport, theValueSet, theCodeSystem, theCode);
if (expansion == null) {
return null;
}
@@ -287,7 +288,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
}
@Nullable
- private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org(IValidationSupport theRootValidationSupport, ValueSet theInput) {
+ private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org(IValidationSupport theRootValidationSupport, ValueSet theInput, @Nullable String theWantSystem, @Nullable String theWantCode) {
Function codeSystemLoader = t -> {
org.hl7.fhir.dstu2.model.ValueSet codeSystem = (org.hl7.fhir.dstu2.model.ValueSet) theRootValidationSupport.fetchCodeSystem(t);
CodeSystem retVal = new CodeSystem();
@@ -300,7 +301,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
};
org.hl7.fhir.r5.model.ValueSet input = ValueSet10_50.convertValueSet(theInput);
- org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(input, codeSystemLoader, valueSetLoader);
+ org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theRootValidationSupport, input, codeSystemLoader, valueSetLoader, theWantSystem, theWantCode);
return (output);
}
@@ -342,7 +343,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
}
@Nullable
- private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3(IValidationSupport theRootValidationSupport, org.hl7.fhir.dstu3.model.ValueSet theInput) {
+ private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3(IValidationSupport theRootValidationSupport, org.hl7.fhir.dstu3.model.ValueSet theInput, @Nullable String theWantSystem, @Nullable String theWantCode) {
Function codeSystemLoader = t -> {
org.hl7.fhir.dstu3.model.CodeSystem codeSystem = (org.hl7.fhir.dstu3.model.CodeSystem) theRootValidationSupport.fetchCodeSystem(t);
return CodeSystem30_50.convertCodeSystem(codeSystem);
@@ -353,12 +354,12 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
};
org.hl7.fhir.r5.model.ValueSet input = ValueSet30_50.convertValueSet(theInput);
- org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(input, codeSystemLoader, valueSetLoader);
+ org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theRootValidationSupport, input, codeSystemLoader, valueSetLoader, theWantSystem, theWantCode);
return (output);
}
@Nullable
- private org.hl7.fhir.r5.model.ValueSet expandValueSetR4(IValidationSupport theRootValidationSupport, org.hl7.fhir.r4.model.ValueSet theInput) {
+ private org.hl7.fhir.r5.model.ValueSet expandValueSetR4(IValidationSupport theRootValidationSupport, org.hl7.fhir.r4.model.ValueSet theInput, @Nullable String theWantSystem, @Nullable String theWantCode) {
Function codeSystemLoader = t -> {
org.hl7.fhir.r4.model.CodeSystem codeSystem = (org.hl7.fhir.r4.model.CodeSystem) theRootValidationSupport.fetchCodeSystem(t);
return CodeSystem40_50.convertCodeSystem(codeSystem);
@@ -369,7 +370,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
};
org.hl7.fhir.r5.model.ValueSet input = ValueSet40_50.convertValueSet(theInput);
- org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(input, codeSystemLoader, valueSetLoader);
+ org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theRootValidationSupport, input, codeSystemLoader, valueSetLoader, theWantSystem, theWantCode);
return (output);
}
@@ -378,16 +379,16 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
Function codeSystemLoader = t -> (org.hl7.fhir.r5.model.CodeSystem) theRootValidationSupport.fetchCodeSystem(t);
Function valueSetLoader = t -> (org.hl7.fhir.r5.model.ValueSet) theRootValidationSupport.fetchValueSet(t);
- return expandValueSetR5(theInput, codeSystemLoader, valueSetLoader);
+ return expandValueSetR5(theRootValidationSupport, theInput, codeSystemLoader, valueSetLoader, null, null);
}
@Nullable
- private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(org.hl7.fhir.r5.model.ValueSet theInput, Function theCodeSystemLoader, Function theValueSetLoader) {
+ private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(IValidationSupport theRootValidationSupport, org.hl7.fhir.r5.model.ValueSet theInput, Function theCodeSystemLoader, Function theValueSetLoader, @Nullable String theWantSystem, @Nullable String theWantCode) {
Set concepts = new HashSet<>();
try {
- expandValueSetR5IncludeOrExclude(concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getInclude(), true);
- expandValueSetR5IncludeOrExclude(concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getExclude(), false);
+ expandValueSetR5IncludeOrExclude(theRootValidationSupport, concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getInclude(), true, theWantSystem, theWantCode);
+ expandValueSetR5IncludeOrExclude(theRootValidationSupport, concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getExclude(), false, theWantSystem, theWantCode);
} catch (ExpansionCouldNotBeCompletedInternallyException e) {
return null;
}
@@ -403,34 +404,70 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
return retVal;
}
- private void expandValueSetR5IncludeOrExclude(Set theConcepts, Function theCodeSystemLoader, Function theValueSetLoader, List theComposeList, boolean theComposeListIsInclude) throws ExpansionCouldNotBeCompletedInternallyException {
+ private void expandValueSetR5IncludeOrExclude(IValidationSupport theRootValidationSupport, Set theConcepts, Function theCodeSystemLoader, Function theValueSetLoader, List theComposeList, boolean theComposeListIsInclude, @Nullable String theWantSystem, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent nextInclude : theComposeList) {
List nextCodeList = new ArrayList<>();
String system = nextInclude.getSystem();
if (isNotBlank(system)) {
+
+ if (theWantSystem != null && !theWantSystem.equals(system)) {
+ continue;
+ }
+
CodeSystem codeSystem = theCodeSystemLoader.apply(system);
- if (codeSystem == null) {
- throw new ExpansionCouldNotBeCompletedInternallyException();
- }
- if (codeSystem.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) {
- throw new ExpansionCouldNotBeCompletedInternallyException();
- }
Set wantCodes;
if (nextInclude.getConcept().isEmpty()) {
wantCodes = null;
} else {
- wantCodes = nextInclude.getConcept().stream().map(t -> t.getCode()).collect(Collectors.toSet());
+ wantCodes = nextInclude
+ .getConcept()
+ .stream().map(t -> t.getCode()).collect(Collectors.toSet());
+ }
+
+ boolean ableToHandleCode = false;
+ if (codeSystem == null) {
+
+ if (theWantCode != null) {
+ LookupCodeResult lookup = theRootValidationSupport.lookupCode(theRootValidationSupport, system, theWantCode);
+ if (lookup != null && lookup.isFound()) {
+ CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent()
+ .addConcept()
+ .setCode(theWantCode)
+ .setDisplay(lookup.getCodeDisplay());
+ List codesList = Collections.singletonList(conceptDefinition);
+ addCodes(system, codesList, nextCodeList, wantCodes);
+ ableToHandleCode = true;
+ }
+ }
+
+ } else {
+
+ ableToHandleCode = true;
+
+ }
+
+ if (!ableToHandleCode) {
+ throw new ExpansionCouldNotBeCompletedInternallyException();
+ }
+
+ if (codeSystem != null) {
+
+ if (codeSystem.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) {
+ throw new ExpansionCouldNotBeCompletedInternallyException();
+ }
+
+ addCodes(system, codeSystem.getConcept(), nextCodeList, wantCodes);
+
}
- addCodes(system, codeSystem.getConcept(), nextCodeList, wantCodes);
}
for (CanonicalType nextValueSetInclude : nextInclude.getValueSet()) {
org.hl7.fhir.r5.model.ValueSet vs = theValueSetLoader.apply(nextValueSetInclude.getValueAsString());
if (vs != null) {
- org.hl7.fhir.r5.model.ValueSet subExpansion = expandValueSetR5(vs, theCodeSystemLoader, theValueSetLoader);
+ org.hl7.fhir.r5.model.ValueSet subExpansion = expandValueSetR5(theRootValidationSupport, vs, theCodeSystemLoader, theValueSetLoader, theWantSystem, theWantCode);
if (subExpansion == null) {
throw new ExpansionCouldNotBeCompletedInternallyException();
}
diff --git a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/SchemaBaseValidatorTest.java b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/SchemaBaseValidatorTest.java
index 9b81cbdf8ac..36b50b7cd37 100644
--- a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/SchemaBaseValidatorTest.java
+++ b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/SchemaBaseValidatorTest.java
@@ -26,7 +26,7 @@ public class SchemaBaseValidatorTest {
validator.loadXml("foo.xsd");
fail();
} catch (InternalErrorException e) {
- assertThat(e.getMessage(), containsString("Schema not found"));
+ assertThat(e.getMessage(), containsString("Unable to find classpath resource"));
}
}
}
diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java
new file mode 100644
index 00000000000..0834c4de2fc
--- /dev/null
+++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java
@@ -0,0 +1,68 @@
+package org.hl7.fhir.common.hapi.validation.support;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.support.ConceptValidationOptions;
+import ca.uhn.fhir.context.support.IValidationSupport;
+import org.hl7.fhir.r4.model.ValueSet;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class CommonCodeSystemsTerminologyServiceTest {
+
+ private CommonCodeSystemsTerminologyService mySvc;
+ private FhirContext myCtx;
+
+ @Before
+ public void before() {
+ myCtx = FhirContext.forR4();
+ mySvc = new CommonCodeSystemsTerminologyService(myCtx);
+ }
+
+ @Test
+ public void testUcum_LookupCode_Good() {
+ IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(myCtx.getValidationSupport(), "http://unitsofmeasure.org", "Cel");
+ assertEquals(true, outcome.isFound());
+ }
+
+ @Test
+ public void testUcum_LookupCode_Bad() {
+ IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(myCtx.getValidationSupport(), "http://unitsofmeasure.org", "AAAAA");
+ assertNull( outcome);
+ }
+
+ @Test
+ public void testUcum_LookupCode_UnknownSystem() {
+ IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(myCtx.getValidationSupport(), "http://foo", "AAAAA");
+ assertNull( outcome);
+ }
+
+ @Test
+ public void testUcum_ValidateCode_Good() {
+ ValueSet vs = new ValueSet();
+ vs.setUrl("http://hl7.org/fhir/ValueSet/ucum-units");
+ IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(myCtx.getValidationSupport(), new ConceptValidationOptions(), "http://unitsofmeasure.org", "mg", null, vs);
+ assertEquals(true, outcome.isOk());
+ assertEquals("(milligram)", outcome.getDisplay());
+ }
+
+ @Test
+ public void testUcum_ValidateCode_Good_SystemInferred() {
+ ValueSet vs = new ValueSet();
+ vs.setUrl("http://hl7.org/fhir/ValueSet/ucum-units");
+ IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(myCtx.getValidationSupport(), new ConceptValidationOptions().setInferSystem(true), null, "mg", null, vs);
+ assertEquals(true, outcome.isOk());
+ assertEquals("(milligram)", outcome.getDisplay());
+ }
+
+ @Test
+ public void testUcum_ValidateCode_Bad() {
+ ValueSet vs = new ValueSet();
+ vs.setUrl("http://hl7.org/fhir/ValueSet/ucum-units");
+ IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(myCtx.getValidationSupport(), new ConceptValidationOptions(), "http://unitsofmeasure.org", "aaaaa", null, vs);
+ assertNull(outcome);
+ }
+
+}
diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java
index f9aa04ae046..af4e78c1fa2 100644
--- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java
+++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java
@@ -17,19 +17,43 @@ import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
-import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
+import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
+import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.conformance.ProfileUtilities;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext;
-import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
-import org.hl7.fhir.r4.model.*;
+import org.hl7.fhir.r4.model.AllergyIntolerance;
+import org.hl7.fhir.r4.model.Base;
+import org.hl7.fhir.r4.model.Base64BinaryType;
+import org.hl7.fhir.r4.model.BooleanType;
+import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
+import org.hl7.fhir.r4.model.CodeSystem;
+import org.hl7.fhir.r4.model.CodeType;
+import org.hl7.fhir.r4.model.Consent;
+import org.hl7.fhir.r4.model.ContactPoint;
+import org.hl7.fhir.r4.model.DateTimeType;
+import org.hl7.fhir.r4.model.Extension;
+import org.hl7.fhir.r4.model.Media;
+import org.hl7.fhir.r4.model.Narrative;
+import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
+import org.hl7.fhir.r4.model.OperationOutcome;
+import org.hl7.fhir.r4.model.Patient;
+import org.hl7.fhir.r4.model.Period;
+import org.hl7.fhir.r4.model.Practitioner;
+import org.hl7.fhir.r4.model.Procedure;
+import org.hl7.fhir.r4.model.QuestionnaireResponse;
+import org.hl7.fhir.r4.model.Reference;
+import org.hl7.fhir.r4.model.RelatedPerson;
+import org.hl7.fhir.r4.model.StringType;
+import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
+import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.r4.terminologies.ValueSetExpander;
import org.hl7.fhir.r4.utils.FHIRPathEngine;
@@ -195,7 +219,16 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
when(mockSupport.fetchCodeSystem(nullable(String.class))).thenAnswer(new Answer() {
@Override
public CodeSystem answer(InvocationOnMock theInvocation) {
- CodeSystem retVal = (CodeSystem) myDefaultValidationSupport.fetchCodeSystem((String) theInvocation.getArguments()[0]);
+ String system = theInvocation.getArgument(0, String.class);
+ if ("http://loinc.org".equals(system)) {
+ CodeSystem retVal = new CodeSystem();
+ retVal.setUrl("http://loinc.org");
+ retVal.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT);
+ ourLog.debug("fetchCodeSystem({}) : {}", new Object[]{theInvocation.getArguments()[0], retVal});
+ return retVal;
+ }
+
+ CodeSystem retVal = (CodeSystem) myDefaultValidationSupport.fetchCodeSystem(system);
ourLog.debug("fetchCodeSystem({}) : {}", new Object[]{theInvocation.getArguments()[0], retVal});
return retVal;
}
@@ -216,6 +249,23 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
return retVal;
}
});
+ when(mockSupport.lookupCode(any(), any(), any())).thenAnswer(t -> {
+ String system = t.getArgument(1, String.class);
+ String code = t.getArgument(2, String.class);
+ if (myValidConcepts.contains(system + "___" + code)) {
+ return new IValidationSupport.LookupCodeResult().setFound(true);
+ } else {
+ return null;
+ }
+ });
+ when(mockSupport.validateCodeInValueSet(any(), any(), any(), any(), any(), any())).thenAnswer(t -> {
+ String system = t.getArgument(2, String.class);
+ String code = t.getArgument(3, String.class);
+ if (myValidConcepts.contains(system + "___" + code)) {
+ return new IValidationSupport.CodeValidationResult().setCode(code).setDisplay(code);
+ }
+ return null;
+ });
}
@@ -1239,6 +1289,25 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
}
+
+ @Test
+ public void testValidateWithUcum() throws IOException {
+ addValidConcept("http://loinc.org", "8310-5");
+
+ Observation input = loadResource(ourCtx, Observation.class, "/r4/observation-with-body-temp-ucum.json");
+ ValidationResult output = myVal.validateWithResult(input);
+ List all = logResultsAndReturnNonInformationalOnes(output);
+ assertThat(all, empty());
+
+ // Change the unit to something not supported
+ input.getValueQuantity().setCode("Heck");
+ output = myVal.validateWithResult(input);
+ all = logResultsAndReturnNonInformationalOnes(output);
+ assertEquals(1, all.size());
+ assertThat(all.get(0).getMessage(), containsString("The value provided (\"Heck\") is not in the value set http://hl7.org/fhir/ValueSet/ucum-bodytemp"));
+
+ }
+
@Test
public void testMultiplePerformer() {
Observation o = new Observation();
diff --git a/hapi-fhir-validation/src/test/resources/r4/observation-with-body-temp-ucum.json b/hapi-fhir-validation/src/test/resources/r4/observation-with-body-temp-ucum.json
new file mode 100644
index 00000000000..2f6e9883305
--- /dev/null
+++ b/hapi-fhir-validation/src/test/resources/r4/observation-with-body-temp-ucum.json
@@ -0,0 +1,38 @@
+{
+ "resourceType": "Observation",
+ "id": "bodytemp",
+ "meta": {
+ "profile": [
+ "http://hl7.org/fhir/StructureDefinition/bodytemp"
+ ]
+ },
+ "status": "final",
+ "category": [
+ {
+ "coding": [
+ {
+ "system": "http://terminology.hl7.org/CodeSystem/observation-category",
+ "code": "vital-signs"
+ }
+ ]
+ }
+ ],
+ "code": {
+ "coding": [
+ {
+ "system": "http://loinc.org",
+ "code": "8310-5"
+ }
+ ]
+ },
+ "subject": {
+ "reference": "Patient/1"
+ },
+ "effectiveDateTime": "2020-04-30T12:00:00+01:00",
+ "valueQuantity": {
+ "value": 37.5,
+ "unit": "Cel",
+ "system": "http://unitsofmeasure.org",
+ "code": "Cel"
+ }
+}
diff --git a/pom.xml b/pom.xml
index 5bd3e7996b9..f6b45adfbb0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -664,7 +664,7 @@
9.4.24.v201911203.0.2
- 6.1.0
+ 6.4.15.4.14.Final
From 41056092edab4aedd9773493cef24894d27a4413 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Thu, 30 Apr 2020 18:24:07 -0400
Subject: [PATCH 34/49] License header updates
---
.../java/ca/uhn/fhir/util/ClasspathUtil.java | 20 +++++++++++++++++++
.../dao/r4/SearchCoordinatorSvcImplTest.java | 8 ++++++++
2 files changed, 28 insertions(+)
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ClasspathUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ClasspathUtil.java
index 5d3e661dce2..16fd3619ccd 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ClasspathUtil.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ClasspathUtil.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.util;
+/*-
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2020 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchCoordinatorSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchCoordinatorSvcImplTest.java
index b8ce8d63a72..1f7714951fe 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchCoordinatorSvcImplTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchCoordinatorSvcImplTest.java
@@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
+import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao;
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchResult;
@@ -37,6 +38,8 @@ public class SearchCoordinatorSvcImplTest extends BaseJpaR4Test {
@Autowired
private ISearchResultDao mySearchResultDao;
+ @Autowired
+ private ISearchIncludeDao mySearchIncludeDao;
@Autowired
private ISearchCoordinatorSvc mySearchCoordinator;
@@ -55,6 +58,11 @@ public class SearchCoordinatorSvcImplTest extends BaseJpaR4Test {
DatabaseSearchCacheSvcImpl.setMaximumResultsToDeleteInOnePassForUnitTest(5);
DatabaseSearchCacheSvcImpl.setMaximumSearchesToCheckForDeletionCandidacyForUnitTest(10);
+ runInTransaction(()->{
+ mySearchResultDao.deleteAll();
+ mySearchIncludeDao.deleteAll();
+ mySearchDao.deleteAll();
+ });
runInTransaction(()->{
assertEquals(0, mySearchDao.count());
assertEquals(0, mySearchResultDao.count());
From c8ad3e80e6ca165ee88d02b77487b277f2219484 Mon Sep 17 00:00:00 2001
From: Bert Roos
Date: Fri, 1 May 2020 11:36:28 +0200
Subject: [PATCH 35/49] Correction in comment (#1734)
The description for SERVER_INCOMING_REQUEST_PRE_HANDLED referred to itself rather than to SERVER_INCOMING_REQUEST_PRE_PROCESSED. Along with this corrected begin into begun.
---
.../src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java
index 3a5d5a9f189..aa5b7b08d6f 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java
@@ -231,9 +231,9 @@ public enum Pointcut {
/**
* Server Hook:
* This hook is invoked before an incoming request is processed. Note that this method is called
- * after the server has begin preparing the response to the incoming client request.
+ * after the server has begun preparing the response to the incoming client request.
* As such, it is not able to supply a response to the incoming request in the way that
- * SERVER_INCOMING_REQUEST_PRE_HANDLED and
+ * SERVER_INCOMING_REQUEST_PRE_PROCESSED and
* {@link #SERVER_INCOMING_REQUEST_POST_PROCESSED}
* are.
*
From 439a901e3082f2c451580d9ba2a73d4f6e3ac451 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Fri, 1 May 2020 05:56:47 -0400
Subject: [PATCH 36/49] Credit for #1734
---
.../hapi/fhir/changelog/4_3_0/1734-fix-pointcut.javadoc.yaml | 5 +++++
pom.xml | 4 ++++
2 files changed, 9 insertions(+)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1734-fix-pointcut.javadoc.yaml
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1734-fix-pointcut.javadoc.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1734-fix-pointcut.javadoc.yaml
new file mode 100644
index 00000000000..464579f6ea2
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1734-fix-pointcut.javadoc.yaml
@@ -0,0 +1,5 @@
+---
+type: fix
+issue: 1734
+title: The Pointcut JavaDoc had an incorrect link from one pointcut to another and has been fixed. Thanks
+ to Bert Roos for the pull request!
diff --git a/pom.xml b/pom.xml
index f6b45adfbb0..3dea9478943 100644
--- a/pom.xml
+++ b/pom.xml
@@ -616,6 +616,10 @@
Thomas PapkeInterComponentWare AG
+
+ Bert-R
+ Bert Roos
+
From 38a899bf649369d0478a91d765da4f44e7d68da2 Mon Sep 17 00:00:00 2001
From: Zhe Wang
Date: Fri, 1 May 2020 13:24:28 +0200
Subject: [PATCH 37/49] Take super class methods into account and adding unit
test (#1812)
* take super class methods into account as well
* added unit test for pull request https://github.com/jamesagnew/hapi-fhir/pull/1704/commits/44f9b416eea74bd5e061427cf7d05ac2dddf73f9
* take super class methods into account as well
* added unit test for pull request https://github.com/jamesagnew/hapi-fhir/pull/1704/commits/44f9b416eea74bd5e061427cf7d05ac2dddf73f9
* added unit test
---
.../server/util/JaxRsMethodBindings.java | 18 ++++++----
.../test/AbstractDummyPatientProvider.java | 34 +++++++++++++++++++
.../test/TestJaxRsDummyPatientProviderR4.java | 10 ++----
...stJaxRsDummyPatientProviderR4MimeType.java | 12 +++++++
.../util/JaxRsMethodBindingsMimeTypeTest.java | 28 +++++++++++++++
5 files changed, 88 insertions(+), 14 deletions(-)
create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/AbstractDummyPatientProvider.java
create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderR4MimeType.java
create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindingsMimeTypeTest.java
diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java
index 8406c5e64b9..4dcb47c3eff 100644
--- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java
+++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java
@@ -20,17 +20,19 @@ package ca.uhn.fhir.jaxrs.server.util;
* #L%
*/
-import java.lang.reflect.Method;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.apache.commons.lang3.StringUtils;
-
import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
-import ca.uhn.fhir.rest.server.method.*;
+import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
+import ca.uhn.fhir.rest.server.method.OperationMethodBinding;
+import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
import ca.uhn.fhir.util.ReflectionUtil;
+import org.apache.commons.lang3.StringUtils;
+
+import java.lang.reflect.Method;
+import java.util.LinkedHashSet;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Class that contains the method bindings defined by a ResourceProvider
@@ -52,7 +54,9 @@ public class JaxRsMethodBindings {
* @param theProviderClass the class definition contaning the operations
*/
public JaxRsMethodBindings(AbstractJaxRsProvider theProvider, Class extends AbstractJaxRsProvider> theProviderClass) {
- for (final Method m : ReflectionUtil.getDeclaredMethods(theProviderClass)) {
+ LinkedHashSet declaredMethodsForCurrentProvider = ReflectionUtil.getDeclaredMethods(theProviderClass);
+ declaredMethodsForCurrentProvider.addAll(ReflectionUtil.getDeclaredMethods(theProviderClass.getSuperclass()));
+ for (final Method m : declaredMethodsForCurrentProvider) {
final BaseMethodBinding> foundMethodBinding = BaseMethodBinding.bindMethod(m, theProvider.getFhirContext(), theProvider);
if (foundMethodBinding == null) {
continue;
diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/AbstractDummyPatientProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/AbstractDummyPatientProvider.java
new file mode 100644
index 00000000000..7a36fbc812b
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/AbstractDummyPatientProvider.java
@@ -0,0 +1,34 @@
+package ca.uhn.fhir.jaxrs.server.test;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider;
+import ca.uhn.fhir.rest.annotation.RequiredParam;
+import ca.uhn.fhir.rest.annotation.Search;
+import ca.uhn.fhir.rest.param.StringParam;
+import org.hl7.fhir.r4.model.Patient;
+
+import java.util.List;
+
+/**
+ * A dummy patient provider exposing no methods
+ */
+public abstract class AbstractDummyPatientProvider extends AbstractJaxRsResourceProvider {
+
+ public AbstractDummyPatientProvider() {
+ super(FhirContext.forR4());
+ }
+
+ @Override
+ public abstract String getBaseForServer();
+
+
+ @Search
+ public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name) {
+ return null;
+ }
+
+ @Override
+ public Class getResourceType() {
+ return Patient.class;
+ }
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderR4.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderR4.java
index 1c3f91f4c1d..981128316bc 100644
--- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderR4.java
+++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderR4.java
@@ -1,15 +1,11 @@
package ca.uhn.fhir.jaxrs.server.test;
-import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider;
-import org.hl7.fhir.r4.model.Patient;
-
/**
* A dummy patient provider exposing no methods
*/
-public class TestJaxRsDummyPatientProviderR4 extends AbstractJaxRsResourceProvider {
+public class TestJaxRsDummyPatientProviderR4 extends AbstractDummyPatientProvider {
- @Override
- public Class getResourceType() {
- return Patient.class;
+ @Override public String getBaseForServer() {
+ return "https://fhirserver/fhir/r4";
}
}
diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderR4MimeType.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderR4MimeType.java
new file mode 100644
index 00000000000..719206b1453
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProviderR4MimeType.java
@@ -0,0 +1,12 @@
+package ca.uhn.fhir.jaxrs.server.test;
+
+/**
+ * A dummy patient provider exposing no methods
+ */
+public class TestJaxRsDummyPatientProviderR4MimeType extends AbstractDummyPatientProvider {
+
+ @Override public String getBaseForServer() {
+ return "https://fhirserver/fhir";
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindingsMimeTypeTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindingsMimeTypeTest.java
new file mode 100644
index 00000000000..c4ea3fb3530
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindingsMimeTypeTest.java
@@ -0,0 +1,28 @@
+package ca.uhn.fhir.jaxrs.server.util;
+
+import ca.uhn.fhir.jaxrs.server.test.AbstractDummyPatientProvider;
+import ca.uhn.fhir.jaxrs.server.test.TestJaxRsDummyPatientProviderR4;
+import ca.uhn.fhir.jaxrs.server.test.TestJaxRsDummyPatientProviderR4MimeType;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+import static org.junit.Assert.assertEquals;
+
+@FixMethodOrder(MethodSorters.DEFAULT)
+public class JaxRsMethodBindingsMimeTypeTest {
+
+ @Before
+ public void setUp() {
+ JaxRsMethodBindings.getClassBindings().clear();
+ }
+
+ @Test
+ public void testFindMethodsFor2ProvidersWithMethods() {
+ assertEquals(AbstractDummyPatientProvider.class, new TestJaxRsDummyPatientProviderR4().getBindings().getBinding(RestOperationTypeEnum.SEARCH_TYPE, "").getMethod().getDeclaringClass());
+ assertEquals(AbstractDummyPatientProvider.class, new TestJaxRsDummyPatientProviderR4MimeType().getBindings().getBinding(RestOperationTypeEnum.SEARCH_TYPE, "").getMethod().getDeclaringClass());
+ }
+
+ }
From a786756169c9caa65debae360d9fe5495a1707a2 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Fri, 1 May 2020 07:27:12 -0400
Subject: [PATCH 38/49] Credit for #1812
---
.../4_3_0/1812-account-for-jaxrs-super-methodss.yaml | 5 +++++
pom.xml | 5 +++++
2 files changed, 10 insertions(+)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1812-account-for-jaxrs-super-methodss.yaml
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1812-account-for-jaxrs-super-methodss.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1812-account-for-jaxrs-super-methodss.yaml
new file mode 100644
index 00000000000..b7222ade76b
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1812-account-for-jaxrs-super-methodss.yaml
@@ -0,0 +1,5 @@
+---
+type: add
+issue: 1812
+title: The JAX-RS server will now scan and serve ResourceProvider methods defined in super-classes as well. Thanks
+ to Zhe Wang for the pull request!
diff --git a/pom.xml b/pom.xml
index 3dea9478943..1e8b474fa41 100644
--- a/pom.xml
+++ b/pom.xml
@@ -620,6 +620,11 @@
Bert-RBert Roos
+
+ zilin375
+ Zhe Wang
+ Agfa Healthcare
+
From 9ace4889bc4245424a35cf084a75a0aa71eb1edf Mon Sep 17 00:00:00 2001
From: gematik FuE <34300220+gematik-fue@users.noreply.github.com>
Date: Fri, 1 May 2020 13:36:45 +0200
Subject: [PATCH 39/49] enhance constructor, so an existing InterceptorService
could be passed in (#1749)
---
.../main/java/ca/uhn/fhir/rest/server/RestfulServer.java | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
index b4361907e96..2221b02b56c 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
@@ -149,8 +149,12 @@ public class RestfulServer extends HttpServlet implements IRestfulServer
Date: Fri, 1 May 2020 07:38:31 -0400
Subject: [PATCH 40/49] Credit for #1749
---
.../4_3_0/1749-add-constructor-to-restfulserver.yaml | 5 +++++
pom.xml | 4 ++++
2 files changed, 9 insertions(+)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1749-add-constructor-to-restfulserver.yaml
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1749-add-constructor-to-restfulserver.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1749-add-constructor-to-restfulserver.yaml
new file mode 100644
index 00000000000..71e8bae87b1
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1749-add-constructor-to-restfulserver.yaml
@@ -0,0 +1,5 @@
+---
+type: add
+issue: 1749
+title: A new constructor has been added to RestfulServer that accepts an InterceptorService. Thanks to gematik FuE for the
+ pull request!
diff --git a/pom.xml b/pom.xml
index 1e8b474fa41..2744019eb29 100644
--- a/pom.xml
+++ b/pom.xml
@@ -625,6 +625,10 @@
Zhe WangAgfa Healthcare
+
+ gematik-fue
+ gematik FuE
+
From 394a47b83aefa41f46983db40e992f3d5c7dd54f Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Fri, 1 May 2020 13:03:59 -0400
Subject: [PATCH 41/49] Fix YAML error
---
.../4_3_0/1499-dont-touch-timezones-on-date-search.yaml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1499-dont-touch-timezones-on-date-search.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1499-dont-touch-timezones-on-date-search.yaml
index c8619a3a602..890f0a947da 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1499-dont-touch-timezones-on-date-search.yaml
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1499-dont-touch-timezones-on-date-search.yaml
@@ -1,5 +1,5 @@
---
type: fix
issue: 1499
-title: When performing a search with a DateParam that has DAY precision, rely on new ordinal date field for comparison
-instead of attempting to find oldest and newest instant that could be valid.
+title: "When performing a search with a DateParam that has DAY precision, rely on new ordinal date field for comparison
+ instead of attempting to find oldest and newest instant that could be valid."
From 31f86953df0d40f936ed98178e3050f85bf0ec80 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Fri, 1 May 2020 13:57:44 -0400
Subject: [PATCH 42/49] Fix compile error
---
.../ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java
index 4dcb47c3eff..df8826dc1d5 100644
--- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java
+++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java
@@ -32,6 +32,7 @@ import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Method;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -54,7 +55,7 @@ public class JaxRsMethodBindings {
* @param theProviderClass the class definition contaning the operations
*/
public JaxRsMethodBindings(AbstractJaxRsProvider theProvider, Class extends AbstractJaxRsProvider> theProviderClass) {
- LinkedHashSet declaredMethodsForCurrentProvider = ReflectionUtil.getDeclaredMethods(theProviderClass);
+ List declaredMethodsForCurrentProvider = ReflectionUtil.getDeclaredMethods(theProviderClass);
declaredMethodsForCurrentProvider.addAll(ReflectionUtil.getDeclaredMethods(theProviderClass.getSuperclass()));
for (final Method m : declaredMethodsForCurrentProvider) {
final BaseMethodBinding> foundMethodBinding = BaseMethodBinding.bindMethod(m, theProvider.getFhirContext(), theProvider);
From 8b49142b946e0d5718f30d1bc297b364400a6fa8 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Fri, 1 May 2020 15:03:36 -0400
Subject: [PATCH 43/49] Clean up migrator tasks
---
.../java/ca/uhn/fhir/util/VersionEnum.java | 1 +
.../tasks/HapiFhirJpaMigrationTasks.java | 24 +++++++++----------
2 files changed, 12 insertions(+), 13 deletions(-)
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java
index 5c6b7b9e4ea..c2114aff18c 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java
@@ -59,6 +59,7 @@ public enum VersionEnum {
V4_0_3,
V4_1_0,
V4_2_0,
+ @Deprecated
V4_3_0, // 4.3.0 was renamed to 5.0.0 during the cycle
V5_0_0;
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
index 7220bf0ced6..e0840ff572e 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
@@ -63,7 +63,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks {
}
protected void init500() { // 20200218 - present
- Builder version = forVersion(VersionEnum.V4_3_0);
+ Builder version = forVersion(VersionEnum.V5_0_0);
// Eliminate circular dependency.
version.onTable("HFJ_RESOURCE").dropColumn("20200218.1", "FORCED_ID_PID");
@@ -72,18 +72,6 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks {
version.onTable("HFJ_RES_VER").modifyColumn("20200220.1", "RES_ID").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
//
- // Add support for integer comparisons during day-precision date search.
- Builder.BuilderWithTableName spidxDate = version.onTable("HFJ_SPIDX_DATE");
- spidxDate.addColumn("20200225.1", "SP_VALUE_LOW_DATE_ORDINAL").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT);
- spidxDate.addColumn("20200225.2", "SP_VALUE_HIGH_DATE_ORDINAL").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT);
-
- spidxDate.addTask(new CalculateOrdinalDatesTask(VersionEnum.V4_3_0, "20200225.3")
- .addCalculator("SP_VALUE_LOW_DATE_ORDINAL", t -> ResourceIndexedSearchParamDate.calculateOrdinalValue(t.getDate("SP_VALUE_LOW")))
- .addCalculator("SP_VALUE_HIGH_DATE_ORDINAL", t -> ResourceIndexedSearchParamDate.calculateOrdinalValue(t.getDate("SP_VALUE_HIGH")))
- .setColumnName("SP_VALUE_LOW_DATE_ORDINAL") //It doesn't matter which of the two we choose as they will both be null.
- );
- //
-
// Drop unused column
version.onTable("HFJ_RESOURCE").dropIndex("20200419.1", "IDX_RES_PROFILE");
version.onTable("HFJ_RESOURCE").dropColumn("20200419.2", "RES_PROFILE").failureAllowed();
@@ -139,6 +127,16 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks {
version.onTable("HFJ_SPIDX_URI").modifyColumn("20200420.41", "SP_MISSING").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN);
version.onTable("HFJ_SPIDX_QUANTITY").modifyColumn("20200420.42", "SP_MISSING").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN);
+ // Add support for integer comparisons during day-precision date search.
+ Builder.BuilderWithTableName spidxDate = version.onTable("HFJ_SPIDX_DATE");
+ spidxDate.addColumn("20200501.1", "SP_VALUE_LOW_DATE_ORDINAL").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT);
+ spidxDate.addColumn("20200501.2", "SP_VALUE_HIGH_DATE_ORDINAL").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT);
+
+ spidxDate.addTask(new CalculateOrdinalDatesTask(VersionEnum.V5_0_0, "20200501.3")
+ .addCalculator("SP_VALUE_LOW_DATE_ORDINAL", t -> ResourceIndexedSearchParamDate.calculateOrdinalValue(t.getDate("SP_VALUE_LOW")))
+ .addCalculator("SP_VALUE_HIGH_DATE_ORDINAL", t -> ResourceIndexedSearchParamDate.calculateOrdinalValue(t.getDate("SP_VALUE_HIGH")))
+ .setColumnName("SP_VALUE_LOW_DATE_ORDINAL") //It doesn't matter which of the two we choose as they will both be null.
+ );
}
From 41c715dc2de01c67da9beaec63bc8c41165bc10a Mon Sep 17 00:00:00 2001
From: Ian <52504170+ibacher@users.noreply.github.com>
Date: Mon, 4 May 2020 05:39:10 -0400
Subject: [PATCH 44/49] Make BaseOrListParam and BaseParam visible outside of
the package (#1710)
---
.../src/main/java/ca/uhn/fhir/rest/param/BaseOrListParam.java | 2 +-
.../src/main/java/ca/uhn/fhir/rest/param/BaseParam.java | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseOrListParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseOrListParam.java
index 201022a1994..21e09b8e19d 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseOrListParam.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseOrListParam.java
@@ -28,7 +28,7 @@ import ca.uhn.fhir.rest.api.QualifiedParamList;
import java.util.ArrayList;
import java.util.List;
-abstract class BaseOrListParam, PT extends IQueryParameterType> implements IQueryParameterOr {
+public abstract class BaseOrListParam, PT extends IQueryParameterType> implements IQueryParameterOr {
private List myList = new ArrayList<>();
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseParam.java
index 498a0765b2f..2df4d9898c6 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseParam.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseParam.java
@@ -31,7 +31,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
/**
* Base class for RESTful operation parameter types
*/
-abstract class BaseParam implements IQueryParameterType {
+public abstract class BaseParam implements IQueryParameterType {
private Boolean myMissing;
From ceae417d350267451f636403505955328607de0b Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Mon, 4 May 2020 05:42:32 -0400
Subject: [PATCH 45/49] Credit for #1710
---
.../changelog/4_3_0/1710-baseorlistparam-visibility.yaml | 5 +++++
pom.xml | 4 ++++
2 files changed, 9 insertions(+)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1710-baseorlistparam-visibility.yaml
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1710-baseorlistparam-visibility.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1710-baseorlistparam-visibility.yaml
new file mode 100644
index 00000000000..a3e9d956f85
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1710-baseorlistparam-visibility.yaml
@@ -0,0 +1,5 @@
+---
+type: add
+issue: 1710
+title: The classes BaseOrListParam and BaseParam now have public visibility in order to make it easier to
+ create more generic APIs. Thanks to GitHub user @ibacher for the pull request!
diff --git a/pom.xml b/pom.xml
index 2744019eb29..3c612f9982a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -629,6 +629,10 @@
gematik-fuegematik FuE
+
+ ibacher
+ Ian
+
From 91147cf6b0150beceacb015f8c1be570e5c9c1a6 Mon Sep 17 00:00:00 2001
From: James Agnew
Date: Tue, 5 May 2020 11:09:46 -0400
Subject: [PATCH 46/49] Scope tag search to correct resource type (#1829)
* failing _tag tests
* cleanup
* Scope tag query to correct resource type
* Changelog
* Test fix
Co-authored-by: Ken Stevens
---
...e-tag-search-to-correct-resource-type.yaml | 5 ++
.../dao/predicate/PredicateBuilderTag.java | 4 --
.../jpa/dao/r4/FhirResourceDaoR4Test.java | 64 ++++++++++++++++++-
.../fhir/jpa/dao/r4/PartitioningR4Test.java | 4 +-
4 files changed, 70 insertions(+), 7 deletions(-)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1829-scope-tag-search-to-correct-resource-type.yaml
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1829-scope-tag-search-to-correct-resource-type.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1829-scope-tag-search-to-correct-resource-type.yaml
new file mode 100644
index 00000000000..205692228d5
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1829-scope-tag-search-to-correct-resource-type.yaml
@@ -0,0 +1,5 @@
+---
+type: fix
+issue: 1829
+title: "In the JPA server, performing a search where the only search parameter was the `_tag` parameter could cause resources
+ of the wrong type to be included in search results. This has been corrected."
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java
index 04bab4248ea..389204c408f 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java
@@ -160,10 +160,6 @@ class PredicateBuilderTag extends BasePredicateBuilder {
continue;
- } else {
-
- myQueryRoot.setHasIndexJoins();
-
}
Join tagJoin = myQueryRoot.join("myTags", JoinType.LEFT);
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java
index facda6c685c..fcae2ae2e59 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java
@@ -1,9 +1,9 @@
package ca.uhn.fhir.jpa.dao.r4;
+import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
-import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.JpaResourceDao;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
@@ -34,6 +34,7 @@ import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
+import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
@@ -51,16 +52,52 @@ import org.hamcrest.core.StringContains;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
-import org.hl7.fhir.r4.model.*;
+import org.hl7.fhir.r4.model.Age;
+import org.hl7.fhir.r4.model.Attachment;
+import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.Bundle.BundleType;
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
+import org.hl7.fhir.r4.model.CanonicalType;
+import org.hl7.fhir.r4.model.CarePlan;
+import org.hl7.fhir.r4.model.CodeSystem;
+import org.hl7.fhir.r4.model.CodeType;
+import org.hl7.fhir.r4.model.CodeableConcept;
+import org.hl7.fhir.r4.model.Coding;
+import org.hl7.fhir.r4.model.CompartmentDefinition;
+import org.hl7.fhir.r4.model.ConceptMap;
+import org.hl7.fhir.r4.model.Condition;
+import org.hl7.fhir.r4.model.Consent;
+import org.hl7.fhir.r4.model.DateTimeType;
+import org.hl7.fhir.r4.model.DateType;
+import org.hl7.fhir.r4.model.Device;
+import org.hl7.fhir.r4.model.DiagnosticReport;
+import org.hl7.fhir.r4.model.Encounter;
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.r4.model.Enumerations.PublicationStatus;
+import org.hl7.fhir.r4.model.IdType;
+import org.hl7.fhir.r4.model.Meta;
+import org.hl7.fhir.r4.model.MolecularSequence;
+import org.hl7.fhir.r4.model.NamingSystem;
+import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
+import org.hl7.fhir.r4.model.OperationDefinition;
+import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.r4.model.OperationOutcome.IssueType;
+import org.hl7.fhir.r4.model.Organization;
+import org.hl7.fhir.r4.model.Patient;
+import org.hl7.fhir.r4.model.Period;
+import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.Quantity.QuantityComparator;
+import org.hl7.fhir.r4.model.Questionnaire;
+import org.hl7.fhir.r4.model.Range;
+import org.hl7.fhir.r4.model.Reference;
+import org.hl7.fhir.r4.model.SimpleQuantity;
+import org.hl7.fhir.r4.model.StringType;
+import org.hl7.fhir.r4.model.StructureDefinition;
+import org.hl7.fhir.r4.model.Timing;
+import org.hl7.fhir.r4.model.UriType;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
@@ -1627,6 +1664,29 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
}
+ @Test
+ public void testDeleteByTagWrongType() {
+ Organization org = new Organization();
+ org.getMeta().addTag().setCode("term");
+ IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
+ myPatientDao.deleteByUrl("Patient?_tag=term", mySrd);
+ // The organization is still there
+ myOrganizationDao.read(orgId);
+ }
+
+ @Test
+ public void testSearchByTagWrongType() {
+ Organization org = new Organization();
+ org.getMeta().addTag().setCode("term");
+ myOrganizationDao.create(org, mySrd);
+ SearchParameterMap map = new SearchParameterMap();
+ map.add("_tag", new UriParam("term"));
+ map.setLoadSynchronous(true);
+ IBundleProvider result = myPatientDao.search(map);
+ List resources = result.getResources(0, 1);
+ assertEquals(0, resources.size());
+ }
+
@Test
public void testDeleteWithMatchUrlQualifierMissing() {
String methodName = "testDeleteWithMatchUrlChainedProfile";
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java
index 8a0a7ab0dc4..f7099709ef4 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java
@@ -1642,7 +1642,9 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
- assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
+
+ // If this ever got optimized down to 1 that would be OK too
+ assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM='http://system'"));
}
From dada2179663bc48bb3f4fdab283d9a4b0f538ea7 Mon Sep 17 00:00:00 2001
From: James Agnew
Date: Tue, 5 May 2020 11:11:24 -0400
Subject: [PATCH 47/49] Clean up hash code calculation (#1830)
* Clean up hash code calculation
* Test fixes
* Test fix
* Test fixes
* Compile fix
* Test fix
---
.../dao/index/DaoSearchParamSynchronizer.java | 10 ++--
.../dao/r4/FhirResourceDaoR4UpdateTest.java | 33 +++++++++++
.../dao/r4/SearchParamExtractorR4Test.java | 16 +++---
.../BaseResourceIndexedSearchParam.java | 9 ---
.../ResourceIndexedSearchParamCoords.java | 27 ++++-----
.../ResourceIndexedSearchParamDate.java | 41 ++++++-------
.../ResourceIndexedSearchParamNumber.java | 29 ++++++----
.../ResourceIndexedSearchParamQuantity.java | 52 ++++++++---------
.../ResourceIndexedSearchParamString.java | 43 +++++++-------
.../ResourceIndexedSearchParamToken.java | 57 ++++++++-----------
.../entity/ResourceIndexedSearchParamUri.java | 32 +++++------
...esourceIndexedSearchParamQuantityTest.java | 1 +
.../ResourceIndexedSearchParamStringTest.java | 6 ++
.../ResourceIndexedSearchParamTokenTest.java | 2 +
.../ResourceIndexedSearchParamUriTest.java | 1 +
.../extractor/BaseSearchParamExtractor.java | 4 +-
.../ResourceIndexedSearchParams.java | 7 +--
.../extractor/SearchParamExtractorDstu2.java | 6 +-
.../extractor/SearchParamExtractorDstu3.java | 5 +-
.../extractor/SearchParamExtractorR4.java | 5 +-
.../extractor/SearchParamExtractorR5.java | 6 +-
.../fhir/jpa/searchparam/IndexStressTest.java | 3 +-
.../SearchParamExtractorDstu3Test.java | 16 +++---
.../SearchParamExtractorMegaTest.java | 9 +--
24 files changed, 227 insertions(+), 193 deletions(-)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java
index 11910a1e6ff..fb7ff3be39e 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java
@@ -61,17 +61,19 @@ public class DaoSearchParamSynchronizer {
}
private void synchronize(ResourceIndexedSearchParams theParams, ResourceTable theEntity, AddRemoveCount theAddRemoveCount, Collection theNewParms, Collection theExistingParms) {
+ for (T next : theNewParms) {
+ next.setPartitionId(theEntity.getPartitionId());
+ next.calculateHashes();
+ }
+
List quantitiesToRemove = subtract(theExistingParms, theNewParms);
List quantitiesToAdd = subtract(theNewParms, theExistingParms);
tryToReuseIndexEntities(quantitiesToRemove, quantitiesToAdd);
+
for (T next : quantitiesToRemove) {
myEntityManager.remove(next);
theEntity.getParamsQuantity().remove(next);
}
- for (T next : quantitiesToAdd) {
- next.setPartitionId(theEntity.getPartitionId());
- }
- theParams.calculateHashes(theNewParms);
for (T next : quantitiesToAdd) {
myEntityManager.merge(next);
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java
index ecc4961b9b7..94674ee4610 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java
@@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.TestUtil;
@@ -85,6 +86,38 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
}
+ /**
+ * Just in case any hash values are missing
+ */
+ @Test
+ public void testCreateAndUpdateStringAndTokenWhereHashesAreNull() {
+ Patient p = new Patient();
+ p.addIdentifier().setSystem("sys1").setValue("val1");
+ p.addName().setFamily("FAMILY1");
+ IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless();
+
+ runInTransaction(()->{
+ myEntityManager.createQuery("UPDATE ResourceIndexedSearchParamString s SET s.myHashIdentity = null").executeUpdate();
+ myEntityManager.createQuery("UPDATE ResourceIndexedSearchParamString s SET s.myHashExact = null").executeUpdate();
+ myEntityManager.createQuery("UPDATE ResourceIndexedSearchParamString s SET s.myHashNormalizedPrefix = null").executeUpdate();
+ myEntityManager.createQuery("UPDATE ResourceIndexedSearchParamToken s SET s.myHashIdentity = null").executeUpdate();
+ myEntityManager.createQuery("UPDATE ResourceIndexedSearchParamToken s SET s.myHashSystem = null").executeUpdate();
+ myEntityManager.createQuery("UPDATE ResourceIndexedSearchParamToken s SET s.myHashValue = null").executeUpdate();
+ myEntityManager.createQuery("UPDATE ResourceIndexedSearchParamToken s SET s.myHashSystemAndValue = null").executeUpdate();
+ });
+
+ p = new Patient();
+ p.setId(id);
+ p.addIdentifier().setSystem("sys2").setValue("val2");
+ p.addName().setFamily("FAMILY2");
+ myPatientDao.update(p);
+
+ SearchParameterMap map = new SearchParameterMap();
+ map.setLoadSynchronous(true);
+ map.add(Patient.SP_FAMILY, new StringParam("FAMILY2"));
+ Patient newPatient = (Patient) myPatientDao.search(map).getResources(0,1).get(0);
+ assertEquals("FAMILY2", newPatient.getName().get(0).getFamily());
+ }
@Test
public void testUpdateNotModifiedDoesNotAffectDates() {
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java
index 87d30dc8172..74f4e13f3ee 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java
@@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
+import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
@@ -17,7 +18,6 @@ import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Sets;
-import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Consent;
@@ -65,7 +65,7 @@ public class SearchParamExtractorR4Test {
Observation obs = new Observation();
obs.addCategory().addCoding().setSystem("SYSTEM").setCode("CODE");
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry, new PartitionSettings());
extractor.setPartitionConfigForUnitTest(new PartitionSettings());
Set tokens = extractor.extractSearchParamTokens(obs);
assertEquals(1, tokens.size());
@@ -80,7 +80,7 @@ public class SearchParamExtractorR4Test {
SearchParameter sp = new SearchParameter();
sp.addUseContext().setCode(new Coding().setSystem("http://system").setCode("code"));
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry, new PartitionSettings());
extractor.setPartitionConfigForUnitTest(new PartitionSettings());
Set tokens = extractor.extractSearchParamTokens(sp);
assertEquals(1, tokens.size());
@@ -95,7 +95,7 @@ public class SearchParamExtractorR4Test {
Encounter enc = new Encounter();
enc.addLocation().setLocation(new Reference("Location/123"));
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry, new PartitionSettings());
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Encounter", "location");
assertNotNull(param);
ISearchParamExtractor.SearchParamSet links = extractor.extractResourceLinks(enc);
@@ -110,7 +110,7 @@ public class SearchParamExtractorR4Test {
Consent consent = new Consent();
consent.setSource(new Reference().setReference("Consent/999"));
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry, new PartitionSettings());
extractor.setPartitionConfigForUnitTest(new PartitionSettings());
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Consent", Consent.SP_SOURCE_REFERENCE);
assertNotNull(param);
@@ -126,7 +126,7 @@ public class SearchParamExtractorR4Test {
Patient p = new Patient();
p.addIdentifier().setSystem("sys").setValue("val");
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry, new PartitionSettings());
extractor.setPartitionConfigForUnitTest(new PartitionSettings());
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Patient", Patient.SP_IDENTIFIER);
assertNotNull(param);
@@ -149,7 +149,7 @@ public class SearchParamExtractorR4Test {
Patient patient = new Patient();
patient.addExtension("http://patext", new Reference("Organization/AAA"));
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry, new PartitionSettings());
ISearchParamExtractor.SearchParamSet links = extractor.extractResourceLinks(patient);
assertEquals(1, links.size());
@@ -165,7 +165,7 @@ public class SearchParamExtractorR4Test {
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code2")))
.setValue(new Quantity().setSystem("http://bar").setCode("code2").setValue(200));
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry, new PartitionSettings());
Set links = extractor.extractSearchParamQuantity(o1);
ourLog.info("Links:\n {}", links.stream().map(t -> t.toString()).collect(Collectors.joining("\n ")));
assertEquals(4, links.size());
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java
index b8bc0aca701..346fdf5340f 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java
@@ -85,13 +85,6 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
@Transient
private transient PartitionSettings myPartitionSettings;
- /**
- * Subclasses may override
- */
- protected void clearHashes() {
- // nothing
- }
-
@Override
public abstract Long getId();
@@ -100,7 +93,6 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
}
public void setParamName(String theName) {
- clearHashes();
myParamName = theName;
}
@@ -109,7 +101,6 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
}
public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
- clearHashes();
myResource = theResource;
myResourceType = theResource.getResourceType();
return this;
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamCoords.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamCoords.java
index b91a7145bc0..aec6f32e7b8 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamCoords.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamCoords.java
@@ -28,7 +28,15 @@ import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.annotations.Field;
-import javax.persistence.*;
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Index;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
@Embeddable
@Entity
@@ -68,21 +76,14 @@ public class ResourceIndexedSearchParamCoords extends BaseResourceIndexedSearchP
setParamName(theParamName);
setLatitude(theLatitude);
setLongitude(theLongitude);
+ calculateHashes();
}
@Override
- @PrePersist
public void calculateHashes() {
- if (myHashIdentity == null && getParamName() != null) {
- String resourceType = getResourceType();
- String paramName = getParamName();
- setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
- }
- }
-
- @Override
- protected void clearHashes() {
- myHashIdentity = null;
+ String resourceType = getResourceType();
+ String paramName = getParamName();
+ setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
}
@Override
@@ -126,7 +127,7 @@ public class ResourceIndexedSearchParamCoords extends BaseResourceIndexedSearchP
@Override
public void setId(Long theId) {
- myId =theId;
+ myId = theId;
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
index 2119e64ad18..d998fab6ae3 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java
@@ -35,7 +35,18 @@ import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.annotations.Field;
import org.hl7.fhir.r4.model.DateTimeType;
-import javax.persistence.*;
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Index;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.Transient;
import java.util.Date;
@Embeddable
@@ -62,9 +73,9 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
* Field which stores an integer representation of YYYYMDD as calculated by Calendar
* e.g. 2019-01-20 -> 20190120
*/
- @Column(name="SP_VALUE_LOW_DATE_ORDINAL")
+ @Column(name = "SP_VALUE_LOW_DATE_ORDINAL")
public Integer myValueLowDateOrdinal;
- @Column(name="SP_VALUE_HIGH_DATE_ORDINAL")
+ @Column(name = "SP_VALUE_HIGH_DATE_ORDINAL")
public Integer myValueHighDateOrdinal;
@Transient
@@ -105,6 +116,7 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
computeValueHighDateOrdinal(theHighString);
computeValueLowDateOrdinal(theLowString);
myOriginalValue = theOriginalValue;
+ calculateHashes();
}
private void computeValueHighDateOrdinal(String theHigh) {
@@ -112,7 +124,8 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
this.myValueHighDateOrdinal = generateOrdinalDateInteger(theHigh);
}
}
- private int generateOrdinalDateInteger(String theDateString){
+
+ private int generateOrdinalDateInteger(String theDateString) {
if (theDateString.contains("T")) {
theDateString = theDateString.substring(0, theDateString.indexOf("T"));
}
@@ -146,18 +159,10 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
}
@Override
- @PrePersist
public void calculateHashes() {
- if (myHashIdentity == null && getParamName() != null) {
- String resourceType = getResourceType();
- String paramName = getParamName();
- setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
- }
- }
-
- @Override
- protected void clearHashes() {
- myHashIdentity = null;
+ String resourceType = getResourceType();
+ String paramName = getParamName();
+ setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
}
@Override
@@ -192,7 +197,7 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
@Override
public void setId(Long theId) {
- myId =theId;
+ myId = theId;
}
protected Long getTimeFromDate(Date date) {
@@ -260,14 +265,12 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
DateRangeParam range = new DateRangeParam(dateParam);
-
-
boolean result;
if (theUseOrdinalDatesForDayComparison) {
result = matchesOrdinalDateBounds(range);
result = matchesDateBounds(range);
} else {
- result = matchesDateBounds(range);
+ result = matchesDateBounds(range);
}
return result;
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java
index 8e71141210c..c58817d2973 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java
@@ -32,7 +32,15 @@ import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.FieldBridge;
import org.hibernate.search.annotations.NumericField;
-import javax.persistence.*;
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Index;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
import java.math.BigDecimal;
import java.util.Objects;
@@ -71,6 +79,7 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
setResourceType(theResourceType);
setParamName(theParamName);
setValue(theValue);
+ calculateHashes();
}
@Override
@@ -83,18 +92,14 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
@Override
- @PrePersist
public void calculateHashes() {
- if (myHashIdentity == null && getParamName() != null) {
- String resourceType = getResourceType();
- String paramName = getParamName();
- setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
- }
+ String resourceType = getResourceType();
+ String paramName = getParamName();
+ setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
}
- @Override
- protected void clearHashes() {
- myHashIdentity = null;
+ public Long getHashIdentity() {
+ return myHashIdentity;
}
@Override
@@ -112,7 +117,7 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
EqualsBuilder b = new EqualsBuilder();
b.append(getResourceType(), obj.getResourceType());
b.append(getParamName(), obj.getParamName());
- b.append(getValue(), obj.getValue());
+ b.append(getHashIdentity(), obj.getHashIdentity());
b.append(isMissing(), obj.isMissing());
return b.isEquals();
}
@@ -128,7 +133,7 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
@Override
public void setId(Long theId) {
- myId =theId;
+ myId = theId;
}
public BigDecimal getValue() {
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java
index c1b5d2617f6..c487641d1ad 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java
@@ -33,7 +33,15 @@ import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.FieldBridge;
import org.hibernate.search.annotations.NumericField;
-import javax.persistence.*;
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Index;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
import java.math.BigDecimal;
import java.util.Objects;
@@ -101,6 +109,7 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
setSystem(theSystem);
setValue(theValue);
setUnits(theUnits);
+ calculateHashes();
}
@Override
@@ -117,23 +126,14 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
@Override
- @PrePersist
public void calculateHashes() {
- if (myHashIdentity == null && getParamName() != null) {
- String resourceType = getResourceType();
- String paramName = getParamName();
- String units = getUnits();
- String system = getSystem();
- setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
- setHashIdentityAndUnits(calculateHashUnits(getPartitionSettings(), getPartitionId(), resourceType, paramName, units));
- setHashIdentitySystemAndUnits(calculateHashSystemAndUnits(getPartitionSettings(), getPartitionId(), resourceType, paramName, system, units));
- }
- }
-
- @Override
- protected void clearHashes() {
- myHashIdentity = null;
- myHashIdentityAndUnits = null;
+ String resourceType = getResourceType();
+ String paramName = getParamName();
+ String units = getUnits();
+ String system = getSystem();
+ setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
+ setHashIdentityAndUnits(calculateHashUnits(getPartitionSettings(), getPartitionId(), resourceType, paramName, units));
+ setHashIdentitySystemAndUnits(calculateHashSystemAndUnits(getPartitionSettings(), getPartitionId(), resourceType, paramName, system, units));
}
@Override
@@ -151,15 +151,14 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
EqualsBuilder b = new EqualsBuilder();
b.append(getResourceType(), obj.getResourceType());
b.append(getParamName(), obj.getParamName());
- b.append(getSystem(), obj.getSystem());
- b.append(getUnits(), obj.getUnits());
- b.append(getValue(), obj.getValue());
+ b.append(getHashIdentity(), obj.getHashIdentity());
+ b.append(getHashIdentityAndUnits(), obj.getHashIdentityAndUnits());
+ b.append(getHashIdentitySystemAndUnits(), obj.getHashIdentitySystemAndUnits());
b.append(isMissing(), obj.isMissing());
return b.isEquals();
}
public Long getHashIdentity() {
- calculateHashes();
return myHashIdentity;
}
@@ -168,7 +167,6 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
}
public Long getHashIdentityAndUnits() {
- calculateHashes();
return myHashIdentityAndUnits;
}
@@ -177,7 +175,6 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
}
private Long getHashIdentitySystemAndUnits() {
- calculateHashes();
return myHashIdentitySystemAndUnits;
}
@@ -200,7 +197,6 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
}
public void setSystem(String theSystem) {
- clearHashes();
mySystem = theSystem;
}
@@ -209,7 +205,6 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
}
public void setUnits(String theUnits) {
- clearHashes();
myUnits = theUnits;
}
@@ -218,7 +213,6 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
}
public ResourceIndexedSearchParamQuantity setValue(BigDecimal theValue) {
- clearHashes();
myValue = theValue;
return this;
}
@@ -228,9 +222,9 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
HashCodeBuilder b = new HashCodeBuilder();
b.append(getResourceType());
b.append(getParamName());
- b.append(getSystem());
- b.append(getUnits());
- b.append(getValue());
+ b.append(getHashIdentity());
+ b.append(getHashIdentityAndUnits());
+ b.append(getHashIdentitySystemAndUnits());
return b.toHashCode();
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java
index 2cf23b8abe9..b930e1a4875 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java
@@ -37,7 +37,19 @@ import org.hibernate.search.annotations.Fields;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store;
-import javax.persistence.*;
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import javax.persistence.Entity;
+import javax.persistence.ForeignKey;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Index;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
+import javax.persistence.Transient;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.left;
@@ -121,6 +133,7 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
setParamName(theParamName);
setValueNormalized(theValueNormalized);
setValueExact(theValueExact);
+ calculateHashes();
}
@Override
@@ -135,25 +148,14 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
}
@Override
- @PrePersist
- @PreUpdate
public void calculateHashes() {
- if ((myHashIdentity == null || myHashNormalizedPrefix == null || myHashExact == null) && getParamName() != null) {
- String resourceType = getResourceType();
- String paramName = getParamName();
- String valueNormalized = getValueNormalized();
- String valueExact = getValueExact();
- setHashNormalizedPrefix(calculateHashNormalized(getPartitionSettings(), getPartitionId(), myModelConfig, resourceType, paramName, valueNormalized));
- setHashExact(calculateHashExact(getPartitionSettings(), getPartitionId(), resourceType, paramName, valueExact));
- setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
- }
- }
-
- @Override
- protected void clearHashes() {
- myHashNormalizedPrefix = null;
- myHashExact = null;
- myHashIdentity = null;
+ String resourceType = getResourceType();
+ String paramName = getParamName();
+ String valueNormalized = getValueNormalized();
+ String valueExact = getValueExact();
+ setHashNormalizedPrefix(calculateHashNormalized(getPartitionSettings(), getPartitionId(), myModelConfig, resourceType, paramName, valueNormalized));
+ setHashExact(calculateHashExact(getPartitionSettings(), getPartitionId(), resourceType, paramName, valueExact));
+ setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
}
@Override
@@ -179,7 +181,6 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
}
private Long getHashIdentity() {
- calculateHashes();
return myHashIdentity;
}
@@ -188,7 +189,6 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
}
public Long getHashExact() {
- calculateHashes();
return myHashExact;
}
@@ -197,7 +197,6 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
}
public Long getHashNormalizedPrefix() {
- calculateHashes();
return myHashNormalizedPrefix;
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java
index ba9b49d07e6..b69edb12fa0 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java
@@ -31,7 +31,15 @@ import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.annotations.Field;
-import javax.persistence.*;
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Index;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.trim;
@@ -110,6 +118,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
setParamName(theParamName);
setSystem(theSystem);
setValue(theValue);
+ calculateHashes();
}
@Override
@@ -127,25 +136,15 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
@Override
- @PrePersist
public void calculateHashes() {
- if (myHashSystem == null && getParamName() != null) {
- String resourceType = getResourceType();
- String paramName = getParamName();
- String system = getSystem();
- String value = getValue();
- setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
- setHashSystem(calculateHashSystem(getPartitionSettings(), getPartitionId(), resourceType, paramName, system));
- setHashSystemAndValue(calculateHashSystemAndValue(getPartitionSettings(), getPartitionId(), resourceType, paramName, system, value));
- setHashValue(calculateHashValue(getPartitionSettings(), getPartitionId(), resourceType, paramName, value));
- }
- }
-
- @Override
- protected void clearHashes() {
- myHashSystem = null;
- myHashSystemAndValue = null;
- myHashValue = null;
+ String resourceType = getResourceType();
+ String paramName = getParamName();
+ String system = getSystem();
+ String value = getValue();
+ setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
+ setHashSystem(calculateHashSystem(getPartitionSettings(), getPartitionId(), resourceType, paramName, system));
+ setHashSystemAndValue(calculateHashSystemAndValue(getPartitionSettings(), getPartitionId(), resourceType, paramName, system, value));
+ setHashValue(calculateHashValue(getPartitionSettings(), getPartitionId(), resourceType, paramName, value));
}
@Override
@@ -161,15 +160,13 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
}
ResourceIndexedSearchParamToken obj = (ResourceIndexedSearchParamToken) theObj;
EqualsBuilder b = new EqualsBuilder();
- b.append(getResourceType(), obj.getResourceType());
- b.append(getParamName(), obj.getParamName());
- b.append(getSystem(), obj.getSystem());
- b.append(getValue(), obj.getValue());
+ b.append(getHashSystem(), obj.getHashSystem());
+ b.append(getHashValue(), obj.getHashValue());
+ b.append(getHashSystemAndValue(), obj.getHashSystemAndValue());
return b.isEquals();
}
Long getHashSystem() {
- calculateHashes();
return myHashSystem;
}
@@ -182,17 +179,14 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
}
Long getHashSystemAndValue() {
- calculateHashes();
return myHashSystemAndValue;
}
private void setHashSystemAndValue(Long theHashSystemAndValue) {
- calculateHashes();
myHashSystemAndValue = theHashSystemAndValue;
}
Long getHashValue() {
- calculateHashes();
return myHashValue;
}
@@ -215,7 +209,6 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
}
public void setSystem(String theSystem) {
- clearHashes();
mySystem = StringUtils.defaultIfBlank(theSystem, null);
}
@@ -224,19 +217,17 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
}
public ResourceIndexedSearchParamToken setValue(String theValue) {
- clearHashes();
myValue = StringUtils.defaultIfBlank(theValue, null);
return this;
}
@Override
public int hashCode() {
- calculateHashes();
HashCodeBuilder b = new HashCodeBuilder();
b.append(getResourceType());
- b.append(getParamName());
- b.append(getSystem());
- b.append(getValue());
+ b.append(getHashValue());
+ b.append(getHashSystem());
+ b.append(getHashSystemAndValue());
return b.toHashCode();
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUri.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUri.java
index 29fc1ff91fb..745d0ede7cb 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUri.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUri.java
@@ -30,7 +30,15 @@ import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hibernate.search.annotations.Field;
-import javax.persistence.*;
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Index;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
import static org.apache.commons.lang3.StringUtils.defaultString;
@@ -87,6 +95,7 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
setResourceType(theResourceType);
setParamName(theParamName);
setUri(theUri);
+ calculateHashes();
}
@Override
@@ -100,20 +109,12 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
@Override
- @PrePersist
public void calculateHashes() {
- if (myHashUri == null && getParamName() != null) {
- String resourceType = getResourceType();
- String paramName = getParamName();
- String uri = getUri();
- setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
- setHashUri(calculateHashUri(getPartitionSettings(), getPartitionId(), resourceType, paramName, uri));
- }
- }
-
- @Override
- protected void clearHashes() {
- myHashUri = null;
+ String resourceType = getResourceType();
+ String paramName = getParamName();
+ String uri = getUri();
+ setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
+ setHashUri(calculateHashUri(getPartitionSettings(), getPartitionId(), resourceType, paramName, uri));
}
@Override
@@ -138,7 +139,6 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
}
private Long getHashIdentity() {
- calculateHashes();
return myHashIdentity;
}
@@ -147,7 +147,6 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
}
public Long getHashUri() {
- calculateHashes();
return myHashUri;
}
@@ -182,6 +181,7 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
b.append(getParamName());
b.append(getUri());
b.append(getHashUri());
+ b.append(getHashIdentity());
return b.toHashCode();
}
diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityTest.java
index 8c20f544833..1e1fda3fd2c 100644
--- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityTest.java
+++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityTest.java
@@ -19,6 +19,7 @@ public class ResourceIndexedSearchParamQuantityTest {
@Test
public void testHashFunctions() {
ResourceIndexedSearchParamQuantity token = createParam("NAME", "123.001", "value", "VALUE");
+ token.calculateHashes();
// Make sure our hashing function gives consistent results
assertEquals(834432764963581074L, token.getHashIdentity().longValue());
diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamStringTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamStringTest.java
index ff07d7c6999..0db843825fb 100644
--- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamStringTest.java
+++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamStringTest.java
@@ -13,6 +13,7 @@ public class ResourceIndexedSearchParamStringTest {
public void testHashFunctions() {
ResourceIndexedSearchParamString token = new ResourceIndexedSearchParamString(new PartitionSettings(), new ModelConfig(), "Patient", "NAME", "value", "VALUE");
token.setResource(new ResourceTable().setResourceType("Patient"));
+ token.calculateHashes();
// Make sure our hashing function gives consistent results
assertEquals(6598082761639188617L, token.getHashNormalizedPrefix().longValue());
@@ -23,6 +24,7 @@ public class ResourceIndexedSearchParamStringTest {
public void testHashFunctionsPrefixOnly() {
ResourceIndexedSearchParamString token = new ResourceIndexedSearchParamString(new PartitionSettings(), new ModelConfig(), "Patient", "NAME", "vZZZZZZZZZZZZZZZZ", "VZZZZZZzzzZzzzZ");
token.setResource(new ResourceTable().setResourceType("Patient"));
+ token.calculateHashes();
// Should be the same as in testHashFunctions()
assertEquals(6598082761639188617L, token.getHashNormalizedPrefix().longValue());
@@ -38,11 +40,13 @@ public class ResourceIndexedSearchParamStringTest {
.setValueExact("aaa")
.setValueNormalized("AAA");
val1.setPartitionSettings(new PartitionSettings());
+ val1.setModelConfig(new ModelConfig());
val1.calculateHashes();
ResourceIndexedSearchParamString val2 = new ResourceIndexedSearchParamString()
.setValueExact("aaa")
.setValueNormalized("AAA");
val2.setPartitionSettings(new PartitionSettings());
+ val2.setModelConfig(new ModelConfig());
val2.calculateHashes();
assertEquals(val1, val1);
assertEquals(val1, val2);
@@ -56,11 +60,13 @@ public class ResourceIndexedSearchParamStringTest {
.setValueExact("aaa")
.setValueNormalized("AAA");
val1.setPartitionSettings(new PartitionSettings().setIncludePartitionInSearchHashes(true));
+ val1.setModelConfig(new ModelConfig());
val1.calculateHashes();
ResourceIndexedSearchParamString val2 = new ResourceIndexedSearchParamString()
.setValueExact("aaa")
.setValueNormalized("AAA");
val2.setPartitionSettings(new PartitionSettings().setIncludePartitionInSearchHashes(true));
+ val2.setModelConfig(new ModelConfig());
val2.calculateHashes();
assertEquals(val1, val1);
assertEquals(val1, val2);
diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamTokenTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamTokenTest.java
index 7bd2113deae..ae5e6151a1d 100644
--- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamTokenTest.java
+++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamTokenTest.java
@@ -12,6 +12,7 @@ public class ResourceIndexedSearchParamTokenTest {
public void testHashFunctions() {
ResourceIndexedSearchParamToken token = new ResourceIndexedSearchParamToken(new PartitionSettings(), "Patient", "NAME", "SYSTEM", "VALUE");
token.setResource(new ResourceTable().setResourceType("Patient"));
+ token.calculateHashes();
// Make sure our hashing function gives consistent results
assertEquals(-8558989679010582575L, token.getHashSystem().longValue());
@@ -23,6 +24,7 @@ public class ResourceIndexedSearchParamTokenTest {
public void testHashFunctionsWithOverlapNames() {
ResourceIndexedSearchParamToken token = new ResourceIndexedSearchParamToken(new PartitionSettings(), "Patient", "NAME", "SYSTEM", "VALUE");
token.setResource(new ResourceTable().setResourceType("Patient"));
+ token.calculateHashes();
// Make sure our hashing function gives consistent results
assertEquals(-8558989679010582575L, token.getHashSystem().longValue());
diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUriTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUriTest.java
index 1aadd00dfe4..8147135b367 100644
--- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUriTest.java
+++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUriTest.java
@@ -12,6 +12,7 @@ public class ResourceIndexedSearchParamUriTest {
public void testHashFunctions() {
ResourceIndexedSearchParamUri token = new ResourceIndexedSearchParamUri(new PartitionSettings(), "Patient", "NAME", "http://example.com");
token.setResource(new ResourceTable().setResourceType("Patient"));
+ token.calculateHashes();
// Make sure our hashing function gives consistent results
assertEquals(-6132951326739875838L, token.getHashUri().longValue());
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
index 355ebf37bec..1414eeb6272 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
@@ -141,9 +141,11 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
/**
* UNIT TEST constructor
*/
- BaseSearchParamExtractor(FhirContext theCtx, ISearchParamRegistry theSearchParamRegistry) {
+ BaseSearchParamExtractor(FhirContext theCtx, ISearchParamRegistry theSearchParamRegistry, ModelConfig theModelConfig, PartitionSettings thePartitionSettings) {
myContext = theCtx;
mySearchParamRegistry = theSearchParamRegistry;
+ myPartitionSettings = thePartitionSettings;
+ myModelConfig = theModelConfig;
}
@VisibleForTesting
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java
index 05df1aaef70..2f8af038d57 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java
@@ -130,12 +130,6 @@ public final class ResourceIndexedSearchParams {
}
}
- public void calculateHashes(Collection extends BaseResourceIndex> theStringParams) {
- for (BaseResourceIndex next : theStringParams) {
- next.calculateHashes();
- }
- }
-
public Set getPopulatedResourceLinkParameters() {
return myPopulatedResourceLinkParameters;
}
@@ -305,6 +299,7 @@ public final class ResourceIndexedSearchParams {
param.setResource(theEntity);
param.setMissing(true);
param.setParamName(nextParamName);
+ param.calculateHashes();
paramCollection.add((RT) param);
}
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu2.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu2.java
index 8c2b46f837e..82a31d10f40 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu2.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu2.java
@@ -21,6 +21,8 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
*/
import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.jpa.model.config.PartitionSettings;
+import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.model.dstu2.composite.ContactPointDt;
import ca.uhn.fhir.util.FhirTerser;
@@ -40,8 +42,8 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
/**
* Constructor for unit tests
*/
- SearchParamExtractorDstu2(FhirContext theCtx, ISearchParamRegistry theSearchParamRegistry) {
- super(theCtx, theSearchParamRegistry);
+ SearchParamExtractorDstu2(FhirContext theCtx, ISearchParamRegistry theSearchParamRegistry, ModelConfig theModelConfig, PartitionSettings thePartitionSettings) {
+ super(theCtx, theSearchParamRegistry, theModelConfig, thePartitionSettings);
start();
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3.java
index 79a7de05c92..f2d0ff05a65 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3.java
@@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
+import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import com.google.common.annotations.VisibleForTesting;
@@ -49,8 +50,8 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
// This constructor is used by tests
@VisibleForTesting
- public SearchParamExtractorDstu3(ModelConfig theModelConfig, FhirContext theCtx, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry) {
- super(theCtx, theSearchParamRegistry);
+ public SearchParamExtractorDstu3(ModelConfig theModelConfig, FhirContext theCtx, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry, PartitionSettings thePartitionSettings) {
+ super(theCtx, theSearchParamRegistry, theModelConfig, thePartitionSettings);
initFhirPathEngine(theValidationSupport);
start();
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java
index f7d216ee66a..6b501bc2d13 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java
@@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
+import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import com.google.common.annotations.VisibleForTesting;
@@ -61,8 +62,8 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
// This constructor is used by tests
@VisibleForTesting
- public SearchParamExtractorR4(ModelConfig theModelConfig, FhirContext theCtx, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry) {
- super(theCtx, theSearchParamRegistry);
+ public SearchParamExtractorR4(ModelConfig theModelConfig, FhirContext theCtx, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry, PartitionSettings thePartitionSettings) {
+ super(theCtx, theSearchParamRegistry,theModelConfig, thePartitionSettings);
initFhirPath(theValidationSupport);
start();
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java
index b882db0f4a5..3aa0132dfc7 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java
@@ -23,6 +23,8 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
+import ca.uhn.fhir.jpa.model.config.PartitionSettings;
+import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
@@ -56,8 +58,8 @@ public class SearchParamExtractorR5 extends BaseSearchParamExtractor implements
/**
* Constructor for unit tests
*/
- public SearchParamExtractorR5(FhirContext theCtx, DefaultProfileValidationSupport theDefaultProfileValidationSupport, ISearchParamRegistry theSearchParamRegistry) {
- super(theCtx, theSearchParamRegistry);
+ public SearchParamExtractorR5(FhirContext theCtx, DefaultProfileValidationSupport theDefaultProfileValidationSupport, ISearchParamRegistry theSearchParamRegistry, ModelConfig theModelConfig, PartitionSettings thePartitionSettings) {
+ super(theCtx, theSearchParamRegistry, theModelConfig, thePartitionSettings);
initFhirPath(theDefaultProfileValidationSupport);
start();
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/IndexStressTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/IndexStressTest.java
index c440243e102..d59aa3f645f 100644
--- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/IndexStressTest.java
+++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/IndexStressTest.java
@@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.searchparam;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.support.IValidationSupport;
+import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu3;
@@ -44,7 +45,7 @@ public class IndexStressTest {
when(mockValidationSupport.getFhirContext()).thenReturn(ctx);
IValidationSupport validationSupport = new CachingValidationSupport(new ValidationSupportChain(new DefaultProfileValidationSupport(ctx), mockValidationSupport));
ISearchParamRegistry searchParamRegistry = mock(ISearchParamRegistry.class);
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ctx, validationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ctx, validationSupport, searchParamRegistry, new PartitionSettings());
extractor.start();
Map spMap = ctx
diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java
index 6a05e7c9280..eba841d870a 100644
--- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java
+++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java
@@ -58,7 +58,7 @@ public class SearchParamExtractorDstu3Test {
ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry, new PartitionSettings());
extractor.setPartitionConfigForUnitTest(new PartitionSettings());
extractor.start();
Set tokens = extractor.extractSearchParamTokens(obs);
@@ -82,7 +82,7 @@ public class SearchParamExtractorDstu3Test {
ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry, new PartitionSettings());
extractor.start();
Set params = extractor.extractSearchParamStrings(questionnaire);
assertEquals(1, params.size());
@@ -100,7 +100,7 @@ public class SearchParamExtractorDstu3Test {
ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry, new PartitionSettings());
extractor.start();
Set params = extractor.extractSearchParamNumber(enc);
assertEquals(1, params.size());
@@ -118,7 +118,7 @@ public class SearchParamExtractorDstu3Test {
ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry, new PartitionSettings());
extractor.start();
Set params = extractor.extractSearchParamNumber(enc);
assertEquals(1, params.size());
@@ -130,7 +130,7 @@ public class SearchParamExtractorDstu3Test {
public void testEmptyPath() {
MySearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry, new PartitionSettings());
extractor.start();
searchParamRegistry.addSearchParam(new RuntimeSearchParam("foo", "foo", "", RestSearchParameterTypeEnum.STRING, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE));
@@ -146,7 +146,7 @@ public class SearchParamExtractorDstu3Test {
public void testStringMissingResourceType() {
MySearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry, new PartitionSettings());
extractor.start();
searchParamRegistry.addSearchParam(new RuntimeSearchParam("foo", "foo", "communication.language.coding.system | communication.language.coding.code", RestSearchParameterTypeEnum.STRING, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE));
@@ -163,7 +163,7 @@ public class SearchParamExtractorDstu3Test {
public void testInvalidType() {
MySearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry, new PartitionSettings());
extractor.setPartitionConfigForUnitTest(new PartitionSettings());
extractor.start();
@@ -215,7 +215,7 @@ public class SearchParamExtractorDstu3Test {
ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry, new PartitionSettings());
extractor.start();
ISearchParamExtractor.SearchParamSet coords = extractor.extractSearchParamTokens(loc);
assertEquals(1, coords.size());
diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java
index e29972743c1..77f6d57db05 100644
--- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java
+++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java
@@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
+import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
@@ -39,19 +40,19 @@ public class SearchParamExtractorMegaTest {
FhirContext ctx = FhirContext.forDstu2();
ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry(ctx);
- process(ctx, new SearchParamExtractorDstu2(ctx, searchParamRegistry).setPartitionConfigForUnitTest(new PartitionSettings()));
+ process(ctx, new SearchParamExtractorDstu2(ctx, searchParamRegistry, new ModelConfig(), new PartitionSettings()).setPartitionConfigForUnitTest(new PartitionSettings()));
ctx = FhirContext.forDstu3();
searchParamRegistry = new MySearchParamRegistry(ctx);
- process(ctx, new SearchParamExtractorDstu3(null, ctx, new DefaultProfileValidationSupport(ctx), searchParamRegistry).setPartitionConfigForUnitTest(new PartitionSettings()));
+ process(ctx, new SearchParamExtractorDstu3(new ModelConfig(), ctx, new DefaultProfileValidationSupport(ctx), searchParamRegistry, new PartitionSettings()).setPartitionConfigForUnitTest(new PartitionSettings()));
ctx = FhirContext.forR4();
searchParamRegistry = new MySearchParamRegistry(ctx);
- process(ctx, new SearchParamExtractorR4(null, ctx, new DefaultProfileValidationSupport(ctx), searchParamRegistry).setPartitionConfigForUnitTest(new PartitionSettings()));
+ process(ctx, new SearchParamExtractorR4(new ModelConfig(), ctx, new DefaultProfileValidationSupport(ctx), searchParamRegistry, new PartitionSettings()).setPartitionConfigForUnitTest(new PartitionSettings()));
ctx = FhirContext.forR5();
searchParamRegistry = new MySearchParamRegistry(ctx);
- process(ctx, new SearchParamExtractorR5(ctx, new DefaultProfileValidationSupport(ctx), searchParamRegistry).setPartitionConfigForUnitTest(new PartitionSettings()));
+ process(ctx, new SearchParamExtractorR5(ctx, new DefaultProfileValidationSupport(ctx), searchParamRegistry, new ModelConfig(), new PartitionSettings()).setPartitionConfigForUnitTest(new PartitionSettings()));
}
private void process(FhirContext theCtx, BaseSearchParamExtractor theExtractor) throws Exception {
From 35c2b7a2c12b9ece8a03ad44862ece89a559930b Mon Sep 17 00:00:00 2001
From: James Agnew
Date: Tue, 5 May 2020 12:16:55 -0400
Subject: [PATCH 48/49] Allow disabling text modifier indexing (#1831)
* Allow disabling text modifier indexing
* Add changelog
* Address review comment
* Resolve merge conflicts
---
.../uhn/fhir/context/RuntimeSearchParam.java | 18 +-
.../ca/uhn/fhir/i18n/hapi-messages.properties | 3 +
.../1831-allow-disabling-text-modifier.yaml | 5 +
.../hapi/fhir/docs/server_jpa/performance.md | 24 +++
.../dao/predicate/PredicateBuilderToken.java | 19 ++
.../r4/FhirResourceDaoR4SearchNoFtTest.java | 47 +++++
.../dao/r4/SearchParamExtractorR4Test.java | 183 ++++++++++++++++--
.../fhir/jpa/model/entity/ModelConfig.java | 57 ++++--
.../uhn/fhir/jpa/model/util/JpaConstants.java | 5 +
.../extractor/BaseSearchParamExtractor.java | 70 +++++--
.../extractor/SearchParamExtractorDstu2.java | 4 +-
.../extractor/SearchParamExtractorDstu3.java | 4 +-
.../extractor/SearchParamExtractorR4.java | 8 +-
.../extractor/SearchParamExtractorR5.java | 4 +-
.../fhir/jpa/searchparam/IndexStressTest.java | 2 +-
.../SearchParamExtractorDstu3Test.java | 18 +-
.../SearchParamExtractorMegaTest.java | 33 +++-
17 files changed, 422 insertions(+), 82 deletions(-)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1831-allow-disabling-text-modifier.yaml
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java
index c7312354925..dbae7e49544 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java
@@ -5,7 +5,6 @@ import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
-import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IIdType;
@@ -105,19 +104,30 @@ public class RuntimeSearchParam {
}
}
+ /**
+ * Constructor
+ */
public RuntimeSearchParam(String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, Set theProvidesMembershipInCompartments, Set theTargets, RuntimeSearchParamStatusEnum theStatus) {
this(null, null, theName, theDescription, thePath, theParamType, null, theProvidesMembershipInCompartments, theTargets, theStatus);
}
/**
- * Retrieve user data - This can be used to store any application-specific data
- *
- * @return
+ * Copy constructor
*/
+ public RuntimeSearchParam(RuntimeSearchParam theSp) {
+ this(theSp.getId(), theSp.getUri(), theSp.getName(), theSp.getDescription(), theSp.getPath(), theSp.getParamType(), theSp.getCompositeOf(), theSp.getProvidesMembershipInCompartments(), theSp.getTargets(), theSp.getStatus(), theSp.getBase());
+ }
+
+ /**
+ * Retrieve user data - This can be used to store any application-specific data
+ */
+ @Nonnull
public List> getExtensions(String theKey) {
List> retVal = myExtensions.get(theKey);
if (retVal != null) {
retVal = Collections.unmodifiableList(retVal);
+ } else {
+ retVal = Collections.emptyList();
}
return retVal;
}
diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
index 605bed74a59..9a1b0c23eb4 100644
--- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
+++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
@@ -122,6 +122,9 @@ ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoConceptMapR4.matchesFound=Matches found!
ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoConceptMapR4.noMatchesFound=No matches found!
ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4.invalidSearchParamExpression=The expression "{0}" can not be evaluated and may be invalid: {1}
+ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderToken.textModifierDisabledForSearchParam=The :text modifier is disabled for this search parameter
+ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderToken.textModifierDisabledForServer=The :text modifier is disabled on this server
+
ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor.successMsg=Cascaded delete to {0} resources: {1}
ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor.noParam=Note that cascading deletes are not active for this request. You can enable cascading deletes by using the "_cascade=delete" URL parameter.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1831-allow-disabling-text-modifier.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1831-allow-disabling-text-modifier.yaml
new file mode 100644
index 00000000000..6af2c4fff2b
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1831-allow-disabling-text-modifier.yaml
@@ -0,0 +1,5 @@
+---
+type: add
+issue: 1831
+title: "Indexing for the :text modifier can now be globally or selectively disabled in the JPA server. This can have a measurable
+ impact on index sizes and write speed in servers with large numbers of token indexes."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/performance.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/performance.md
index 68eac58c2af..0c7f94f2ef4 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/performance.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/performance.md
@@ -9,3 +9,27 @@ On servers where a large amount of data will be ingested, the following consider
* Optimize your database thread pool count and HTTP client thread count: Every environment will have a different optimal setting for the number of concurrent writes that are permitted, and the maximum number of database connections allowed.
* Disable deletes: If the JPA server is configured to have the FHIR delete operation disabled, it is able to skip some resource reference deletion checks during resource creation, which can have a measurable improvement to performance over large datasets.
+
+# Disabling :text Indexing
+
+On servers storing large numbers of Codings and CodeableConcepts (as well as any other token SearchParameter target where the `:text` modifier is supported), the indexes required to support the `:text` modifier can consume a large amount of index space, and cause a masurable impact on write times.
+
+This modifier can be disabled globally by using the ModelConfig#setSuppressStringIndexingInTokens setting.
+
+It can also be disabled at a more granular level (or selectively re-enabled if it disabled globally) by using an extension on individual SearchParameter resources. For example, the following SearchParameter disables text indexing on the Observation:code parameter:
+
+```json
+{
+ "resourceType": "SearchParameter",
+ "id": "observation-code",
+ "extension": [ {
+ "url": "http://hapifhir.io/fhir/StructureDefinition/searchparameter-token-suppress-text-index",
+ "valueBoolean": true
+ } ],
+ "status": "active",
+ "code": "code",
+ "base": [ "Observation" ],
+ "type": "token",
+ "expression": "Observation.code"
+}
+```
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java
index b2f62941c5f..6a91e496fd1 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java
@@ -28,8 +28,10 @@ import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
+import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
+import ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.model.api.IQueryParameterType;
@@ -39,6 +41,7 @@ import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.TokenParamModifier;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.util.VersionIndependentConcept;
import com.google.common.collect.Sets;
import org.hibernate.query.criteria.internal.CriteriaBuilderImpl;
@@ -72,6 +75,8 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
private ITermReadSvc myTerminologySvc;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
+ @Autowired
+ private ModelConfig myModelConfig;
PredicateBuilderToken(SearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
super(theSearchBuilder);
@@ -99,6 +104,20 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
if (nextOr instanceof TokenParam) {
TokenParam id = (TokenParam) nextOr;
if (id.isText()) {
+
+ // Check whether the :text modifier is actually enabled here
+ RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName);
+ boolean tokenTextIndexingEnabled = BaseSearchParamExtractor.tokenTextIndexingEnabledForSearchParam(myModelConfig, param);
+ if (!tokenTextIndexingEnabled) {
+ String msg;
+ if (myModelConfig.isSuppressStringIndexingInTokens()) {
+ msg = myContext.getLocalizer().getMessage(PredicateBuilderToken.class, "textModifierDisabledForServer");
+ }else{
+ msg = myContext.getLocalizer().getMessage(PredicateBuilderToken.class, "textModifierDisabledForSearchParam");
+ }
+ throw new MethodNotAllowedException(msg);
+ }
+
myPredicateBuilder.addPredicateString(theResourceName, theParamName, theList, theRequestPartitionId);
break;
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java
index 0784897bf5e..2d8b3c5da40 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java
@@ -7,6 +7,7 @@ import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
+import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
@@ -16,6 +17,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
+import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
@@ -108,6 +110,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
@Before
public void beforeDisableCacheReuse() {
+ myModelConfig.setSuppressStringIndexingInTokens(new ModelConfig().isSuppressStringIndexingInTokens());
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
}
@@ -4355,6 +4358,50 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
assertEquals(2, mySearchEntityDao.count());
}
+ @Test
+ public void testTokenTextDisabled_Global() {
+ myModelConfig.setSuppressStringIndexingInTokens(true);
+
+ SearchParameterMap map = new SearchParameterMap();
+ map.setLoadSynchronous(true);
+ map.add(Observation.SP_CODE, new TokenParam("hello").setModifier(TokenParamModifier.TEXT));
+ try {
+ myObservationDao.search(map);
+ } catch (MethodNotAllowedException e) {
+ assertEquals("The :text modifier is disabled on this server", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testTokenTextDisabled_ForSearchParam() {
+ {
+ SearchParameter sp = new SearchParameter();
+ sp.setId("observation-code");
+ sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
+ sp.addBase("Observation");
+ sp.setType(Enumerations.SearchParamType.TOKEN);
+ sp.setCode("code");
+ sp.setExpression("Observation.code");
+ sp.addExtension()
+ .setUrl(JpaConstants.EXT_SEARCHPARAM_TOKEN_SUPPRESS_TEXT_INDEXING)
+ .setValue(new BooleanType(true));
+ ourLog.info("SP:\n{}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(sp));
+ mySearchParameterDao.update(sp);
+ mySearchParamRegistry.forceRefresh();
+ }
+
+
+ SearchParameterMap map = new SearchParameterMap();
+ map.setLoadSynchronous(true);
+ map.add(Observation.SP_CODE, new TokenParam("hello").setModifier(TokenParamModifier.TEXT));
+ try {
+ myObservationDao.search(map);
+ } catch (MethodNotAllowedException e) {
+ assertEquals("The :text modifier is disabled for this search parameter", e.getMessage());
+ }
+ }
+
+
@Test
public void testDateSearchParametersShouldBeTimezoneIndependent() {
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java
index 74f4e13f3ee..a185e21c874 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java
@@ -9,7 +9,9 @@ import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
+import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.jpa.searchparam.extractor.PathAndRef;
@@ -18,10 +20,12 @@ import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Sets;
+import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.Encounter;
+import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Quantity;
@@ -43,6 +47,7 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
+import static java.util.Comparator.comparing;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -52,11 +57,13 @@ public class SearchParamExtractorR4Test {
private static FhirContext ourCtx = FhirContext.forR4();
private static IValidationSupport ourValidationSupport;
private MySearchParamRegistry mySearchParamRegistry;
+ private PartitionSettings myPartitionSettings;
@Before
public void before() {
mySearchParamRegistry = new MySearchParamRegistry();
+ myPartitionSettings = new PartitionSettings();
}
@@ -65,8 +72,7 @@ public class SearchParamExtractorR4Test {
Observation obs = new Observation();
obs.addCategory().addCoding().setSystem("SYSTEM").setCode("CODE");
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry, new PartitionSettings());
- extractor.setPartitionConfigForUnitTest(new PartitionSettings());
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
Set tokens = extractor.extractSearchParamTokens(obs);
assertEquals(1, tokens.size());
ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.iterator().next();
@@ -80,8 +86,7 @@ public class SearchParamExtractorR4Test {
SearchParameter sp = new SearchParameter();
sp.addUseContext().setCode(new Coding().setSystem("http://system").setCode("code"));
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry, new PartitionSettings());
- extractor.setPartitionConfigForUnitTest(new PartitionSettings());
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
Set tokens = extractor.extractSearchParamTokens(sp);
assertEquals(1, tokens.size());
ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.iterator().next();
@@ -90,12 +95,170 @@ public class SearchParamExtractorR4Test {
assertEquals("code", token.getValue());
}
+ @Test
+ public void testTokenText_Enabled_Coding() {
+ Observation obs = new Observation();
+ obs.getCode().addCoding().setSystem("http://system").setCode("code").setDisplay("Help Im a Bug");
+
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), myPartitionSettings, ourCtx, ourValidationSupport, mySearchParamRegistry);
+
+ List tokens = extractor.extractSearchParamTokens(obs)
+ .stream()
+ .filter(t -> t.getParamName().equals("code"))
+ .sorted(comparing(o -> o.getClass().getName()).reversed())
+ .collect(Collectors.toList());
+ assertEquals(2, tokens.size());
+
+ ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.get(0);
+ assertEquals("code", token.getParamName());
+ assertEquals("http://system", token.getSystem());
+ assertEquals("code", token.getValue());
+
+ ResourceIndexedSearchParamString string = (ResourceIndexedSearchParamString) tokens.get(1);
+ assertEquals("code", string.getParamName());
+ assertEquals("Help Im a Bug", string.getValueExact());
+ }
+
+ @Test
+ public void testTokenText_DisabledInSearchParam_Coding() {
+ RuntimeSearchParam existingCodeSp = mySearchParamRegistry.getActiveSearchParams("Observation").get("code");
+ RuntimeSearchParam codeSearchParam = new RuntimeSearchParam(existingCodeSp);
+ codeSearchParam.addExtension(JpaConstants.EXT_SEARCHPARAM_TOKEN_SUPPRESS_TEXT_INDEXING, new Extension(JpaConstants.EXT_SEARCHPARAM_TOKEN_SUPPRESS_TEXT_INDEXING, new BooleanType(true)));
+ mySearchParamRegistry.addSearchParam(codeSearchParam);
+
+ Observation obs = new Observation();
+ obs.getCode().addCoding().setSystem("http://system").setCode("code").setDisplay("Help Im a Bug");
+
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), myPartitionSettings, ourCtx, ourValidationSupport, mySearchParamRegistry);
+
+ List tokens = extractor.extractSearchParamTokens(obs)
+ .stream()
+ .filter(t -> t.getParamName().equals("code"))
+ .sorted(comparing(o -> o.getClass().getName()).reversed())
+ .collect(Collectors.toList());
+ assertEquals(1, tokens.size());
+
+ ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.get(0);
+ assertEquals("code", token.getParamName());
+ assertEquals("http://system", token.getSystem());
+ assertEquals("code", token.getValue());
+
+ }
+
+ @Test
+ public void testTokenText_DisabledInModelConfig_Coding() {
+ ModelConfig modelConfig = new ModelConfig();
+ modelConfig.setSuppressStringIndexingInTokens(true);
+
+ Observation obs = new Observation();
+ obs.getCode().addCoding().setSystem("http://system").setCode("code").setDisplay("Help Im a Bug");
+
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(modelConfig, myPartitionSettings, ourCtx, ourValidationSupport, mySearchParamRegistry);
+
+ List tokens = extractor.extractSearchParamTokens(obs)
+ .stream()
+ .filter(t -> t.getParamName().equals("code"))
+ .sorted(comparing(o -> o.getClass().getName()).reversed())
+ .collect(Collectors.toList());
+ assertEquals(1, tokens.size());
+
+ ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.get(0);
+ assertEquals("code", token.getParamName());
+ assertEquals("http://system", token.getSystem());
+ assertEquals("code", token.getValue());
+
+ }
+
+ @Test
+ public void testTokenText_DisabledInModelConfigButForcedInSearchParam_Coding() {
+ ModelConfig modelConfig = new ModelConfig();
+ modelConfig.setSuppressStringIndexingInTokens(true);
+
+ RuntimeSearchParam existingCodeSp = mySearchParamRegistry.getActiveSearchParams("Observation").get("code");
+ RuntimeSearchParam codeSearchParam = new RuntimeSearchParam(existingCodeSp);
+ codeSearchParam.addExtension(JpaConstants.EXT_SEARCHPARAM_TOKEN_SUPPRESS_TEXT_INDEXING, new Extension(JpaConstants.EXT_SEARCHPARAM_TOKEN_SUPPRESS_TEXT_INDEXING, new BooleanType(false)));
+ mySearchParamRegistry.addSearchParam(codeSearchParam);
+
+ Observation obs = new Observation();
+ obs.getCode().addCoding().setSystem("http://system").setCode("code").setDisplay("Help Im a Bug");
+
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(modelConfig, myPartitionSettings, ourCtx, ourValidationSupport, mySearchParamRegistry);
+
+ List tokens = extractor.extractSearchParamTokens(obs)
+ .stream()
+ .filter(t -> t.getParamName().equals("code"))
+ .sorted(comparing(o -> o.getClass().getName()).reversed())
+ .collect(Collectors.toList());
+ assertEquals(2, tokens.size());
+
+ ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.get(0);
+ assertEquals("code", token.getParamName());
+ assertEquals("http://system", token.getSystem());
+ assertEquals("code", token.getValue());
+
+ ResourceIndexedSearchParamString string = (ResourceIndexedSearchParamString) tokens.get(1);
+ assertEquals("code", string.getParamName());
+ assertEquals("Help Im a Bug", string.getValueExact());
+ }
+
+
+ @Test
+ public void testTokenText_Enabled_Identifier() {
+ Observation obs = new Observation();
+ obs.addIdentifier().setSystem("sys").setValue("val").getType().setText("Help Im a Bug");
+
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), myPartitionSettings, ourCtx, ourValidationSupport, mySearchParamRegistry);
+
+ List tokens = extractor.extractSearchParamTokens(obs)
+ .stream()
+ .filter(t -> t.getParamName().equals("identifier"))
+ .sorted(comparing(o -> o.getClass().getName()).reversed())
+ .collect(Collectors.toList());
+ assertEquals(2, tokens.size());
+
+ ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.get(0);
+ assertEquals("identifier", token.getParamName());
+ assertEquals("sys", token.getSystem());
+ assertEquals("val", token.getValue());
+
+ ResourceIndexedSearchParamString string = (ResourceIndexedSearchParamString) tokens.get(1);
+ assertEquals("identifier", string.getParamName());
+ assertEquals("Help Im a Bug", string.getValueExact());
+ }
+
+ @Test
+ public void testTokenText_DisabledInSearchParam_Identifier() {
+ RuntimeSearchParam existingCodeSp = mySearchParamRegistry.getActiveSearchParams("Observation").get("identifier");
+ RuntimeSearchParam codeSearchParam = new RuntimeSearchParam(existingCodeSp);
+ codeSearchParam.addExtension(JpaConstants.EXT_SEARCHPARAM_TOKEN_SUPPRESS_TEXT_INDEXING, new Extension(JpaConstants.EXT_SEARCHPARAM_TOKEN_SUPPRESS_TEXT_INDEXING, new BooleanType(true)));
+
+ mySearchParamRegistry.addSearchParam(codeSearchParam);
+
+ Observation obs = new Observation();
+ obs.addIdentifier().setSystem("sys").setValue("val").getType().setText("Help Im a Bug");
+
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), myPartitionSettings, ourCtx, ourValidationSupport, mySearchParamRegistry);
+
+ List tokens = extractor.extractSearchParamTokens(obs)
+ .stream()
+ .filter(t -> t.getParamName().equals("identifier"))
+ .sorted(comparing(o -> o.getClass().getName()).reversed())
+ .collect(Collectors.toList());
+ assertEquals(1, tokens.size());
+
+ ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.get(0);
+ assertEquals("identifier", token.getParamName());
+ assertEquals("sys", token.getSystem());
+ assertEquals("val", token.getValue());
+
+ }
+
@Test
public void testReferenceWithResolve() {
Encounter enc = new Encounter();
enc.addLocation().setLocation(new Reference("Location/123"));
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry, new PartitionSettings());
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Encounter", "location");
assertNotNull(param);
ISearchParamExtractor.SearchParamSet links = extractor.extractResourceLinks(enc);
@@ -110,8 +273,7 @@ public class SearchParamExtractorR4Test {
Consent consent = new Consent();
consent.setSource(new Reference().setReference("Consent/999"));
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry, new PartitionSettings());
- extractor.setPartitionConfigForUnitTest(new PartitionSettings());
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Consent", Consent.SP_SOURCE_REFERENCE);
assertNotNull(param);
ISearchParamExtractor.SearchParamSet links = extractor.extractResourceLinks(consent);
@@ -126,8 +288,7 @@ public class SearchParamExtractorR4Test {
Patient p = new Patient();
p.addIdentifier().setSystem("sys").setValue("val");
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry, new PartitionSettings());
- extractor.setPartitionConfigForUnitTest(new PartitionSettings());
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Patient", Patient.SP_IDENTIFIER);
assertNotNull(param);
ISearchParamExtractor.SearchParamSet params = extractor.extractSearchParamTokens(p, param);
@@ -149,7 +310,7 @@ public class SearchParamExtractorR4Test {
Patient patient = new Patient();
patient.addExtension("http://patext", new Reference("Organization/AAA"));
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry, new PartitionSettings());
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
ISearchParamExtractor.SearchParamSet links = extractor.extractResourceLinks(patient);
assertEquals(1, links.size());
@@ -165,7 +326,7 @@ public class SearchParamExtractorR4Test {
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code2")))
.setValue(new Quantity().setSystem("http://bar").setCode("code2").setValue(200));
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry, new PartitionSettings());
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
Set links = extractor.extractSearchParamQuantity(o1);
ourLog.info("Links:\n {}", links.stream().map(t -> t.toString()).collect(Collectors.joining("\n ")));
assertEquals(4, links.size());
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java
index 7aa6deb8ced..3f4c5c9fdc1 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java
@@ -65,6 +65,7 @@ public class ModelConfig {
* Update setter javadoc if default changes.
*/
private boolean myUseOrdinalDatesForDayPrecisionSearches = true;
+ private boolean mySuppressStringIndexingInTokens = false;
/**
* Constructor
@@ -318,7 +319,6 @@ public class ModelConfig {
/**
* This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted
* to the server matching these types will be activated.
- *
*/
public ModelConfig addSupportedSubscriptionType(Subscription.SubscriptionChannelType theSubscriptionChannelType) {
mySupportedSubscriptionTypes.add(theSubscriptionChannelType);
@@ -328,7 +328,6 @@ public class ModelConfig {
/**
* This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted
* to the server matching these types will be activated.
- *
*/
public Set getSupportedSubscriptionTypes() {
return Collections.unmodifiableSet(mySupportedSubscriptionTypes);
@@ -376,18 +375,18 @@ public class ModelConfig {
* Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
* {@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
* precision of {@link ca.uhn.fhir.model.api.TemporalPrecisionEnum#DAY}.
- *
+ *
* For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
- * ordinal {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
+ * integer representing the ordinal date {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
* and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
*
- * Default is {@literal true} beginning in HAPI FHIR 5.0
+ * Default is {@literal true} beginning in HAPI FHIR 5.0.0
*
*
- * @since 5.0
+ * @since 5.0.0
*/
- public void setUseOrdinalDatesForDayPrecisionSearches(boolean theUseOrdinalDates) {
- myUseOrdinalDatesForDayPrecisionSearches = theUseOrdinalDates;
+ public boolean getUseOrdinalDatesForDayPrecisionSearches() {
+ return myUseOrdinalDatesForDayPrecisionSearches;
}
/**
@@ -395,18 +394,48 @@ public class ModelConfig {
* Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
* {@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
* precision of {@link ca.uhn.fhir.model.api.TemporalPrecisionEnum#DAY}.
- *
+ *
* For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
- * integer representing the ordinal date {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
+ * ordinal {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
* and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
*
- * Default is {@literal true} beginning in HAPI FHIR 5.0
+ * Default is {@literal true} beginning in HAPI FHIR 5.0.0
*
*
- * @since 5.0
+ * @since 5.0.0
*/
- public boolean getUseOrdinalDatesForDayPrecisionSearches() {
- return myUseOrdinalDatesForDayPrecisionSearches;
+ public void setUseOrdinalDatesForDayPrecisionSearches(boolean theUseOrdinalDates) {
+ myUseOrdinalDatesForDayPrecisionSearches = theUseOrdinalDates;
+ }
+
+ /**
+ * If set to true (default is false), when indexing SearchParameter values for token SearchParameter,
+ * the string component to support the :text modifier will be disabled. This means that the following fields
+ * will not be indexed for tokens:
+ *
+ *
CodeableConcept.text
+ *
Coding.display
+ *
Identifier.use.text
+ *
+ * @since 5.0.0
+ */
+ public boolean isSuppressStringIndexingInTokens() {
+ return mySuppressStringIndexingInTokens;
+ }
+
+ /**
+ * If set to true (default is false), when indexing SearchParameter values for token SearchParameter,
+ * the string component to support the :text modifier will be disabled. This means that the following fields
+ * will not be indexed for tokens:
+ *
+ *
CodeableConcept.text
+ *
Coding.display
+ *
Identifier.use.text
+ *
+ * @since 5.0.0
+ */
+ public void setSuppressStringIndexingInTokens(boolean theSuppressStringIndexingInTokens) {
+ mySuppressStringIndexingInTokens = theSuppressStringIndexingInTokens;
}
private static void validateTreatBaseUrlsAsLocal(String theUrl) {
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java
index e93be6d91dd..f274290c593 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java
@@ -238,6 +238,11 @@ public class JpaConstants {
*/
public static final String PARAM_EXPORT_TYPE_FILTER = "_typeFilter";
+ /**
+ * Extension URL for extension on a SearchParameter indicating that text values should not be indexed
+ */
+ public static final String EXT_SEARCHPARAM_TOKEN_SUPPRESS_TEXT_INDEXING = "http://hapifhir.io/fhir/StructureDefinition/searchparameter-token-suppress-text-index";
+
/**
* Non-instantiable
*/
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
index 1414eeb6272..62a6b738a2f 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
@@ -37,6 +37,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
+import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.StringNormalizer;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
@@ -45,6 +46,7 @@ import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.Validate;
import org.hibernate.search.spatial.impl.Point;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBase;
@@ -131,6 +133,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
private BaseRuntimeChildDefinition myCodingDisplayValueChild;
private BaseRuntimeChildDefinition myContactPointSystemValueChild;
private BaseRuntimeChildDefinition myPatientCommunicationLanguageValueChild;
+
/**
* Constructor
*/
@@ -141,17 +144,15 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
/**
* UNIT TEST constructor
*/
- BaseSearchParamExtractor(FhirContext theCtx, ISearchParamRegistry theSearchParamRegistry, ModelConfig theModelConfig, PartitionSettings thePartitionSettings) {
+ BaseSearchParamExtractor(ModelConfig theModelConfig, PartitionSettings thePartitionSettings, FhirContext theCtx, ISearchParamRegistry theSearchParamRegistry) {
+ Validate.notNull(theModelConfig);
+ Validate.notNull(theCtx);
+ Validate.notNull(theSearchParamRegistry);
+
+ myModelConfig = theModelConfig;
myContext = theCtx;
mySearchParamRegistry = theSearchParamRegistry;
myPartitionSettings = thePartitionSettings;
- myModelConfig = theModelConfig;
- }
-
- @VisibleForTesting
- public BaseSearchParamExtractor setPartitionConfigForUnitTest(PartitionSettings thePartitionSettings) {
- myPartitionSettings = thePartitionSettings;
- return this;
}
@Override
@@ -533,23 +534,31 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
createTokenIndexIfNotBlank(theResourceType, theParams, theSearchParam, system, value);
}
- Optional type = myIdentifierTypeValueChild.getAccessor().getFirstValueOrNull(theValue);
- if (type.isPresent()) {
- String text = extractValueAsString(myIdentifierTypeTextValueChild, type.get());
- createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text);
+ if (shouldIndexTextComponentOfToken(theSearchParam)) {
+ Optional type = myIdentifierTypeValueChild.getAccessor().getFirstValueOrNull(theValue);
+ if (type.isPresent()) {
+ String text = extractValueAsString(myIdentifierTypeTextValueChild, type.get());
+ createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text);
+ }
}
}
+ protected boolean shouldIndexTextComponentOfToken(RuntimeSearchParam theSearchParam) {
+ return tokenTextIndexingEnabledForSearchParam(myModelConfig, theSearchParam);
+ }
+
private void addToken_CodeableConcept(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
List codings = myCodeableConceptCodingValueChild.getAccessor().getValues(theValue);
for (IBase nextCoding : codings) {
addToken_Coding(theResourceType, theParams, theSearchParam, nextCoding);
}
- String text = extractValueAsString(myCodeableConceptTextValueChild, theValue);
- if (isNotBlank(text)) {
- createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text);
+ if (shouldIndexTextComponentOfToken(theSearchParam)) {
+ String text = extractValueAsString(myCodeableConceptTextValueChild, theValue);
+ if (isNotBlank(text)) {
+ createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text);
+ }
}
}
@@ -558,8 +567,10 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
String code = extractValueAsString(myCodingCodeValueChild, theValue);
createTokenIndexIfNotBlank(theResourceType, theParams, theSearchParam, system, code);
- String text = extractValueAsString(myCodingDisplayValueChild, theValue);
- createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text);
+ if (shouldIndexTextComponentOfToken(theSearchParam)) {
+ String text = extractValueAsString(myCodingDisplayValueChild, theValue);
+ createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text);
+ }
}
private void addToken_ContactPoint(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
@@ -589,7 +600,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
String endAsString = extractValueAsString(myPeriodEndValueChild, theValue);
if (start != null || end != null) {
- ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(myPartitionSettings ,theResourceType, theSearchParam.getName(), start, startAsString, end, endAsString, startAsString);
+ ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), start, startAsString, end, endAsString, startAsString);
theParams.add(nextEntity);
}
}
@@ -773,7 +784,6 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
-
private SearchParamSet extractSearchParams(IBaseResource theResource, IExtractor theExtractor, RestSearchParameterTypeEnum theSearchParamType) {
SearchParamSet retVal = new SearchParamSet<>();
@@ -823,12 +833,11 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
private void addDateTimeTypes(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
IPrimitiveType nextBaseDateTime = (IPrimitiveType) theValue;
if (nextBaseDateTime.getValue() != null) {
- ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(myPartitionSettings ,theResourceType, theSearchParam.getName(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValueAsString());
+ ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValueAsString());
theParams.add(param);
}
}
-
private void addUri_Uri(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
IPrimitiveType> value = (IPrimitiveType>) theValue;
String valueAsString = value.getValueAsString();
@@ -1114,6 +1123,25 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
}
+ public static boolean tokenTextIndexingEnabledForSearchParam(ModelConfig theModelConfig, RuntimeSearchParam theSearchParam) {
+ Optional noSuppressForSearchParam = theSearchParam.getExtensions(JpaConstants.EXT_SEARCHPARAM_TOKEN_SUPPRESS_TEXT_INDEXING).stream()
+ .map(IBaseExtension::getValue)
+ .map(val -> (IPrimitiveType>) val)
+ .map(IPrimitiveType::getValueAsString)
+ .map(Boolean::parseBoolean)
+ .findFirst();
+
+ //if the SP doesn't care, use the system default.
+ if (!noSuppressForSearchParam.isPresent()) {
+ return !theModelConfig.isSuppressStringIndexingInTokens();
+ //If the SP does care, use its value.
+ } else {
+ boolean suppressForSearchParam = noSuppressForSearchParam.get();
+ ourLog.trace("Text indexing for SearchParameter {}: {}", theSearchParam.getName(), suppressForSearchParam);
+ return !suppressForSearchParam;
+ }
+ }
+
private static void addIgnoredType(FhirContext theCtx, String theType, Set theIgnoredTypes) {
BaseRuntimeElementDefinition> elementDefinition = theCtx.getElementDefinition(theType);
if (elementDefinition != null) {
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu2.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu2.java
index 82a31d10f40..bd8e724902d 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu2.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu2.java
@@ -42,8 +42,8 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
/**
* Constructor for unit tests
*/
- SearchParamExtractorDstu2(FhirContext theCtx, ISearchParamRegistry theSearchParamRegistry, ModelConfig theModelConfig, PartitionSettings thePartitionSettings) {
- super(theCtx, theSearchParamRegistry, theModelConfig, thePartitionSettings);
+ SearchParamExtractorDstu2(ModelConfig theModelConfig, PartitionSettings thePartitionSettings, FhirContext theCtx, ISearchParamRegistry theSearchParamRegistry) {
+ super(theModelConfig, thePartitionSettings, theCtx, theSearchParamRegistry);
start();
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3.java
index f2d0ff05a65..11a23bca41f 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3.java
@@ -50,8 +50,8 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
// This constructor is used by tests
@VisibleForTesting
- public SearchParamExtractorDstu3(ModelConfig theModelConfig, FhirContext theCtx, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry, PartitionSettings thePartitionSettings) {
- super(theCtx, theSearchParamRegistry, theModelConfig, thePartitionSettings);
+ public SearchParamExtractorDstu3(ModelConfig theModelConfig, PartitionSettings thePartitionSettings, FhirContext theCtx, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry) {
+ super(theModelConfig, thePartitionSettings, theCtx, theSearchParamRegistry);
initFhirPathEngine(theValidationSupport);
start();
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java
index 6b501bc2d13..9c5321f5404 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java
@@ -62,8 +62,8 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
// This constructor is used by tests
@VisibleForTesting
- public SearchParamExtractorR4(ModelConfig theModelConfig, FhirContext theCtx, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry, PartitionSettings thePartitionSettings) {
- super(theCtx, theSearchParamRegistry,theModelConfig, thePartitionSettings);
+ public SearchParamExtractorR4(ModelConfig theModelConfig, PartitionSettings thePartitionSettings, FhirContext theCtx, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry) {
+ super(theModelConfig, thePartitionSettings, theCtx, theSearchParamRegistry);
initFhirPath(theValidationSupport);
start();
}
@@ -77,10 +77,6 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
}
-
-
-
-
@Override
@PostConstruct
public void start() {
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java
index 3aa0132dfc7..a7877830f8b 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java
@@ -58,8 +58,8 @@ public class SearchParamExtractorR5 extends BaseSearchParamExtractor implements
/**
* Constructor for unit tests
*/
- public SearchParamExtractorR5(FhirContext theCtx, DefaultProfileValidationSupport theDefaultProfileValidationSupport, ISearchParamRegistry theSearchParamRegistry, ModelConfig theModelConfig, PartitionSettings thePartitionSettings) {
- super(theCtx, theSearchParamRegistry, theModelConfig, thePartitionSettings);
+ public SearchParamExtractorR5(ModelConfig theModelConfig, PartitionSettings thePartitionSettings, FhirContext theCtx, DefaultProfileValidationSupport theDefaultProfileValidationSupport, ISearchParamRegistry theSearchParamRegistry) {
+ super(theModelConfig, thePartitionSettings, theCtx, theSearchParamRegistry);
initFhirPath(theDefaultProfileValidationSupport);
start();
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/IndexStressTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/IndexStressTest.java
index d59aa3f645f..70d281668f8 100644
--- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/IndexStressTest.java
+++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/IndexStressTest.java
@@ -45,7 +45,7 @@ public class IndexStressTest {
when(mockValidationSupport.getFhirContext()).thenReturn(ctx);
IValidationSupport validationSupport = new CachingValidationSupport(new ValidationSupportChain(new DefaultProfileValidationSupport(ctx), mockValidationSupport));
ISearchParamRegistry searchParamRegistry = mock(ISearchParamRegistry.class);
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ctx, validationSupport, searchParamRegistry, new PartitionSettings());
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ctx, validationSupport, searchParamRegistry);
extractor.start();
Map spMap = ctx
diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java
index eba841d870a..92e4c8e642c 100644
--- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java
+++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java
@@ -58,8 +58,7 @@ public class SearchParamExtractorDstu3Test {
ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry, new PartitionSettings());
- extractor.setPartitionConfigForUnitTest(new PartitionSettings());
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, searchParamRegistry);
extractor.start();
Set tokens = extractor.extractSearchParamTokens(obs);
assertEquals(1, tokens.size());
@@ -82,7 +81,7 @@ public class SearchParamExtractorDstu3Test {
ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry, new PartitionSettings());
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, searchParamRegistry);
extractor.start();
Set params = extractor.extractSearchParamStrings(questionnaire);
assertEquals(1, params.size());
@@ -100,7 +99,7 @@ public class SearchParamExtractorDstu3Test {
ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry, new PartitionSettings());
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, searchParamRegistry);
extractor.start();
Set params = extractor.extractSearchParamNumber(enc);
assertEquals(1, params.size());
@@ -118,7 +117,7 @@ public class SearchParamExtractorDstu3Test {
ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry, new PartitionSettings());
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, searchParamRegistry);
extractor.start();
Set params = extractor.extractSearchParamNumber(enc);
assertEquals(1, params.size());
@@ -130,7 +129,7 @@ public class SearchParamExtractorDstu3Test {
public void testEmptyPath() {
MySearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry, new PartitionSettings());
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, searchParamRegistry);
extractor.start();
searchParamRegistry.addSearchParam(new RuntimeSearchParam("foo", "foo", "", RestSearchParameterTypeEnum.STRING, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE));
@@ -146,7 +145,7 @@ public class SearchParamExtractorDstu3Test {
public void testStringMissingResourceType() {
MySearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry, new PartitionSettings());
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, searchParamRegistry);
extractor.start();
searchParamRegistry.addSearchParam(new RuntimeSearchParam("foo", "foo", "communication.language.coding.system | communication.language.coding.code", RestSearchParameterTypeEnum.STRING, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE));
@@ -163,8 +162,7 @@ public class SearchParamExtractorDstu3Test {
public void testInvalidType() {
MySearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry, new PartitionSettings());
- extractor.setPartitionConfigForUnitTest(new PartitionSettings());
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, searchParamRegistry);
extractor.start();
{
@@ -215,7 +213,7 @@ public class SearchParamExtractorDstu3Test {
ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry, new PartitionSettings());
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, searchParamRegistry);
extractor.start();
ISearchParamExtractor.SearchParamSet coords = extractor.extractSearchParamTokens(loc);
assertEquals(1, coords.size());
diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java
index 77f6d57db05..58124e39de6 100644
--- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java
+++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java
@@ -1,11 +1,26 @@
package ca.uhn.fhir.jpa.searchparam.extractor;
-import ca.uhn.fhir.context.*;
+import ca.uhn.fhir.context.BaseRuntimeChildDatatypeDefinition;
+import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
+import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
+import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.RuntimeChildChoiceDefinition;
+import ca.uhn.fhir.context.RuntimeChildContainedResources;
+import ca.uhn.fhir.context.RuntimeChildDirectResource;
+import ca.uhn.fhir.context.RuntimeChildExtension;
+import ca.uhn.fhir.context.RuntimeChildResourceBlockDefinition;
+import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
+import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition;
+import ca.uhn.fhir.context.RuntimePrimitiveDatatypeNarrativeDefinition;
+import ca.uhn.fhir.context.RuntimePrimitiveDatatypeXhtmlHl7OrgDefinition;
+import ca.uhn.fhir.context.RuntimeResourceDefinition;
+import ca.uhn.fhir.context.RuntimeSearchParam;
+import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
-import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseEnumeration;
import org.hl7.fhir.instance.model.api.IBaseResource;
@@ -30,29 +45,30 @@ public class SearchParamExtractorMegaTest {
/**
* This test is my magnum opus :P
- *
+ *
- * Hooks should return an instance of ca.uhn.fhir.jpa.api.model.RequestPartitionId or null.
+ * Hooks must return an instance of ca.uhn.fhir.interceptor.model.RequestPartitionId.
*
- * Hooks should return an instance of ca.uhn.fhir.jpa.api.model.RequestPartitionId or null.
+ * Hooks must return an instance of ca.uhn.fhir.interceptor.model.RequestPartitionId.
*
*/
STORAGE_PARTITION_IDENTIFY_READ(
@@ -1451,6 +1451,49 @@ public enum Pointcut {
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
),
+ /**
+ * Storage Hook:
+ * Invoked before any partition aware FHIR operation, when the selected partition has been identified (ie. after the
+ * {@link #STORAGE_PARTITION_IDENTIFY_CREATE} or {@link #STORAGE_PARTITION_IDENTIFY_READ} hook was called. This allows
+ * a separate hook to register, and potentially make decisions about whether the request should be allowed to proceed.
+ *
+ * This hook will only be called if
+ * partitioning is enabled in the JPA server.
+ *
+ *
+ * Hooks may accept the following parameters:
+ *
+ *
+ *
+ * ca.uhn.fhir.interceptor.model.RequestPartitionId - The partition ID that was selected
+ *
+ *
+ * ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the
+ * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
+ * pulled out of the servlet request. Note that the bean
+ * properties are not all guaranteed to be populated, depending on how early during processing the
+ * exception occurred.
+ *
+ *
+ * ca.uhn.fhir.rest.server.servlet.ServletRequestDetails - A bean containing details about the request that is about to be processed, including details such as the
+ * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
+ * pulled out of the servlet request. This parameter is identical to the RequestDetails parameter above but will
+ * only be populated when operating in a RestfulServer implementation. It is provided as a convenience.
+ *
+ *
+ *
+ * Hooks must return void.
+ *
+ */
+ STORAGE_PARTITION_SELECTED(
+ // Return type
+ void.class,
+ // Params
+ "ca.uhn.fhir.interceptor.model.RequestPartitionId",
+ "ca.uhn.fhir.rest.api.server.RequestDetails",
+ "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
+ ),
+
/**
* Performance Tracing Hook:
* This hook is invoked when any informational messages generated by the
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/model/RequestPartitionId.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/model/RequestPartitionId.java
index c1d0f8291c1..d66e74208d5 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/model/RequestPartitionId.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/model/RequestPartitionId.java
@@ -20,27 +20,55 @@ package ca.uhn.fhir.interceptor.model;
* #L%
*/
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.time.LocalDate;
-import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
-
+/**
+ * @since 5.0.0
+ */
public class RequestPartitionId {
- private final Integer myPartitionId;
+ private static final RequestPartitionId ALL_PARTITIONS = new RequestPartitionId();
private final LocalDate myPartitionDate;
+ private final boolean myAllPartitions;
+ private final Integer myPartitionId;
private final String myPartitionName;
/**
- * Constructor
+ * Constructor for a single partition
*/
private RequestPartitionId(@Nullable String thePartitionName, @Nullable Integer thePartitionId, @Nullable LocalDate thePartitionDate) {
- myPartitionName = thePartitionName;
myPartitionId = thePartitionId;
+ myPartitionName = thePartitionName;
myPartitionDate = thePartitionDate;
+ myAllPartitions = false;
}
+ /**
+ * Constructor for all partitions
+ */
+ private RequestPartitionId() {
+ super();
+ myPartitionDate = null;
+ myPartitionName = null;
+ myPartitionId = null;
+ myAllPartitions = true;
+ }
+
+ public boolean isAllPartitions() {
+ return myAllPartitions;
+ }
+
+ @Nullable
+ public LocalDate getPartitionDate() {
+ return myPartitionDate;
+ }
+
+ @Nullable
public String getPartitionName() {
return myPartitionName;
}
@@ -50,32 +78,59 @@ public class RequestPartitionId {
return myPartitionId;
}
- @Nullable
- public LocalDate getPartitionDate() {
- return myPartitionDate;
- }
-
@Override
public String toString() {
- return getPartitionIdStringOrNullString();
+ return "RequestPartitionId[id=" + getPartitionId() + ", name=" + getPartitionName() + "]";
}
/**
* Returns the partition ID (numeric) as a string, or the string "null"
*/
public String getPartitionIdStringOrNullString() {
- return defaultIfNull(myPartitionId, "null").toString();
+ if (myPartitionId == null) {
+ return "null";
+ }
+ return myPartitionId.toString();
}
- /**
- * Create a string representation suitable for use as a cache key. Null aware.
- */
- public static String stringifyForKey(RequestPartitionId theRequestPartitionId) {
- String retVal = "(null)";
- if (theRequestPartitionId != null) {
- retVal = theRequestPartitionId.getPartitionIdStringOrNullString();
+ @Override
+ public boolean equals(Object theO) {
+ if (this == theO) {
+ return true;
}
- return retVal;
+
+ if (theO == null || getClass() != theO.getClass()) {
+ return false;
+ }
+
+ RequestPartitionId that = (RequestPartitionId) theO;
+
+ return new EqualsBuilder()
+ .append(myAllPartitions, that.myAllPartitions)
+ .append(myPartitionDate, that.myPartitionDate)
+ .append(myPartitionId, that.myPartitionId)
+ .append(myPartitionName, that.myPartitionName)
+ .isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(17, 37)
+ .append(myPartitionDate)
+ .append(myAllPartitions)
+ .append(myPartitionId)
+ .append(myPartitionName)
+ .toHashCode();
+ }
+
+ @Nonnull
+ public static RequestPartitionId allPartitions() {
+ return ALL_PARTITIONS;
+ }
+
+ @Nonnull
+ public static RequestPartitionId defaultPartition() {
+ return fromPartitionId(null);
}
@Nonnull
@@ -99,8 +154,23 @@ public class RequestPartitionId {
}
@Nonnull
- public static RequestPartitionId forPartitionNameAndId(@Nullable String thePartitionName, @Nullable Integer thePartitionId, @Nullable LocalDate thePartitionDate) {
+ public static RequestPartitionId fromPartitionIdAndName(@Nullable Integer thePartitionId, @Nullable String thePartitionName) {
+ return new RequestPartitionId(thePartitionName, thePartitionId, null);
+ }
+
+ @Nonnull
+ public static RequestPartitionId forPartitionIdAndName(@Nullable Integer thePartitionId, @Nullable String thePartitionName, @Nullable LocalDate thePartitionDate) {
return new RequestPartitionId(thePartitionName, thePartitionId, thePartitionDate);
}
+ /**
+ * Create a string representation suitable for use as a cache key. Null aware.
+ */
+ public static String stringifyForKey(RequestPartitionId theRequestPartitionId) {
+ String retVal = "(null)";
+ if (theRequestPartitionId != null) {
+ retVal = theRequestPartitionId.getPartitionIdStringOrNullString();
+ }
+ return retVal;
+ }
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
index 9628034b3f9..b8d66cac6ce 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
@@ -264,6 +264,13 @@ public class Constants {
public static final String PARAM_FHIRPATH = "_fhirpath";
public static final String PARAM_TYPE = "_type";
+ /**
+ * {@link org.hl7.fhir.instance.model.api.IBaseResource#getUserData(String) User metadata key} used
+ * to store the partition ID (if any) associated with the given resource. Value for this
+ * key will be of type {@link ca.uhn.fhir.interceptor.model.RequestPartitionId}.
+ */
+ public static final String RESOURCE_PARTITION_ID = Constants.class.getName() + "_RESOURCE_PARTITION_ID";
+
static {
CHARSET_UTF8 = StandardCharsets.UTF_8;
CHARSET_US_ASCII = StandardCharsets.ISO_8859_1;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/BaseHttpRequest.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/BaseHttpRequest.java
new file mode 100644
index 00000000000..2ced28af0fe
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/BaseHttpRequest.java
@@ -0,0 +1,37 @@
+package ca.uhn.fhir.rest.client.api;
+
+/*-
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2020 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+public abstract class BaseHttpRequest implements IHttpRequest {
+
+ private UrlSourceEnum myUrlSource;
+
+ @Override
+ public UrlSourceEnum getUrlSource() {
+ return myUrlSource;
+ }
+
+ @Override
+ public void setUrlSource(UrlSourceEnum theUrlSource) {
+ myUrlSource = theUrlSource;
+ }
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpRequest.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpRequest.java
index 62389f6c5b3..a2c7445849f 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpRequest.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpRequest.java
@@ -60,11 +60,15 @@ public interface IHttpRequest {
/**
* Return the request URI, or null
+ *
+ * @see #getUri()
*/
String getUri();
/**
* Modify the request URI, or null
+ *
+ * @see #setUrlSource(UrlSourceEnum)
*/
void setUri(String theUrl);
@@ -79,4 +83,19 @@ public interface IHttpRequest {
* @param theHeaderName The header name, e.g. "Accept" (must not be null or blank)
*/
void removeHeaders(String theHeaderName);
+
+ /**
+ * Where was the URL from?
+ *
+ * @since 5.0.0
+ */
+ UrlSourceEnum getUrlSource();
+
+ /**
+ * Where was the URL from?
+ *
+ * @since 5.0.0
+ */
+ void setUrlSource(UrlSourceEnum theUrlSource);
+
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/UrlSourceEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/UrlSourceEnum.java
new file mode 100644
index 00000000000..26230194c0a
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/UrlSourceEnum.java
@@ -0,0 +1,35 @@
+package ca.uhn.fhir.rest.client.api;
+
+/*-
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2020 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+public enum UrlSourceEnum {
+
+ /**
+ * URL was generated (typically by adding the base URL + other things)
+ */
+ GENERATED,
+
+ /**
+ * URL was supplied (i.e. it came from a paging link in a bundle)
+ */
+ EXPLICIT
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java
index 609a49ebd06..89cb4343942 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java
@@ -9,9 +9,16 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.DateUtils;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.TimeZone;
-import static ca.uhn.fhir.rest.param.ParamPrefixEnum.*;
+import static ca.uhn.fhir.rest.param.ParamPrefixEnum.EQUAL;
+import static ca.uhn.fhir.rest.param.ParamPrefixEnum.GREATERTHAN_OR_EQUALS;
+import static ca.uhn.fhir.rest.param.ParamPrefixEnum.LESSTHAN_OR_EQUALS;
import static java.lang.String.format;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/DateUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/DateUtils.java
index d208e3fe599..58b83b7b630 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/DateUtils.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/DateUtils.java
@@ -23,7 +23,12 @@ package ca.uhn.fhir.util;
import java.lang.ref.SoftReference;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
-import java.util.*;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
/**
* A utility class for parsing and formatting HTTP dates as used in cookies and
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/MetaUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/MetaUtil.java
index cd6c9384906..775c274e929 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/MetaUtil.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/MetaUtil.java
@@ -96,7 +96,7 @@ public class MetaUtil {
value.setValue(theValue);
sourceExtension.setValue(value);
} else {
- ourLog.error(MetaUtil.class.getSimpleName() + ".setSource() not supported on FHIR Version " + theContext.getVersion().getVersion());
+ ourLog.debug(MetaUtil.class.getSimpleName() + ".setSource() not supported on FHIR Version " + theContext.getVersion().getVersion());
}
}
diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IPrimitiveType.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IPrimitiveType.java
index 089aba760e7..8a782d961ec 100644
--- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IPrimitiveType.java
+++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IPrimitiveType.java
@@ -22,16 +22,26 @@ package org.hl7.fhir.instance.model.api;
*/
-public interface IPrimitiveType extends IBaseDatatype {
+import javax.annotation.Nullable;
- void setValueAsString(String theValue) throws IllegalArgumentException;
+public interface IPrimitiveType extends IBaseDatatype {
String getValueAsString();
+ void setValueAsString(String theValue) throws IllegalArgumentException;
+
T getValue();
- boolean hasValue();
-
IPrimitiveType setValue(T theValue) throws IllegalArgumentException;
-
+
+ boolean hasValue();
+
+ /**
+ * If the supplied argument is non-null, returns the results of {@link #getValue()}. If the supplied argument is null, returns null.
+ */
+ @Nullable
+ static T toValueOrNull(@Nullable IPrimitiveType thePrimitiveType) {
+ return thePrimitiveType != null ? thePrimitiveType.getValue() : null;
+ }
+
}
diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
index 9a1b0c23eb4..ac017230a12 100644
--- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
+++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
@@ -83,7 +83,6 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionContainsMultipleWithDuplica
ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionEntryHasInvalidVerb=Transaction bundle entry has missing or invalid HTTP Verb specified in Bundle.entry({1}).request.method. Found value: "{0}"
ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionMissingUrl=Unable to perform {0}, no URL provided.
ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionInvalidUrl=Unable to perform {0}, URL provided is invalid: {1}
-ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.noSystemOrTypeHistoryForPartitionAwareServer=Type- and Server- level history operation not supported on partitioned server
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.cantValidateWithNoResource=No resource supplied for $validate operation (resource is required unless mode is \"delete\")
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.deleteBlockedBecauseDisabled=Resource deletion is not permitted on this server
@@ -144,9 +143,9 @@ ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply
ca.uhn.fhir.jpa.graphql.JpaStorageServices.invalidGraphqlArgument=Unknown GraphQL argument "{0}". Value GraphQL argument for this type are: {1}
-ca.uhn.fhir.jpa.partition.RequestPartitionHelperService.blacklistedResourceTypeForPartitioning=Resource type {0} can not be partitioned
-ca.uhn.fhir.jpa.partition.RequestPartitionHelperService.unknownPartitionId=Unknown partition ID: {0}
-ca.uhn.fhir.jpa.partition.RequestPartitionHelperService.unknownPartitionName=Unknown partition name: {0}
+ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc.blacklistedResourceTypeForPartitioning=Resource type {0} can not be partitioned
+ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc.unknownPartitionId=Unknown partition ID: {0}
+ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc.unknownPartitionName=Unknown partition name: {0}
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidTargetTypeForChain=Resource type "{0}" is not a valid target type for reference search parameter: {1}
@@ -154,6 +153,7 @@ ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidResourceType=Inva
ca.uhn.fhir.jpa.dao.index.IdHelperService.nonUniqueForcedId=Non-unique ID specified, can not process request
+ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.noIdSupplied=No Partition ID supplied
ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.missingPartitionIdOrName=Partition must have an ID and a Name
ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.cantCreatePartition0=Can not create a partition with ID 0 (this is a reserved value)
ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.unknownPartitionId=No partition exists with ID {0}
@@ -163,3 +163,5 @@ ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.cantDeleteDefaultPartition=Can
ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.cantRenameDefaultPartition=Can not rename default partition
ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor.unknownTenantName=Unknown tenant: {0}
+
+ca.uhn.fhir.jpa.dao.HistoryBuilder.noSystemOrTypeHistoryForPartitionAwareServer=Type- and Server- level history operation not supported across partitions on partitioned server
diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/interceptor/model/RequestPartitionIdTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/interceptor/model/RequestPartitionIdTest.java
new file mode 100644
index 00000000000..4664c991f11
--- /dev/null
+++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/interceptor/model/RequestPartitionIdTest.java
@@ -0,0 +1,24 @@
+package ca.uhn.fhir.interceptor.model;
+
+import org.junit.Test;
+
+import java.time.LocalDate;
+
+import static org.junit.Assert.*;
+
+public class RequestPartitionIdTest {
+
+ @Test
+ public void testHashCode() {
+ assertEquals(31860737, RequestPartitionId.allPartitions().hashCode());
+ }
+
+ @Test
+ public void testEquals() {
+ assertEquals(RequestPartitionId.fromPartitionId(123, LocalDate.of(2020,1,1)), RequestPartitionId.fromPartitionId(123, LocalDate.of(2020,1,1)));
+ assertNotEquals(RequestPartitionId.fromPartitionId(123, LocalDate.of(2020,1,1)), null);
+ assertNotEquals(RequestPartitionId.fromPartitionId(123, LocalDate.of(2020,1,1)), "123");
+ }
+
+
+}
diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on-debug.xml b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on-debug.xml
index fa16cfb7451..d5fa11fe0f9 100644
--- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on-debug.xml
+++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on-debug.xml
@@ -16,6 +16,12 @@
utf-8
+
+
+
+
+
+
diff --git a/hapi-fhir-client-okhttp/src/main/java/ca/uhn/fhir/okhttp/client/OkHttpRestfulRequest.java b/hapi-fhir-client-okhttp/src/main/java/ca/uhn/fhir/okhttp/client/OkHttpRestfulRequest.java
index ce2c7e7d347..9f8a393e0e1 100644
--- a/hapi-fhir-client-okhttp/src/main/java/ca/uhn/fhir/okhttp/client/OkHttpRestfulRequest.java
+++ b/hapi-fhir-client-okhttp/src/main/java/ca/uhn/fhir/okhttp/client/OkHttpRestfulRequest.java
@@ -26,6 +26,7 @@ import java.util.Map;
*/
import ca.uhn.fhir.rest.api.RequestTypeEnum;
+import ca.uhn.fhir.rest.client.api.BaseHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.util.StopWatch;
@@ -39,7 +40,7 @@ import okhttp3.RequestBody;
*
* @author Matthew Clarke | matthew.clarke@orionhealth.com | Orion Health
*/
-public class OkHttpRestfulRequest implements IHttpRequest {
+public class OkHttpRestfulRequest extends BaseHttpRequest implements IHttpRequest {
private final Request.Builder myRequestBuilder;
private Factory myClient;
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpRequest.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpRequest.java
index 5f9a2334a27..63ea8dee384 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpRequest.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpRequest.java
@@ -20,6 +20,7 @@ package ca.uhn.fhir.rest.client.apache;
* #L%
*/
+import ca.uhn.fhir.rest.client.api.BaseHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.util.StopWatch;
@@ -36,7 +37,11 @@ import org.apache.http.entity.ContentType;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.Charset;
-import java.util.*;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
/**
* A Http Request based on Apache. This is an adapter around the class
@@ -44,7 +49,7 @@ import java.util.*;
*
* @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
*/
-public class ApacheHttpRequest implements IHttpRequest {
+public class ApacheHttpRequest extends BaseHttpRequest implements IHttpRequest {
private HttpClient myClient;
private HttpRequestBase myRequest;
@@ -112,13 +117,13 @@ public class ApacheHttpRequest implements IHttpRequest {
}
@Override
- public void setUri(String theUrl) {
- myRequest.setURI(URI.create(theUrl));
+ public String getUri() {
+ return myRequest.getURI().toString();
}
@Override
- public String getUri() {
- return myRequest.getURI().toString();
+ public void setUri(String theUrl) {
+ myRequest.setURI(URI.create(theUrl));
}
@Override
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java
index 45918f97c00..63e92b431a0 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java
@@ -20,7 +20,13 @@ package ca.uhn.fhir.rest.client.impl;
* #L%
*/
-import ca.uhn.fhir.context.*;
+import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
+import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
+import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.context.IRuntimeDatatypeDefinition;
+import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
@@ -30,16 +36,92 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
-import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
-import ca.uhn.fhir.rest.api.*;
+import ca.uhn.fhir.rest.api.CacheControlDirective;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.DeleteCascadeModeEnum;
+import ca.uhn.fhir.rest.api.EncodingEnum;
+import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.PatchTypeEnum;
+import ca.uhn.fhir.rest.api.PreferReturnEnum;
+import ca.uhn.fhir.rest.api.SearchStyleEnum;
+import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
+import ca.uhn.fhir.rest.api.SortOrderEnum;
+import ca.uhn.fhir.rest.api.SortSpec;
+import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.IHttpClient;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
+import ca.uhn.fhir.rest.client.api.UrlSourceEnum;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
-import ca.uhn.fhir.rest.client.method.*;
-import ca.uhn.fhir.rest.gclient.*;
+import ca.uhn.fhir.rest.client.method.DeleteMethodBinding;
+import ca.uhn.fhir.rest.client.method.HistoryMethodBinding;
+import ca.uhn.fhir.rest.client.method.HttpDeleteClientInvocation;
+import ca.uhn.fhir.rest.client.method.HttpGetClientInvocation;
+import ca.uhn.fhir.rest.client.method.HttpSimpleGetClientInvocation;
+import ca.uhn.fhir.rest.client.method.IClientResponseHandler;
+import ca.uhn.fhir.rest.client.method.MethodUtil;
+import ca.uhn.fhir.rest.client.method.OperationMethodBinding;
+import ca.uhn.fhir.rest.client.method.ReadMethodBinding;
+import ca.uhn.fhir.rest.client.method.SearchMethodBinding;
+import ca.uhn.fhir.rest.client.method.SortParameter;
+import ca.uhn.fhir.rest.client.method.TransactionMethodBinding;
+import ca.uhn.fhir.rest.client.method.ValidateMethodBindingDstu2Plus;
+import ca.uhn.fhir.rest.gclient.IBaseQuery;
+import ca.uhn.fhir.rest.gclient.IClientExecutable;
+import ca.uhn.fhir.rest.gclient.ICreate;
+import ca.uhn.fhir.rest.gclient.ICreateTyped;
+import ca.uhn.fhir.rest.gclient.ICreateWithQuery;
+import ca.uhn.fhir.rest.gclient.ICreateWithQueryTyped;
+import ca.uhn.fhir.rest.gclient.ICriterion;
+import ca.uhn.fhir.rest.gclient.ICriterionInternal;
+import ca.uhn.fhir.rest.gclient.IDelete;
+import ca.uhn.fhir.rest.gclient.IDeleteTyped;
+import ca.uhn.fhir.rest.gclient.IDeleteWithQuery;
+import ca.uhn.fhir.rest.gclient.IDeleteWithQueryTyped;
+import ca.uhn.fhir.rest.gclient.IFetchConformanceTyped;
+import ca.uhn.fhir.rest.gclient.IFetchConformanceUntyped;
+import ca.uhn.fhir.rest.gclient.IGetPage;
+import ca.uhn.fhir.rest.gclient.IGetPageTyped;
+import ca.uhn.fhir.rest.gclient.IGetPageUntyped;
+import ca.uhn.fhir.rest.gclient.IHistory;
+import ca.uhn.fhir.rest.gclient.IHistoryTyped;
+import ca.uhn.fhir.rest.gclient.IHistoryUntyped;
+import ca.uhn.fhir.rest.gclient.IMeta;
+import ca.uhn.fhir.rest.gclient.IMetaAddOrDeleteSourced;
+import ca.uhn.fhir.rest.gclient.IMetaAddOrDeleteUnsourced;
+import ca.uhn.fhir.rest.gclient.IMetaGetUnsourced;
+import ca.uhn.fhir.rest.gclient.IOperation;
+import ca.uhn.fhir.rest.gclient.IOperationProcessMsg;
+import ca.uhn.fhir.rest.gclient.IOperationProcessMsgMode;
+import ca.uhn.fhir.rest.gclient.IOperationUnnamed;
+import ca.uhn.fhir.rest.gclient.IOperationUntyped;
+import ca.uhn.fhir.rest.gclient.IOperationUntypedWithInput;
+import ca.uhn.fhir.rest.gclient.IOperationUntypedWithInputAndPartialOutput;
+import ca.uhn.fhir.rest.gclient.IParam;
+import ca.uhn.fhir.rest.gclient.IPatch;
+import ca.uhn.fhir.rest.gclient.IPatchExecutable;
+import ca.uhn.fhir.rest.gclient.IPatchWithBody;
+import ca.uhn.fhir.rest.gclient.IPatchWithQuery;
+import ca.uhn.fhir.rest.gclient.IPatchWithQueryTyped;
+import ca.uhn.fhir.rest.gclient.IQuery;
+import ca.uhn.fhir.rest.gclient.IRead;
+import ca.uhn.fhir.rest.gclient.IReadExecutable;
+import ca.uhn.fhir.rest.gclient.IReadIfNoneMatch;
+import ca.uhn.fhir.rest.gclient.IReadTyped;
+import ca.uhn.fhir.rest.gclient.ISort;
+import ca.uhn.fhir.rest.gclient.ITransaction;
+import ca.uhn.fhir.rest.gclient.ITransactionTyped;
+import ca.uhn.fhir.rest.gclient.IUntypedQuery;
+import ca.uhn.fhir.rest.gclient.IUpdate;
+import ca.uhn.fhir.rest.gclient.IUpdateExecutable;
+import ca.uhn.fhir.rest.gclient.IUpdateTyped;
+import ca.uhn.fhir.rest.gclient.IUpdateWithQuery;
+import ca.uhn.fhir.rest.gclient.IUpdateWithQueryTyped;
+import ca.uhn.fhir.rest.gclient.IValidate;
+import ca.uhn.fhir.rest.gclient.IValidateUntyped;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.TokenParam;
@@ -53,14 +135,38 @@ import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
-import org.hl7.fhir.instance.model.api.*;
+import org.hl7.fhir.instance.model.api.IAnyResource;
+import org.hl7.fhir.instance.model.api.IBase;
+import org.hl7.fhir.instance.model.api.IBaseBundle;
+import org.hl7.fhir.instance.model.api.IBaseConformance;
+import org.hl7.fhir.instance.model.api.IBaseDatatype;
+import org.hl7.fhir.instance.model.api.IBaseMetaType;
+import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
+import org.hl7.fhir.instance.model.api.IBaseParameters;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.io.IOException;
import java.io.InputStream;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
-import static org.apache.commons.lang3.StringUtils.*;
+import static org.apache.commons.lang3.StringUtils.defaultString;
+import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* @author James Agnew
@@ -749,6 +855,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
IClientResponseHandler binding;
binding = new ResourceResponseHandler(myBundleType, getPreferResponseTypes());
HttpSimpleGetClientInvocation invocation = new HttpSimpleGetClientInvocation(myContext, myUrl);
+ invocation.setUrlSource(UrlSourceEnum.EXPLICIT);
Map> params = null;
return invoke(params, binding, invocation);
@@ -1838,7 +1945,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
BaseHttpClientInvocation invocation;
if (mySearchUrl != null) {
- invocation = SearchMethodBinding.createSearchInvocation(myContext, mySearchUrl, params);
+ invocation = SearchMethodBinding.createSearchInvocation(myContext, mySearchUrl, UrlSourceEnum.EXPLICIT, params);
} else {
invocation = SearchMethodBinding.createSearchInvocation(myContext, myResourceName, params, resourceId, myCompartmentName, mySearchStyle);
}
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/UrlTenantSelectionInterceptor.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/UrlTenantSelectionInterceptor.java
index 26e0bf5c36c..90d419a552d 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/UrlTenantSelectionInterceptor.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/UrlTenantSelectionInterceptor.java
@@ -24,6 +24,7 @@ import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
+import ca.uhn.fhir.rest.client.api.UrlSourceEnum;
import org.apache.commons.lang3.Validate;
import static org.apache.commons.lang3.StringUtils.isBlank;
@@ -81,6 +82,10 @@ public class UrlTenantSelectionInterceptor {
Validate.isTrue(requestUri.startsWith(serverBase), "Request URI %s does not start with server base %s", requestUri, serverBase);
+ if (theRequest.getUrlSource() == UrlSourceEnum.EXPLICIT) {
+ return;
+ }
+
String newUri = serverBase + "/" + tenantId + requestUri.substring(serverBase.length());
theRequest.setUri(newUri);
}
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpGetClientInvocation.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpGetClientInvocation.java
index 5e3a5b71114..eddf2375ffc 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpGetClientInvocation.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpGetClientInvocation.java
@@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
+import ca.uhn.fhir.rest.client.api.UrlSourceEnum;
import ca.uhn.fhir.rest.client.impl.BaseHttpClientInvocation;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.lang3.StringUtils;
@@ -41,17 +42,24 @@ public class HttpGetClientInvocation extends BaseHttpClientInvocation {
private final Map> myParameters;
private final String myUrlPath;
+ private final UrlSourceEnum myUrlSource;
public HttpGetClientInvocation(FhirContext theContext, Map> theParameters, String... theUrlFragments) {
+ this(theContext, theParameters, UrlSourceEnum.GENERATED, theUrlFragments);
+ }
+
+ public HttpGetClientInvocation(FhirContext theContext, Map> theParameters, UrlSourceEnum theUrlSource, String... theUrlFragments) {
super(theContext);
myParameters = theParameters;
myUrlPath = StringUtils.join(theUrlFragments, '/');
+ myUrlSource = theUrlSource;
}
public HttpGetClientInvocation(FhirContext theContext, String theUrlPath) {
super(theContext);
myParameters = new HashMap<>();
myUrlPath = theUrlPath;
+ myUrlSource = UrlSourceEnum.GENERATED;
}
@@ -95,7 +103,10 @@ public class HttpGetClientInvocation extends BaseHttpClientInvocation {
appendExtraParamsWithQuestionMark(theExtraParams, b, first);
- return super.createHttpRequest(b.toString(), theEncoding, RequestTypeEnum.GET);
+ IHttpRequest retVal = super.createHttpRequest(b.toString(), theEncoding, RequestTypeEnum.GET);
+ retVal.setUrlSource(myUrlSource);
+
+ return retVal;
}
public Map> getParameters() {
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpSimpleGetClientInvocation.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpSimpleGetClientInvocation.java
index 2908f612ded..6dde247b5fc 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpSimpleGetClientInvocation.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpSimpleGetClientInvocation.java
@@ -27,11 +27,13 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
+import ca.uhn.fhir.rest.client.api.UrlSourceEnum;
import ca.uhn.fhir.rest.client.impl.BaseHttpClientInvocation;
public class HttpSimpleGetClientInvocation extends BaseHttpClientInvocation {
private final String myUrl;
+ private UrlSourceEnum myUrlSource = UrlSourceEnum.GENERATED;
public HttpSimpleGetClientInvocation(FhirContext theContext, String theUrlPath) {
super(theContext);
@@ -40,7 +42,12 @@ public class HttpSimpleGetClientInvocation extends BaseHttpClientInvocation {
@Override
public IHttpRequest asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) {
- return createHttpRequest(myUrl, theEncoding, RequestTypeEnum.GET);
+ IHttpRequest retVal = createHttpRequest(myUrl, theEncoding, RequestTypeEnum.GET);
+ retVal.setUrlSource(myUrlSource);
+ return retVal;
}
+ public void setUrlSource(UrlSourceEnum theUrlSource) {
+ myUrlSource = theUrlSource;
+ }
}
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/SearchMethodBinding.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/SearchMethodBinding.java
index 2649d487655..4194493df37 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/SearchMethodBinding.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/SearchMethodBinding.java
@@ -19,27 +19,33 @@ package ca.uhn.fhir.rest.client.method;
* limitations under the License.
* #L%
*/
-import static org.apache.commons.lang3.StringUtils.isBlank;
-import static org.apache.commons.lang3.StringUtils.isNotBlank;
-
-import java.lang.reflect.Method;
-import java.util.*;
-import java.util.Map.Entry;
-
-import org.apache.commons.lang3.StringUtils;
-import org.hl7.fhir.instance.model.api.IBaseResource;
-import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.Search;
-import ca.uhn.fhir.rest.api.*;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
+import ca.uhn.fhir.rest.api.SearchStyleEnum;
+import ca.uhn.fhir.rest.client.api.UrlSourceEnum;
import ca.uhn.fhir.rest.client.impl.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import org.apache.commons.lang3.StringUtils;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
private String myCompartmentName;
@@ -157,8 +163,8 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
return getMethod().toString();
}
- public static BaseHttpClientInvocation createSearchInvocation(FhirContext theContext, String theSearchUrl, Map> theParams) {
- return new HttpGetClientInvocation(theContext, theParams, theSearchUrl);
+ public static BaseHttpClientInvocation createSearchInvocation(FhirContext theContext, String theSearchUrl, UrlSourceEnum theUrlSource, Map> theParams) {
+ return new HttpGetClientInvocation(theContext, theParams, theUrlSource, theSearchUrl);
}
diff --git a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ClientExamples.java b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ClientExamples.java
index 15a2def45ba..e8a7a512b3a 100644
--- a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ClientExamples.java
+++ b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ClientExamples.java
@@ -34,7 +34,8 @@ import ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor;
import ca.uhn.fhir.rest.client.interceptor.BearerTokenAuthInterceptor;
import ca.uhn.fhir.rest.client.interceptor.CookieInterceptor;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
-import org.hl7.fhir.r4.model.*;
+import ca.uhn.fhir.rest.client.interceptor.UrlTenantSelectionInterceptor;
+import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Patient;
public class ClientExamples {
@@ -60,6 +61,29 @@ public class ClientExamples {
// END SNIPPET: proxy
}
+
+ public void tenantId() {
+ // START SNIPPET: tenantId
+ FhirContext ctx = FhirContext.forR4();
+
+ // Create the client
+ IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir");
+
+ // Register the interceptor
+ UrlTenantSelectionInterceptor tenantSelection = new UrlTenantSelectionInterceptor();
+ genericClient.registerInterceptor(tenantSelection);
+
+ // Read from tenant A
+ tenantSelection.setTenantId("TENANT-A");
+ Patient patientA = genericClient.read().resource(Patient.class).withId("123").execute();
+
+ // Read from tenant B
+ tenantSelection.setTenantId("TENANT-B");
+ Patient patientB = genericClient.read().resource(Patient.class).withId("456").execute();
+ // END SNIPPET: tenantId
+ }
+
+
@SuppressWarnings("unused")
public void processMessage() {
// START SNIPPET: processMessage
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1813-reduce-history-sql-operations.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1813-reduce-history-sql-operations.yaml
new file mode 100644
index 00000000000..2a2cf44ad88
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1813-reduce-history-sql-operations.yaml
@@ -0,0 +1,7 @@
+---
+type: perf
+issue: 1813
+title: History operations in the JPA server have been significantly optimized to remove the number of SQL SELECT statements,
+ and to completely eliminate any INSERT statements. This should have a positive effect on heavy users of history
+ operations. In addition, history operations will no longer write an entry in the query cache (HFJ_SEARCH) table which
+ should further improve performance of this operation.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
index 4d12c1b1907..8ee1344115c 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
@@ -58,7 +58,7 @@
issue: "1807"
type: "change"
title: "**New Feature**:
- A new feature has been added to the JPA server called **[Partitioning](/hapi-fhir/docs/server_jpa/partitioning.html). This
+ A new feature has been added to the JPA server called **[Partitioning](/hapi-fhir/docs/server_jpa_partitioning/partitioning.html). This
feature allows data to be segregated using a user defined partitioning strategy. This can be leveraged to take
advantags of native RDBMS partition strategies, and also to implement **multitenant servers**.
"
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/examples.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/examples.md
index f79d97cbfff..e3267ffed28 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/examples.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/examples.md
@@ -2,79 +2,63 @@
This page contains examples of how to use the client to perform complete tasks. If you have an example you could contribute, we'd love to hear from you!
-# Transaction With Placeholder IDs
+# Transaction With Conditional Create
-The following example shows how to post a transaction with two resources, where one resource contains a reference to the other. A temporary ID (a UUID) is used as an ID to refer to, and this ID will be replaced by the server by a permanent ID.
+The following example demonstrates a common scenario: How to create a new piece of data for a Patient (in this case, an Observation) where the identifier of the Patient is known, but the ID is not.
-```java
-{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ClientTransactionExamples.java|conditional}}
-```
+In this scenario, we want to look up the Patient record and reference it from the newly created Observation. In the event that no Patient record already exists with the given identifier, a new one will be created and the Observation will reference it. This is known in FHIR as a [Conditional Create](http://hl7.org/fhir/http.html#ccreate).
-This code creates the following transaction bundle:
-
**JSON**:
```json
{
"resourceType": "Bundle",
"type": "transaction",
- "entry": [
- {
- "fullUrl": "urn:uuid:3bc44de3-069d-442d-829b-f3ef68cae371",
- "resource": {
- "resourceType": "Patient",
- "identifier": [
- {
- "system": "http://acme.org/mrns",
- "value": "12345"
- }
- ],
- "name": [
- {
- "family": "Jameson",
- "given": [
- "J",
- "Jonah"
- ]
- }
- ],
- "gender": "male"
+ "entry": [ {
+ "fullUrl": "urn:uuid:3bc44de3-069d-442d-829b-f3ef68cae371",
+ "resource": {
+ "resourceType": "Patient",
+ "identifier": [ {
+ "system": "http://acme.org/mrns",
+ "value": "12345"
+ } ],
+ "name": [ {
+ "family": "Jameson",
+ "given": [ "J", "Jonah" ]
+ } ],
+ "gender": "male"
+ },
+ "request": {
+ "method": "POST",
+ "url": "Patient",
+ "ifNoneExist": "identifier=http://acme.org/mrns|12345"
+ }
+ }, {
+ "resource": {
+ "resourceType": "Observation",
+ "status": "final",
+ "code": {
+ "coding": [ {
+ "system": "http://loinc.org",
+ "code": "789-8",
+ "display": "Erythrocytes [#/volume] in Blood by Automated count"
+ } ]
},
- "request": {
- "method": "POST",
- "url": "Patient",
- "ifNoneExist": "identifier=http://acme.org/mrns|12345"
+ "subject": {
+ "reference": "urn:uuid:3bc44de3-069d-442d-829b-f3ef68cae371"
+ },
+ "valueQuantity": {
+ "value": 4.12,
+ "unit": "10 trillion/L",
+ "system": "http://unitsofmeasure.org",
+ "code": "10*12/L"
}
},
- {
- "resource": {
- "resourceType": "Observation",
- "status": "final",
- "code": {
- "coding": [
- {
- "system": "http://loinc.org",
- "code": "789-8",
- "display": "Erythrocytes [#/volume] in Blood by Automated count"
- }
- ]
- },
- "subject": {
- "reference": "urn:uuid:3bc44de3-069d-442d-829b-f3ef68cae371"
- },
- "valueQuantity": {
- "value": 4.12,
- "unit": "10 trillion/L",
- "system": "http://unitsofmeasure.org",
- "code": "10*12/L"
- }
- },
- "request": {
- "method": "POST",
- "url": "Observation"
- }
+ "request": {
+ "method": "POST",
+ "url": "Observation"
}
- ]
+ } ]
}
```
@@ -164,6 +148,12 @@ The server responds with the following response. Note that the ID of the already
```
+To produce this transaction in Java code:
+
+```java
+{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ClientTransactionExamples.java|conditional}}
+```
+
# Fetch all Pages of a Bundle
This following example shows how to load all pages of a bundle by fetching each page one-after-the-other and then joining the results.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties
index b766e3fa56f..899241f5e0e 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties
@@ -45,9 +45,14 @@ page.server_jpa.schema=Database Schema
page.server_jpa.configuration=Configuration
page.server_jpa.search=Search
page.server_jpa.performance=Performance
-page.server_jpa.partitioning=Partitioning and Multitenancy
page.server_jpa.upgrading=Upgrade Guide
+section.server_jpa_partitioning.title=JPA Server: Partitioning and Multitenancy
+page.server_jpa_partitioning.partitioning=Partitioning and Multitenancy
+page.server_jpa_partitioning.partitioning_management_operations=Partitioning Management Operations
+page.server_jpa_partitioning.enabling_in_hapi_fhir=Enabling Partitioning in HAPI FHIR
+
+
section.interceptors.title=Interceptors
page.interceptors.interceptors=Interceptors Overview
page.interceptors.client_interceptors=Client Interceptors
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_client_interceptors.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_client_interceptors.md
index 0d83f189397..9df29a8335f 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_client_interceptors.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_client_interceptors.md
@@ -82,6 +82,9 @@ When communicating with a server that supports [URL Base Multitenancy](/docs/ser
* [UrlTenantSelectionInterceptor JavaDoc](/apidocs/hapi-fhir-client/ca/uhn/fhir/rest/client/interceptor/UrlTenantSelectionInterceptor.html)
* [UrlTenantSelectionInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/UrlTenantSelectionInterceptor.java)
+```java
+{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ClientExamples.java|tenantId}}
+```
# Performance: GZip Outgoing Request Bodies
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
index eaf3690558c..bb6fccad007 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
@@ -28,7 +28,7 @@ This interceptor will then produce output similar to the following:
# Partitioning: Multitenant Request Partition
-If the JPA server has [partitioning](/docs/server_jpa/partitioning.html) enabled, the RequestTenantPartitionInterceptor can be used in combination with a [Tenant Identification Strategy](/docs/server_plain/multitenancy.html) in order to achieve a multitenant solution. See [JPA Server Partitioning](/docs/server_jpa/partitioning.html) for more information on partitioning.
+If the JPA server has [partitioning](/docs/server_jpa_partitioning/partitioning.html) enabled, the RequestTenantPartitionInterceptor can be used in combination with a [Tenant Identification Strategy](/docs/server_plain/multitenancy.html) in order to achieve a multitenant solution. See [JPA Server Partitioning](/docs/server_jpa_partitioning/partitioning.html) for more information on partitioning.
* [RequestTenantPartitionInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/partition/RequestTenantPartitionInterceptor.html)
* [RequestTenantPartitionInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/partition/RequestTenantPartitionInterceptor.java)
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/schema.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/schema.md
index bdcd28b8976..3514d0fbd0d 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/schema.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/schema.md
@@ -39,7 +39,7 @@ The HFJ_RESOURCE table indicates a single resource of any type in the database.
Integer
Nullable
- This is the optional partition ID, if the resource is in a partition. See Partitioning.
+ This is the optional partition ID, if the resource is in a partition. See Partitioning.
@@ -48,7 +48,7 @@ The HFJ_RESOURCE table indicates a single resource of any type in the database.
Timestamp
Nullable
- This is the optional partition date, if the resource is in a partition. See Partitioning.
+ This is the optional partition date, if the resource is in a partition. See Partitioning.
@@ -154,7 +154,7 @@ The complete raw contents of the resource is stored in the `RES_TEXT` column, us
Integer
Nullable
- This is the optional partition ID, if the resource is in a partition. See Partitioning.
+ This is the optional partition ID, if the resource is in a partition. See Partitioning.
@@ -163,7 +163,7 @@ The complete raw contents of the resource is stored in the `RES_TEXT` column, us
Timestamp
Nullable
- This is the optional partition date, if the resource is in a partition. See Partitioning.
+ This is the optional partition date, if the resource is in a partition. See Partitioning.
@@ -263,7 +263,7 @@ If the server has been configured with a [Resource Server ID Strategy](/apidocs/
Integer
Nullable
- This is the optional partition ID, if the resource is in a partition. See Partitioning.
+ This is the optional partition ID, if the resource is in a partition. See Partitioning.
@@ -272,7 +272,7 @@ If the server has been configured with a [Resource Server ID Strategy](/apidocs/
Timestamp
Nullable
- This is the optional partition date, if the resource is in a partition. See Partitioning.
+ This is the optional partition date, if the resource is in a partition. See Partitioning.
@@ -332,7 +332,7 @@ When a resource is created or updated, it is indexed for searching. Any search p
Integer
Nullable
- This is the optional partition ID, if the resource is in a partition. See Partitioning.
+ This is the optional partition ID, if the resource is in a partition. See Partitioning.
Note that the partition indicated by the PARTITION_ID and PARTITION_DATE columns refers to the partition
of the SOURCE resource, and not necessarily the TARGET.
@@ -343,7 +343,7 @@ When a resource is created or updated, it is indexed for searching. Any search p
Timestamp
Nullable
- This is the optional partition date, if the resource is in a partition. See Partitioning.
+ This is the optional partition date, if the resource is in a partition. See Partitioning.
Note that the partition indicated by the PARTITION_ID and PARTITION_DATE columns refers to the partition
of the SOURCE resource, and not necessarily the TARGET.
@@ -448,7 +448,7 @@ The following columns are common to **all HFJ_SPIDX_xxx tables**.
Integer
Nullable
- This is the optional partition ID, if the resource is in a partition. See Partitioning.
+ This is the optional partition ID, if the resource is in a partition. See Partitioning.
Note that the partition indicated by the PARTITION_ID and PARTITION_DATE columns refers to the partition
of the SOURCE resource, and not necessarily the TARGET.
@@ -459,7 +459,7 @@ The following columns are common to **all HFJ_SPIDX_xxx tables**.
Timestamp
Nullable
- This is the optional partition date, if the resource is in a partition. See Partitioning.
+ This is the optional partition date, if the resource is in a partition. See Partitioning.
Note that the partition indicated by the PARTITION_ID and PARTITION_DATE columns refers to the partition
of the SOURCE resource, and not necessarily the TARGET.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_partitioning/enabling_in_hapi_fhir.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_partitioning/enabling_in_hapi_fhir.md
new file mode 100644
index 00000000000..ec177cccad5
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_partitioning/enabling_in_hapi_fhir.md
@@ -0,0 +1,11 @@
+# Enabling Partitioning in HAPI FHIR
+
+Follow these steps to enable partitioning on the server:
+
+The [PartitionSettings](/hapi-fhir/apidocs/hapi-fhir-jpaserver-model/ca/uhn/fhir/jpa/model/config/PartitionSettings.html) bean contains configuration settings related to partitioning within the server. To enable partitioning, the [setPartitioningEnabled(boolean)](/hapi-fhir/apidocs/hapi-fhir-jpaserver-model/ca/uhn/fhir/jpa/model/config/PartitionSettings.html#setPartitioningEnabled(boolean)) property should be enabled.
+
+The following settings can be enabled:
+
+* **Include Partition in Search Hashes** ([JavaDoc](/hapi-fhir/apidocs/hapi-fhir-jpaserver-model/ca/uhn/fhir/jpa/model/config/PartitionSettings.html#setIncludePartitionInSearchHashes(boolean))): If this feature is enabled, partition IDs will be factored into [Search Hashes](/hapi-fhir/docs/server_jpa/schema.html#search-hashes). When this flag is not set (as is the default), when a search requests a specific partition, an additional SQL WHERE predicate is added to the query to explicitly request the given partition ID. When this flag is set, this additional WHERE predicate is not necessary since the partition is factored into the hash value being searched on. Setting this flag avoids the need to manually adjust indexes against the HFJ_SPIDX tables. Note that this flag should **not be used in environments where partitioning is being used for security purposes**, since it is possible for a user to reverse engineer false hash collisions.
+
+* **Cross-Partition Reference Mode**: ([JavaDoc](/hapi-fhir/apidocs/hapi-fhir-jpaserver-model/ca/uhn/fhir/jpa/model/config/PartitionSettings.html#setAllowReferencesAcrossPartitions(ca.uhn.fhir.jpa.model.config.PartitionSettings.CrossPartitionReferenceMode))): This setting controls whether resources in one partition should be allowed to create references to resources in other partitions.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/partitioning.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_partitioning/partitioning.md
similarity index 57%
rename from hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/partitioning.md
rename to hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_partitioning/partitioning.md
index ae1bd0f108c..a9f6510d447 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/partitioning.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_partitioning/partitioning.md
@@ -26,16 +26,16 @@ Partitioning in HAPI FHIR JPA means that every resource has a partition identity
* **Partition Date**: This is an additional partition discriminator that can be used to implement partitioning strategies using a date axis.
-Mappings between the **Partition Name** and the **Partition ID** are maintained using the [Partition Mapping Operations](#partition-mapping-operations).
+Mappings between the **Partition Name** and the **Partition ID** are maintained using the [Partition Management Operations](./partitioning_management_operations.html).
## Logical Architecture
-At the database level, partitioning involves the use of two dedicated columns to many tables within the HAPI FHIR JPA [database schema](./schema.html):
+At the database level, partitioning involves the use of two dedicated columns to many tables within the HAPI FHIR JPA [database schema](/hapi-fhir/docs/server_jpa/schema.html):
* **PARTITION_ID** – This is an integer indicating the specific partition that a given resource is placed in. This column can also be *NULL*, meaning that the given resource is in the **Default Partition**.
* **PARTITION_DATE** – This is a date/time column that can be assigned an arbitrary value depending on your use case. Typically, this would be used for use cases where data should be automatically dropped after a certain time period using native database partition drops.
-When partitioning is used, these two columns will be populated with the same value for a given resource on all resource-specific tables (this includes [HFJ_RESOURCE](./schema.html#HFJ_RESOURCE) and all tables that have a foreign key relationship to it including [HFJ_RES_VER](./schema.html#HFJ_RES_VER), [HFJ_RESLINK](./schema.html#HFJ_RES_LINK), [HFJ_SPIDX_*](./schema.html#search-indexes), etc.)
+When partitioning is used, these two columns will be populated with the same value for a given resource on all resource-specific tables (this includes [HFJ_RESOURCE](/hapi-fhir/docs/server_jpa/schema.html#HFJ_RESOURCE) and all tables that have a foreign key relationship to it including [HFJ_RES_VER](/hapi-fhir/docs/server_jpa/schema.html#HFJ_RES_VER), [HFJ_RESLINK](/hapi-fhir/docs/server_jpa/schema.html#HFJ_RES_LINK), [HFJ_SPIDX_*](/hapi-fhir/docs/server_jpa/schema.html#search-indexes), etc.)
When a new resource is **created**, an [interceptor hook](#partition-interceptors) is invoked to request the partition ID and date to be assigned to the resource.
@@ -46,18 +46,6 @@ When a **read operation** is being performed (e.g. a read, search, history, etc.
* The system can be configured to operate as a **multitenant** solution by configuring the partition interceptor to scope all read operations to read data only from the partition that request has access to.```
* The system can be configured to operate with logical segments by configuring the partition interceptor to scope read operations to access all partitions.
-# Enabling Partitioning in HAPI FHIR
-
-Follow these steps to enable partitioning on the server:
-
-The [PartitionSettings](/hapi-fhir/apidocs/hapi-fhir-jpaserver-model/ca/uhn/fhir/jpa/model/config/PartitionSettings.html) bean contains configuration settings related to partitioning within the server. To enable partitioning, the [setPartitioningEnabled(boolean)](/hapi-fhir/apidocs/hapi-fhir-jpaserver-model/ca/uhn/fhir/jpa/model/config/PartitionSettings.html#setPartitioningEnabled(boolean)) property should be enabled.
-
-The following settings can be enabled:
-
-* **Include Partition in Search Hashes** ([JavaDoc](/hapi-fhir/apidocs/hapi-fhir-jpaserver-model/ca/uhn/fhir/jpa/model/config/PartitionSettings.html#setIncludePartitionInSearchHashes(boolean))): If this feature is enabled, partition IDs will be factored into [Search Hashes](./schema.html#search-hashes). When this flag is not set (as is the default), when a search requests a specific partition, an additional SQL WHERE predicate is added to the query to explicitly request the given partition ID. When this flag is set, this additional WHERE predicate is not necessary since the partition is factored into the hash value being searched on. Setting this flag avoids the need to manually adjust indexes against the HFJ_SPIDX tables. Note that this flag should **not be used in environments where partitioning is being used for security purposes**, since it is possible for a user to reverse engineer false hash collisions.
-
-* **Cross-Partition Reference Mode**: ([JavaDoc](/hapi-fhir/apidocs/hapi-fhir-jpaserver-model/ca/uhn/fhir/jpa/model/config/PartitionSettings.html#setAllowReferencesAcrossPartitions(ca.uhn.fhir.jpa.model.config.PartitionSettings.CrossPartitionReferenceMode))): This setting controls whether resources in one partition should be allowed to create references to resources in other partitions.
-
# Partition Interceptors
@@ -122,193 +110,6 @@ The following snippet shows a server with this configuration.
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/PartitionExamples.java|multitenantServer}}
```
-
-
-# Partition Mapping Operations
-
-Several operations exist that can be used to manage the existence of partitions. These operations are supplied by a [plain provider](/docs/server_plain/resource_providers.html#plain-providers) called [PartitionManagementProvider](/hapi-fhir/apidocs/hapi-fhir-jpaserver-base/ca/uhn/fhir/jpa/partition/PartitionManagementProvider.html).
-
-Before a partition can be used, it must be registered using these methods.
-
-## Creating a Partition
-
-The `$partition-management-add-partition` operation can be used to create a new partition. This operation takes the following parameters:
-
-
-
-
-
Name
-
Type
-
Cardinality
-
Description
-
-
-
-
-
id
-
Integer
-
1..1
-
- The numeric ID for the partition. This value can be any integer, positive or negative or zero. It must not be a value that has already been used.
-
-
-
-
name
-
Code
-
1..1
-
- A code (string) to assign to the partition.
-
-
-
-
description
-
String
-
0..1
-
- An optional description for the partition.
-
-
-
-
-
-### Example
-
-An HTTP POST to the following URL would be used to invoke this operation:
-
-```url
-http://example.com/$partition-management-add-partition
-```
-
-The following request body could be used:
-
-```json
-{
- "resourceType": "Parameters",
- "parameter": [ {
- "name": "id",
- "valueInteger": 123
- }, {
- "name": "name",
- "valueCode": "PARTITION-123"
- }, {
- "name": "description",
- "valueString": "a description"
- } ]
-}
-```
-
-## Updating a Partition
-
-The `$partition-management-update-partition` operation can be used to update an existing partition. This operation takes the following parameters:
-
-
-
-
-
Name
-
Type
-
Cardinality
-
Description
-
-
-
-
-
id
-
Integer
-
1..1
-
- The numeric ID for the partition to update. This ID must already exist.
-
-
-
-
name
-
Code
-
1..1
-
- A code (string) to assign to the partition. Note that it is acceptable to change the name of a partition, but this should be done with caution since partition names may be referenced by URLs, caches, etc.
-
-
-
-
description
-
String
-
0..1
-
- An optional description for the partition.
-
-
-
-
-
-### Example
-
-An HTTP POST to the following URL would be used to invoke this operation:
-
-```url
-http://example.com/$partition-management-add-partition
-```
-
-The following request body could be used:
-
-```json
-{
- "resourceType": "Parameters",
- "parameter": [ {
- "name": "id",
- "valueInteger": 123
- }, {
- "name": "name",
- "valueCode": "PARTITION-123"
- }, {
- "name": "description",
- "valueString": "a description"
- } ]
-}
-```
-
-## Deleting a Partition
-
-The `$partition-management-delete-partition` operation can be used to delete an existing partition. This operation takes the following parameters:
-
-
-
-
-
Name
-
Type
-
Cardinality
-
Description
-
-
-
-
-
id
-
Integer
-
1..1
-
- The numeric ID for the partition to update. This ID must already exist.
-
-
-
-
-
-### Example
-
-An HTTP POST to the following URL would be used to invoke this operation:
-
-```url
-http://example.com/$partition-management-delete-partition
-```
-
-The following request body could be used:
-
-```json
-{
- "resourceType": "Parameters",
- "parameter": [ {
- "name": "id",
- "valueInteger": 123
- } ]
-}
-```
-
# Limitations
@@ -328,5 +129,7 @@ None of the limitations listed here are considered permanent. Over time the HAPI
* ConceptMap
* **Search Parameters are not partitioned**: There is only one set of SearchParameter resources for the entire system, and any search parameters will apply to resources in all partitions. All SearchParameter resources must be stored in the default partition.
+
+* **Cross-partition History Operations are not supported**: It is not possible to perform a `_history` operation that spans all partitions (`_history` does work when applied to a single partition however).
* **Bulk Operations are not partition aware**: Bulk export operations will export data across all partitions.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_partitioning/partitioning_management_operations.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_partitioning/partitioning_management_operations.md
new file mode 100644
index 00000000000..610d3f8a1d6
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_partitioning/partitioning_management_operations.md
@@ -0,0 +1,185 @@
+# Partition Mapping Operations
+
+Several operations exist that can be used to manage the existence of partitions. These operations are supplied by a [plain provider](/docs/server_plain/resource_providers.html#plain-providers) called [PartitionManagementProvider](/hapi-fhir/apidocs/hapi-fhir-jpaserver-base/ca/uhn/fhir/jpa/partition/PartitionManagementProvider.html).
+
+Before a partition can be used, it must be registered using these methods.
+
+## Creating a Partition
+
+The `$partition-management-create-partition` operation can be used to create a new partition. This operation takes the following parameters:
+
+
+
+
+
Name
+
Type
+
Cardinality
+
Description
+
+
+
+
+
id
+
Integer
+
1..1
+
+ The numeric ID for the partition. This value can be any integer, positive or negative or zero. It must not be a value that has already been used.
+
+
+
+
name
+
Code
+
1..1
+
+ A code (string) to assign to the partition.
+
+
+
+
description
+
String
+
0..1
+
+ An optional description for the partition.
+
+
+
+
+
+### Example
+
+An HTTP POST to the following URL would be used to invoke this operation:
+
+```url
+http://example.com/$partition-management-create-partition
+```
+
+The following request body could be used:
+
+```json
+{
+ "resourceType": "Parameters",
+ "parameter": [ {
+ "name": "id",
+ "valueInteger": 123
+ }, {
+ "name": "name",
+ "valueCode": "PARTITION-123"
+ }, {
+ "name": "description",
+ "valueString": "a description"
+ } ]
+}
+```
+
+## Updating a Partition
+
+The `$partition-management-update-partition` operation can be used to update an existing partition. This operation takes the following parameters:
+
+
+
+
+
Name
+
Type
+
Cardinality
+
Description
+
+
+
+
+
id
+
Integer
+
1..1
+
+ The numeric ID for the partition to update. This ID must already exist.
+
+
+
+
name
+
Code
+
1..1
+
+ A code (string) to assign to the partition. Note that it is acceptable to change the name of a partition, but this should be done with caution since partition names may be referenced by URLs, caches, etc.
+
+
+
+
description
+
String
+
0..1
+
+ An optional description for the partition.
+
+
+
+
+
+### Example
+
+An HTTP POST to the following URL would be used to invoke this operation:
+
+```url
+http://example.com/$partition-management-create-partition
+```
+
+The following request body could be used:
+
+```json
+{
+ "resourceType": "Parameters",
+ "parameter": [ {
+ "name": "id",
+ "valueInteger": 123
+ }, {
+ "name": "name",
+ "valueCode": "PARTITION-123"
+ }, {
+ "name": "description",
+ "valueString": "a description"
+ } ]
+}
+```
+
+## Deleting a Partition
+
+The `$partition-management-delete-partition` operation can be used to delete an existing partition. This operation takes the following parameters:
+
+
+
+
+
Name
+
Type
+
Cardinality
+
Description
+
+
+
+
+
id
+
Integer
+
1..1
+
+ The numeric ID for the partition to update. This ID must already exist.
+
+
+
+
+
+### Example
+
+An HTTP POST to the following URL would be used to invoke this operation:
+
+```url
+http://example.com/$partition-management-delete-partition
+```
+
+The following request body could be used:
+
+```json
+{
+ "resourceType": "Parameters",
+ "parameter": [ {
+ "name": "id",
+ "valueInteger": 123
+ } ]
+}
+```
+
diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpRequest.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpRequest.java
index ea02fb64e91..86cc93bda7c 100644
--- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpRequest.java
+++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpRequest.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.jaxrs.client;
*/
import ca.uhn.fhir.rest.api.RequestTypeEnum;
+import ca.uhn.fhir.rest.client.api.BaseHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.util.StopWatch;
@@ -28,7 +29,11 @@ import ca.uhn.fhir.util.StopWatch;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.core.Response;
-import java.util.*;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
/**
* A Http Request based on JaxRs. This is an adapter around the class
@@ -36,7 +41,7 @@ import java.util.*;
*
* @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
*/
-public class JaxRsHttpRequest implements IHttpRequest {
+public class JaxRsHttpRequest extends BaseHttpRequest implements IHttpRequest {
private final Map> myHeaders = new HashMap<>();
private Invocation.Builder myRequest;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java
index 4c5547fc7cc..0dbf4bcfd4e 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.bulk;
*/
import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
@@ -231,7 +232,7 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc {
map.setLastUpdated(new DateRangeParam(job.getSince(), null));
}
- IResultIterator resultIterator = sb.createQuery(map, new SearchRuntimeDetails(null, theJobUuid), null, null);
+ IResultIterator resultIterator = sb.createQuery(map, new SearchRuntimeDetails(null, theJobUuid), null, RequestPartitionId.allPartitions());
storeResultsToFiles(nextCollection, sb, resultIterator, jobResourceCounter, jobStopwatch);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
index 06379bf1d55..e77589637d7 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
@@ -5,22 +5,27 @@ import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.interceptor.executor.InterceptorService;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
+import ca.uhn.fhir.jpa.api.dao.IDao;
import ca.uhn.fhir.jpa.binstore.BinaryAccessProvider;
import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor;
import ca.uhn.fhir.jpa.bulk.BulkDataExportProvider;
import ca.uhn.fhir.jpa.bulk.BulkDataExportSvcImpl;
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
+import ca.uhn.fhir.jpa.dao.HistoryBuilder;
+import ca.uhn.fhir.jpa.dao.HistoryBuilderFactory;
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
+import ca.uhn.fhir.jpa.dao.SearchBuilder;
+import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
import ca.uhn.fhir.jpa.dao.index.DaoResourceLinkResolver;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
-import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperService;
+import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl;
import ca.uhn.fhir.jpa.partition.PartitionManagementProvider;
-import ca.uhn.fhir.jpa.partition.RequestPartitionHelperService;
+import ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.sched.AutowiringSpringBeanJobFactory;
@@ -44,6 +49,7 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.interceptor.consent.IConsentContextServices;
import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor;
import org.hibernate.jpa.HibernatePersistenceProvider;
+import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@@ -64,6 +70,9 @@ import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
import org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
+import javax.annotation.Nullable;
+import java.util.Date;
+
/*
* #%L
* HAPI FHIR JPA Server
@@ -103,9 +112,12 @@ public abstract class BaseConfig {
public static final String JPA_VALIDATION_SUPPORT_CHAIN = "myJpaValidationSupportChain";
public static final String TASK_EXECUTOR_NAME = "hapiJpaTaskExecutor";
public static final String GRAPHQL_PROVIDER_NAME = "myGraphQLProvider";
- private static final String HAPI_DEFAULT_SCHEDULER_GROUP = "HAPI";
public static final String PERSISTED_JPA_BUNDLE_PROVIDER = "PersistedJpaBundleProvider";
+ public static final String PERSISTED_JPA_BUNDLE_PROVIDER_BY_SEARCH = "PersistedJpaBundleProvider_BySearch";
public static final String PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER = "PersistedJpaSearchFirstPageBundleProvider";
+ private static final String HAPI_DEFAULT_SCHEDULER_GROUP = "HAPI";
+ public static final String SEARCH_BUILDER = "SearchBuilder";
+ public static final String HISTORY_BUILDER = "HistoryBuilder";
@Autowired
protected Environment myEnv;
@@ -213,8 +225,8 @@ public abstract class BaseConfig {
}
@Bean
- public IRequestPartitionHelperService requestPartitionHelperService() {
- return new RequestPartitionHelperService();
+ public IRequestPartitionHelperSvc requestPartitionHelperService() {
+ return new RequestPartitionHelperSvc();
}
@Bean
@@ -291,18 +303,46 @@ public abstract class BaseConfig {
return new PersistedJpaBundleProviderFactory();
}
- @Bean(name= PERSISTED_JPA_BUNDLE_PROVIDER)
+ @Bean(name = PERSISTED_JPA_BUNDLE_PROVIDER)
@Scope("prototype")
public PersistedJpaBundleProvider persistedJpaBundleProvider(RequestDetails theRequest, String theUuid) {
return new PersistedJpaBundleProvider(theRequest, theUuid);
}
- @Bean(name= PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER)
+ @Bean(name = PERSISTED_JPA_BUNDLE_PROVIDER_BY_SEARCH)
+ @Scope("prototype")
+ public PersistedJpaBundleProvider persistedJpaBundleProvider(RequestDetails theRequest, Search theSearch) {
+ return new PersistedJpaBundleProvider(theRequest, theSearch);
+ }
+
+ @Bean(name = PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER)
@Scope("prototype")
public PersistedJpaSearchFirstPageBundleProvider persistedJpaSearchFirstPageBundleProvider(RequestDetails theRequest, Search theSearch, SearchCoordinatorSvcImpl.SearchTask theSearchTask, ISearchBuilder theSearchBuilder) {
return new PersistedJpaSearchFirstPageBundleProvider(theSearch, theSearchTask, theSearchBuilder, theRequest);
}
+ @Bean
+ public SearchBuilderFactory searchBuilderFactory() {
+ return new SearchBuilderFactory();
+ }
+
+ @Bean(name = SEARCH_BUILDER)
+ @Scope("prototype")
+ public SearchBuilder persistedJpaSearchFirstPageBundleProvider(IDao theDao, String theResourceName, Class extends IBaseResource> theResourceType) {
+ return new SearchBuilder(theDao, theResourceName, theResourceType);
+ }
+
+ @Bean
+ public HistoryBuilderFactory historyBuilderFactory() {
+ return new HistoryBuilderFactory();
+ }
+
+ @Bean(name = HISTORY_BUILDER)
+ @Scope("prototype")
+ public HistoryBuilder persistedJpaSearchFirstPageBundleProvider(@Nullable String theResourceType, @Nullable Long theResourceId, @Nullable Date theRangeStartInclusive, @Nullable Date theRangeEndInclusive) {
+ return new HistoryBuilder(theResourceType, theResourceId, theRangeStartInclusive, theRangeEndInclusive);
+ }
+
public static void configureEntityManagerFactory(LocalContainerEntityManagerFactoryBean theFactory, FhirContext theCtx) {
theFactory.setJpaDialect(hibernateJpaDialect(theCtx.getLocalizer()));
theFactory.setPackagesToScan("ca.uhn.fhir.jpa.model.entity", "ca.uhn.fhir.jpa.entity");
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
index 4892135426e..efa2e1f8cfc 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
@@ -11,6 +11,7 @@ import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
+import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IDao;
@@ -26,15 +27,28 @@ import ca.uhn.fhir.jpa.dao.index.DaoSearchParamSynchronizer;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.index.SearchParamWithInlineReferencesExtractor;
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
+import ca.uhn.fhir.jpa.entity.PartitionEntity;
import ca.uhn.fhir.jpa.entity.ResourceSearchView;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
-import ca.uhn.fhir.jpa.model.entity.*;
+import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
+import ca.uhn.fhir.jpa.model.entity.BaseTag;
+import ca.uhn.fhir.jpa.model.entity.ForcedId;
+import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity;
+import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
+import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity;
+import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
+import ca.uhn.fhir.jpa.model.entity.ResourceTable;
+import ca.uhn.fhir.jpa.model.entity.ResourceTag;
+import ca.uhn.fhir.jpa.model.entity.TagDefinition;
+import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
+import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
+import ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProviderFactory;
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
@@ -76,7 +90,16 @@ import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.Validate;
-import org.hl7.fhir.instance.model.api.*;
+import org.hl7.fhir.instance.model.api.IAnyResource;
+import org.hl7.fhir.instance.model.api.IBase;
+import org.hl7.fhir.instance.model.api.IBaseCoding;
+import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
+import org.hl7.fhir.instance.model.api.IBaseMetaType;
+import org.hl7.fhir.instance.model.api.IBaseReference;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IDomainResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -100,8 +123,19 @@ import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
import java.util.Map.Entry;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.UUID;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import static org.apache.commons.lang3.StringUtils.defaultString;
@@ -164,14 +198,17 @@ public abstract class BaseHapiFhirDao extends BaseStora
protected IResourceTableDao myResourceTableDao;
@Autowired
protected IResourceTagDao myResourceTagDao;
-
@Autowired
protected DeleteConflictService myDeleteConflictService;
@Autowired
protected IInterceptorBroadcaster myInterceptorBroadcaster;
@Autowired
+ protected DaoRegistry myDaoRegistry;
+ @Autowired
ExpungeService myExpungeService;
@Autowired
+ private HistoryBuilderFactory myHistoryBuilderFactory;
+ @Autowired
private DaoConfig myConfig;
@Autowired
private PlatformTransactionManager myPlatformTransactionManager;
@@ -180,8 +217,6 @@ public abstract class BaseHapiFhirDao extends BaseStora
@Autowired
private ISearchParamPresenceSvc mySearchParamPresenceSvc;
@Autowired
- protected DaoRegistry myDaoRegistry;
- @Autowired
private SearchParamWithInlineReferencesExtractor mySearchParamWithInlineReferencesExtractor;
@Autowired
private DaoSearchParamSynchronizer myDaoSearchParamSynchronizer;
@@ -191,6 +226,12 @@ public abstract class BaseHapiFhirDao extends BaseStora
private ApplicationContext myApplicationContext;
@Autowired
private PartitionSettings myPartitionSettings;
+ @Autowired
+ private RequestPartitionHelperSvc myRequestPartitionHelperSvc;
+ @Autowired
+ private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
+ @Autowired
+ private IPartitionLookupSvc myPartitionLookupSvc;
@Override
protected IInterceptorBroadcaster getInterceptorBroadcaster() {
@@ -384,48 +425,23 @@ public abstract class BaseHapiFhirDao extends BaseStora
}
}
+ protected IBundleProvider history(RequestDetails theRequest, String theResourceType, Long theResourcePid, Date theRangeStartInclusive, Date theRangeEndInclusive) {
- protected IBundleProvider history(RequestDetails theRequest, String theResourceName, Long theId, Date theSince, Date theUntil) {
-
- String resourceName = defaultIfBlank(theResourceName, null);
+ String resourceName = defaultIfBlank(theResourceType, null);
Search search = new Search();
search.setDeleted(false);
search.setCreated(new Date());
- search.setLastUpdated(theSince, theUntil);
+ search.setLastUpdated(theRangeStartInclusive, theRangeEndInclusive);
search.setUuid(UUID.randomUUID().toString());
search.setResourceType(resourceName);
- search.setResourceId(theId);
+ search.setResourceId(theResourcePid);
search.setSearchType(SearchTypeEnum.HISTORY);
search.setStatus(SearchStatusEnum.FINISHED);
- if (theSince != null) {
- if (resourceName == null) {
- search.setTotalCount(myResourceHistoryTableDao.countForAllResourceTypes(theSince));
- } else if (theId == null) {
- search.setTotalCount(myResourceHistoryTableDao.countForResourceType(resourceName, theSince));
- } else {
- search.setTotalCount(myResourceHistoryTableDao.countForResourceInstance(theId, theSince));
- }
- } else {
- if (resourceName == null) {
- search.setTotalCount(myResourceHistoryTableDao.countForAllResourceTypes());
- } else if (theId == null) {
- search.setTotalCount(myResourceHistoryTableDao.countForResourceType(resourceName));
- } else {
- search.setTotalCount(myResourceHistoryTableDao.countForResourceInstance(theId));
- }
- }
-
- search = mySearchCacheSvc.save(search);
-
- return myPersistedJpaBundleProviderFactory.newInstance(theRequest, search.getUuid());
+ return myPersistedJpaBundleProviderFactory.newInstance(theRequest, search);
}
- @Autowired
- private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
-
-
void incrementId(T theResource, ResourceTable theSavedEntity, IIdType theResourceId) {
String newVersion;
long newVersionLong;
@@ -807,7 +823,11 @@ public abstract class BaseHapiFhirDao extends BaseStora
ResourceHistoryTable history = (ResourceHistoryTable) theEntity;
resourceBytes = history.getResource();
resourceEncoding = history.getEncoding();
- myTagList = history.getTags();
+ if (history.isHasTags()) {
+ myTagList = history.getTags();
+ } else {
+ myTagList = Collections.emptyList();
+ }
version = history.getVersion();
if (history.getProvenance() != null) {
provenanceRequestId = history.getProvenance().getRequestId();
@@ -829,7 +849,11 @@ public abstract class BaseHapiFhirDao extends BaseStora
}
resourceBytes = history.getResource();
resourceEncoding = history.getEncoding();
- myTagList = resource.getTags();
+ if (resource.isHasTags()) {
+ myTagList = resource.getTags();
+ } else {
+ myTagList = Collections.emptyList();
+ }
version = history.getVersion();
if (history.getProvenance() != null) {
provenanceRequestId = history.getProvenance().getRequestId();
@@ -924,6 +948,17 @@ public abstract class BaseHapiFhirDao extends BaseStora
}
+ // 7. Add partition information
+ if (myPartitionSettings.isPartitioningEnabled()) {
+ RequestPartitionId partitionId = theEntity.getPartitionId();
+ if (partitionId != null && partitionId.getPartitionId() != null) {
+ PartitionEntity persistedPartition = myPartitionLookupSvc.getPartitionById(partitionId.getPartitionId());
+ retVal.setUserData(Constants.RESOURCE_PARTITION_ID, persistedPartition.toRequestPartitionId());
+ } else {
+ retVal.setUserData(Constants.RESOURCE_PARTITION_ID, null);
+ }
+ }
+
return retVal;
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
index 45d07ea8fa0..08996e75323 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
@@ -45,7 +45,7 @@ import ca.uhn.fhir.jpa.model.entity.TagDefinition;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
-import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperService;
+import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
@@ -145,7 +145,7 @@ public abstract class BaseHapiFhirResourceDao extends B
private String myResourceName;
private Class myResourceType;
@Autowired
- private IRequestPartitionHelperService myRequestPartitionHelperService;
+ private IRequestPartitionHelperSvc myRequestPartitionHelperService;
@Autowired
private PartitionSettings myPartitionSettings;
@@ -216,7 +216,7 @@ public abstract class BaseHapiFhirResourceDao extends B
theResource.setUserData(JpaConstants.RESOURCE_ID_SERVER_ASSIGNED, Boolean.TRUE);
}
- RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequestDetails, theResource);
+ RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequestDetails, theResource, getResourceName());
return doCreate(theResource, theIfNoneExist, thePerformIndexing, theUpdateTimestamp, theRequestDetails, requestPartitionId);
}
@@ -685,11 +685,6 @@ public abstract class BaseHapiFhirResourceDao extends B
@Override
public IBundleProvider history(Date theSince, Date theUntil, RequestDetails theRequestDetails) {
- if (myPartitionSettings.isPartitioningEnabled()) {
- String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "noSystemOrTypeHistoryForPartitionAwareServer");
- throw new MethodNotAllowedException(msg);
- }
-
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails);
notifyInterceptors(RestOperationTypeEnum.HISTORY_TYPE, requestDetails);
@@ -1005,19 +1000,19 @@ public abstract class BaseHapiFhirResourceDao extends B
public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) {
validateResourceTypeAndThrowInvalidRequestException(theId);
- @Nullable RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName());
+ RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName());
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(requestPartitionId, getResourceName(), theId.getIdPart());
BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid.getIdAsLong());
// Verify that the resource is for the correct partition
- if (requestPartitionId != null) {
+ if (!requestPartitionId.isAllPartitions()) {
if (requestPartitionId.getPartitionId() == null) {
- if (entity.getPartitionId() != null) {
+ if (entity.getPartitionId().getPartitionId() != null) {
ourLog.debug("Performing a read for PartitionId={} but entity has partition: {}", requestPartitionId, entity.getPartitionId());
entity = null;
}
- } else if (entity.getPartitionId() != null) {
- if (!entity.getPartitionId().getPartitionId().equals(requestPartitionId.getPartitionId())) {
+ } else if (entity.getPartitionId().getPartitionId() != null) {
+ if (!requestPartitionId.getPartitionId().equals(entity.getPartitionId().getPartitionId())) {
ourLog.debug("Performing a read for PartitionId={} but entity has partition: {}", requestPartitionId, entity.getPartitionId());
entity = null;
}
@@ -1314,7 +1309,7 @@ public abstract class BaseHapiFhirResourceDao extends B
*/
resourceId = theResource.getIdElement();
- RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequest, theResource);
+ RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequest, theResource, getResourceName());
try {
entity = readEntityLatestVersion(resourceId, requestPartitionId);
} catch (ResourceNotFoundException e) {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java
index 7d179146267..10adc9abcb7 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java
@@ -8,7 +8,6 @@ import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
-import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.StopWatch;
import org.hl7.fhir.instance.model.api.IBaseResource;
@@ -80,11 +79,6 @@ public abstract class BaseHapiFhirSystemDao extends BaseHapiFhirDao criteriaQuery = cb.createQuery(Long.class);
+ Root from = criteriaQuery.from(ResourceHistoryTable.class);
+ criteriaQuery.select(cb.count(from));
+
+ addPredicatesToQuery(cb, thePartitionId, criteriaQuery, from);
+
+ TypedQuery query = myEntityManager.createQuery(criteriaQuery);
+ return query.getSingleResult();
+ }
+
+ @SuppressWarnings("OptionalIsPresent")
+ public List fetchEntities(RequestPartitionId thePartitionId, int theFromIndex, int theToIndex) {
+ CriteriaBuilder cb = myEntityManager.getCriteriaBuilder();
+ CriteriaQuery criteriaQuery = cb.createQuery(ResourceHistoryTable.class);
+ Root from = criteriaQuery.from(ResourceHistoryTable.class);
+
+ addPredicatesToQuery(cb, thePartitionId, criteriaQuery, from);
+
+ from.fetch("myProvenance", JoinType.LEFT);
+
+ criteriaQuery.orderBy(cb.desc(from.get("myUpdated")));
+
+ TypedQuery query = myEntityManager.createQuery(criteriaQuery);
+
+ query.setFirstResult(theFromIndex);
+ query.setMaxResults(theToIndex - theFromIndex);
+
+ List tables = query.getResultList();
+ if (tables.size() > 0) {
+ ImmutableListMultimap resourceIdToHistoryEntries = Multimaps.index(tables, ResourceHistoryTable::getResourceId);
+
+ Map> pidToForcedId = myIdHelperService.translatePidsToForcedIds(resourceIdToHistoryEntries.keySet());
+ ourLog.trace("Translated IDs: {}", pidToForcedId);
+
+ for (Long nextResourceId : resourceIdToHistoryEntries.keySet()) {
+ List historyTables = resourceIdToHistoryEntries.get(nextResourceId);
+
+ String resourceId;
+ Optional forcedId = pidToForcedId.get(nextResourceId);
+ if (forcedId.isPresent()) {
+ resourceId = forcedId.get();
+ } else {
+ resourceId = nextResourceId.toString();
+ }
+
+ for (ResourceHistoryTable nextHistoryTable : historyTables) {
+ nextHistoryTable.setTransientForcedId(resourceId);
+ }
+ }
+ }
+
+ return tables;
+ }
+
+ private void addPredicatesToQuery(CriteriaBuilder theCriteriaBuilder, RequestPartitionId thePartitionId, CriteriaQuery> theQuery, Root theFrom) {
+ List predicates = new ArrayList<>();
+
+ if (!thePartitionId.isAllPartitions()) {
+ if (thePartitionId.getPartitionId() != null) {
+ predicates.add(theCriteriaBuilder.equal(theFrom.get("myPartitionIdValue").as(Integer.class), thePartitionId.getPartitionId()));
+ } else {
+ predicates.add(theCriteriaBuilder.isNull(theFrom.get("myPartitionIdValue").as(Integer.class)));
+ }
+ }
+
+ if (myResourceId != null) {
+ predicates.add(theCriteriaBuilder.equal(theFrom.get("myResourceId"), myResourceId));
+ } else if (myResourceType != null) {
+ validateNotSearchingAllPartitions(thePartitionId);
+ predicates.add(theCriteriaBuilder.equal(theFrom.get("myResourceType"), myResourceType));
+ } else {
+ validateNotSearchingAllPartitions(thePartitionId);
+ }
+
+ if (myRangeStartInclusive != null) {
+ predicates.add(theCriteriaBuilder.greaterThanOrEqualTo(theFrom.get("myUpdated").as(Date.class), myRangeStartInclusive));
+ }
+ if (myRangeEndInclusive != null) {
+ predicates.add(theCriteriaBuilder.lessThanOrEqualTo(theFrom.get("myUpdated").as(Date.class), myRangeEndInclusive));
+ }
+
+ if (predicates.size() > 0) {
+ theQuery.where(toPredicateArray(predicates));
+ }
+ }
+
+ private void validateNotSearchingAllPartitions(RequestPartitionId thePartitionId) {
+ if (myPartitionSettings.isPartitioningEnabled()) {
+ if (thePartitionId.isAllPartitions()) {
+ String msg = myCtx.getLocalizer().getMessage(HistoryBuilder.class, "noSystemOrTypeHistoryForPartitionAwareServer");
+ throw new InvalidRequestException(msg);
+ }
+ }
+ }
+
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/HistoryBuilderFactory.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/HistoryBuilderFactory.java
new file mode 100644
index 00000000000..0905ac92311
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/HistoryBuilderFactory.java
@@ -0,0 +1,39 @@
+package ca.uhn.fhir.jpa.dao;
+
+/*-
+ * #%L
+ * HAPI FHIR JPA Server
+ * %%
+ * Copyright (C) 2014 - 2020 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.jpa.config.BaseConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+
+import javax.annotation.Nullable;
+import java.util.Date;
+
+public class HistoryBuilderFactory {
+
+ @Autowired
+ private ApplicationContext myApplicationContext;
+
+ public HistoryBuilder newHistoryBuilder(@Nullable String theResourceType, @Nullable Long theResourceId, @Nullable Date theRangeStartInclusive, @Nullable Date theRangeEndInclusive) {
+ return (HistoryBuilder) myApplicationContext.getBean(BaseConfig.HISTORY_BUILDER, theResourceType, theResourceId, theRangeStartInclusive, theRangeEndInclusive);
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java
index 090e47a7a1e..a8055e0a8e7 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java
@@ -30,6 +30,7 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateRangeParam;
import org.hl7.fhir.instance.model.api.IBaseResource;
+import javax.annotation.Nonnull;
import javax.persistence.EntityManager;
import java.util.Collection;
import java.util.Iterator;
@@ -38,7 +39,7 @@ import java.util.Set;
public interface ISearchBuilder {
- IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntime, RequestDetails theRequest, RequestPartitionId theRequestPartitionId);
+ IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntime, RequestDetails theRequest, @Nonnull RequestPartitionId theRequestPartitionId);
Iterator createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, RequestPartitionId theRequestPartitionId);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
index ea93205d9ab..795fbc65877 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
@@ -56,6 +56,7 @@ import ca.uhn.fhir.jpa.searchparam.util.Dstu3DistanceHelper;
import ca.uhn.fhir.jpa.util.BaseIterator;
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
+import ca.uhn.fhir.jpa.util.QueryChunker;
import ca.uhn.fhir.jpa.util.ScrollableResultsIterator;
import ca.uhn.fhir.jpa.util.SqlQueryList;
import ca.uhn.fhir.model.api.IQueryParameterType;
@@ -88,8 +89,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Scope;
-import org.springframework.stereotype.Component;
import javax.annotation.Nonnull;
import javax.persistence.EntityManager;
@@ -122,8 +121,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* The SearchBuilder is responsible for actually forming the SQL query that handles
* searches for resources
*/
-@Component
-@Scope("prototype")
public class SearchBuilder implements ISearchBuilder {
/**
@@ -175,7 +172,7 @@ public class SearchBuilder implements ISearchBuilder {
/**
* Constructor
*/
- SearchBuilder(IDao theDao, String theResourceName, Class extends IBaseResource> theResourceType) {
+ public SearchBuilder(IDao theDao, String theResourceName, Class extends IBaseResource> theResourceType) {
myCallingDao = theDao;
myResourceName = theResourceName;
myResourceType = theResourceType;
@@ -225,7 +222,9 @@ public class SearchBuilder implements ISearchBuilder {
}
@Override
- public Iterator createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
+ public Iterator createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, @Nonnull RequestPartitionId theRequestPartitionId) {
+ assert theRequestPartitionId != null;
+
init(theParams, theSearchUuid, theRequestPartitionId);
TypedQuery query = createQuery(null, null, true, theRequest);
@@ -241,7 +240,9 @@ public class SearchBuilder implements ISearchBuilder {
}
@Override
- public IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
+ public IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest, @Nonnull RequestPartitionId theRequestPartitionId) {
+ assert theRequestPartitionId != null;
+
init(theParams, theSearchRuntimeDetails.getSearchUuid(), theRequestPartitionId);
if (myPidSet == null) {
@@ -359,7 +360,7 @@ public class SearchBuilder implements ISearchBuilder {
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myResourceType"), myResourceName));
}
myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted")));
- if (myRequestPartitionId != null) {
+ if (!myRequestPartitionId.isAllPartitions()) {
if (myRequestPartitionId.getPartitionId() != null) {
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myPartitionIdValue").as(Integer.class), myRequestPartitionId.getPartitionId()));
} else {
@@ -614,19 +615,10 @@ public class SearchBuilder implements ISearchBuilder {
theResourceListToPopulate.add(null);
}
- /*
- * As always, Oracle can't handle things that other databases don't mind.. In this
- * case it doesn't like more than ~1000 IDs in a single load, so we break this up
- * if it's lots of IDs. I suppose maybe we should be doing this as a join anyhow
- * but this should work too. Sigh.
- */
List pids = new ArrayList<>(thePids);
- for (int i = 0; i < pids.size(); i += MAXIMUM_PAGE_SIZE) {
- int to = i + MAXIMUM_PAGE_SIZE;
- to = Math.min(to, pids.size());
- List pidsSubList = pids.subList(i, to);
- doLoadPids(pidsSubList, theIncludedPids, theResourceListToPopulate, theForHistoryOperation, position, theDetails);
- }
+ new QueryChunker().chunk(pids, t->{
+ doLoadPids(t, theIncludedPids, theResourceListToPopulate, theForHistoryOperation, position, theDetails);
+ });
}
@@ -895,7 +887,7 @@ public class SearchBuilder implements ISearchBuilder {
private void addPredicateCompositeStringUnique(@Nonnull SearchParameterMap theParams, String theIndexedString, RequestPartitionId theRequestPartitionId) {
Join join = myQueryRoot.join("myParamsCompositeStringUnique", JoinType.LEFT);
- if (theRequestPartitionId != null) {
+ if (!theRequestPartitionId.isAllPartitions()) {
Integer partitionId = theRequestPartitionId.getPartitionId();
Predicate predicate = myCriteriaBuilder.equal(join.get("myPartitionIdValue").as(Integer.class), partitionId);
myQueryRoot.addPredicate(predicate);
@@ -1276,7 +1268,7 @@ public class SearchBuilder implements ISearchBuilder {
return ResourcePersistentId.fromLongList(query.getResultList());
}
- private static Predicate[] toPredicateArray(List thePredicates) {
+ static Predicate[] toPredicateArray(List thePredicates) {
return thePredicates.toArray(new Predicate[0]);
}
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilderFactory.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilderFactory.java
index 820e45e7875..f1d93c91d13 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilderFactory.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilderFactory.java
@@ -21,12 +21,18 @@ package ca.uhn.fhir.jpa.dao;
*/
import ca.uhn.fhir.jpa.api.dao.IDao;
+import ca.uhn.fhir.jpa.config.BaseConfig;
import org.hl7.fhir.instance.model.api.IBaseResource;
-import org.springframework.beans.factory.annotation.Lookup;
-import org.springframework.stereotype.Service;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+
+public class SearchBuilderFactory {
+
+ @Autowired
+ private ApplicationContext myApplicationContext;
+
+ public ISearchBuilder newSearchBuilder(IDao theDao, String theResourceName, Class extends IBaseResource> theResourceType) {
+ return (ISearchBuilder) myApplicationContext.getBean(BaseConfig.SEARCH_BUILDER, theDao, theResourceName, theResourceType);
+ }
-@Service
-public abstract class SearchBuilderFactory {
- @Lookup
- public abstract ISearchBuilder newSearchBuilder(IDao theDao, String theResourceName, Class extends IBaseResource> theResourceType);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java
index 1a8d31c684e..a64c375adcb 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java
@@ -1,5 +1,11 @@
package ca.uhn.fhir.jpa.dao.data;
+import ca.uhn.fhir.jpa.model.entity.ForcedId;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@@ -24,15 +30,11 @@ import java.util.Optional;
* #L%
*/
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.Modifying;
-import org.springframework.data.jpa.repository.Query;
-import org.springframework.data.repository.query.Param;
-
-import ca.uhn.fhir.jpa.model.entity.ForcedId;
-
public interface IForcedIdDao extends JpaRepository {
+ @Query("SELECT f FROM ForcedId f WHERE myResourcePid IN (:resource_pids)")
+ List findAllByResourcePid(@Param("resource_pids") List theResourcePids);
+
@Query("SELECT f.myResourcePid FROM ForcedId f WHERE myForcedId IN (:forced_id)")
List findByForcedId(@Param("forced_id") Collection theForcedId);
@@ -46,7 +48,7 @@ public interface IForcedIdDao extends JpaRepository {
Optional findByPartitionIdAndTypeAndForcedId(@Param("partition_id") Integer thePartitionId, @Param("resource_type") String theResourceType, @Param("forced_id") String theForcedId);
@Query("SELECT f FROM ForcedId f WHERE f.myResourcePid = :resource_pid")
- ForcedId findByResourcePid(@Param("resource_pid") Long theResourcePid);
+ Optional findByResourcePid(@Param("resource_pid") Long theResourcePid);
@Modifying
@Query("DELETE FROM ForcedId t WHERE t.myId = :pid")
@@ -75,7 +77,7 @@ public interface IForcedIdDao extends JpaRepository {
/**
* Warning: No DB index exists for this particular query, so it may not perform well
- *
+ *
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way.
*/
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceHistoryTableDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceHistoryTableDao.java
index 2366c7d9eaa..63d1779ae2d 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceHistoryTableDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceHistoryTableDao.java
@@ -6,13 +6,8 @@ import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
-import org.springframework.data.jpa.repository.Temporal;
import org.springframework.data.repository.query.Param;
-import javax.persistence.TemporalType;
-import java.util.Collection;
-import java.util.Date;
-
/*
* #%L
* HAPI FHIR JPA Server
@@ -35,36 +30,6 @@ import java.util.Date;
public interface IResourceHistoryTableDao extends JpaRepository {
- @Query("SELECT COUNT(*) FROM ResourceHistoryTable t WHERE t.myUpdated >= :cutoff")
- int countForAllResourceTypes(
- @Temporal(value = TemporalType.TIMESTAMP) @Param("cutoff") Date theCutoff
- );
-
- @Query("SELECT COUNT(*) FROM ResourceHistoryTable t")
- int countForAllResourceTypes(
- );
-
- @Query("SELECT COUNT(*) FROM ResourceHistoryTable t WHERE t.myResourceId = :id AND t.myUpdated >= :cutoff")
- int countForResourceInstance(
- @Param("id") Long theId,
- @Temporal(value = TemporalType.TIMESTAMP) @Param("cutoff") Date theCutoff
- );
-
- @Query("SELECT COUNT(*) FROM ResourceHistoryTable t WHERE t.myResourceId = :id")
- int countForResourceInstance(
- @Param("id") Long theId
- );
-
- @Query("SELECT COUNT(*) FROM ResourceHistoryTable t WHERE t.myResourceType = :type AND t.myUpdated >= :cutoff")
- int countForResourceType(
- @Param("type") String theType,
- @Temporal(value = TemporalType.TIMESTAMP) @Param("cutoff") Date theCutoff
- );
-
- @Query("SELECT COUNT(*) FROM ResourceHistoryTable t WHERE t.myResourceType = :type")
- int countForResourceType(
- @Param("type") String theType
- );
@Query("SELECT t FROM ResourceHistoryTable t LEFT OUTER JOIN FETCH t.myProvenance WHERE t.myResourceId = :id AND t.myResourceVersion = :version")
ResourceHistoryTable findForIdAndVersionAndFetchProvenance(@Param("id") long theId, @Param("version") long theVersion);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java
index 88c86d150bd..25fc81202f6 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java
@@ -43,6 +43,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
+import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@@ -63,7 +64,7 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
private DaoRegistry myDaoRegistry;
@Override
- public IResourceLookup findTargetResource(RequestPartitionId theRequestPartitionId, RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theResourceType, Class extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest) {
+ public IResourceLookup findTargetResource(@Nonnull RequestPartitionId theRequestPartitionId, RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theResourceType, Class extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest) {
IResourceLookup resolvedResource;
String idPart = theSourceResourceId.getIdPart();
try {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java
index fb7ff3be39e..5610817446d 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java
@@ -21,7 +21,9 @@ package ca.uhn.fhir.jpa.dao.index;
*/
import ca.uhn.fhir.jpa.api.config.DaoConfig;
+import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndex;
+import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
import ca.uhn.fhir.jpa.util.AddRemoveCount;
@@ -41,18 +43,22 @@ public class DaoSearchParamSynchronizer {
protected EntityManager myEntityManager;
@Autowired
private DaoConfig myDaoConfig;
+ @Autowired
+ private PartitionSettings myPartitionSettings;
+ @Autowired
+ private ModelConfig myModelConfig;
public AddRemoveCount synchronizeSearchParamsToDatabase(ResourceIndexedSearchParams theParams, ResourceTable theEntity, ResourceIndexedSearchParams existingParams) {
AddRemoveCount retVal = new AddRemoveCount();
- synchronize(theParams, theEntity, retVal, theParams.myStringParams, existingParams.myStringParams);
- synchronize(theParams, theEntity, retVal, theParams.myTokenParams, existingParams.myTokenParams);
- synchronize(theParams, theEntity, retVal, theParams.myNumberParams, existingParams.myNumberParams);
- synchronize(theParams, theEntity, retVal, theParams.myQuantityParams, existingParams.myQuantityParams);
- synchronize(theParams, theEntity, retVal, theParams.myDateParams, existingParams.myDateParams);
- synchronize(theParams, theEntity, retVal, theParams.myUriParams, existingParams.myUriParams);
- synchronize(theParams, theEntity, retVal, theParams.myCoordsParams, existingParams.myCoordsParams);
- synchronize(theParams, theEntity, retVal, theParams.myLinks, existingParams.myLinks);
+ synchronize(theEntity, retVal, theParams.myStringParams, existingParams.myStringParams);
+ synchronize(theEntity, retVal, theParams.myTokenParams, existingParams.myTokenParams);
+ synchronize(theEntity, retVal, theParams.myNumberParams, existingParams.myNumberParams);
+ synchronize(theEntity, retVal, theParams.myQuantityParams, existingParams.myQuantityParams);
+ synchronize(theEntity, retVal, theParams.myDateParams, existingParams.myDateParams);
+ synchronize(theEntity, retVal, theParams.myUriParams, existingParams.myUriParams);
+ synchronize(theEntity, retVal, theParams.myCoordsParams, existingParams.myCoordsParams);
+ synchronize(theEntity, retVal, theParams.myLinks, existingParams.myLinks);
// make sure links are indexed
theEntity.setResourceLinks(theParams.myLinks);
@@ -60,26 +66,26 @@ public class DaoSearchParamSynchronizer {
return retVal;
}
- private void synchronize(ResourceIndexedSearchParams theParams, ResourceTable theEntity, AddRemoveCount theAddRemoveCount, Collection theNewParms, Collection theExistingParms) {
- for (T next : theNewParms) {
+ private void synchronize(ResourceTable theEntity, AddRemoveCount theAddRemoveCount, Collection theNewParams, Collection theExistingParams) {
+ for (T next : theNewParams) {
next.setPartitionId(theEntity.getPartitionId());
next.calculateHashes();
}
- List quantitiesToRemove = subtract(theExistingParms, theNewParms);
- List quantitiesToAdd = subtract(theNewParms, theExistingParms);
- tryToReuseIndexEntities(quantitiesToRemove, quantitiesToAdd);
+ List paramsToRemove = subtract(theExistingParams, theNewParams);
+ List paramsToAdd = subtract(theNewParams, theExistingParams);
+ tryToReuseIndexEntities(paramsToRemove, paramsToAdd);
- for (T next : quantitiesToRemove) {
+ for (T next : paramsToRemove) {
myEntityManager.remove(next);
theEntity.getParamsQuantity().remove(next);
}
- for (T next : quantitiesToAdd) {
+ for (T next : paramsToAdd) {
myEntityManager.merge(next);
}
- theAddRemoveCount.addToAddCount(quantitiesToAdd.size());
- theAddRemoveCount.addToRemoveCount(quantitiesToRemove.size());
+ theAddRemoveCount.addToAddCount(paramsToRemove.size());
+ theAddRemoveCount.addToRemoveCount(paramsToRemove.size());
}
/**
@@ -108,6 +114,7 @@ public class DaoSearchParamSynchronizer {
// Take a row we were going to remove, and repurpose its ID
T entityToReuse = theIndexesToRemove.remove(theIndexesToRemove.size() - 1);
entityToReuse.copyMutableValuesFrom(targetEntity);
+ entityToReuse.calculateHashes();
theIndexesToAdd.set(addIndex, entityToReuse);
}
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java
index 49f9c9e18b8..674843fc957 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java
@@ -30,6 +30,7 @@ import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
import ca.uhn.fhir.jpa.model.cross.ResourceLookup;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
+import ca.uhn.fhir.jpa.util.QueryChunker;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
@@ -42,23 +43,22 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.hl7.fhir.instance.model.api.IIdType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.stereotype.Service;
import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -84,7 +84,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
*/
@Service
public class IdHelperService {
- private static final Logger ourLog = LoggerFactory.getLogger(IdHelperService.class);
@Autowired
protected IForcedIdDao myForcedIdDao;
@@ -99,11 +98,13 @@ public class IdHelperService {
private Cache myPersistentIdCache;
private Cache myResourceLookupCache;
+ private Cache> myForcedIdCache;
@PostConstruct
public void start() {
myPersistentIdCache = newCache();
myResourceLookupCache = newCache();
+ myForcedIdCache = newCache();
}
@@ -118,7 +119,7 @@ public class IdHelperService {
* @throws ResourceNotFoundException If the ID can not be found
*/
@Nonnull
- public IResourceLookup resolveResourceIdentity(RequestPartitionId theRequestPartitionId, String theResourceType, String theResourceId, RequestDetails theRequestDetails) throws ResourceNotFoundException {
+ public IResourceLookup resolveResourceIdentity(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theResourceId, RequestDetails theRequestDetails) throws ResourceNotFoundException {
// We only pass 1 input in so only 0..1 will come back
IdDt id = new IdDt(theResourceType, theResourceId);
Collection matches = translateForcedIdToPids(theRequestPartitionId, theRequestDetails, Collections.singletonList(id));
@@ -135,7 +136,7 @@ public class IdHelperService {
* @throws ResourceNotFoundException If the ID can not be found
*/
@Nonnull
- public ResourcePersistentId resolveResourcePersistentIds(RequestPartitionId theRequestPartitionId, String theResourceType, String theId) {
+ public ResourcePersistentId resolveResourcePersistentIds(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theId) {
Long retVal;
if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(theId)) {
if (myDaoConfig.isDeleteEnabled()) {
@@ -159,7 +160,7 @@ public class IdHelperService {
* are deleted (but note that forced IDs can't change, so the cache can't return incorrect results)
*/
@Nonnull
- public List resolveResourcePersistentIdsWithCache(RequestPartitionId theRequestPartitionId, List theIds, RequestDetails theRequest) {
+ public List resolveResourcePersistentIdsWithCache(RequestPartitionId theRequestPartitionId, List theIds) {
theIds.forEach(id -> Validate.isTrue(id.hasIdPart()));
if (theIds.isEmpty()) {
@@ -202,14 +203,14 @@ public class IdHelperService {
if (nextIds.size() > 0) {
Collection