diff --git a/.editorconfig b/.editorconfig
index 499513576f9..f19de7e2a01 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -5,12 +5,7 @@ end_of_line = lf
insert_final_newline = true
tab_width = 3
indent_size = 3
-
-[*.java]
charset = utf-8
-indent_style = tab
-tab_width = 3
-indent_size = 3
[*.xml]
charset = utf-8
@@ -30,3 +25,259 @@ indent_style = tab
tab_width = 3
indent_size = 3
+
+[*.java]
+charset = utf-8
+indent_style = tab
+tab_width = 3
+indent_size = 3
+ij_java_align_consecutive_assignments = false
+ij_java_align_consecutive_variable_declarations = false
+ij_java_align_group_field_declarations = false
+ij_java_align_multiline_annotation_parameters = false
+ij_java_align_multiline_array_initializer_expression = false
+ij_java_align_multiline_assignment = false
+ij_java_align_multiline_binary_operation = false
+ij_java_align_multiline_chained_methods = false
+ij_java_align_multiline_extends_list = false
+ij_java_align_multiline_for = true
+ij_java_align_multiline_method_parentheses = false
+ij_java_align_multiline_parameters = true
+ij_java_align_multiline_parameters_in_calls = false
+ij_java_align_multiline_parenthesized_expression = false
+ij_java_align_multiline_records = true
+ij_java_align_multiline_resources = true
+ij_java_align_multiline_ternary_operation = false
+ij_java_align_multiline_text_blocks = false
+ij_java_align_multiline_throws_list = false
+ij_java_align_subsequent_simple_methods = false
+ij_java_align_throws_keyword = false
+ij_java_annotation_parameter_wrap = off
+ij_java_array_initializer_new_line_after_left_brace = false
+ij_java_array_initializer_right_brace_on_new_line = false
+ij_java_array_initializer_wrap = off
+ij_java_assert_statement_colon_on_next_line = false
+ij_java_assert_statement_wrap = off
+ij_java_assignment_wrap = off
+ij_java_binary_operation_sign_on_next_line = false
+ij_java_binary_operation_wrap = off
+ij_java_blank_lines_after_anonymous_class_header = 0
+ij_java_blank_lines_after_class_header = 0
+ij_java_blank_lines_after_imports = 1
+ij_java_blank_lines_after_package = 1
+ij_java_blank_lines_around_class = 1
+ij_java_blank_lines_around_field = 0
+ij_java_blank_lines_around_field_in_interface = 0
+ij_java_blank_lines_around_initializer = 1
+ij_java_blank_lines_around_method = 1
+ij_java_blank_lines_around_method_in_interface = 1
+ij_java_blank_lines_before_class_end = 0
+ij_java_blank_lines_before_imports = 1
+ij_java_blank_lines_before_method_body = 0
+ij_java_blank_lines_before_package = 0
+ij_java_block_brace_style = end_of_line
+ij_java_block_comment_at_first_column = true
+ij_java_call_parameters_new_line_after_left_paren = false
+ij_java_call_parameters_right_paren_on_new_line = false
+ij_java_call_parameters_wrap = off
+ij_java_case_statement_on_separate_line = true
+ij_java_catch_on_new_line = false
+ij_java_class_annotation_wrap = split_into_lines
+ij_java_class_brace_style = end_of_line
+ij_java_class_count_to_use_import_on_demand = 999
+ij_java_class_names_in_javadoc = 1
+ij_java_do_not_indent_top_level_class_members = false
+ij_java_do_not_wrap_after_single_annotation = false
+ij_java_do_while_brace_force = never
+ij_java_doc_add_blank_line_after_description = true
+ij_java_doc_add_blank_line_after_param_comments = false
+ij_java_doc_add_blank_line_after_return = false
+ij_java_doc_add_p_tag_on_empty_lines = true
+ij_java_doc_align_exception_comments = true
+ij_java_doc_align_param_comments = true
+ij_java_doc_do_not_wrap_if_one_line = false
+ij_java_doc_enable_formatting = true
+ij_java_doc_enable_leading_asterisks = true
+ij_java_doc_indent_on_continuation = false
+ij_java_doc_keep_empty_lines = true
+ij_java_doc_keep_empty_parameter_tag = true
+ij_java_doc_keep_empty_return_tag = true
+ij_java_doc_keep_empty_throws_tag = true
+ij_java_doc_keep_invalid_tags = true
+ij_java_doc_param_description_on_new_line = false
+ij_java_doc_preserve_line_breaks = false
+ij_java_doc_use_throws_not_exception_tag = true
+ij_java_else_on_new_line = false
+ij_java_entity_dd_suffix = EJB
+ij_java_entity_eb_suffix = Bean
+ij_java_entity_hi_suffix = Home
+ij_java_entity_lhi_prefix = Local
+ij_java_entity_lhi_suffix = Home
+ij_java_entity_li_prefix = Local
+ij_java_entity_pk_class = java.lang.String
+ij_java_entity_vo_suffix = VO
+ij_java_enum_constants_wrap = off
+ij_java_extends_keyword_wrap = off
+ij_java_extends_list_wrap = off
+ij_java_field_annotation_wrap = split_into_lines
+ij_java_field_name_prefix = my
+ij_java_finally_on_new_line = false
+ij_java_for_brace_force = never
+ij_java_for_statement_new_line_after_left_paren = false
+ij_java_for_statement_right_paren_on_new_line = false
+ij_java_for_statement_wrap = off
+ij_java_generate_final_locals = false
+ij_java_generate_final_parameters = false
+ij_java_if_brace_force = never
+ij_java_imports_layout = *,|,javax.**,java.**,|,$*
+ij_java_indent_case_from_switch = true
+ij_java_insert_inner_class_imports = false
+ij_java_insert_override_annotation = true
+ij_java_keep_blank_lines_before_right_brace = 2
+ij_java_keep_blank_lines_between_package_declaration_and_header = 2
+ij_java_keep_blank_lines_in_code = 2
+ij_java_keep_blank_lines_in_declarations = 2
+ij_java_keep_control_statement_in_one_line = true
+ij_java_keep_first_column_comment = true
+ij_java_keep_indents_on_empty_lines = false
+ij_java_keep_line_breaks = true
+ij_java_keep_multiple_expressions_in_one_line = false
+ij_java_keep_simple_blocks_in_one_line = false
+ij_java_keep_simple_classes_in_one_line = false
+ij_java_keep_simple_lambdas_in_one_line = false
+ij_java_keep_simple_methods_in_one_line = false
+ij_java_label_indent_absolute = false
+ij_java_label_indent_size = 0
+ij_java_lambda_brace_style = end_of_line
+ij_java_layout_static_imports_separately = true
+ij_java_line_comment_add_space = false
+ij_java_line_comment_at_first_column = true
+ij_java_message_dd_suffix = EJB
+ij_java_message_eb_suffix = Bean
+ij_java_method_annotation_wrap = split_into_lines
+ij_java_method_brace_style = end_of_line
+ij_java_method_call_chain_wrap = off
+ij_java_method_parameters_new_line_after_left_paren = false
+ij_java_method_parameters_right_paren_on_new_line = false
+ij_java_method_parameters_wrap = off
+ij_java_modifier_list_wrap = false
+ij_java_names_count_to_use_import_on_demand = 999
+ij_java_new_line_after_lparen_in_record_header = false
+ij_java_packages_to_use_import_on_demand = java.awt.*,javax.swing.*
+ij_java_parameter_annotation_wrap = off
+ij_java_parameter_name_prefix = the
+ij_java_parentheses_expression_new_line_after_left_paren = false
+ij_java_parentheses_expression_right_paren_on_new_line = false
+ij_java_place_assignment_sign_on_next_line = false
+ij_java_prefer_longer_names = true
+ij_java_prefer_parameters_wrap = false
+ij_java_record_components_wrap = normal
+ij_java_repeat_synchronized = true
+ij_java_replace_instanceof_and_cast = false
+ij_java_replace_null_check = true
+ij_java_replace_sum_lambda_with_method_ref = true
+ij_java_resource_list_new_line_after_left_paren = false
+ij_java_resource_list_right_paren_on_new_line = false
+ij_java_resource_list_wrap = off
+ij_java_rparen_on_new_line_in_record_header = false
+ij_java_session_dd_suffix = EJB
+ij_java_session_eb_suffix = Bean
+ij_java_session_hi_suffix = Home
+ij_java_session_lhi_prefix = Local
+ij_java_session_lhi_suffix = Home
+ij_java_session_li_prefix = Local
+ij_java_session_si_suffix = Service
+ij_java_space_after_closing_angle_bracket_in_type_argument = false
+ij_java_space_after_colon = true
+ij_java_space_after_comma = true
+ij_java_space_after_comma_in_type_arguments = true
+ij_java_space_after_for_semicolon = true
+ij_java_space_after_quest = true
+ij_java_space_after_type_cast = true
+ij_java_space_before_annotation_array_initializer_left_brace = false
+ij_java_space_before_annotation_parameter_list = false
+ij_java_space_before_array_initializer_left_brace = false
+ij_java_space_before_catch_keyword = true
+ij_java_space_before_catch_left_brace = true
+ij_java_space_before_catch_parentheses = true
+ij_java_space_before_class_left_brace = true
+ij_java_space_before_colon = true
+ij_java_space_before_colon_in_foreach = true
+ij_java_space_before_comma = false
+ij_java_space_before_do_left_brace = true
+ij_java_space_before_else_keyword = true
+ij_java_space_before_else_left_brace = true
+ij_java_space_before_finally_keyword = true
+ij_java_space_before_finally_left_brace = true
+ij_java_space_before_for_left_brace = true
+ij_java_space_before_for_parentheses = true
+ij_java_space_before_for_semicolon = false
+ij_java_space_before_if_left_brace = true
+ij_java_space_before_if_parentheses = true
+ij_java_space_before_method_call_parentheses = false
+ij_java_space_before_method_left_brace = true
+ij_java_space_before_method_parentheses = false
+ij_java_space_before_opening_angle_bracket_in_type_parameter = false
+ij_java_space_before_quest = true
+ij_java_space_before_switch_left_brace = true
+ij_java_space_before_switch_parentheses = true
+ij_java_space_before_synchronized_left_brace = true
+ij_java_space_before_synchronized_parentheses = true
+ij_java_space_before_try_left_brace = true
+ij_java_space_before_try_parentheses = true
+ij_java_space_before_type_parameter_list = false
+ij_java_space_before_while_keyword = true
+ij_java_space_before_while_left_brace = true
+ij_java_space_before_while_parentheses = true
+ij_java_space_inside_one_line_enum_braces = false
+ij_java_space_within_empty_array_initializer_braces = false
+ij_java_space_within_empty_method_call_parentheses = false
+ij_java_space_within_empty_method_parentheses = false
+ij_java_spaces_around_additive_operators = true
+ij_java_spaces_around_assignment_operators = true
+ij_java_spaces_around_bitwise_operators = true
+ij_java_spaces_around_equality_operators = true
+ij_java_spaces_around_lambda_arrow = true
+ij_java_spaces_around_logical_operators = true
+ij_java_spaces_around_method_ref_dbl_colon = false
+ij_java_spaces_around_multiplicative_operators = true
+ij_java_spaces_around_relational_operators = true
+ij_java_spaces_around_shift_operators = true
+ij_java_spaces_around_type_bounds_in_type_parameters = true
+ij_java_spaces_around_unary_operator = false
+ij_java_spaces_within_angle_brackets = false
+ij_java_spaces_within_annotation_parentheses = false
+ij_java_spaces_within_array_initializer_braces = false
+ij_java_spaces_within_braces = false
+ij_java_spaces_within_brackets = false
+ij_java_spaces_within_cast_parentheses = false
+ij_java_spaces_within_catch_parentheses = false
+ij_java_spaces_within_for_parentheses = false
+ij_java_spaces_within_if_parentheses = false
+ij_java_spaces_within_method_call_parentheses = false
+ij_java_spaces_within_method_parentheses = false
+ij_java_spaces_within_parentheses = false
+ij_java_spaces_within_switch_parentheses = false
+ij_java_spaces_within_synchronized_parentheses = false
+ij_java_spaces_within_try_parentheses = false
+ij_java_spaces_within_while_parentheses = false
+ij_java_special_else_if_treatment = true
+ij_java_static_field_name_prefix = our
+ij_java_subclass_name_suffix = Impl
+ij_java_ternary_operation_signs_on_next_line = false
+ij_java_ternary_operation_wrap = off
+ij_java_test_name_suffix = Test
+ij_java_throws_keyword_wrap = off
+ij_java_throws_list_wrap = off
+ij_java_use_external_annotations = false
+ij_java_use_fq_class_names = false
+ij_java_use_relative_indents = false
+ij_java_use_single_class_imports = true
+ij_java_variable_annotation_wrap = off
+ij_java_visibility = public
+ij_java_while_brace_force = never
+ij_java_while_on_new_line = false
+ij_java_wrap_comments = false
+ij_java_wrap_first_method_in_call_chain = false
+ij_java_wrap_long_lines = false
+
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
@@ -425,7 +425,7 @@ public enum Pointcut {
"java.io.Writer",
"ca.uhn.fhir.rest.api.server.RequestDetails",
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
- ),
+ ),
/**
@@ -1401,7 +1401,7 @@ public enum Pointcut {
*
*
*
- * Hooks should return an instance of
- * Hooks should return an instance of
+ * This hook will only be called if
+ * partitioning is enabled in the JPA server.
+ *
+ * Hooks may accept the following parameters:
+ *
+ * Hooks must return void.
+ *
+ * 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()}
+ * ca.uhn.fhir.jpa.api.model.RequestPartitionId
or null
.
+ * Hooks must return an instance of ca.uhn.fhir.interceptor.model.RequestPartitionId
.
* ca.uhn.fhir.jpa.api.model.RequestPartitionId
or null
.
+ * Hooks must return an instance of ca.uhn.fhir.interceptor.model.RequestPartitionId
.
*
+ *
+ * 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/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
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:
-
-
-
-
-
-### 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. 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"
- } ]
-}
-```
-
-## 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.
-
-
-
- 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-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 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-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. 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"
+ } ]
+}
+```
+
+## 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.
+
+
+
+ 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-delete-partition
+```
+
+The following request body could be used:
+
+```json
+{
+ "resourceType": "Parameters",
+ "parameter": [ {
+ "name": "id",
+ "valueInteger": 123
+ } ]
+}
+```
+
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.
+
+
+
+ Name
+ Type
+ Cardinality
+ Description
+
+
+
+id
+ Integer
+ 1..1
+
+ The numeric ID for the partition to update. This ID must already exist.
+
+
+
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 MapUnified Codes for Units of Measure (UCUM)
+
+ ValueSet:
+ (...)/ValueSet/ucum-units
+
+ CodeSystem: http://unitsofmeasure.org
+
+ Codes are validated using the UcumEssenceService provided by the UCUM Java library.
+
+
+ * 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-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 01d1e9332ff..7afabe7f2ee 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())); // TODO: Looking at moving the lastn entities into jpa.model.entity package. Note that moving the lastn entities may require re-building elasticsearch indexes. 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
* 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
+ * 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()} + *
+ * Default is {@literal true} beginning in HAPI FHIR 5.0.0 + * + * + * @since 5.0.0 + */ + public void setUseOrdinalDatesForDayPrecisionSearches(boolean theUseOrdinalDates) { + myUseOrdinalDatesForDayPrecisionSearches = theUseOrdinalDates; + } + + /** + * If set totrue
(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:
+ * 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:
+ *
* It navigates almost every possible path in every FHIR resource in every version of FHIR,
* and creates a resource with that path populated, just to ensure that we can index it
* without generating any warnings.
*/
@Test
public void testAllCombinations() throws Exception {
+ PartitionSettings partitionSettings = new PartitionSettings();
FhirContext ctx = FhirContext.forDstu2();
ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry(ctx);
- process(ctx, new SearchParamExtractorDstu2(ctx, searchParamRegistry).setPartitionConfigForUnitTest(new PartitionSettings()));
+ process(ctx, new SearchParamExtractorDstu2(new ModelConfig(), partitionSettings, ctx, searchParamRegistry));
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(), partitionSettings, ctx, new DefaultProfileValidationSupport(ctx), searchParamRegistry));
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(), partitionSettings, ctx, new DefaultProfileValidationSupport(ctx), searchParamRegistry));
ctx = FhirContext.forR5();
searchParamRegistry = new MySearchParamRegistry(ctx);
- process(ctx, new SearchParamExtractorR5(ctx, new DefaultProfileValidationSupport(ctx), searchParamRegistry).setPartitionConfigForUnitTest(new PartitionSettings()));
+ process(ctx, new SearchParamExtractorR5(new ModelConfig(), partitionSettings, ctx, new DefaultProfileValidationSupport(ctx), searchParamRegistry));
}
private void process(FhirContext theCtx, BaseSearchParamExtractor theExtractor) throws Exception {
@@ -75,7 +92,6 @@ public class SearchParamExtractorMegaTest {
}
-
theElementStack.add(theElementDef);
if (theElementDef instanceof BaseRuntimeElementCompositeDefinition) {
@@ -136,7 +152,7 @@ public class SearchParamExtractorMegaTest {
BaseRuntimeElementDefinition nextElement = theElementStack.get(i);
if (i > 0) {
- previousChildArguments = theChildStack.get(i-1).getInstanceConstructorArguments();
+ previousChildArguments = theChildStack.get(i - 1).getInstanceConstructorArguments();
}
IBase nextObject = nextElement.newInstance(previousChildArguments);
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 cb073567d14..869cc01ea95 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
@@ -30,7 +30,9 @@ import java.time.Duration;
import java.time.Instant;
import java.util.Date;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@@ -149,8 +151,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);
@@ -166,7 +168,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);
@@ -209,7 +211,7 @@ public class InMemoryResourceMatcherR5Test {
private ResourceIndexedSearchParams extractDateSearchParam(Observation theObservation) {
ResourceIndexedSearchParams retval = new ResourceIndexedSearchParams();
BaseDateTimeType dateValue = (BaseDateTimeType) theObservation.getEffective();
- ResourceIndexedSearchParamDate dateParam = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "date", dateValue.getValue(), dateValue.getValue(), dateValue.getValueAsString());
+ ResourceIndexedSearchParamDate dateParam = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "date", dateValue.getValue(), dateValue.getValueAsString(), dateValue.getValue(), dateValue.getValueAsString(), dateValue.getValueAsString());
retval.myDateParams.add(dateParam);
return retval;
}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionRegistry.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionRegistry.java
index fd55e180981..4cb5ed5e0f3 100644
--- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionRegistry.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionRegistry.java
@@ -90,11 +90,12 @@ public class SubscriptionRegistry {
String channelName = mySubscriptionDeliveryChannelNamer.nameFromSubscription(canonicalized);
- ourLog.info("Registering active subscription {}", subscriptionId);
ActiveSubscription activeSubscription = new ActiveSubscription(canonicalized, channelName);
mySubscriptionChannelRegistry.add(activeSubscription);
myActiveSubscriptionCache.put(subscriptionId, activeSubscription);
+ ourLog.info("Registered active subscription {} - Have {} registered", subscriptionId, myActiveSubscriptionCache.size());
+
// Interceptor call: SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED
HookParams params = new HookParams()
.add(CanonicalSubscription.class, canonicalized);
@@ -105,10 +106,10 @@ public class SubscriptionRegistry {
public void unregisterSubscriptionIfRegistered(String theSubscriptionId) {
Validate.notNull(theSubscriptionId);
- ourLog.info("Unregistering active subscription {}", theSubscriptionId);
ActiveSubscription activeSubscription = myActiveSubscriptionCache.remove(theSubscriptionId);
if (activeSubscription != null) {
mySubscriptionChannelRegistry.remove(activeSubscription);
+ ourLog.info("Unregistered active subscription {} - Have {} registered", theSubscriptionId, myActiveSubscriptionCache.size());
}
}
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/IBundleProvider.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/IBundleProvider.java
index 26e54eae2c6..3f87a817433 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/IBundleProvider.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/IBundleProvider.java
@@ -1,5 +1,6 @@
package ca.uhn.fhir.rest.api.server;
+import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
@@ -163,4 +164,12 @@ public interface IBundleProvider {
return getResources(0, 1).isEmpty();
}
+ /**
+ * Returns the value of {@link #size()} and throws a {@link NullPointerException} of it is null
+ */
+ default int sizeOrThrowNpe() {
+ Integer retVal = size();
+ Validate.notNull(retVal, "size() returned null");
+ return retVal;
+ }
}
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/SimplePreResourceShowDetails.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/SimplePreResourceShowDetails.java
index ed7b8b79a32..3bc356d9d9c 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/SimplePreResourceShowDetails.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/SimplePreResourceShowDetails.java
@@ -24,50 +24,64 @@ import com.google.common.collect.Lists;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Iterator;
import java.util.List;
-public class SimplePreResourceShowDetails implements IPreResourceShowDetails, Iterable
- * If the Apache Http Server
- * If you want to set the protocol based on something other than the constructor argument, you should be able to do so by overriding
- * 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
+ * If the Apache Http Server
+ * List of supported forward headers:
+ * 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)
- * protocol
.
- * 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.
+ * 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
*
null
*/
- IAuthRuleFinished withTester(IAuthRuleTester theTester);
+ IAuthRuleFinished withTester(@Nullable IAuthRuleTester theTester);
}
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/IAuthRuleTester.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/IAuthRuleTester.java
index 0ccb37fd85b..2b15386f5d1 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/IAuthRuleTester.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/IAuthRuleTester.java
@@ -41,8 +41,28 @@ public interface IAuthRuleTester {
* THIS IS AN EXPERIMENTAL API! Feedback is welcome, and this API
* may change.
*
+ * @param theOperation The FHIR operation being performed - Note that this is not necessarily the same as the value obtained from invoking
+ * {@link RequestDetails#getRestOperationType()} on {@literal theRequestDetails} because multiple operations can be nested within
+ * an HTTP request using FHIR transaction and batch operations
* @since 3.4.0
*/
- boolean matches(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IIdType theInputResourceId, IBaseResource theInputResource);
+ default boolean matches(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IIdType theInputResourceId, IBaseResource theInputResource) {
+ return true;
+ }
+
+ /**
+ * Allows user-supplied logic for authorization rules.
+ *
+ * THIS IS AN EXPERIMENTAL API! Feedback is welcome, and this API
+ * may change.
+ *
+ * @param theOperation The FHIR operation being performed - Note that this is not necessarily the same as the value obtained from invoking
+ * {@link RequestDetails#getRestOperationType()} on {@literal theRequestDetails} because multiple operations can be nested within
+ * an HTTP request using FHIR transaction and batch operations
+ * @since 5.0.0
+ */
+ default boolean matchesOutput(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theOutputResource) {
+ return true;
+ }
}
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/OperationRule.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/OperationRule.java
index 0eaea1e5798..38e8a10783b 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/OperationRule.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/OperationRule.java
@@ -84,10 +84,6 @@ class OperationRule extends BaseRule implements IAuthRule {
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier, Set