mirror of
https://github.com/hapifhir/hapi-fhir.git
synced 2025-03-09 14:33:32 +00:00
* Work on multitenancy * Second ticket started * More multitenancy work * Work on tenancy security * Work on multitenancy * Test fixes * Add docs * cs tweak * Multitenancy fixes * Add debug log * Fix build * Add headers * Add partition selection interceptor hook * Update hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java Co-authored-by: Ken Stevens <khstevens@gmail.com> * Account for review comments * Resolve FIXMES * Work on tests * Test fixes * A bit more cleanup * Work on multitenancy * Update hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ClientExamples.java Co-authored-by: Ken Stevens <khstevens@gmail.com> * Update hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/examples.md Co-authored-by: Ken Stevens <khstevens@gmail.com> * Address review comments * Allow disabling text modifier indexing * Add changelog * Docs fixes * Address review comment * Resolve merge conflicts * Merge master * Compile fix * Test fix Co-authored-by: Ken Stevens <khstevens@gmail.com>
This commit is contained in:
parent
35c2b7a2c1
commit
a77aa6a28e
261
.editorconfig
261
.editorconfig
@ -5,12 +5,7 @@ end_of_line = lf
|
|||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
tab_width = 3
|
tab_width = 3
|
||||||
indent_size = 3
|
indent_size = 3
|
||||||
|
|
||||||
[*.java]
|
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
indent_style = tab
|
|
||||||
tab_width = 3
|
|
||||||
indent_size = 3
|
|
||||||
|
|
||||||
[*.xml]
|
[*.xml]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
@ -30,3 +25,259 @@ indent_style = tab
|
|||||||
tab_width = 3
|
tab_width = 3
|
||||||
indent_size = 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
|
||||||
|
|
||||||
|
@ -425,7 +425,7 @@ public enum Pointcut {
|
|||||||
"java.io.Writer",
|
"java.io.Writer",
|
||||||
"ca.uhn.fhir.rest.api.server.RequestDetails",
|
"ca.uhn.fhir.rest.api.server.RequestDetails",
|
||||||
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
|
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1401,7 +1401,7 @@ public enum Pointcut {
|
|||||||
* </li>
|
* </li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <p>
|
||||||
* Hooks should return an instance of <code>ca.uhn.fhir.jpa.api.model.RequestPartitionId</code> or <code>null</code>.
|
* Hooks must return an instance of <code>ca.uhn.fhir.interceptor.model.RequestPartitionId</code>.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
STORAGE_PARTITION_IDENTIFY_CREATE(
|
STORAGE_PARTITION_IDENTIFY_CREATE(
|
||||||
@ -1440,7 +1440,7 @@ public enum Pointcut {
|
|||||||
* </li>
|
* </li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <p>
|
||||||
* Hooks should return an instance of <code>ca.uhn.fhir.jpa.api.model.RequestPartitionId</code> or <code>null</code>.
|
* Hooks must return an instance of <code>ca.uhn.fhir.interceptor.model.RequestPartitionId</code>.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
STORAGE_PARTITION_IDENTIFY_READ(
|
STORAGE_PARTITION_IDENTIFY_READ(
|
||||||
@ -1451,6 +1451,49 @@ public enum Pointcut {
|
|||||||
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
|
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
|
||||||
),
|
),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>Storage Hook:</b>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* This hook will only be called if
|
||||||
|
* partitioning is enabled in the JPA server.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Hooks may accept the following parameters:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>
|
||||||
|
* ca.uhn.fhir.interceptor.model.RequestPartitionId - The partition ID that was selected
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* 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.
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* 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.
|
||||||
|
* </li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* Hooks must return void.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
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"
|
||||||
|
),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <b>Performance Tracing Hook:</b>
|
* <b>Performance Tracing Hook:</b>
|
||||||
* This hook is invoked when any informational messages generated by the
|
* This hook is invoked when any informational messages generated by the
|
||||||
|
@ -20,27 +20,55 @@ package ca.uhn.fhir.interceptor.model;
|
|||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||||
|
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
/**
|
||||||
|
* @since 5.0.0
|
||||||
|
*/
|
||||||
public class RequestPartitionId {
|
public class RequestPartitionId {
|
||||||
|
|
||||||
private final Integer myPartitionId;
|
private static final RequestPartitionId ALL_PARTITIONS = new RequestPartitionId();
|
||||||
private final LocalDate myPartitionDate;
|
private final LocalDate myPartitionDate;
|
||||||
|
private final boolean myAllPartitions;
|
||||||
|
private final Integer myPartitionId;
|
||||||
private final String myPartitionName;
|
private final String myPartitionName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor for a single partition
|
||||||
*/
|
*/
|
||||||
private RequestPartitionId(@Nullable String thePartitionName, @Nullable Integer thePartitionId, @Nullable LocalDate thePartitionDate) {
|
private RequestPartitionId(@Nullable String thePartitionName, @Nullable Integer thePartitionId, @Nullable LocalDate thePartitionDate) {
|
||||||
myPartitionName = thePartitionName;
|
|
||||||
myPartitionId = thePartitionId;
|
myPartitionId = thePartitionId;
|
||||||
|
myPartitionName = thePartitionName;
|
||||||
myPartitionDate = thePartitionDate;
|
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() {
|
public String getPartitionName() {
|
||||||
return myPartitionName;
|
return myPartitionName;
|
||||||
}
|
}
|
||||||
@ -50,32 +78,59 @@ public class RequestPartitionId {
|
|||||||
return myPartitionId;
|
return myPartitionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public LocalDate getPartitionDate() {
|
|
||||||
return myPartitionDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getPartitionIdStringOrNullString();
|
return "RequestPartitionId[id=" + getPartitionId() + ", name=" + getPartitionName() + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the partition ID (numeric) as a string, or the string "null"
|
* Returns the partition ID (numeric) as a string, or the string "null"
|
||||||
*/
|
*/
|
||||||
public String getPartitionIdStringOrNullString() {
|
public String getPartitionIdStringOrNullString() {
|
||||||
return defaultIfNull(myPartitionId, "null").toString();
|
if (myPartitionId == null) {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
return myPartitionId.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Create a string representation suitable for use as a cache key. Null aware.
|
public boolean equals(Object theO) {
|
||||||
*/
|
if (this == theO) {
|
||||||
public static String stringifyForKey(RequestPartitionId theRequestPartitionId) {
|
return true;
|
||||||
String retVal = "(null)";
|
|
||||||
if (theRequestPartitionId != null) {
|
|
||||||
retVal = theRequestPartitionId.getPartitionIdStringOrNullString();
|
|
||||||
}
|
}
|
||||||
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
|
@Nonnull
|
||||||
@ -99,8 +154,23 @@ public class RequestPartitionId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@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);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -264,6 +264,13 @@ public class Constants {
|
|||||||
public static final String PARAM_FHIRPATH = "_fhirpath";
|
public static final String PARAM_FHIRPATH = "_fhirpath";
|
||||||
public static final String PARAM_TYPE = "_type";
|
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 {
|
static {
|
||||||
CHARSET_UTF8 = StandardCharsets.UTF_8;
|
CHARSET_UTF8 = StandardCharsets.UTF_8;
|
||||||
CHARSET_US_ASCII = StandardCharsets.ISO_8859_1;
|
CHARSET_US_ASCII = StandardCharsets.ISO_8859_1;
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -60,11 +60,15 @@ public interface IHttpRequest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the request URI, or null
|
* Return the request URI, or null
|
||||||
|
*
|
||||||
|
* @see #getUri()
|
||||||
*/
|
*/
|
||||||
String getUri();
|
String getUri();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modify the request URI, or null
|
* Modify the request URI, or null
|
||||||
|
*
|
||||||
|
* @see #setUrlSource(UrlSourceEnum)
|
||||||
*/
|
*/
|
||||||
void setUri(String theUrl);
|
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)
|
* @param theHeaderName The header name, e.g. "Accept" (must not be null or blank)
|
||||||
*/
|
*/
|
||||||
void removeHeaders(String theHeaderName);
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
}
|
@ -9,9 +9,16 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|||||||
import ca.uhn.fhir.util.DateUtils;
|
import ca.uhn.fhir.util.DateUtils;
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
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 java.lang.String.format;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
|
@ -23,7 +23,12 @@ package ca.uhn.fhir.util;
|
|||||||
import java.lang.ref.SoftReference;
|
import java.lang.ref.SoftReference;
|
||||||
import java.text.ParsePosition;
|
import java.text.ParsePosition;
|
||||||
import java.text.SimpleDateFormat;
|
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
|
* A utility class for parsing and formatting HTTP dates as used in cookies and
|
||||||
|
@ -96,7 +96,7 @@ public class MetaUtil {
|
|||||||
value.setValue(theValue);
|
value.setValue(theValue);
|
||||||
sourceExtension.setValue(value);
|
sourceExtension.setValue(value);
|
||||||
} else {
|
} 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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,16 +22,26 @@ package org.hl7.fhir.instance.model.api;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
public interface IPrimitiveType<T> extends IBaseDatatype {
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
void setValueAsString(String theValue) throws IllegalArgumentException;
|
public interface IPrimitiveType<T> extends IBaseDatatype {
|
||||||
|
|
||||||
String getValueAsString();
|
String getValueAsString();
|
||||||
|
|
||||||
T getValue();
|
void setValueAsString(String theValue) throws IllegalArgumentException;
|
||||||
|
|
||||||
boolean hasValue();
|
T getValue();
|
||||||
|
|
||||||
IPrimitiveType<T> setValue(T theValue) throws IllegalArgumentException;
|
IPrimitiveType<T> 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> T toValueOrNull(@Nullable IPrimitiveType<T> thePrimitiveType) {
|
||||||
|
return thePrimitiveType != null ? thePrimitiveType.getValue() : null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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.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.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.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.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
|
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.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.RequestPartitionHelperSvc.blacklistedResourceTypeForPartitioning=Resource type {0} can not be partitioned
|
||||||
ca.uhn.fhir.jpa.partition.RequestPartitionHelperService.unknownPartitionId=Unknown partition ID: {0}
|
ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc.unknownPartitionId=Unknown partition ID: {0}
|
||||||
ca.uhn.fhir.jpa.partition.RequestPartitionHelperService.unknownPartitionName=Unknown partition name: {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}
|
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.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.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.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}
|
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.jpa.partition.PartitionLookupSvcImpl.cantRenameDefaultPartition=Can not rename default partition
|
||||||
|
|
||||||
ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor.unknownTenantName=Unknown tenant: {0}
|
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
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -16,6 +16,12 @@
|
|||||||
<Encoding>utf-8</Encoding>
|
<Encoding>utf-8</Encoding>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
|
<!-- HAPI HL7v2 App Module -->
|
||||||
|
<logger name="ca.uhn.hl7v2.app" additivity="false" level="debug">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
<appender-ref ref="FILE" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
<logger name="ca.uhn.fhir" additivity="false" level="debug">
|
<logger name="ca.uhn.fhir" additivity="false" level="debug">
|
||||||
<appender-ref ref="STDOUT" />
|
<appender-ref ref="STDOUT" />
|
||||||
<appender-ref ref="FILE" />
|
<appender-ref ref="FILE" />
|
||||||
|
@ -26,6 +26,7 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
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.IHttpRequest;
|
||||||
import ca.uhn.fhir.rest.client.api.IHttpResponse;
|
import ca.uhn.fhir.rest.client.api.IHttpResponse;
|
||||||
import ca.uhn.fhir.util.StopWatch;
|
import ca.uhn.fhir.util.StopWatch;
|
||||||
@ -39,7 +40,7 @@ import okhttp3.RequestBody;
|
|||||||
*
|
*
|
||||||
* @author Matthew Clarke | matthew.clarke@orionhealth.com | Orion Health
|
* @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 final Request.Builder myRequestBuilder;
|
||||||
private Factory myClient;
|
private Factory myClient;
|
||||||
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.rest.client.apache;
|
|||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.client.api.BaseHttpRequest;
|
||||||
import ca.uhn.fhir.rest.client.api.IHttpRequest;
|
import ca.uhn.fhir.rest.client.api.IHttpRequest;
|
||||||
import ca.uhn.fhir.rest.client.api.IHttpResponse;
|
import ca.uhn.fhir.rest.client.api.IHttpResponse;
|
||||||
import ca.uhn.fhir.util.StopWatch;
|
import ca.uhn.fhir.util.StopWatch;
|
||||||
@ -36,7 +37,11 @@ import org.apache.http.entity.ContentType;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.Charset;
|
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
|
* 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
|
* @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 HttpClient myClient;
|
||||||
private HttpRequestBase myRequest;
|
private HttpRequestBase myRequest;
|
||||||
@ -112,13 +117,13 @@ public class ApacheHttpRequest implements IHttpRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUri(String theUrl) {
|
public String getUri() {
|
||||||
myRequest.setURI(URI.create(theUrl));
|
return myRequest.getURI().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUri() {
|
public void setUri(String theUrl) {
|
||||||
return myRequest.getURI().toString();
|
myRequest.setURI(URI.create(theUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -20,7 +20,13 @@ package ca.uhn.fhir.rest.client.impl;
|
|||||||
* #L%
|
* #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.IQueryParameterType;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.model.api.Include;
|
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.InstantDt;
|
||||||
import ca.uhn.fhir.model.primitive.UriDt;
|
import ca.uhn.fhir.model.primitive.UriDt;
|
||||||
import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
|
import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
|
||||||
import ca.uhn.fhir.parser.IParser;
|
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.IGenericClient;
|
||||||
import ca.uhn.fhir.rest.client.api.IHttpClient;
|
import ca.uhn.fhir.rest.client.api.IHttpClient;
|
||||||
import ca.uhn.fhir.rest.client.api.IHttpRequest;
|
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.exceptions.NonFhirResponseException;
|
||||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||||
import ca.uhn.fhir.rest.client.method.*;
|
import ca.uhn.fhir.rest.client.method.DeleteMethodBinding;
|
||||||
import ca.uhn.fhir.rest.gclient.*;
|
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.DateParam;
|
||||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
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.io.IOUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.instance.model.api.*;
|
import org.hl7.fhir.instance.model.api.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.IOException;
|
||||||
import java.io.InputStream;
|
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.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
|
* @author James Agnew
|
||||||
@ -749,6 +855,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
IClientResponseHandler binding;
|
IClientResponseHandler binding;
|
||||||
binding = new ResourceResponseHandler(myBundleType, getPreferResponseTypes());
|
binding = new ResourceResponseHandler(myBundleType, getPreferResponseTypes());
|
||||||
HttpSimpleGetClientInvocation invocation = new HttpSimpleGetClientInvocation(myContext, myUrl);
|
HttpSimpleGetClientInvocation invocation = new HttpSimpleGetClientInvocation(myContext, myUrl);
|
||||||
|
invocation.setUrlSource(UrlSourceEnum.EXPLICIT);
|
||||||
|
|
||||||
Map<String, List<String>> params = null;
|
Map<String, List<String>> params = null;
|
||||||
return invoke(params, binding, invocation);
|
return invoke(params, binding, invocation);
|
||||||
@ -1838,7 +1945,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
|
|
||||||
BaseHttpClientInvocation invocation;
|
BaseHttpClientInvocation invocation;
|
||||||
if (mySearchUrl != null) {
|
if (mySearchUrl != null) {
|
||||||
invocation = SearchMethodBinding.createSearchInvocation(myContext, mySearchUrl, params);
|
invocation = SearchMethodBinding.createSearchInvocation(myContext, mySearchUrl, UrlSourceEnum.EXPLICIT, params);
|
||||||
} else {
|
} else {
|
||||||
invocation = SearchMethodBinding.createSearchInvocation(myContext, myResourceName, params, resourceId, myCompartmentName, mySearchStyle);
|
invocation = SearchMethodBinding.createSearchInvocation(myContext, myResourceName, params, resourceId, myCompartmentName, mySearchStyle);
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import ca.uhn.fhir.interceptor.api.Hook;
|
|||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.rest.client.api.IHttpRequest;
|
import ca.uhn.fhir.rest.client.api.IHttpRequest;
|
||||||
import ca.uhn.fhir.rest.client.api.IRestfulClient;
|
import ca.uhn.fhir.rest.client.api.IRestfulClient;
|
||||||
|
import ca.uhn.fhir.rest.client.api.UrlSourceEnum;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
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);
|
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());
|
String newUri = serverBase + "/" + tenantId + requestUri.substring(serverBase.length());
|
||||||
theRequest.setUri(newUri);
|
theRequest.setUri(newUri);
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirContext;
|
|||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
import ca.uhn.fhir.rest.client.api.IHttpRequest;
|
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.rest.client.impl.BaseHttpClientInvocation;
|
||||||
import ca.uhn.fhir.util.UrlUtil;
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@ -41,17 +42,24 @@ public class HttpGetClientInvocation extends BaseHttpClientInvocation {
|
|||||||
|
|
||||||
private final Map<String, List<String>> myParameters;
|
private final Map<String, List<String>> myParameters;
|
||||||
private final String myUrlPath;
|
private final String myUrlPath;
|
||||||
|
private final UrlSourceEnum myUrlSource;
|
||||||
|
|
||||||
public HttpGetClientInvocation(FhirContext theContext, Map<String, List<String>> theParameters, String... theUrlFragments) {
|
public HttpGetClientInvocation(FhirContext theContext, Map<String, List<String>> theParameters, String... theUrlFragments) {
|
||||||
|
this(theContext, theParameters, UrlSourceEnum.GENERATED, theUrlFragments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpGetClientInvocation(FhirContext theContext, Map<String, List<String>> theParameters, UrlSourceEnum theUrlSource, String... theUrlFragments) {
|
||||||
super(theContext);
|
super(theContext);
|
||||||
myParameters = theParameters;
|
myParameters = theParameters;
|
||||||
myUrlPath = StringUtils.join(theUrlFragments, '/');
|
myUrlPath = StringUtils.join(theUrlFragments, '/');
|
||||||
|
myUrlSource = theUrlSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpGetClientInvocation(FhirContext theContext, String theUrlPath) {
|
public HttpGetClientInvocation(FhirContext theContext, String theUrlPath) {
|
||||||
super(theContext);
|
super(theContext);
|
||||||
myParameters = new HashMap<>();
|
myParameters = new HashMap<>();
|
||||||
myUrlPath = theUrlPath;
|
myUrlPath = theUrlPath;
|
||||||
|
myUrlSource = UrlSourceEnum.GENERATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -95,7 +103,10 @@ public class HttpGetClientInvocation extends BaseHttpClientInvocation {
|
|||||||
|
|
||||||
appendExtraParamsWithQuestionMark(theExtraParams, b, first);
|
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<String, List<String>> getParameters() {
|
public Map<String, List<String>> getParameters() {
|
||||||
|
@ -27,11 +27,13 @@ import ca.uhn.fhir.context.FhirContext;
|
|||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
import ca.uhn.fhir.rest.client.api.IHttpRequest;
|
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.rest.client.impl.BaseHttpClientInvocation;
|
||||||
|
|
||||||
public class HttpSimpleGetClientInvocation extends BaseHttpClientInvocation {
|
public class HttpSimpleGetClientInvocation extends BaseHttpClientInvocation {
|
||||||
|
|
||||||
private final String myUrl;
|
private final String myUrl;
|
||||||
|
private UrlSourceEnum myUrlSource = UrlSourceEnum.GENERATED;
|
||||||
|
|
||||||
public HttpSimpleGetClientInvocation(FhirContext theContext, String theUrlPath) {
|
public HttpSimpleGetClientInvocation(FhirContext theContext, String theUrlPath) {
|
||||||
super(theContext);
|
super(theContext);
|
||||||
@ -40,7 +42,12 @@ public class HttpSimpleGetClientInvocation extends BaseHttpClientInvocation {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IHttpRequest asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) {
|
public IHttpRequest asHttpRequest(String theUrlBase, Map<String, List<String>> 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,27 +19,33 @@ package ca.uhn.fhir.rest.client.method;
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
* #L%
|
* #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.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.annotation.Description;
|
import ca.uhn.fhir.model.api.annotation.Description;
|
||||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||||
import ca.uhn.fhir.rest.annotation.Search;
|
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.client.impl.BaseHttpClientInvocation;
|
||||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
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 {
|
public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
private String myCompartmentName;
|
private String myCompartmentName;
|
||||||
@ -157,8 +163,8 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||||||
return getMethod().toString();
|
return getMethod().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BaseHttpClientInvocation createSearchInvocation(FhirContext theContext, String theSearchUrl, Map<String, List<String>> theParams) {
|
public static BaseHttpClientInvocation createSearchInvocation(FhirContext theContext, String theSearchUrl, UrlSourceEnum theUrlSource, Map<String, List<String>> theParams) {
|
||||||
return new HttpGetClientInvocation(theContext, theParams, theSearchUrl);
|
return new HttpGetClientInvocation(theContext, theParams, theUrlSource, theSearchUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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.BearerTokenAuthInterceptor;
|
||||||
import ca.uhn.fhir.rest.client.interceptor.CookieInterceptor;
|
import ca.uhn.fhir.rest.client.interceptor.CookieInterceptor;
|
||||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
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;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
|
|
||||||
public class ClientExamples {
|
public class ClientExamples {
|
||||||
@ -60,6 +61,29 @@ public class ClientExamples {
|
|||||||
// END SNIPPET: proxy
|
// 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")
|
@SuppressWarnings("unused")
|
||||||
public void processMessage() {
|
public void processMessage() {
|
||||||
// START SNIPPET: processMessage
|
// START SNIPPET: processMessage
|
||||||
|
@ -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.
|
@ -58,7 +58,7 @@
|
|||||||
issue: "1807"
|
issue: "1807"
|
||||||
type: "change"
|
type: "change"
|
||||||
title: "**New Feature**:
|
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
|
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**.
|
advantags of native RDBMS partition strategies, and also to implement **multitenant servers**.
|
||||||
"
|
"
|
||||||
|
@ -2,15 +2,11 @@
|
|||||||
|
|
||||||
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!
|
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
|
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).
|
||||||
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ClientTransactionExamples.java|conditional}}
|
|
||||||
```
|
|
||||||
|
|
||||||
This code creates the following transaction bundle:
|
|
||||||
|
|
||||||
**JSON**:
|
**JSON**:
|
||||||
|
|
||||||
@ -18,63 +14,51 @@ This code creates the following transaction bundle:
|
|||||||
{
|
{
|
||||||
"resourceType": "Bundle",
|
"resourceType": "Bundle",
|
||||||
"type": "transaction",
|
"type": "transaction",
|
||||||
"entry": [
|
"entry": [ {
|
||||||
{
|
"fullUrl": "urn:uuid:3bc44de3-069d-442d-829b-f3ef68cae371",
|
||||||
"fullUrl": "urn:uuid:3bc44de3-069d-442d-829b-f3ef68cae371",
|
"resource": {
|
||||||
"resource": {
|
"resourceType": "Patient",
|
||||||
"resourceType": "Patient",
|
"identifier": [ {
|
||||||
"identifier": [
|
"system": "http://acme.org/mrns",
|
||||||
{
|
"value": "12345"
|
||||||
"system": "http://acme.org/mrns",
|
} ],
|
||||||
"value": "12345"
|
"name": [ {
|
||||||
}
|
"family": "Jameson",
|
||||||
],
|
"given": [ "J", "Jonah" ]
|
||||||
"name": [
|
} ],
|
||||||
{
|
"gender": "male"
|
||||||
"family": "Jameson",
|
},
|
||||||
"given": [
|
"request": {
|
||||||
"J",
|
"method": "POST",
|
||||||
"Jonah"
|
"url": "Patient",
|
||||||
]
|
"ifNoneExist": "identifier=http://acme.org/mrns|12345"
|
||||||
}
|
}
|
||||||
],
|
}, {
|
||||||
"gender": "male"
|
"resource": {
|
||||||
|
"resourceType": "Observation",
|
||||||
|
"status": "final",
|
||||||
|
"code": {
|
||||||
|
"coding": [ {
|
||||||
|
"system": "http://loinc.org",
|
||||||
|
"code": "789-8",
|
||||||
|
"display": "Erythrocytes [#/volume] in Blood by Automated count"
|
||||||
|
} ]
|
||||||
},
|
},
|
||||||
"request": {
|
"subject": {
|
||||||
"method": "POST",
|
"reference": "urn:uuid:3bc44de3-069d-442d-829b-f3ef68cae371"
|
||||||
"url": "Patient",
|
},
|
||||||
"ifNoneExist": "identifier=http://acme.org/mrns|12345"
|
"valueQuantity": {
|
||||||
|
"value": 4.12,
|
||||||
|
"unit": "10 trillion/L",
|
||||||
|
"system": "http://unitsofmeasure.org",
|
||||||
|
"code": "10*12/L"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
"request": {
|
||||||
"resource": {
|
"method": "POST",
|
||||||
"resourceType": "Observation",
|
"url": "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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
} ]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -164,6 +148,12 @@ The server responds with the following response. Note that the ID of the already
|
|||||||
</Bundle>
|
</Bundle>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To produce this transaction in Java code:
|
||||||
|
|
||||||
|
```java
|
||||||
|
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ClientTransactionExamples.java|conditional}}
|
||||||
|
```
|
||||||
|
|
||||||
# Fetch all Pages of a Bundle
|
# 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.
|
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.
|
||||||
|
@ -45,9 +45,14 @@ page.server_jpa.schema=Database Schema
|
|||||||
page.server_jpa.configuration=Configuration
|
page.server_jpa.configuration=Configuration
|
||||||
page.server_jpa.search=Search
|
page.server_jpa.search=Search
|
||||||
page.server_jpa.performance=Performance
|
page.server_jpa.performance=Performance
|
||||||
page.server_jpa.partitioning=Partitioning and Multitenancy
|
|
||||||
page.server_jpa.upgrading=Upgrade Guide
|
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
|
section.interceptors.title=Interceptors
|
||||||
page.interceptors.interceptors=Interceptors Overview
|
page.interceptors.interceptors=Interceptors Overview
|
||||||
page.interceptors.client_interceptors=Client Interceptors
|
page.interceptors.client_interceptors=Client Interceptors
|
||||||
|
@ -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 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)
|
* [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
|
# Performance: GZip Outgoing Request Bodies
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ This interceptor will then produce output similar to the following:
|
|||||||
|
|
||||||
# Partitioning: Multitenant Request Partition
|
# 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 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)
|
* [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)
|
||||||
|
@ -39,7 +39,7 @@ The HFJ_RESOURCE table indicates a single resource of any type in the database.
|
|||||||
<td>Integer</td>
|
<td>Integer</td>
|
||||||
<td>Nullable</td>
|
<td>Nullable</td>
|
||||||
<td>
|
<td>
|
||||||
This is the optional partition ID, if the resource is in a partition. See <a href="./partitioning.html">Partitioning</a>.
|
This is the optional partition ID, if the resource is in a partition. See <a href="/hapi-fhir/docs/server_jpa_partitioning/partitioning.html">Partitioning</a>.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -48,7 +48,7 @@ The HFJ_RESOURCE table indicates a single resource of any type in the database.
|
|||||||
<td>Timestamp</td>
|
<td>Timestamp</td>
|
||||||
<td>Nullable</td>
|
<td>Nullable</td>
|
||||||
<td>
|
<td>
|
||||||
This is the optional partition date, if the resource is in a partition. See <a href="./partitioning.html">Partitioning</a>.
|
This is the optional partition date, if the resource is in a partition. See <a href="/hapi-fhir/docs/server_jpa_partitioning/partitioning.html">Partitioning</a>.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -154,7 +154,7 @@ The complete raw contents of the resource is stored in the `RES_TEXT` column, us
|
|||||||
<td>Integer</td>
|
<td>Integer</td>
|
||||||
<td>Nullable</td>
|
<td>Nullable</td>
|
||||||
<td>
|
<td>
|
||||||
This is the optional partition ID, if the resource is in a partition. See <a href="./partitioning.html">Partitioning</a>.
|
This is the optional partition ID, if the resource is in a partition. See <a href="/hapi-fhir/docs/server_jpa_partitioning/partitioning.html">Partitioning</a>.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -163,7 +163,7 @@ The complete raw contents of the resource is stored in the `RES_TEXT` column, us
|
|||||||
<td>Timestamp</td>
|
<td>Timestamp</td>
|
||||||
<td>Nullable</td>
|
<td>Nullable</td>
|
||||||
<td>
|
<td>
|
||||||
This is the optional partition date, if the resource is in a partition. See <a href="./partitioning.html">Partitioning</a>.
|
This is the optional partition date, if the resource is in a partition. See <a href="/hapi-fhir/docs/server_jpa_partitioning/partitioning.html">Partitioning</a>.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -263,7 +263,7 @@ If the server has been configured with a [Resource Server ID Strategy](/apidocs/
|
|||||||
<td>Integer</td>
|
<td>Integer</td>
|
||||||
<td>Nullable</td>
|
<td>Nullable</td>
|
||||||
<td>
|
<td>
|
||||||
This is the optional partition ID, if the resource is in a partition. See <a href="./partitioning.html">Partitioning</a>.
|
This is the optional partition ID, if the resource is in a partition. See <a href="/hapi-fhir/docs/server_jpa_partitioning/partitioning.html">Partitioning</a>.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -272,7 +272,7 @@ If the server has been configured with a [Resource Server ID Strategy](/apidocs/
|
|||||||
<td>Timestamp</td>
|
<td>Timestamp</td>
|
||||||
<td>Nullable</td>
|
<td>Nullable</td>
|
||||||
<td>
|
<td>
|
||||||
This is the optional partition date, if the resource is in a partition. See <a href="./partitioning.html">Partitioning</a>.
|
This is the optional partition date, if the resource is in a partition. See <a href="/hapi-fhir/docs/server_jpa_partitioning/partitioning.html">Partitioning</a>.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -332,7 +332,7 @@ When a resource is created or updated, it is indexed for searching. Any search p
|
|||||||
<td>Integer</td>
|
<td>Integer</td>
|
||||||
<td>Nullable</td>
|
<td>Nullable</td>
|
||||||
<td>
|
<td>
|
||||||
This is the optional partition ID, if the resource is in a partition. See <a href="./partitioning.html">Partitioning</a>.
|
This is the optional partition ID, if the resource is in a partition. See <a href="/hapi-fhir/docs/server_jpa_partitioning/partitioning.html">Partitioning</a>.
|
||||||
Note that the partition indicated by the <b>PARTITION_ID</b> and <b>PARTITION_DATE</b> columns refers to the partition
|
Note that the partition indicated by the <b>PARTITION_ID</b> and <b>PARTITION_DATE</b> columns refers to the partition
|
||||||
of the <i>SOURCE</i> resource, and not necessarily the <i>TARGET</i>.
|
of the <i>SOURCE</i> resource, and not necessarily the <i>TARGET</i>.
|
||||||
</td>
|
</td>
|
||||||
@ -343,7 +343,7 @@ When a resource is created or updated, it is indexed for searching. Any search p
|
|||||||
<td>Timestamp</td>
|
<td>Timestamp</td>
|
||||||
<td>Nullable</td>
|
<td>Nullable</td>
|
||||||
<td>
|
<td>
|
||||||
This is the optional partition date, if the resource is in a partition. See <a href="./partitioning.html">Partitioning</a>.
|
This is the optional partition date, if the resource is in a partition. See <a href="/hapi-fhir/docs/server_jpa_partitioning/partitioning.html">Partitioning</a>.
|
||||||
Note that the partition indicated by the <b>PARTITION_ID</b> and <b>PARTITION_DATE</b> columns refers to the partition
|
Note that the partition indicated by the <b>PARTITION_ID</b> and <b>PARTITION_DATE</b> columns refers to the partition
|
||||||
of the <i>SOURCE</i> resource, and not necessarily the <i>TARGET</i>.
|
of the <i>SOURCE</i> resource, and not necessarily the <i>TARGET</i>.
|
||||||
</td>
|
</td>
|
||||||
@ -448,7 +448,7 @@ The following columns are common to **all HFJ_SPIDX_xxx tables**.
|
|||||||
<td>Integer</td>
|
<td>Integer</td>
|
||||||
<td>Nullable</td>
|
<td>Nullable</td>
|
||||||
<td>
|
<td>
|
||||||
This is the optional partition ID, if the resource is in a partition. See <a href="./partitioning.html">Partitioning</a>.
|
This is the optional partition ID, if the resource is in a partition. See <a href="/hapi-fhir/docs/server_jpa_partitioning/partitioning.html">Partitioning</a>.
|
||||||
Note that the partition indicated by the <b>PARTITION_ID</b> and <b>PARTITION_DATE</b> columns refers to the partition
|
Note that the partition indicated by the <b>PARTITION_ID</b> and <b>PARTITION_DATE</b> columns refers to the partition
|
||||||
of the <i>SOURCE</i> resource, and not necessarily the <i>TARGET</i>.
|
of the <i>SOURCE</i> resource, and not necessarily the <i>TARGET</i>.
|
||||||
</td>
|
</td>
|
||||||
@ -459,7 +459,7 @@ The following columns are common to **all HFJ_SPIDX_xxx tables**.
|
|||||||
<td>Timestamp</td>
|
<td>Timestamp</td>
|
||||||
<td>Nullable</td>
|
<td>Nullable</td>
|
||||||
<td>
|
<td>
|
||||||
This is the optional partition date, if the resource is in a partition. See <a href="./partitioning.html">Partitioning</a>.
|
This is the optional partition date, if the resource is in a partition. See <a href="/hapi-fhir/docs/server_jpa_partitioning/partitioning.html">Partitioning</a>.
|
||||||
Note that the partition indicated by the <b>PARTITION_ID</b> and <b>PARTITION_DATE</b> columns refers to the partition
|
Note that the partition indicated by the <b>PARTITION_ID</b> and <b>PARTITION_DATE</b> columns refers to the partition
|
||||||
of the <i>SOURCE</i> resource, and not necessarily the <i>TARGET</i>.
|
of the <i>SOURCE</i> resource, and not necessarily the <i>TARGET</i>.
|
||||||
</td>
|
</td>
|
||||||
|
@ -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.
|
@ -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.
|
* **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
|
## 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_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.
|
* **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.
|
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 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.
|
* 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
|
# 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}}
|
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/PartitionExamples.java|multitenantServer}}
|
||||||
```
|
```
|
||||||
|
|
||||||
<a name="partition-mapping-operations"/>
|
|
||||||
|
|
||||||
# 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:
|
|
||||||
|
|
||||||
<table class="table table-striped table-condensed">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>Cardinality</th>
|
|
||||||
<th>Description</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>id</td>
|
|
||||||
<td>Integer</td>
|
|
||||||
<td>1..1</td>
|
|
||||||
<td>
|
|
||||||
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.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>name</td>
|
|
||||||
<td>Code</td>
|
|
||||||
<td>1..1</td>
|
|
||||||
<td>
|
|
||||||
A code (string) to assign to the partition.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>description</td>
|
|
||||||
<td>String</td>
|
|
||||||
<td>0..1</td>
|
|
||||||
<td>
|
|
||||||
An optional description for the partition.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
### 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:
|
|
||||||
|
|
||||||
<table class="table table-striped table-condensed">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>Cardinality</th>
|
|
||||||
<th>Description</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>id</td>
|
|
||||||
<td>Integer</td>
|
|
||||||
<td>1..1</td>
|
|
||||||
<td>
|
|
||||||
The numeric ID for the partition to update. This ID must already exist.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>name</td>
|
|
||||||
<td>Code</td>
|
|
||||||
<td>1..1</td>
|
|
||||||
<td>
|
|
||||||
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.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>description</td>
|
|
||||||
<td>String</td>
|
|
||||||
<td>0..1</td>
|
|
||||||
<td>
|
|
||||||
An optional description for the partition.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
### 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:
|
|
||||||
|
|
||||||
<table class="table table-striped table-condensed">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>Cardinality</th>
|
|
||||||
<th>Description</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>id</td>
|
|
||||||
<td>Integer</td>
|
|
||||||
<td>1..1</td>
|
|
||||||
<td>
|
|
||||||
The numeric ID for the partition to update. This ID must already exist.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
### 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
|
# Limitations
|
||||||
|
|
||||||
@ -329,4 +130,6 @@ None of the limitations listed here are considered permanent. Over time the HAPI
|
|||||||
|
|
||||||
* **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.
|
* **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.
|
* **Bulk Operations are not partition aware**: Bulk export operations will export data across all partitions.
|
@ -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:
|
||||||
|
|
||||||
|
<table class="table table-striped table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Cardinality</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>id</td>
|
||||||
|
<td>Integer</td>
|
||||||
|
<td>1..1</td>
|
||||||
|
<td>
|
||||||
|
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.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>name</td>
|
||||||
|
<td>Code</td>
|
||||||
|
<td>1..1</td>
|
||||||
|
<td>
|
||||||
|
A code (string) to assign to the partition.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>description</td>
|
||||||
|
<td>String</td>
|
||||||
|
<td>0..1</td>
|
||||||
|
<td>
|
||||||
|
An optional description for the partition.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
### 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:
|
||||||
|
|
||||||
|
<table class="table table-striped table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Cardinality</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>id</td>
|
||||||
|
<td>Integer</td>
|
||||||
|
<td>1..1</td>
|
||||||
|
<td>
|
||||||
|
The numeric ID for the partition to update. This ID must already exist.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>name</td>
|
||||||
|
<td>Code</td>
|
||||||
|
<td>1..1</td>
|
||||||
|
<td>
|
||||||
|
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.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>description</td>
|
||||||
|
<td>String</td>
|
||||||
|
<td>0..1</td>
|
||||||
|
<td>
|
||||||
|
An optional description for the partition.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
### 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:
|
||||||
|
|
||||||
|
<table class="table table-striped table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Cardinality</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>id</td>
|
||||||
|
<td>Integer</td>
|
||||||
|
<td>1..1</td>
|
||||||
|
<td>
|
||||||
|
The numeric ID for the partition to update. This ID must already exist.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
### 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
|
||||||
|
} ]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jaxrs.client;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
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.IHttpRequest;
|
||||||
import ca.uhn.fhir.rest.client.api.IHttpResponse;
|
import ca.uhn.fhir.rest.client.api.IHttpResponse;
|
||||||
import ca.uhn.fhir.util.StopWatch;
|
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.Entity;
|
||||||
import javax.ws.rs.client.Invocation;
|
import javax.ws.rs.client.Invocation;
|
||||||
import javax.ws.rs.core.Response;
|
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
|
* 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
|
* @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<String, List<String>> myHeaders = new HashMap<>();
|
private final Map<String, List<String>> myHeaders = new HashMap<>();
|
||||||
private Invocation.Builder myRequest;
|
private Invocation.Builder myRequest;
|
||||||
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.bulk;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
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.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
|
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
|
||||||
@ -231,7 +232,7 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc {
|
|||||||
map.setLastUpdated(new DateRangeParam(job.getSince(), null));
|
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);
|
storeResultsToFiles(nextCollection, sb, resultIterator, jobResourceCounter, jobStopwatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,22 +5,27 @@ import ca.uhn.fhir.i18n.HapiLocalizer;
|
|||||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||||
import ca.uhn.fhir.interceptor.executor.InterceptorService;
|
import ca.uhn.fhir.interceptor.executor.InterceptorService;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
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.BinaryAccessProvider;
|
||||||
import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor;
|
import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor;
|
||||||
import ca.uhn.fhir.jpa.bulk.BulkDataExportProvider;
|
import ca.uhn.fhir.jpa.bulk.BulkDataExportProvider;
|
||||||
import ca.uhn.fhir.jpa.bulk.BulkDataExportSvcImpl;
|
import ca.uhn.fhir.jpa.bulk.BulkDataExportSvcImpl;
|
||||||
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
|
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.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.dao.index.DaoResourceLinkResolver;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
|
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
|
||||||
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
|
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
|
||||||
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
|
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
|
||||||
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
|
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.PartitionLookupSvcImpl;
|
||||||
import ca.uhn.fhir.jpa.partition.PartitionManagementProvider;
|
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.SubscriptionTriggeringProvider;
|
||||||
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
|
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
|
||||||
import ca.uhn.fhir.jpa.sched.AutowiringSpringBeanJobFactory;
|
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.consent.IConsentContextServices;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor;
|
||||||
import org.hibernate.jpa.HibernatePersistenceProvider;
|
import org.hibernate.jpa.HibernatePersistenceProvider;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
|
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
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.scheduling.concurrent.ScheduledExecutorFactoryBean;
|
||||||
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR JPA Server
|
* 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 JPA_VALIDATION_SUPPORT_CHAIN = "myJpaValidationSupportChain";
|
||||||
public static final String TASK_EXECUTOR_NAME = "hapiJpaTaskExecutor";
|
public static final String TASK_EXECUTOR_NAME = "hapiJpaTaskExecutor";
|
||||||
public static final String GRAPHQL_PROVIDER_NAME = "myGraphQLProvider";
|
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 = "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";
|
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
|
@Autowired
|
||||||
protected Environment myEnv;
|
protected Environment myEnv;
|
||||||
@ -213,8 +225,8 @@ public abstract class BaseConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public IRequestPartitionHelperService requestPartitionHelperService() {
|
public IRequestPartitionHelperSvc requestPartitionHelperService() {
|
||||||
return new RequestPartitionHelperService();
|
return new RequestPartitionHelperSvc();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ -291,18 +303,46 @@ public abstract class BaseConfig {
|
|||||||
return new PersistedJpaBundleProviderFactory();
|
return new PersistedJpaBundleProviderFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name= PERSISTED_JPA_BUNDLE_PROVIDER)
|
@Bean(name = PERSISTED_JPA_BUNDLE_PROVIDER)
|
||||||
@Scope("prototype")
|
@Scope("prototype")
|
||||||
public PersistedJpaBundleProvider persistedJpaBundleProvider(RequestDetails theRequest, String theUuid) {
|
public PersistedJpaBundleProvider persistedJpaBundleProvider(RequestDetails theRequest, String theUuid) {
|
||||||
return new PersistedJpaBundleProvider(theRequest, 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")
|
@Scope("prototype")
|
||||||
public PersistedJpaSearchFirstPageBundleProvider persistedJpaSearchFirstPageBundleProvider(RequestDetails theRequest, Search theSearch, SearchCoordinatorSvcImpl.SearchTask theSearchTask, ISearchBuilder theSearchBuilder) {
|
public PersistedJpaSearchFirstPageBundleProvider persistedJpaSearchFirstPageBundleProvider(RequestDetails theRequest, Search theSearch, SearchCoordinatorSvcImpl.SearchTask theSearchTask, ISearchBuilder theSearchBuilder) {
|
||||||
return new PersistedJpaSearchFirstPageBundleProvider(theSearch, theSearchTask, theSearchBuilder, theRequest);
|
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) {
|
public static void configureEntityManagerFactory(LocalContainerEntityManagerFactoryBean theFactory, FhirContext theCtx) {
|
||||||
theFactory.setJpaDialect(hibernateJpaDialect(theCtx.getLocalizer()));
|
theFactory.setJpaDialect(hibernateJpaDialect(theCtx.getLocalizer()));
|
||||||
theFactory.setPackagesToScan("ca.uhn.fhir.jpa.model.entity", "ca.uhn.fhir.jpa.entity");
|
theFactory.setPackagesToScan("ca.uhn.fhir.jpa.model.entity", "ca.uhn.fhir.jpa.entity");
|
||||||
|
@ -11,6 +11,7 @@ import ca.uhn.fhir.context.RuntimeSearchParam;
|
|||||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
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.config.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IDao;
|
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.IdHelperService;
|
||||||
import ca.uhn.fhir.jpa.dao.index.SearchParamWithInlineReferencesExtractor;
|
import ca.uhn.fhir.jpa.dao.index.SearchParamWithInlineReferencesExtractor;
|
||||||
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
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.ResourceSearchView;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
||||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
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.SearchStatusEnum;
|
||||||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
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.PersistedJpaBundleProviderFactory;
|
||||||
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
||||||
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
|
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
|
||||||
@ -76,7 +90,16 @@ import com.google.common.hash.HashFunction;
|
|||||||
import com.google.common.hash.Hashing;
|
import com.google.common.hash.Hashing;
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.instance.model.api.*;
|
import org.hl7.fhir.instance.model.api.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.hl7.fhir.r4.model.Bundle.HTTPVerb;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -100,8 +123,19 @@ import javax.persistence.criteria.CriteriaQuery;
|
|||||||
import javax.persistence.criteria.Root;
|
import javax.persistence.criteria.Root;
|
||||||
import javax.xml.stream.events.Characters;
|
import javax.xml.stream.events.Characters;
|
||||||
import javax.xml.stream.events.XMLEvent;
|
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.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.defaultIfBlank;
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
@ -164,14 +198,17 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||||||
protected IResourceTableDao myResourceTableDao;
|
protected IResourceTableDao myResourceTableDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
protected IResourceTagDao myResourceTagDao;
|
protected IResourceTagDao myResourceTagDao;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
protected DeleteConflictService myDeleteConflictService;
|
protected DeleteConflictService myDeleteConflictService;
|
||||||
@Autowired
|
@Autowired
|
||||||
protected IInterceptorBroadcaster myInterceptorBroadcaster;
|
protected IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
protected DaoRegistry myDaoRegistry;
|
||||||
|
@Autowired
|
||||||
ExpungeService myExpungeService;
|
ExpungeService myExpungeService;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private HistoryBuilderFactory myHistoryBuilderFactory;
|
||||||
|
@Autowired
|
||||||
private DaoConfig myConfig;
|
private DaoConfig myConfig;
|
||||||
@Autowired
|
@Autowired
|
||||||
private PlatformTransactionManager myPlatformTransactionManager;
|
private PlatformTransactionManager myPlatformTransactionManager;
|
||||||
@ -180,8 +217,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ISearchParamPresenceSvc mySearchParamPresenceSvc;
|
private ISearchParamPresenceSvc mySearchParamPresenceSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
protected DaoRegistry myDaoRegistry;
|
|
||||||
@Autowired
|
|
||||||
private SearchParamWithInlineReferencesExtractor mySearchParamWithInlineReferencesExtractor;
|
private SearchParamWithInlineReferencesExtractor mySearchParamWithInlineReferencesExtractor;
|
||||||
@Autowired
|
@Autowired
|
||||||
private DaoSearchParamSynchronizer myDaoSearchParamSynchronizer;
|
private DaoSearchParamSynchronizer myDaoSearchParamSynchronizer;
|
||||||
@ -191,6 +226,12 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||||||
private ApplicationContext myApplicationContext;
|
private ApplicationContext myApplicationContext;
|
||||||
@Autowired
|
@Autowired
|
||||||
private PartitionSettings myPartitionSettings;
|
private PartitionSettings myPartitionSettings;
|
||||||
|
@Autowired
|
||||||
|
private RequestPartitionHelperSvc myRequestPartitionHelperSvc;
|
||||||
|
@Autowired
|
||||||
|
private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
|
||||||
|
@Autowired
|
||||||
|
private IPartitionLookupSvc myPartitionLookupSvc;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IInterceptorBroadcaster getInterceptorBroadcaster() {
|
protected IInterceptorBroadcaster getInterceptorBroadcaster() {
|
||||||
@ -384,48 +425,23 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> 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(theResourceType, null);
|
||||||
|
|
||||||
String resourceName = defaultIfBlank(theResourceName, null);
|
|
||||||
|
|
||||||
Search search = new Search();
|
Search search = new Search();
|
||||||
search.setDeleted(false);
|
search.setDeleted(false);
|
||||||
search.setCreated(new Date());
|
search.setCreated(new Date());
|
||||||
search.setLastUpdated(theSince, theUntil);
|
search.setLastUpdated(theRangeStartInclusive, theRangeEndInclusive);
|
||||||
search.setUuid(UUID.randomUUID().toString());
|
search.setUuid(UUID.randomUUID().toString());
|
||||||
search.setResourceType(resourceName);
|
search.setResourceType(resourceName);
|
||||||
search.setResourceId(theId);
|
search.setResourceId(theResourcePid);
|
||||||
search.setSearchType(SearchTypeEnum.HISTORY);
|
search.setSearchType(SearchTypeEnum.HISTORY);
|
||||||
search.setStatus(SearchStatusEnum.FINISHED);
|
search.setStatus(SearchStatusEnum.FINISHED);
|
||||||
|
|
||||||
if (theSince != null) {
|
return myPersistedJpaBundleProviderFactory.newInstance(theRequest, search);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
|
|
||||||
|
|
||||||
|
|
||||||
void incrementId(T theResource, ResourceTable theSavedEntity, IIdType theResourceId) {
|
void incrementId(T theResource, ResourceTable theSavedEntity, IIdType theResourceId) {
|
||||||
String newVersion;
|
String newVersion;
|
||||||
long newVersionLong;
|
long newVersionLong;
|
||||||
@ -807,7 +823,11 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||||||
ResourceHistoryTable history = (ResourceHistoryTable) theEntity;
|
ResourceHistoryTable history = (ResourceHistoryTable) theEntity;
|
||||||
resourceBytes = history.getResource();
|
resourceBytes = history.getResource();
|
||||||
resourceEncoding = history.getEncoding();
|
resourceEncoding = history.getEncoding();
|
||||||
myTagList = history.getTags();
|
if (history.isHasTags()) {
|
||||||
|
myTagList = history.getTags();
|
||||||
|
} else {
|
||||||
|
myTagList = Collections.emptyList();
|
||||||
|
}
|
||||||
version = history.getVersion();
|
version = history.getVersion();
|
||||||
if (history.getProvenance() != null) {
|
if (history.getProvenance() != null) {
|
||||||
provenanceRequestId = history.getProvenance().getRequestId();
|
provenanceRequestId = history.getProvenance().getRequestId();
|
||||||
@ -829,7 +849,11 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||||||
}
|
}
|
||||||
resourceBytes = history.getResource();
|
resourceBytes = history.getResource();
|
||||||
resourceEncoding = history.getEncoding();
|
resourceEncoding = history.getEncoding();
|
||||||
myTagList = resource.getTags();
|
if (resource.isHasTags()) {
|
||||||
|
myTagList = resource.getTags();
|
||||||
|
} else {
|
||||||
|
myTagList = Collections.emptyList();
|
||||||
|
}
|
||||||
version = history.getVersion();
|
version = history.getVersion();
|
||||||
if (history.getProvenance() != null) {
|
if (history.getProvenance() != null) {
|
||||||
provenanceRequestId = history.getProvenance().getRequestId();
|
provenanceRequestId = history.getProvenance().getRequestId();
|
||||||
@ -924,6 +948,17 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> 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;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.entity.TagTypeEnum;
|
||||||
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
|
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
|
||||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
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.DatabaseBackedPagingProvider;
|
||||||
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
||||||
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
|
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
|
||||||
@ -145,7 +145,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||||||
private String myResourceName;
|
private String myResourceName;
|
||||||
private Class<T> myResourceType;
|
private Class<T> myResourceType;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IRequestPartitionHelperService myRequestPartitionHelperService;
|
private IRequestPartitionHelperSvc myRequestPartitionHelperService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private PartitionSettings myPartitionSettings;
|
private PartitionSettings myPartitionSettings;
|
||||||
|
|
||||||
@ -216,7 +216,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||||||
theResource.setUserData(JpaConstants.RESOURCE_ID_SERVER_ASSIGNED, Boolean.TRUE);
|
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);
|
return doCreate(theResource, theIfNoneExist, thePerformIndexing, theUpdateTimestamp, theRequestDetails, requestPartitionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -685,11 +685,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBundleProvider history(Date theSince, Date theUntil, RequestDetails theRequestDetails) {
|
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
|
// Notify interceptors
|
||||||
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails);
|
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails);
|
||||||
notifyInterceptors(RestOperationTypeEnum.HISTORY_TYPE, requestDetails);
|
notifyInterceptors(RestOperationTypeEnum.HISTORY_TYPE, requestDetails);
|
||||||
@ -1005,19 +1000,19 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||||||
public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) {
|
public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) {
|
||||||
validateResourceTypeAndThrowInvalidRequestException(theId);
|
validateResourceTypeAndThrowInvalidRequestException(theId);
|
||||||
|
|
||||||
@Nullable RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName());
|
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName());
|
||||||
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(requestPartitionId, getResourceName(), theId.getIdPart());
|
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(requestPartitionId, getResourceName(), theId.getIdPart());
|
||||||
BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid.getIdAsLong());
|
BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid.getIdAsLong());
|
||||||
|
|
||||||
// Verify that the resource is for the correct partition
|
// Verify that the resource is for the correct partition
|
||||||
if (requestPartitionId != null) {
|
if (!requestPartitionId.isAllPartitions()) {
|
||||||
if (requestPartitionId.getPartitionId() == null) {
|
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());
|
ourLog.debug("Performing a read for PartitionId={} but entity has partition: {}", requestPartitionId, entity.getPartitionId());
|
||||||
entity = null;
|
entity = null;
|
||||||
}
|
}
|
||||||
} else if (entity.getPartitionId() != null) {
|
} else if (entity.getPartitionId().getPartitionId() != null) {
|
||||||
if (!entity.getPartitionId().getPartitionId().equals(requestPartitionId.getPartitionId())) {
|
if (!requestPartitionId.getPartitionId().equals(entity.getPartitionId().getPartitionId())) {
|
||||||
ourLog.debug("Performing a read for PartitionId={} but entity has partition: {}", requestPartitionId, entity.getPartitionId());
|
ourLog.debug("Performing a read for PartitionId={} but entity has partition: {}", requestPartitionId, entity.getPartitionId());
|
||||||
entity = null;
|
entity = null;
|
||||||
}
|
}
|
||||||
@ -1314,7 +1309,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||||||
*/
|
*/
|
||||||
resourceId = theResource.getIdElement();
|
resourceId = theResource.getIdElement();
|
||||||
|
|
||||||
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequest, theResource);
|
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequest, theResource, getResourceName());
|
||||||
try {
|
try {
|
||||||
entity = readEntityLatestVersion(resourceId, requestPartitionId);
|
entity = readEntityLatestVersion(resourceId, requestPartitionId);
|
||||||
} catch (ResourceNotFoundException e) {
|
} catch (ResourceNotFoundException e) {
|
||||||
|
@ -8,7 +8,6 @@ import ca.uhn.fhir.jpa.util.ResourceCountCache;
|
|||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
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.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||||
import ca.uhn.fhir.util.StopWatch;
|
import ca.uhn.fhir.util.StopWatch;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
@ -80,11 +79,6 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBundleProvider history(Date theSince, Date theUntil, RequestDetails theRequestDetails) {
|
public IBundleProvider history(Date theSince, Date theUntil, RequestDetails theRequestDetails) {
|
||||||
if (myPartitionSettings.isPartitioningEnabled()) {
|
|
||||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "noSystemOrTypeHistoryForPartitionAwareServer");
|
|
||||||
throw new MethodNotAllowedException(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (theRequestDetails != null) {
|
if (theRequestDetails != null) {
|
||||||
// Notify interceptors
|
// Notify interceptors
|
||||||
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails);
|
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails);
|
||||||
|
@ -27,7 +27,7 @@ import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
|||||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperService;
|
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
@ -282,7 +282,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IRequestPartitionHelperService myRequestPartitionHelperService;
|
private IRequestPartitionHelperSvc myRequestPartitionHelperService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private PartitionSettings myPartitionSettings;
|
private PartitionSettings myPartitionSettings;
|
||||||
|
@ -0,0 +1,183 @@
|
|||||||
|
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.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import com.google.common.collect.ImmutableListMultimap;
|
||||||
|
import com.google.common.collect.Multimaps;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.PersistenceContext;
|
||||||
|
import javax.persistence.PersistenceContextType;
|
||||||
|
import javax.persistence.TypedQuery;
|
||||||
|
import javax.persistence.criteria.CriteriaBuilder;
|
||||||
|
import javax.persistence.criteria.CriteriaQuery;
|
||||||
|
import javax.persistence.criteria.JoinType;
|
||||||
|
import javax.persistence.criteria.Predicate;
|
||||||
|
import javax.persistence.criteria.Root;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static ca.uhn.fhir.jpa.dao.SearchBuilder.toPredicateArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The HistoryBuilder is responsible for building history queries
|
||||||
|
*/
|
||||||
|
public class HistoryBuilder {
|
||||||
|
|
||||||
|
private static final Logger ourLog = LoggerFactory.getLogger(HistoryBuilder.class);
|
||||||
|
private final String myResourceType;
|
||||||
|
private final Long myResourceId;
|
||||||
|
private final Date myRangeStartInclusive;
|
||||||
|
private final Date myRangeEndInclusive;
|
||||||
|
@Autowired
|
||||||
|
protected IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||||
|
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
||||||
|
protected EntityManager myEntityManager;
|
||||||
|
@Autowired
|
||||||
|
private PartitionSettings myPartitionSettings;
|
||||||
|
@Autowired
|
||||||
|
private FhirContext myCtx;
|
||||||
|
@Autowired
|
||||||
|
private IdHelperService myIdHelperService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public HistoryBuilder(@Nullable String theResourceType, @Nullable Long theResourceId, @Nullable Date theRangeStartInclusive, @Nullable Date theRangeEndInclusive) {
|
||||||
|
myResourceType = theResourceType;
|
||||||
|
myResourceId = theResourceId;
|
||||||
|
myRangeStartInclusive = theRangeStartInclusive;
|
||||||
|
myRangeEndInclusive = theRangeEndInclusive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long fetchCount(RequestPartitionId thePartitionId) {
|
||||||
|
CriteriaBuilder cb = myEntityManager.getCriteriaBuilder();
|
||||||
|
CriteriaQuery<Long> criteriaQuery = cb.createQuery(Long.class);
|
||||||
|
Root<ResourceHistoryTable> from = criteriaQuery.from(ResourceHistoryTable.class);
|
||||||
|
criteriaQuery.select(cb.count(from));
|
||||||
|
|
||||||
|
addPredicatesToQuery(cb, thePartitionId, criteriaQuery, from);
|
||||||
|
|
||||||
|
TypedQuery<Long> query = myEntityManager.createQuery(criteriaQuery);
|
||||||
|
return query.getSingleResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("OptionalIsPresent")
|
||||||
|
public List<ResourceHistoryTable> fetchEntities(RequestPartitionId thePartitionId, int theFromIndex, int theToIndex) {
|
||||||
|
CriteriaBuilder cb = myEntityManager.getCriteriaBuilder();
|
||||||
|
CriteriaQuery<ResourceHistoryTable> criteriaQuery = cb.createQuery(ResourceHistoryTable.class);
|
||||||
|
Root<ResourceHistoryTable> from = criteriaQuery.from(ResourceHistoryTable.class);
|
||||||
|
|
||||||
|
addPredicatesToQuery(cb, thePartitionId, criteriaQuery, from);
|
||||||
|
|
||||||
|
from.fetch("myProvenance", JoinType.LEFT);
|
||||||
|
|
||||||
|
criteriaQuery.orderBy(cb.desc(from.get("myUpdated")));
|
||||||
|
|
||||||
|
TypedQuery<ResourceHistoryTable> query = myEntityManager.createQuery(criteriaQuery);
|
||||||
|
|
||||||
|
query.setFirstResult(theFromIndex);
|
||||||
|
query.setMaxResults(theToIndex - theFromIndex);
|
||||||
|
|
||||||
|
List<ResourceHistoryTable> tables = query.getResultList();
|
||||||
|
if (tables.size() > 0) {
|
||||||
|
ImmutableListMultimap<Long, ResourceHistoryTable> resourceIdToHistoryEntries = Multimaps.index(tables, ResourceHistoryTable::getResourceId);
|
||||||
|
|
||||||
|
Map<Long, Optional<String>> pidToForcedId = myIdHelperService.translatePidsToForcedIds(resourceIdToHistoryEntries.keySet());
|
||||||
|
ourLog.trace("Translated IDs: {}", pidToForcedId);
|
||||||
|
|
||||||
|
for (Long nextResourceId : resourceIdToHistoryEntries.keySet()) {
|
||||||
|
List<ResourceHistoryTable> historyTables = resourceIdToHistoryEntries.get(nextResourceId);
|
||||||
|
|
||||||
|
String resourceId;
|
||||||
|
Optional<String> 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<ResourceHistoryTable> theFrom) {
|
||||||
|
List<Predicate> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -30,6 +30,7 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
|
|||||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -38,7 +39,7 @@ import java.util.Set;
|
|||||||
|
|
||||||
public interface ISearchBuilder {
|
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<Long> createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, RequestPartitionId theRequestPartitionId);
|
Iterator<Long> createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, RequestPartitionId theRequestPartitionId);
|
||||||
|
|
||||||
|
@ -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.BaseIterator;
|
||||||
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
|
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
|
||||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
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.ScrollableResultsIterator;
|
||||||
import ca.uhn.fhir.jpa.util.SqlQueryList;
|
import ca.uhn.fhir.jpa.util.SqlQueryList;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Scope;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.persistence.EntityManager;
|
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
|
* The SearchBuilder is responsible for actually forming the SQL query that handles
|
||||||
* searches for resources
|
* searches for resources
|
||||||
*/
|
*/
|
||||||
@Component
|
|
||||||
@Scope("prototype")
|
|
||||||
public class SearchBuilder implements ISearchBuilder {
|
public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -175,7 +172,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
SearchBuilder(IDao theDao, String theResourceName, Class<? extends IBaseResource> theResourceType) {
|
public SearchBuilder(IDao theDao, String theResourceName, Class<? extends IBaseResource> theResourceType) {
|
||||||
myCallingDao = theDao;
|
myCallingDao = theDao;
|
||||||
myResourceName = theResourceName;
|
myResourceName = theResourceName;
|
||||||
myResourceType = theResourceType;
|
myResourceType = theResourceType;
|
||||||
@ -225,7 +222,9 @@ public class SearchBuilder implements ISearchBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<Long> createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
|
public Iterator<Long> createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, @Nonnull RequestPartitionId theRequestPartitionId) {
|
||||||
|
assert theRequestPartitionId != null;
|
||||||
|
|
||||||
init(theParams, theSearchUuid, theRequestPartitionId);
|
init(theParams, theSearchUuid, theRequestPartitionId);
|
||||||
|
|
||||||
TypedQuery<Long> query = createQuery(null, null, true, theRequest);
|
TypedQuery<Long> query = createQuery(null, null, true, theRequest);
|
||||||
@ -241,7 +240,9 @@ public class SearchBuilder implements ISearchBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
init(theParams, theSearchRuntimeDetails.getSearchUuid(), theRequestPartitionId);
|
||||||
|
|
||||||
if (myPidSet == null) {
|
if (myPidSet == null) {
|
||||||
@ -359,7 +360,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||||||
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myResourceType"), myResourceName));
|
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myResourceType"), myResourceName));
|
||||||
}
|
}
|
||||||
myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted")));
|
myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted")));
|
||||||
if (myRequestPartitionId != null) {
|
if (!myRequestPartitionId.isAllPartitions()) {
|
||||||
if (myRequestPartitionId.getPartitionId() != null) {
|
if (myRequestPartitionId.getPartitionId() != null) {
|
||||||
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myPartitionIdValue").as(Integer.class), myRequestPartitionId.getPartitionId()));
|
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myPartitionIdValue").as(Integer.class), myRequestPartitionId.getPartitionId()));
|
||||||
} else {
|
} else {
|
||||||
@ -614,19 +615,10 @@ public class SearchBuilder implements ISearchBuilder {
|
|||||||
theResourceListToPopulate.add(null);
|
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<ResourcePersistentId> pids = new ArrayList<>(thePids);
|
List<ResourcePersistentId> pids = new ArrayList<>(thePids);
|
||||||
for (int i = 0; i < pids.size(); i += MAXIMUM_PAGE_SIZE) {
|
new QueryChunker<ResourcePersistentId>().chunk(pids, t->{
|
||||||
int to = i + MAXIMUM_PAGE_SIZE;
|
doLoadPids(t, theIncludedPids, theResourceListToPopulate, theForHistoryOperation, position, theDetails);
|
||||||
to = Math.min(to, pids.size());
|
});
|
||||||
List<ResourcePersistentId> pidsSubList = pids.subList(i, to);
|
|
||||||
doLoadPids(pidsSubList, theIncludedPids, theResourceListToPopulate, theForHistoryOperation, position, theDetails);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -895,7 +887,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||||||
private void addPredicateCompositeStringUnique(@Nonnull SearchParameterMap theParams, String theIndexedString, RequestPartitionId theRequestPartitionId) {
|
private void addPredicateCompositeStringUnique(@Nonnull SearchParameterMap theParams, String theIndexedString, RequestPartitionId theRequestPartitionId) {
|
||||||
Join<ResourceTable, ResourceIndexedCompositeStringUnique> join = myQueryRoot.join("myParamsCompositeStringUnique", JoinType.LEFT);
|
Join<ResourceTable, ResourceIndexedCompositeStringUnique> join = myQueryRoot.join("myParamsCompositeStringUnique", JoinType.LEFT);
|
||||||
|
|
||||||
if (theRequestPartitionId != null) {
|
if (!theRequestPartitionId.isAllPartitions()) {
|
||||||
Integer partitionId = theRequestPartitionId.getPartitionId();
|
Integer partitionId = theRequestPartitionId.getPartitionId();
|
||||||
Predicate predicate = myCriteriaBuilder.equal(join.get("myPartitionIdValue").as(Integer.class), partitionId);
|
Predicate predicate = myCriteriaBuilder.equal(join.get("myPartitionIdValue").as(Integer.class), partitionId);
|
||||||
myQueryRoot.addPredicate(predicate);
|
myQueryRoot.addPredicate(predicate);
|
||||||
@ -1276,7 +1268,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||||||
return ResourcePersistentId.fromLongList(query.getResultList());
|
return ResourcePersistentId.fromLongList(query.getResultList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Predicate[] toPredicateArray(List<Predicate> thePredicates) {
|
static Predicate[] toPredicateArray(List<Predicate> thePredicates) {
|
||||||
return thePredicates.toArray(new Predicate[0]);
|
return thePredicates.toArray(new Predicate[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,18 @@ package ca.uhn.fhir.jpa.dao;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.api.dao.IDao;
|
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.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.springframework.beans.factory.annotation.Lookup;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
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);
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
package ca.uhn.fhir.jpa.dao.data;
|
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.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -24,15 +30,11 @@ import java.util.Optional;
|
|||||||
* #L%
|
* #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<ForcedId, Long> {
|
public interface IForcedIdDao extends JpaRepository<ForcedId, Long> {
|
||||||
|
|
||||||
|
@Query("SELECT f FROM ForcedId f WHERE myResourcePid IN (:resource_pids)")
|
||||||
|
List<ForcedId> findAllByResourcePid(@Param("resource_pids") List<Long> theResourcePids);
|
||||||
|
|
||||||
@Query("SELECT f.myResourcePid FROM ForcedId f WHERE myForcedId IN (:forced_id)")
|
@Query("SELECT f.myResourcePid FROM ForcedId f WHERE myForcedId IN (:forced_id)")
|
||||||
List<Long> findByForcedId(@Param("forced_id") Collection<String> theForcedId);
|
List<Long> findByForcedId(@Param("forced_id") Collection<String> theForcedId);
|
||||||
|
|
||||||
@ -46,7 +48,7 @@ public interface IForcedIdDao extends JpaRepository<ForcedId, Long> {
|
|||||||
Optional<Long> findByPartitionIdAndTypeAndForcedId(@Param("partition_id") Integer thePartitionId, @Param("resource_type") String theResourceType, @Param("forced_id") String theForcedId);
|
Optional<Long> 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")
|
@Query("SELECT f FROM ForcedId f WHERE f.myResourcePid = :resource_pid")
|
||||||
ForcedId findByResourcePid(@Param("resource_pid") Long theResourcePid);
|
Optional<ForcedId> findByResourcePid(@Param("resource_pid") Long theResourcePid);
|
||||||
|
|
||||||
@Modifying
|
@Modifying
|
||||||
@Query("DELETE FROM ForcedId t WHERE t.myId = :pid")
|
@Query("DELETE FROM ForcedId t WHERE t.myId = :pid")
|
||||||
@ -75,7 +77,7 @@ public interface IForcedIdDao extends JpaRepository<ForcedId, Long> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Warning: No DB index exists for this particular query, so it may not perform well
|
* Warning: No DB index exists for this particular query, so it may not perform well
|
||||||
*
|
* <p>
|
||||||
* This method returns a Collection where each row is an element in the collection. Each element in the collection
|
* 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.
|
* 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.
|
||||||
*/
|
*/
|
||||||
|
@ -6,13 +6,8 @@ import org.springframework.data.domain.Slice;
|
|||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.Modifying;
|
import org.springframework.data.jpa.repository.Modifying;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.jpa.repository.Temporal;
|
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
import javax.persistence.TemporalType;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR JPA Server
|
* HAPI FHIR JPA Server
|
||||||
@ -35,36 +30,6 @@ import java.util.Date;
|
|||||||
|
|
||||||
public interface IResourceHistoryTableDao extends JpaRepository<ResourceHistoryTable, Long> {
|
public interface IResourceHistoryTableDao extends JpaRepository<ResourceHistoryTable, Long> {
|
||||||
|
|
||||||
@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")
|
@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);
|
ResourceHistoryTable findForIdAndVersionAndFetchProvenance(@Param("id") long theId, @Param("version") long theVersion);
|
||||||
|
@ -43,6 +43,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
|
|||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
import javax.persistence.PersistenceContext;
|
import javax.persistence.PersistenceContext;
|
||||||
@ -63,7 +64,7 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
|
|||||||
private DaoRegistry myDaoRegistry;
|
private DaoRegistry myDaoRegistry;
|
||||||
|
|
||||||
@Override
|
@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;
|
IResourceLookup resolvedResource;
|
||||||
String idPart = theSourceResourceId.getIdPart();
|
String idPart = theSourceResourceId.getIdPart();
|
||||||
try {
|
try {
|
||||||
|
@ -21,7 +21,9 @@ package ca.uhn.fhir.jpa.dao.index;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
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.BaseResourceIndex;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
|
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
|
||||||
import ca.uhn.fhir.jpa.util.AddRemoveCount;
|
import ca.uhn.fhir.jpa.util.AddRemoveCount;
|
||||||
@ -41,18 +43,22 @@ public class DaoSearchParamSynchronizer {
|
|||||||
protected EntityManager myEntityManager;
|
protected EntityManager myEntityManager;
|
||||||
@Autowired
|
@Autowired
|
||||||
private DaoConfig myDaoConfig;
|
private DaoConfig myDaoConfig;
|
||||||
|
@Autowired
|
||||||
|
private PartitionSettings myPartitionSettings;
|
||||||
|
@Autowired
|
||||||
|
private ModelConfig myModelConfig;
|
||||||
|
|
||||||
public AddRemoveCount synchronizeSearchParamsToDatabase(ResourceIndexedSearchParams theParams, ResourceTable theEntity, ResourceIndexedSearchParams existingParams) {
|
public AddRemoveCount synchronizeSearchParamsToDatabase(ResourceIndexedSearchParams theParams, ResourceTable theEntity, ResourceIndexedSearchParams existingParams) {
|
||||||
AddRemoveCount retVal = new AddRemoveCount();
|
AddRemoveCount retVal = new AddRemoveCount();
|
||||||
|
|
||||||
synchronize(theParams, theEntity, retVal, theParams.myStringParams, existingParams.myStringParams);
|
synchronize(theEntity, retVal, theParams.myStringParams, existingParams.myStringParams);
|
||||||
synchronize(theParams, theEntity, retVal, theParams.myTokenParams, existingParams.myTokenParams);
|
synchronize(theEntity, retVal, theParams.myTokenParams, existingParams.myTokenParams);
|
||||||
synchronize(theParams, theEntity, retVal, theParams.myNumberParams, existingParams.myNumberParams);
|
synchronize(theEntity, retVal, theParams.myNumberParams, existingParams.myNumberParams);
|
||||||
synchronize(theParams, theEntity, retVal, theParams.myQuantityParams, existingParams.myQuantityParams);
|
synchronize(theEntity, retVal, theParams.myQuantityParams, existingParams.myQuantityParams);
|
||||||
synchronize(theParams, theEntity, retVal, theParams.myDateParams, existingParams.myDateParams);
|
synchronize(theEntity, retVal, theParams.myDateParams, existingParams.myDateParams);
|
||||||
synchronize(theParams, theEntity, retVal, theParams.myUriParams, existingParams.myUriParams);
|
synchronize(theEntity, retVal, theParams.myUriParams, existingParams.myUriParams);
|
||||||
synchronize(theParams, theEntity, retVal, theParams.myCoordsParams, existingParams.myCoordsParams);
|
synchronize(theEntity, retVal, theParams.myCoordsParams, existingParams.myCoordsParams);
|
||||||
synchronize(theParams, theEntity, retVal, theParams.myLinks, existingParams.myLinks);
|
synchronize(theEntity, retVal, theParams.myLinks, existingParams.myLinks);
|
||||||
|
|
||||||
// make sure links are indexed
|
// make sure links are indexed
|
||||||
theEntity.setResourceLinks(theParams.myLinks);
|
theEntity.setResourceLinks(theParams.myLinks);
|
||||||
@ -60,26 +66,26 @@ public class DaoSearchParamSynchronizer {
|
|||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T extends BaseResourceIndex> void synchronize(ResourceIndexedSearchParams theParams, ResourceTable theEntity, AddRemoveCount theAddRemoveCount, Collection<T> theNewParms, Collection<T> theExistingParms) {
|
private <T extends BaseResourceIndex> void synchronize(ResourceTable theEntity, AddRemoveCount theAddRemoveCount, Collection<T> theNewParams, Collection<T> theExistingParams) {
|
||||||
for (T next : theNewParms) {
|
for (T next : theNewParams) {
|
||||||
next.setPartitionId(theEntity.getPartitionId());
|
next.setPartitionId(theEntity.getPartitionId());
|
||||||
next.calculateHashes();
|
next.calculateHashes();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<T> quantitiesToRemove = subtract(theExistingParms, theNewParms);
|
List<T> paramsToRemove = subtract(theExistingParams, theNewParams);
|
||||||
List<T> quantitiesToAdd = subtract(theNewParms, theExistingParms);
|
List<T> paramsToAdd = subtract(theNewParams, theExistingParams);
|
||||||
tryToReuseIndexEntities(quantitiesToRemove, quantitiesToAdd);
|
tryToReuseIndexEntities(paramsToRemove, paramsToAdd);
|
||||||
|
|
||||||
for (T next : quantitiesToRemove) {
|
for (T next : paramsToRemove) {
|
||||||
myEntityManager.remove(next);
|
myEntityManager.remove(next);
|
||||||
theEntity.getParamsQuantity().remove(next);
|
theEntity.getParamsQuantity().remove(next);
|
||||||
}
|
}
|
||||||
for (T next : quantitiesToAdd) {
|
for (T next : paramsToAdd) {
|
||||||
myEntityManager.merge(next);
|
myEntityManager.merge(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
theAddRemoveCount.addToAddCount(quantitiesToAdd.size());
|
theAddRemoveCount.addToAddCount(paramsToRemove.size());
|
||||||
theAddRemoveCount.addToRemoveCount(quantitiesToRemove.size());
|
theAddRemoveCount.addToRemoveCount(paramsToRemove.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,6 +114,7 @@ public class DaoSearchParamSynchronizer {
|
|||||||
// Take a row we were going to remove, and repurpose its ID
|
// Take a row we were going to remove, and repurpose its ID
|
||||||
T entityToReuse = theIndexesToRemove.remove(theIndexesToRemove.size() - 1);
|
T entityToReuse = theIndexesToRemove.remove(theIndexesToRemove.size() - 1);
|
||||||
entityToReuse.copyMutableValuesFrom(targetEntity);
|
entityToReuse.copyMutableValuesFrom(targetEntity);
|
||||||
|
entityToReuse.calculateHashes();
|
||||||
theIndexesToAdd.set(addIndex, entityToReuse);
|
theIndexesToAdd.set(addIndex, entityToReuse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.ResourceLookup;
|
||||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
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.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
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.apache.commons.lang3.Validate;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -84,7 +84,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class IdHelperService {
|
public class IdHelperService {
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(IdHelperService.class);
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
protected IForcedIdDao myForcedIdDao;
|
protected IForcedIdDao myForcedIdDao;
|
||||||
@ -99,11 +98,13 @@ public class IdHelperService {
|
|||||||
|
|
||||||
private Cache<String, Long> myPersistentIdCache;
|
private Cache<String, Long> myPersistentIdCache;
|
||||||
private Cache<String, IResourceLookup> myResourceLookupCache;
|
private Cache<String, IResourceLookup> myResourceLookupCache;
|
||||||
|
private Cache<Long, Optional<String>> myForcedIdCache;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void start() {
|
public void start() {
|
||||||
myPersistentIdCache = newCache();
|
myPersistentIdCache = newCache();
|
||||||
myResourceLookupCache = newCache();
|
myResourceLookupCache = newCache();
|
||||||
|
myForcedIdCache = newCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -118,7 +119,7 @@ public class IdHelperService {
|
|||||||
* @throws ResourceNotFoundException If the ID can not be found
|
* @throws ResourceNotFoundException If the ID can not be found
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@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
|
// We only pass 1 input in so only 0..1 will come back
|
||||||
IdDt id = new IdDt(theResourceType, theResourceId);
|
IdDt id = new IdDt(theResourceType, theResourceId);
|
||||||
Collection<IResourceLookup> matches = translateForcedIdToPids(theRequestPartitionId, theRequestDetails, Collections.singletonList(id));
|
Collection<IResourceLookup> matches = translateForcedIdToPids(theRequestPartitionId, theRequestDetails, Collections.singletonList(id));
|
||||||
@ -135,7 +136,7 @@ public class IdHelperService {
|
|||||||
* @throws ResourceNotFoundException If the ID can not be found
|
* @throws ResourceNotFoundException If the ID can not be found
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public ResourcePersistentId resolveResourcePersistentIds(RequestPartitionId theRequestPartitionId, String theResourceType, String theId) {
|
public ResourcePersistentId resolveResourcePersistentIds(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theId) {
|
||||||
Long retVal;
|
Long retVal;
|
||||||
if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(theId)) {
|
if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(theId)) {
|
||||||
if (myDaoConfig.isDeleteEnabled()) {
|
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)
|
* are deleted (but note that forced IDs can't change, so the cache can't return incorrect results)
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public List<ResourcePersistentId> resolveResourcePersistentIdsWithCache(RequestPartitionId theRequestPartitionId, List<IIdType> theIds, RequestDetails theRequest) {
|
public List<ResourcePersistentId> resolveResourcePersistentIdsWithCache(RequestPartitionId theRequestPartitionId, List<IIdType> theIds) {
|
||||||
theIds.forEach(id -> Validate.isTrue(id.hasIdPart()));
|
theIds.forEach(id -> Validate.isTrue(id.hasIdPart()));
|
||||||
|
|
||||||
if (theIds.isEmpty()) {
|
if (theIds.isEmpty()) {
|
||||||
@ -202,14 +203,14 @@ public class IdHelperService {
|
|||||||
if (nextIds.size() > 0) {
|
if (nextIds.size() > 0) {
|
||||||
|
|
||||||
Collection<Object[]> views;
|
Collection<Object[]> views;
|
||||||
if (theRequestPartitionId != null) {
|
if (theRequestPartitionId.isAllPartitions()) {
|
||||||
|
views = myForcedIdDao.findByTypeAndForcedId(nextResourceType, nextIds);
|
||||||
|
} else {
|
||||||
if (theRequestPartitionId.getPartitionId() != null) {
|
if (theRequestPartitionId.getPartitionId() != null) {
|
||||||
views = myForcedIdDao.findByTypeAndForcedIdInPartition(nextResourceType, nextIds, theRequestPartitionId.getPartitionId());
|
views = myForcedIdDao.findByTypeAndForcedIdInPartition(nextResourceType, nextIds, theRequestPartitionId.getPartitionId());
|
||||||
} else {
|
} else {
|
||||||
views = myForcedIdDao.findByTypeAndForcedIdInPartitionNull(nextResourceType, nextIds);
|
views = myForcedIdDao.findByTypeAndForcedIdInPartitionNull(nextResourceType, nextIds);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
views = myForcedIdDao.findByTypeAndForcedId(nextResourceType, nextIds);
|
|
||||||
}
|
}
|
||||||
for (Object[] nextView : views) {
|
for (Object[] nextView : views) {
|
||||||
String forcedId = (String) nextView[0];
|
String forcedId = (String) nextView[0];
|
||||||
@ -233,17 +234,20 @@ public class IdHelperService {
|
|||||||
@Nonnull
|
@Nonnull
|
||||||
public IIdType translatePidIdToForcedId(FhirContext theCtx, String theResourceType, ResourcePersistentId theId) {
|
public IIdType translatePidIdToForcedId(FhirContext theCtx, String theResourceType, ResourcePersistentId theId) {
|
||||||
IIdType retVal = theCtx.getVersion().newIdType();
|
IIdType retVal = theCtx.getVersion().newIdType();
|
||||||
retVal.setValue(translatePidIdToForcedId(theResourceType, theId));
|
|
||||||
|
Optional<String> forcedId = translatePidIdToForcedId(theId);
|
||||||
|
if (forcedId.isPresent()) {
|
||||||
|
retVal.setValue(theResourceType + '/' + forcedId.get());
|
||||||
|
} else {
|
||||||
|
retVal.setValue(theResourceType + '/' + theId.toString());
|
||||||
|
}
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String translatePidIdToForcedId(String theResourceType, ResourcePersistentId theId) {
|
|
||||||
ForcedId forcedId = myForcedIdDao.findByResourcePid(theId.getIdAsLong());
|
public Optional<String> translatePidIdToForcedId(ResourcePersistentId theId) {
|
||||||
if (forcedId != null) {
|
return myForcedIdCache.get(theId.getIdAsLong(), pid -> myForcedIdDao.findByResourcePid(pid).map(t -> t.getForcedId()));
|
||||||
return forcedId.getResourceType() + '/' + forcedId.getForcedId();
|
|
||||||
} else {
|
|
||||||
return theResourceType + '/' + theId.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ListMultimap<String, String> organizeIdsByResourceType(Collection<IIdType> theIds) {
|
private ListMultimap<String, String> organizeIdsByResourceType(Collection<IIdType> theIds) {
|
||||||
@ -260,15 +264,9 @@ public class IdHelperService {
|
|||||||
return typeToIds;
|
return typeToIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Long resolveResourceIdentity(@Nullable RequestPartitionId theRequestPartitionId, @Nonnull String theResourceType, @Nonnull String theId) {
|
private Long resolveResourceIdentity(@Nonnull RequestPartitionId theRequestPartitionId, @Nonnull String theResourceType, @Nonnull String theId) {
|
||||||
Optional<Long> pid;
|
Optional<Long> pid;
|
||||||
if (theRequestPartitionId != null) {
|
if (theRequestPartitionId.isAllPartitions()) {
|
||||||
if (theRequestPartitionId.getPartitionId() == null) {
|
|
||||||
pid = myForcedIdDao.findByPartitionIdNullAndTypeAndForcedId(theResourceType, theId);
|
|
||||||
} else {
|
|
||||||
pid = myForcedIdDao.findByPartitionIdAndTypeAndForcedId(theRequestPartitionId.getPartitionId(), theResourceType, theId);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
try {
|
||||||
pid = myForcedIdDao.findByTypeAndForcedId(theResourceType, theId);
|
pid = myForcedIdDao.findByTypeAndForcedId(theResourceType, theId);
|
||||||
} catch (IncorrectResultSizeDataAccessException e) {
|
} catch (IncorrectResultSizeDataAccessException e) {
|
||||||
@ -280,6 +278,12 @@ public class IdHelperService {
|
|||||||
String msg = myFhirCtx.getLocalizer().getMessage(IdHelperService.class, "nonUniqueForcedId");
|
String msg = myFhirCtx.getLocalizer().getMessage(IdHelperService.class, "nonUniqueForcedId");
|
||||||
throw new PreconditionFailedException(msg);
|
throw new PreconditionFailedException(msg);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (theRequestPartitionId.getPartitionId() == null) {
|
||||||
|
pid = myForcedIdDao.findByPartitionIdNullAndTypeAndForcedId(theResourceType, theId);
|
||||||
|
} else {
|
||||||
|
pid = myForcedIdDao.findByPartitionIdAndTypeAndForcedId(theRequestPartitionId.getPartitionId(), theResourceType, theId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pid.isPresent()) {
|
if (!pid.isPresent()) {
|
||||||
@ -288,7 +292,7 @@ public class IdHelperService {
|
|||||||
return pid.get();
|
return pid.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<IResourceLookup> translateForcedIdToPids(RequestPartitionId theRequestPartitionId, RequestDetails theRequest, Collection<IIdType> theId) {
|
private Collection<IResourceLookup> translateForcedIdToPids(@Nonnull RequestPartitionId theRequestPartitionId, RequestDetails theRequest, Collection<IIdType> theId) {
|
||||||
theId.forEach(id -> Validate.isTrue(id.hasIdPart()));
|
theId.forEach(id -> Validate.isTrue(id.hasIdPart()));
|
||||||
|
|
||||||
if (theId.isEmpty()) {
|
if (theId.isEmpty()) {
|
||||||
@ -329,14 +333,14 @@ public class IdHelperService {
|
|||||||
Collection<Object[]> views;
|
Collection<Object[]> views;
|
||||||
assert isNotBlank(nextResourceType);
|
assert isNotBlank(nextResourceType);
|
||||||
|
|
||||||
if (theRequestPartitionId != null) {
|
if (theRequestPartitionId.isAllPartitions()) {
|
||||||
|
views = myForcedIdDao.findAndResolveByForcedIdWithNoType(nextResourceType, nextIds);
|
||||||
|
} else {
|
||||||
if (theRequestPartitionId.getPartitionId() != null) {
|
if (theRequestPartitionId.getPartitionId() != null) {
|
||||||
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(nextResourceType, nextIds, theRequestPartitionId.getPartitionId());
|
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(nextResourceType, nextIds, theRequestPartitionId.getPartitionId());
|
||||||
} else {
|
} else {
|
||||||
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(nextResourceType, nextIds);
|
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(nextResourceType, nextIds);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
views = myForcedIdDao.findAndResolveByForcedIdWithNoType(nextResourceType, nextIds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Object[] next : views) {
|
for (Object[] next : views) {
|
||||||
@ -359,16 +363,16 @@ public class IdHelperService {
|
|||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resolvePids(RequestPartitionId theRequestPartitionId, List<Long> thePidsToResolve, List<IResourceLookup> theTarget) {
|
private void resolvePids(@Nonnull RequestPartitionId theRequestPartitionId, List<Long> thePidsToResolve, List<IResourceLookup> theTarget) {
|
||||||
Collection<Object[]> lookup;
|
Collection<Object[]> lookup;
|
||||||
if (theRequestPartitionId != null) {
|
if (theRequestPartitionId.isAllPartitions()) {
|
||||||
|
lookup = myResourceTableDao.findLookupFieldsByResourcePid(thePidsToResolve);
|
||||||
|
} else {
|
||||||
if (theRequestPartitionId.getPartitionId() != null) {
|
if (theRequestPartitionId.getPartitionId() != null) {
|
||||||
lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartition(thePidsToResolve, theRequestPartitionId.getPartitionId());
|
lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartition(thePidsToResolve, theRequestPartitionId.getPartitionId());
|
||||||
} else {
|
} else {
|
||||||
lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartitionNull(thePidsToResolve);
|
lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartitionNull(thePidsToResolve);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
lookup = myResourceTableDao.findLookupFieldsByResourcePid(thePidsToResolve);
|
|
||||||
}
|
}
|
||||||
lookup
|
lookup
|
||||||
.stream()
|
.stream()
|
||||||
@ -379,6 +383,7 @@ public class IdHelperService {
|
|||||||
public void clearCache() {
|
public void clearCache() {
|
||||||
myPersistentIdCache.invalidateAll();
|
myPersistentIdCache.invalidateAll();
|
||||||
myResourceLookupCache.invalidateAll();
|
myResourceLookupCache.invalidateAll();
|
||||||
|
myForcedIdCache.invalidateAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T, V> @NonNull Cache<T, V> newCache() {
|
private <T, V> @NonNull Cache<T, V> newCache() {
|
||||||
@ -389,6 +394,38 @@ public class IdHelperService {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<Long, Optional<String>> translatePidsToForcedIds(Set<Long> thePids) {
|
||||||
|
|
||||||
|
Map<Long, Optional<String>> retVal = new HashMap<>(myForcedIdCache.getAllPresent(thePids));
|
||||||
|
|
||||||
|
List<Long> remainingPids = thePids
|
||||||
|
.stream()
|
||||||
|
.filter(t -> !retVal.containsKey(t))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
new QueryChunker<Long>().chunk(remainingPids, t -> {
|
||||||
|
List<ForcedId> forcedIds = myForcedIdDao.findAllByResourcePid(t);
|
||||||
|
|
||||||
|
for (ForcedId forcedId : forcedIds) {
|
||||||
|
Long nextResourcePid = forcedId.getResourceId();
|
||||||
|
Optional<String> nextForcedId = Optional.of(forcedId.getForcedId());
|
||||||
|
retVal.put(nextResourcePid, nextForcedId);
|
||||||
|
myForcedIdCache.put(nextResourcePid, nextForcedId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
remainingPids = thePids
|
||||||
|
.stream()
|
||||||
|
.filter(t -> !retVal.containsKey(t))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
for (Long nextResourcePid : remainingPids) {
|
||||||
|
retVal.put(nextResourcePid, Optional.empty());
|
||||||
|
myForcedIdCache.put(nextResourcePid, Optional.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isValidPid(IIdType theId) {
|
public static boolean isValidPid(IIdType theId) {
|
||||||
if (theId == null) {
|
if (theId == null) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.dao.index;
|
|||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||||
import ca.uhn.fhir.jpa.dao.MatchResourceUrlService;
|
import ca.uhn.fhir.jpa.dao.MatchResourceUrlService;
|
||||||
@ -96,7 +97,14 @@ public class SearchParamWithInlineReferencesExtractor {
|
|||||||
public void populateFromResource(ResourceIndexedSearchParams theParams, Date theUpdateTime, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams theExistingParams, RequestDetails theRequest) {
|
public void populateFromResource(ResourceIndexedSearchParams theParams, Date theUpdateTime, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams theExistingParams, RequestDetails theRequest) {
|
||||||
extractInlineReferences(theResource, theRequest);
|
extractInlineReferences(theResource, theRequest);
|
||||||
|
|
||||||
mySearchParamExtractorService.extractFromResource(theEntity.getPartitionId(), theRequest, theParams, theEntity, theResource, theUpdateTime, true);
|
RequestPartitionId partitionId;
|
||||||
|
if (myPartitionSettings.isPartitioningEnabled()) {
|
||||||
|
partitionId = theEntity.getPartitionId();
|
||||||
|
} else {
|
||||||
|
partitionId = RequestPartitionId.allPartitions();
|
||||||
|
}
|
||||||
|
|
||||||
|
mySearchParamExtractorService.extractFromResource(partitionId, theRequest, theParams, theEntity, theResource, theUpdateTime, true);
|
||||||
|
|
||||||
Set<Map.Entry<String, RuntimeSearchParam>> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet();
|
Set<Map.Entry<String, RuntimeSearchParam>> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet();
|
||||||
if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.ENABLED) {
|
if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.ENABLED) {
|
||||||
|
@ -131,7 +131,7 @@ abstract class BasePredicateBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void addPredicateParamMissingForNonReference(String theResourceName, String theParamName, boolean theMissing, Join<ResourceTable, ? extends BaseResourceIndexedSearchParam> theJoin, RequestPartitionId theRequestPartitionId) {
|
void addPredicateParamMissingForNonReference(String theResourceName, String theParamName, boolean theMissing, Join<ResourceTable, ? extends BaseResourceIndexedSearchParam> theJoin, RequestPartitionId theRequestPartitionId) {
|
||||||
if (theRequestPartitionId != null) {
|
if (!theRequestPartitionId.isAllPartitions()) {
|
||||||
if (theRequestPartitionId.getPartitionId() != null) {
|
if (theRequestPartitionId.getPartitionId() != null) {
|
||||||
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myPartitionIdValue"), theRequestPartitionId.getPartitionId()));
|
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myPartitionIdValue"), theRequestPartitionId.getPartitionId()));
|
||||||
} else {
|
} else {
|
||||||
@ -224,7 +224,7 @@ abstract class BasePredicateBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void addPartitionIdPredicate(RequestPartitionId theRequestPartitionId, From<?, ? extends BasePartitionable> theJoin, List<Predicate> theCodePredicates) {
|
void addPartitionIdPredicate(RequestPartitionId theRequestPartitionId, From<?, ? extends BasePartitionable> theJoin, List<Predicate> theCodePredicates) {
|
||||||
if (theRequestPartitionId != null) {
|
if (!theRequestPartitionId.isAllPartitions()) {
|
||||||
Integer partitionId = theRequestPartitionId.getPartitionId();
|
Integer partitionId = theRequestPartitionId.getPartitionId();
|
||||||
Predicate partitionPredicate;
|
Predicate partitionPredicate;
|
||||||
if (partitionId != null) {
|
if (partitionId != null) {
|
||||||
|
@ -61,7 +61,17 @@ import ca.uhn.fhir.rest.api.Constants;
|
|||||||
import ca.uhn.fhir.rest.api.QualifiedParamList;
|
import ca.uhn.fhir.rest.api.QualifiedParamList;
|
||||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.param.*;
|
import ca.uhn.fhir.rest.param.CompositeParam;
|
||||||
|
import ca.uhn.fhir.rest.param.DateParam;
|
||||||
|
import ca.uhn.fhir.rest.param.HasParam;
|
||||||
|
import ca.uhn.fhir.rest.param.NumberParam;
|
||||||
|
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||||
|
import ca.uhn.fhir.rest.param.QuantityParam;
|
||||||
|
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||||
|
import ca.uhn.fhir.rest.param.SpecialParam;
|
||||||
|
import ca.uhn.fhir.rest.param.StringParam;
|
||||||
|
import ca.uhn.fhir.rest.param.TokenParam;
|
||||||
|
import ca.uhn.fhir.rest.param.UriParam;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||||
@ -201,7 +211,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resources by ID
|
// Resources by ID
|
||||||
List<ResourcePersistentId> targetPids = myIdHelperService.resolveResourcePersistentIdsWithCache(theRequestPartitionId, targetIds, theRequest);
|
List<ResourcePersistentId> targetPids = myIdHelperService.resolveResourcePersistentIdsWithCache(theRequestPartitionId, targetIds);
|
||||||
if (!targetPids.isEmpty()) {
|
if (!targetPids.isEmpty()) {
|
||||||
ourLog.debug("Searching for resource link with target PIDs: {}", targetPids);
|
ourLog.debug("Searching for resource link with target PIDs: {}", targetPids);
|
||||||
Predicate pathPredicate;
|
Predicate pathPredicate;
|
||||||
@ -565,7 +575,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
|||||||
if (nextParamDef != null) {
|
if (nextParamDef != null) {
|
||||||
|
|
||||||
if (myPartitionSettings.isPartitioningEnabled() && myPartitionSettings.isIncludePartitionInSearchHashes()) {
|
if (myPartitionSettings.isPartitioningEnabled() && myPartitionSettings.isIncludePartitionInSearchHashes()) {
|
||||||
if (theRequestPartitionId == null) {
|
if (theRequestPartitionId.isAllPartitions()) {
|
||||||
throw new PreconditionFailedException("This server is not configured to support search against all partitions");
|
throw new PreconditionFailedException("This server is not configured to support search against all partitions");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.entity;
|
|||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
|
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
@ -73,4 +75,7 @@ public class PartitionEntity {
|
|||||||
myDescription = theDescription;
|
myDescription = theDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RequestPartitionId toRequestPartitionId() {
|
||||||
|
return RequestPartitionId.fromPartitionIdAndName(getId(), getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.entity;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||||
import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity;
|
import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
|
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
|
||||||
@ -48,6 +49,7 @@ import java.util.Date;
|
|||||||
" h.res_updated as res_updated, " +
|
" h.res_updated as res_updated, " +
|
||||||
" h.res_text as res_text, " +
|
" h.res_text as res_text, " +
|
||||||
" h.res_encoding as res_encoding, " +
|
" h.res_encoding as res_encoding, " +
|
||||||
|
" h.PARTITION_ID as PARTITION_ID, " +
|
||||||
" p.SOURCE_URI as PROV_SOURCE_URI," +
|
" p.SOURCE_URI as PROV_SOURCE_URI," +
|
||||||
" p.REQUEST_ID as PROV_REQUEST_ID," +
|
" p.REQUEST_ID as PROV_REQUEST_ID," +
|
||||||
" f.forced_id as FORCED_PID " +
|
" f.forced_id as FORCED_PID " +
|
||||||
@ -94,6 +96,8 @@ public class ResourceSearchView implements IBaseResourceEntity, Serializable {
|
|||||||
private ResourceEncodingEnum myEncoding;
|
private ResourceEncodingEnum myEncoding;
|
||||||
@Column(name = "FORCED_PID", length = ForcedId.MAX_FORCED_ID_LENGTH)
|
@Column(name = "FORCED_PID", length = ForcedId.MAX_FORCED_ID_LENGTH)
|
||||||
private String myForcedPid;
|
private String myForcedPid;
|
||||||
|
@Column(name = "PARTITION_ID")
|
||||||
|
private Integer myPartitionId;
|
||||||
|
|
||||||
public ResourceSearchView() {
|
public ResourceSearchView() {
|
||||||
}
|
}
|
||||||
@ -187,6 +191,11 @@ public class ResourceSearchView implements IBaseResourceEntity, Serializable {
|
|||||||
return myHasTags;
|
return myHasTags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestPartitionId getPartitionId() {
|
||||||
|
return RequestPartitionId.fromPartitionId(myPartitionId);
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getResource() {
|
public byte[] getResource() {
|
||||||
return myResource;
|
return myResource;
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.partition;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
|
|
||||||
public interface IPartitionLookupSvc {
|
public interface IPartitionLookupSvc {
|
||||||
|
|
||||||
@ -30,11 +31,14 @@ public interface IPartitionLookupSvc {
|
|||||||
void start();
|
void start();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws IllegalArgumentException If the name is not known
|
* @throws ResourceNotFoundException If the name is not known
|
||||||
*/
|
*/
|
||||||
PartitionEntity getPartitionByName(String theName) throws IllegalArgumentException;
|
PartitionEntity getPartitionByName(String theName) throws ResourceNotFoundException;
|
||||||
|
|
||||||
PartitionEntity getPartitionById(Integer theId);
|
/**
|
||||||
|
* @throws ResourceNotFoundException If the ID is not known
|
||||||
|
*/
|
||||||
|
PartitionEntity getPartitionById(Integer theId) throws ResourceNotFoundException;
|
||||||
|
|
||||||
void clearCaches();
|
void clearCaches();
|
||||||
|
|
||||||
|
@ -27,10 +27,10 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
|
|||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
public interface IRequestPartitionHelperService {
|
public interface IRequestPartitionHelperSvc {
|
||||||
@Nullable
|
@Nonnull
|
||||||
RequestPartitionId determineReadPartitionForRequest(@Nullable RequestDetails theRequest, String theResourceType);
|
RequestPartitionId determineReadPartitionForRequest(@Nullable RequestDetails theRequest, String theResourceType);
|
||||||
|
|
||||||
@Nullable
|
@Nonnull
|
||||||
RequestPartitionId determineCreatePartitionForRequest(@Nullable RequestDetails theRequest, @Nonnull IBaseResource theResource);
|
RequestPartitionId determineCreatePartitionForRequest(@Nullable RequestDetails theRequest, @Nonnull IBaseResource theResource, @Nonnull String theResourceType);
|
||||||
}
|
}
|
@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirContext;
|
|||||||
import ca.uhn.fhir.jpa.dao.data.IPartitionDao;
|
import ca.uhn.fhir.jpa.dao.data.IPartitionDao;
|
||||||
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import com.github.benmanes.caffeine.cache.CacheLoader;
|
import com.github.benmanes.caffeine.cache.CacheLoader;
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||||
@ -97,9 +98,9 @@ public class PartitionLookupSvcImpl implements IPartitionLookupSvc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PartitionEntity getPartitionById(Integer theId) {
|
public PartitionEntity getPartitionById(Integer thePartitionId) {
|
||||||
Validate.notNull(theId, "The ID must not be null");
|
validatePartitionIdSupplied(myFhirCtx, thePartitionId);
|
||||||
return myIdToPartitionCache.get(theId);
|
return myIdToPartitionCache.get(thePartitionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -158,7 +159,7 @@ public class PartitionLookupSvcImpl implements IPartitionLookupSvc {
|
|||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void deletePartition(Integer thePartitionId) {
|
public void deletePartition(Integer thePartitionId) {
|
||||||
Validate.notNull(thePartitionId);
|
validatePartitionIdSupplied(myFhirCtx, thePartitionId);
|
||||||
|
|
||||||
if (DEFAULT_PERSISTED_PARTITION_ID == thePartitionId) {
|
if (DEFAULT_PERSISTED_PARTITION_ID == thePartitionId) {
|
||||||
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "cantDeleteDefaultPartition");
|
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "cantDeleteDefaultPartition");
|
||||||
@ -204,7 +205,7 @@ public class PartitionLookupSvcImpl implements IPartitionLookupSvc {
|
|||||||
.findForName(theName)
|
.findForName(theName)
|
||||||
.orElseThrow(() -> {
|
.orElseThrow(() -> {
|
||||||
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "invalidName", theName);
|
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "invalidName", theName);
|
||||||
return new IllegalArgumentException(msg);
|
return new ResourceNotFoundException(msg);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -217,8 +218,15 @@ public class PartitionLookupSvcImpl implements IPartitionLookupSvc {
|
|||||||
.findById(theId)
|
.findById(theId)
|
||||||
.orElseThrow(() -> {
|
.orElseThrow(() -> {
|
||||||
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "unknownPartitionId", theId);
|
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "unknownPartitionId", theId);
|
||||||
return new IllegalArgumentException(msg);
|
return new ResourceNotFoundException(msg);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void validatePartitionIdSupplied(FhirContext theFhirContext, Integer thePartitionId) {
|
||||||
|
if (thePartitionId == null) {
|
||||||
|
String msg = theFhirContext.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "noIdSupplied");
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,12 +32,14 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import static ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.validatePartitionIdSupplied;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
import static org.hl7.fhir.instance.model.api.IPrimitiveType.toValueOrNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This HAPI FHIR Server Plain Provider class provides the following operations:
|
* This HAPI FHIR Server Plain Provider class provides the following operations:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li><code>partition-management-add-partition</code></li>
|
* <li><code>partition-management-create-partition</code></li>
|
||||||
* <li><code>partition-management-update-partition</code></li>
|
* <li><code>partition-management-update-partition</code></li>
|
||||||
* <li><code>partition-management-delete-partition</code></li>
|
* <li><code>partition-management-delete-partition</code></li>
|
||||||
* </ul>
|
* </ul>
|
||||||
@ -47,29 +49,52 @@ public class PartitionManagementProvider {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private FhirContext myCtx;
|
private FhirContext myCtx;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IPartitionLookupSvc myPartitionConfigSvc;
|
private IPartitionLookupSvc myPartitionLookupSvc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add Partition:
|
* Add Partition:
|
||||||
* <code>
|
* <code>
|
||||||
* $partition-management-add-partition
|
* $partition-management-create-partition
|
||||||
* </code>
|
* </code>
|
||||||
*/
|
*/
|
||||||
@Operation(name = ProviderConstants.PARTITION_MANAGEMENT_ADD_PARTITION)
|
@Operation(name = ProviderConstants.PARTITION_MANAGEMENT_CREATE_PARTITION)
|
||||||
public IBaseParameters addPartition(
|
public IBaseParameters addPartition(
|
||||||
@ResourceParam IBaseParameters theRequest,
|
@ResourceParam IBaseParameters theRequest,
|
||||||
@OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, min = 1, max = 1, typeName = "integer") IPrimitiveType<Integer> thePartitionId,
|
@OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, min = 1, max = 1, typeName = "integer") IPrimitiveType<Integer> thePartitionId,
|
||||||
@OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, min = 1, max = 1, typeName = "code") IPrimitiveType<String> thePartitionName,
|
@OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, min = 1, max = 1, typeName = "code") IPrimitiveType<String> thePartitionName,
|
||||||
@OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, min = 0, max = 1, typeName = "string") IPrimitiveType<String> thePartitionDescription
|
@OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, min = 0, max = 1, typeName = "string") IPrimitiveType<String> thePartitionDescription
|
||||||
) {
|
) {
|
||||||
|
validatePartitionIdSupplied(myCtx, toValueOrNull(thePartitionId));
|
||||||
|
|
||||||
PartitionEntity input = parseInput(thePartitionId, thePartitionName, thePartitionDescription);
|
PartitionEntity input = parseInput(thePartitionId, thePartitionName, thePartitionDescription);
|
||||||
PartitionEntity output = myPartitionConfigSvc.createPartition(input);
|
|
||||||
|
// Note: Input validation happens inside IPartitionLookupSvc
|
||||||
|
PartitionEntity output = myPartitionLookupSvc.createPartition(input);
|
||||||
|
|
||||||
IBaseParameters retVal = prepareOutput(output);
|
IBaseParameters retVal = prepareOutput(output);
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Partition:
|
||||||
|
* <code>
|
||||||
|
* $partition-management-read-partition
|
||||||
|
* </code>
|
||||||
|
*/
|
||||||
|
@Operation(name = ProviderConstants.PARTITION_MANAGEMENT_READ_PARTITION, idempotent = true)
|
||||||
|
public IBaseParameters addPartition(
|
||||||
|
@ResourceParam IBaseParameters theRequest,
|
||||||
|
@OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, min = 1, max = 1, typeName = "integer") IPrimitiveType<Integer> thePartitionId
|
||||||
|
) {
|
||||||
|
validatePartitionIdSupplied(myCtx, toValueOrNull(thePartitionId));
|
||||||
|
|
||||||
|
// Note: Input validation happens inside IPartitionLookupSvc
|
||||||
|
PartitionEntity output = myPartitionLookupSvc.getPartitionById(thePartitionId.getValue());
|
||||||
|
|
||||||
|
return prepareOutput(output);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add Partition:
|
* Add Partition:
|
||||||
* <code>
|
* <code>
|
||||||
@ -83,9 +108,13 @@ public class PartitionManagementProvider {
|
|||||||
@OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, min = 1, max = 1, typeName = "code") IPrimitiveType<String> thePartitionName,
|
@OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, min = 1, max = 1, typeName = "code") IPrimitiveType<String> thePartitionName,
|
||||||
@OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, min = 0, max = 1, typeName = "string") IPrimitiveType<String> thePartitionDescription
|
@OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, min = 0, max = 1, typeName = "string") IPrimitiveType<String> thePartitionDescription
|
||||||
) {
|
) {
|
||||||
|
validatePartitionIdSupplied(myCtx, toValueOrNull(thePartitionId));
|
||||||
|
|
||||||
PartitionEntity input = parseInput(thePartitionId, thePartitionName, thePartitionDescription);
|
PartitionEntity input = parseInput(thePartitionId, thePartitionName, thePartitionDescription);
|
||||||
PartitionEntity output = myPartitionConfigSvc.updatePartition(input);
|
|
||||||
|
// Note: Input validation happens inside IPartitionLookupSvc
|
||||||
|
PartitionEntity output = myPartitionLookupSvc.updatePartition(input);
|
||||||
|
|
||||||
IBaseParameters retVal = prepareOutput(output);
|
IBaseParameters retVal = prepareOutput(output);
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
@ -102,8 +131,9 @@ public class PartitionManagementProvider {
|
|||||||
@ResourceParam IBaseParameters theRequest,
|
@ResourceParam IBaseParameters theRequest,
|
||||||
@OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, min = 1, max = 1, typeName = "integer") IPrimitiveType<Integer> thePartitionId
|
@OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, min = 1, max = 1, typeName = "integer") IPrimitiveType<Integer> thePartitionId
|
||||||
) {
|
) {
|
||||||
|
validatePartitionIdSupplied(myCtx, toValueOrNull(thePartitionId));
|
||||||
|
|
||||||
myPartitionConfigSvc.deletePartition(thePartitionId.getValue());
|
myPartitionLookupSvc.deletePartition(thePartitionId.getValue());
|
||||||
|
|
||||||
IBaseParameters retVal = ParametersUtil.newInstance(myCtx);
|
IBaseParameters retVal = ParametersUtil.newInstance(myCtx);
|
||||||
ParametersUtil.addParameterToParametersString(myCtx, retVal, "message", "Success");
|
ParametersUtil.addParameterToParametersString(myCtx, retVal, "message", "Success");
|
||||||
|
@ -24,7 +24,6 @@ import ca.uhn.fhir.context.FhirContext;
|
|||||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
||||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
@ -41,14 +40,13 @@ import javax.annotation.Nonnull;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import static ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster.doCallHooks;
|
||||||
import static ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster.doCallHooksAndReturnObject;
|
import static ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster.doCallHooksAndReturnObject;
|
||||||
|
|
||||||
public class RequestPartitionHelperService implements IRequestPartitionHelperService {
|
public class RequestPartitionHelperSvc implements IRequestPartitionHelperSvc {
|
||||||
|
|
||||||
private final HashSet<Object> myPartitioningBlacklist;
|
private final HashSet<Object> myPartitioningBlacklist;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private DaoConfig myDaoConfig;
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -58,7 +56,7 @@ public class RequestPartitionHelperService implements IRequestPartitionHelperSer
|
|||||||
@Autowired
|
@Autowired
|
||||||
private PartitionSettings myPartitionSettings;
|
private PartitionSettings myPartitionSettings;
|
||||||
|
|
||||||
public RequestPartitionHelperService() {
|
public RequestPartitionHelperSvc() {
|
||||||
myPartitioningBlacklist = new HashSet<>();
|
myPartitioningBlacklist = new HashSet<>();
|
||||||
|
|
||||||
// Infrastructure
|
// Infrastructure
|
||||||
@ -78,37 +76,45 @@ public class RequestPartitionHelperService implements IRequestPartitionHelperSer
|
|||||||
/**
|
/**
|
||||||
* Invoke the <code>STORAGE_PARTITION_IDENTIFY_READ</code> interceptor pointcut to determine the tenant for a read request
|
* Invoke the <code>STORAGE_PARTITION_IDENTIFY_READ</code> interceptor pointcut to determine the tenant for a read request
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public RequestPartitionId determineReadPartitionForRequest(@Nullable RequestDetails theRequest, String theResourceType) {
|
public RequestPartitionId determineReadPartitionForRequest(@Nullable RequestDetails theRequest, String theResourceType) {
|
||||||
if (myPartitioningBlacklist.contains(theResourceType)) {
|
RequestPartitionId requestPartitionId;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
RequestPartitionId requestPartitionId = null;
|
|
||||||
|
|
||||||
if (myPartitionSettings.isPartitioningEnabled()) {
|
if (myPartitionSettings.isPartitioningEnabled()) {
|
||||||
|
// Handle system requests
|
||||||
|
if (theRequest == null && myPartitioningBlacklist.contains(theResourceType)) {
|
||||||
|
return RequestPartitionId.defaultPartition();
|
||||||
|
}
|
||||||
|
|
||||||
// Interceptor call: STORAGE_PARTITION_IDENTIFY_READ
|
// Interceptor call: STORAGE_PARTITION_IDENTIFY_READ
|
||||||
HookParams params = new HookParams()
|
HookParams params = new HookParams()
|
||||||
.add(RequestDetails.class, theRequest)
|
.add(RequestDetails.class, theRequest)
|
||||||
.addIfMatchesType(ServletRequestDetails.class, theRequest);
|
.addIfMatchesType(ServletRequestDetails.class, theRequest);
|
||||||
requestPartitionId = (RequestPartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_READ, params);
|
requestPartitionId = (RequestPartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_READ, params);
|
||||||
|
|
||||||
validatePartition(requestPartitionId, theResourceType);
|
validatePartition(requestPartitionId, theResourceType, Pointcut.STORAGE_PARTITION_IDENTIFY_READ);
|
||||||
|
|
||||||
|
return normalizeAndNotifyHooks(requestPartitionId, theRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
return normalize(requestPartitionId);
|
return RequestPartitionId.allPartitions();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoke the <code>STORAGE_PARTITION_IDENTIFY_CREATE</code> interceptor pointcut to determine the tenant for a read request
|
* Invoke the <code>STORAGE_PARTITION_IDENTIFY_CREATE</code> interceptor pointcut to determine the tenant for a read request
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public RequestPartitionId determineCreatePartitionForRequest(@Nullable RequestDetails theRequest, @Nonnull IBaseResource theResource) {
|
public RequestPartitionId determineCreatePartitionForRequest(@Nullable RequestDetails theRequest, @Nonnull IBaseResource theResource, @Nonnull String theResourceType) {
|
||||||
|
RequestPartitionId requestPartitionId;
|
||||||
|
|
||||||
RequestPartitionId requestPartitionId = null;
|
|
||||||
if (myPartitionSettings.isPartitioningEnabled()) {
|
if (myPartitionSettings.isPartitioningEnabled()) {
|
||||||
|
// Handle system requests
|
||||||
|
if (theRequest == null && myPartitioningBlacklist.contains(theResourceType)) {
|
||||||
|
return RequestPartitionId.defaultPartition();
|
||||||
|
}
|
||||||
|
|
||||||
// Interceptor call: STORAGE_PARTITION_IDENTIFY_CREATE
|
// Interceptor call: STORAGE_PARTITION_IDENTIFY_CREATE
|
||||||
HookParams params = new HookParams()
|
HookParams params = new HookParams()
|
||||||
.add(IBaseResource.class, theResource)
|
.add(IBaseResource.class, theResource)
|
||||||
@ -117,62 +123,70 @@ public class RequestPartitionHelperService implements IRequestPartitionHelperSer
|
|||||||
requestPartitionId = (RequestPartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE, params);
|
requestPartitionId = (RequestPartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE, params);
|
||||||
|
|
||||||
String resourceName = myFhirContext.getResourceDefinition(theResource).getName();
|
String resourceName = myFhirContext.getResourceDefinition(theResource).getName();
|
||||||
validatePartition(requestPartitionId, resourceName);
|
validatePartition(requestPartitionId, resourceName, Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE);
|
||||||
|
|
||||||
|
return normalizeAndNotifyHooks(requestPartitionId, theRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
return normalize(requestPartitionId);
|
return RequestPartitionId.allPartitions();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the partition only has a name but not an ID, this method resolves the ID
|
* If the partition only has a name but not an ID, this method resolves the ID
|
||||||
* @param theRequestPartitionId
|
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
private RequestPartitionId normalize(RequestPartitionId theRequestPartitionId) {
|
@Nonnull
|
||||||
if (theRequestPartitionId != null) {
|
private RequestPartitionId normalizeAndNotifyHooks(@Nonnull RequestPartitionId theRequestPartitionId, RequestDetails theRequest) {
|
||||||
if (theRequestPartitionId.getPartitionName() != null) {
|
RequestPartitionId retVal = theRequestPartitionId;
|
||||||
|
|
||||||
PartitionEntity partition;
|
if (retVal.getPartitionName() != null) {
|
||||||
try {
|
|
||||||
partition = myPartitionConfigSvc.getPartitionByName(theRequestPartitionId.getPartitionName());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
String msg = myFhirContext.getLocalizer().getMessage(RequestPartitionHelperService.class, "unknownPartitionName", theRequestPartitionId.getPartitionName());
|
|
||||||
throw new ResourceNotFoundException(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (theRequestPartitionId.getPartitionId() != null) {
|
PartitionEntity partition;
|
||||||
Validate.isTrue(theRequestPartitionId.getPartitionId().equals(partition.getId()), "Partition name %s does not match ID %n", theRequestPartitionId.getPartitionName(), theRequestPartitionId.getPartitionId());
|
try {
|
||||||
return theRequestPartitionId;
|
partition = myPartitionConfigSvc.getPartitionByName(retVal.getPartitionName());
|
||||||
} else {
|
} catch (IllegalArgumentException e) {
|
||||||
return RequestPartitionId.forPartitionNameAndId(theRequestPartitionId.getPartitionName(), partition.getId(), theRequestPartitionId.getPartitionDate());
|
String msg = myFhirContext.getLocalizer().getMessage(RequestPartitionHelperSvc.class, "unknownPartitionName", retVal.getPartitionName());
|
||||||
}
|
throw new ResourceNotFoundException(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theRequestPartitionId.getPartitionId() != null) {
|
if (retVal.getPartitionId() != null) {
|
||||||
PartitionEntity partition;
|
Validate.isTrue(retVal.getPartitionId().equals(partition.getId()), "Partition name %s does not match ID %n", retVal.getPartitionName(), retVal.getPartitionId());
|
||||||
try {
|
} else {
|
||||||
partition = myPartitionConfigSvc.getPartitionById(theRequestPartitionId.getPartitionId());
|
retVal = RequestPartitionId.forPartitionIdAndName(partition.getId(), retVal.getPartitionName(), retVal.getPartitionDate());
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
String msg = myFhirContext.getLocalizer().getMessage(RequestPartitionHelperService.class, "unknownPartitionId", theRequestPartitionId.getPartitionId());
|
|
||||||
throw new ResourceNotFoundException(msg);
|
|
||||||
}
|
|
||||||
return RequestPartitionId.forPartitionNameAndId(partition.getName(), partition.getId(), theRequestPartitionId.getPartitionDate());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if (retVal.getPartitionId() != null) {
|
||||||
|
|
||||||
|
PartitionEntity partition;
|
||||||
|
try {
|
||||||
|
partition = myPartitionConfigSvc.getPartitionById(retVal.getPartitionId());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
String msg = myFhirContext.getLocalizer().getMessage(RequestPartitionHelperSvc.class, "unknownPartitionId", retVal.getPartitionId());
|
||||||
|
throw new ResourceNotFoundException(msg);
|
||||||
|
}
|
||||||
|
retVal = RequestPartitionId.forPartitionIdAndName(partition.getId(), partition.getName(), retVal.getPartitionDate());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's still possible that the partition only has a date but no name/id,
|
// Note: It's still possible that the partition only has a date but no name/id
|
||||||
// or it could just be null
|
|
||||||
return theRequestPartitionId;
|
HookParams params = new HookParams()
|
||||||
|
.add(RequestPartitionId.class, retVal)
|
||||||
|
.add(RequestDetails.class, theRequest)
|
||||||
|
.addIfMatchesType(ServletRequestDetails.class, theRequest);
|
||||||
|
doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_SELECTED, params);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validatePartition(@Nullable RequestPartitionId theRequestPartitionId, @Nonnull String theResourceName) {
|
private void validatePartition(@Nullable RequestPartitionId theRequestPartitionId, @Nonnull String theResourceName, Pointcut thePointcut) {
|
||||||
if (theRequestPartitionId != null && theRequestPartitionId.getPartitionId() != null) {
|
Validate.notNull(theRequestPartitionId, "Interceptor did not provide a value for pointcut: %s", thePointcut);
|
||||||
|
|
||||||
|
if (theRequestPartitionId.getPartitionId() != null) {
|
||||||
|
|
||||||
// Make sure we're not using one of the conformance resources in a non-default partition
|
// Make sure we're not using one of the conformance resources in a non-default partition
|
||||||
if (myPartitioningBlacklist.contains(theResourceName)) {
|
if (myPartitioningBlacklist.contains(theResourceName)) {
|
||||||
String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestPartitionHelperService.class, "blacklistedResourceTypeForPartitioning", theResourceName);
|
String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestPartitionHelperSvc.class, "blacklistedResourceTypeForPartitioning", theResourceName);
|
||||||
throw new UnprocessableEntityException(msg);
|
throw new UnprocessableEntityException(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +194,7 @@ public class RequestPartitionHelperService implements IRequestPartitionHelperSer
|
|||||||
try {
|
try {
|
||||||
myPartitionConfigSvc.getPartitionById(theRequestPartitionId.getPartitionId());
|
myPartitionConfigSvc.getPartitionById(theRequestPartitionId.getPartitionId());
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestPartitionHelperService.class, "unknownPartitionId", theRequestPartitionId.getPartitionId());
|
String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestPartitionHelperSvc.class, "unknownPartitionId", theRequestPartitionId.getPartitionId());
|
||||||
throw new InvalidRequestException(msg);
|
throw new InvalidRequestException(msg);
|
||||||
}
|
}
|
||||||
|
|
@ -24,9 +24,12 @@ import ca.uhn.fhir.context.FhirContext;
|
|||||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
||||||
|
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.ISearchBuilder;
|
||||||
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
@ -34,6 +37,7 @@ import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
|||||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
|
import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
||||||
|
import ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc;
|
||||||
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
||||||
import ca.uhn.fhir.jpa.util.InterceptorUtil;
|
import ca.uhn.fhir.jpa.util.InterceptorUtil;
|
||||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||||
@ -47,6 +51,7 @@ import ca.uhn.fhir.rest.api.server.SimplePreResourceShowDetails;
|
|||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -59,14 +64,8 @@ import org.springframework.transaction.support.TransactionTemplate;
|
|||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
import javax.persistence.PersistenceContext;
|
import javax.persistence.PersistenceContext;
|
||||||
import javax.persistence.TypedQuery;
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
|
||||||
import javax.persistence.criteria.CriteriaQuery;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
import javax.persistence.criteria.Root;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -87,6 +86,8 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private SearchBuilderFactory mySearchBuilderFactory;
|
private SearchBuilderFactory mySearchBuilderFactory;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private HistoryBuilderFactory myHistoryBuilderFactory;
|
||||||
|
@Autowired
|
||||||
private DaoRegistry myDaoRegistry;
|
private DaoRegistry myDaoRegistry;
|
||||||
@Autowired
|
@Autowired
|
||||||
protected PlatformTransactionManager myTxManager;
|
protected PlatformTransactionManager myTxManager;
|
||||||
@ -96,6 +97,8 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
|||||||
private ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
private ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISearchCacheSvc mySearchCacheSvc;
|
private ISearchCacheSvc mySearchCacheSvc;
|
||||||
|
@Autowired
|
||||||
|
private RequestPartitionHelperSvc myRequestPartitionHelperSvc;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Non autowired fields (will be different for every instance
|
* Non autowired fields (will be different for every instance
|
||||||
@ -106,6 +109,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
|||||||
private Search mySearchEntity;
|
private Search mySearchEntity;
|
||||||
private String myUuid;
|
private String myUuid;
|
||||||
private boolean myCacheHit;
|
private boolean myCacheHit;
|
||||||
|
private RequestPartitionId myRequestPartitionId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
@ -115,6 +119,14 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
|||||||
myUuid = theSearchUuid;
|
myUuid = theSearchUuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public PersistedJpaBundleProvider(RequestDetails theRequest, Search theSearch) {
|
||||||
|
myRequest = theRequest;
|
||||||
|
mySearchEntity = theSearch;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When HAPI FHIR server is running "for real", a new
|
* When HAPI FHIR server is running "for real", a new
|
||||||
* instance of the bundle provider is created to serve
|
* instance of the bundle provider is created to serve
|
||||||
@ -127,45 +139,17 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
|||||||
mySearchEntity = null;
|
mySearchEntity = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a history search
|
||||||
|
*/
|
||||||
private List<IBaseResource> doHistoryInTransaction(int theFromIndex, int theToIndex) {
|
private List<IBaseResource> doHistoryInTransaction(int theFromIndex, int theToIndex) {
|
||||||
List<ResourceHistoryTable> results;
|
|
||||||
|
|
||||||
CriteriaBuilder cb = myEntityManager.getCriteriaBuilder();
|
HistoryBuilder historyBuilder = myHistoryBuilderFactory.newHistoryBuilder(mySearchEntity.getResourceType(), mySearchEntity.getResourceId(), mySearchEntity.getLastUpdatedLow(), mySearchEntity.getLastUpdatedHigh());
|
||||||
CriteriaQuery<ResourceHistoryTable> q = cb.createQuery(ResourceHistoryTable.class);
|
|
||||||
Root<ResourceHistoryTable> from = q.from(ResourceHistoryTable.class);
|
|
||||||
List<Predicate> predicates = new ArrayList<>();
|
|
||||||
|
|
||||||
if (mySearchEntity.getResourceType() == null) {
|
RequestPartitionId partitionId = getRequestPartitionId();
|
||||||
// All resource types
|
List<ResourceHistoryTable> results = historyBuilder.fetchEntities(partitionId, theFromIndex, theToIndex);
|
||||||
} else if (mySearchEntity.getResourceId() == null) {
|
|
||||||
predicates.add(cb.equal(from.get("myResourceType"), mySearchEntity.getResourceType()));
|
|
||||||
} else {
|
|
||||||
predicates.add(cb.equal(from.get("myResourceId"), mySearchEntity.getResourceId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mySearchEntity.getLastUpdatedLow() != null) {
|
List<IBaseResource> retVal = new ArrayList<>();
|
||||||
predicates.add(cb.greaterThanOrEqualTo(from.get("myUpdated").as(Date.class), mySearchEntity.getLastUpdatedLow()));
|
|
||||||
}
|
|
||||||
if (mySearchEntity.getLastUpdatedHigh() != null) {
|
|
||||||
predicates.add(cb.lessThanOrEqualTo(from.get("myUpdated").as(Date.class), mySearchEntity.getLastUpdatedHigh()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (predicates.size() > 0) {
|
|
||||||
q.where(predicates.toArray(new Predicate[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
q.orderBy(cb.desc(from.get("myUpdated")));
|
|
||||||
|
|
||||||
TypedQuery<ResourceHistoryTable> query = myEntityManager.createQuery(q);
|
|
||||||
|
|
||||||
if (theToIndex - theFromIndex > 0) {
|
|
||||||
query.setFirstResult(theFromIndex);
|
|
||||||
query.setMaxResults(theToIndex - theFromIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
results = query.getResultList();
|
|
||||||
|
|
||||||
ArrayList<IBaseResource> retVal = new ArrayList<>();
|
|
||||||
for (ResourceHistoryTable next : results) {
|
for (ResourceHistoryTable next : results) {
|
||||||
BaseHasResource resource;
|
BaseHasResource resource;
|
||||||
resource = next;
|
resource = next;
|
||||||
@ -199,12 +183,26 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
|||||||
.add(RequestDetails.class, myRequest)
|
.add(RequestDetails.class, myRequest)
|
||||||
.addIfMatchesType(ServletRequestDetails.class, myRequest);
|
.addIfMatchesType(ServletRequestDetails.class, myRequest);
|
||||||
JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, myRequest, Pointcut.STORAGE_PRESHOW_RESOURCES, params);
|
JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, myRequest, Pointcut.STORAGE_PRESHOW_RESOURCES, params);
|
||||||
|
retVal = showDetails.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private RequestPartitionId getRequestPartitionId() {
|
||||||
|
if (myRequestPartitionId == null) {
|
||||||
|
if (mySearchEntity.getResourceId() != null) {
|
||||||
|
// If we have an ID, we've already checked the partition and made sure it's appropriate
|
||||||
|
myRequestPartitionId = RequestPartitionId.allPartitions();
|
||||||
|
} else {
|
||||||
|
myRequestPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest(myRequest, mySearchEntity.getResourceType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return myRequestPartitionId;
|
||||||
|
}
|
||||||
|
|
||||||
protected List<IBaseResource> doSearchOrEverything(final int theFromIndex, final int theToIndex) {
|
protected List<IBaseResource> doSearchOrEverything(final int theFromIndex, final int theToIndex) {
|
||||||
if (mySearchEntity.getTotalCount() != null && mySearchEntity.getNumFound() <= 0) {
|
if (mySearchEntity.getTotalCount() != null && mySearchEntity.getNumFound() <= 0) {
|
||||||
// No resources to fetch (e.g. we did a _summary=count search)
|
// No resources to fetch (e.g. we did a _summary=count search)
|
||||||
@ -239,6 +237,17 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mySearchEntity.getSearchType() == SearchTypeEnum.HISTORY) {
|
||||||
|
if (mySearchEntity.getTotalCount() == null) {
|
||||||
|
new TransactionTemplate(myTxManager).executeWithoutResult(t->{
|
||||||
|
HistoryBuilder historyBuilder = myHistoryBuilderFactory.newHistoryBuilder(mySearchEntity.getResourceType(), mySearchEntity.getResourceId(), mySearchEntity.getLastUpdatedLow(), mySearchEntity.getLastUpdatedHigh());
|
||||||
|
Long count = historyBuilder.fetchCount(getRequestPartitionId());
|
||||||
|
mySearchEntity.setTotalCount(count.intValue());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,7 +370,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
|||||||
List<IBaseResource> resources = new ArrayList<>();
|
List<IBaseResource> resources = new ArrayList<>();
|
||||||
theSearchBuilder.loadResourcesByPid(thePids, includedPidList, resources, false, myRequest);
|
theSearchBuilder.loadResourcesByPid(thePids, includedPidList, resources, false, myRequest);
|
||||||
|
|
||||||
InterceptorUtil.fireStoragePreshowResource(resources, myRequest, myInterceptorBroadcaster);
|
resources = InterceptorUtil.fireStoragePreshowResource(resources, myRequest, myInterceptorBroadcaster);
|
||||||
|
|
||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,11 @@ public class PersistedJpaBundleProviderFactory {
|
|||||||
return (PersistedJpaBundleProvider) retVal;
|
return (PersistedJpaBundleProvider) retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PersistedJpaBundleProvider newInstance(RequestDetails theRequest, Search theSearch) {
|
||||||
|
Object retVal = myApplicationContext.getBean(BaseConfig.PERSISTED_JPA_BUNDLE_PROVIDER_BY_SEARCH, theRequest, theSearch);
|
||||||
|
return (PersistedJpaBundleProvider) retVal;
|
||||||
|
}
|
||||||
|
|
||||||
public PersistedJpaSearchFirstPageBundleProvider newInstanceFirstPage(RequestDetails theRequestDetails, Search theSearch, SearchCoordinatorSvcImpl.SearchTask theTask, ISearchBuilder theSearchBuilder) {
|
public PersistedJpaSearchFirstPageBundleProvider newInstanceFirstPage(RequestDetails theRequestDetails, Search theSearch, SearchCoordinatorSvcImpl.SearchTask theTask, ISearchBuilder theSearchBuilder) {
|
||||||
return (PersistedJpaSearchFirstPageBundleProvider) myApplicationContext.getBean(BaseConfig.PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER, theRequestDetails, theSearch, theTask, theSearchBuilder);
|
return (PersistedJpaSearchFirstPageBundleProvider) myApplicationContext.getBean(BaseConfig.PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER, theRequestDetails, theSearch, theTask, theSearchBuilder);
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
|||||||
import ca.uhn.fhir.jpa.dao.IResultIterator;
|
import ca.uhn.fhir.jpa.dao.IResultIterator;
|
||||||
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
||||||
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
||||||
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperService;
|
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
import ca.uhn.fhir.jpa.entity.SearchInclude;
|
import ca.uhn.fhir.jpa.entity.SearchInclude;
|
||||||
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
||||||
@ -156,7 +156,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
|
private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IRequestPartitionHelperService myRequestPartitionHelperService;
|
private IRequestPartitionHelperSvc myRequestPartitionHelperService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
@ -514,7 +514,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||||||
theSb.loadResourcesByPid(pids, includedPidsList, resources, false, theRequestDetails);
|
theSb.loadResourcesByPid(pids, includedPidsList, resources, false, theRequestDetails);
|
||||||
|
|
||||||
// Hook: STORAGE_PRESHOW_RESOURCES
|
// Hook: STORAGE_PRESHOW_RESOURCES
|
||||||
InterceptorUtil.fireStoragePreshowResource(resources, theRequestDetails, myInterceptorBroadcaster);
|
resources = InterceptorUtil.fireStoragePreshowResource(resources, theRequestDetails, myInterceptorBroadcaster);
|
||||||
|
|
||||||
return new SimpleBundleProvider(resources);
|
return new SimpleBundleProvider(resources);
|
||||||
});
|
});
|
||||||
@ -594,7 +594,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public void setRequestPartitionHelperService(IRequestPartitionHelperService theRequestPartitionHelperService) {
|
public void setRequestPartitionHelperService(IRequestPartitionHelperSvc theRequestPartitionHelperService) {
|
||||||
myRequestPartitionHelperService = theRequestPartitionHelperService;
|
myRequestPartitionHelperService = theRequestPartitionHelperService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.term;
|
|||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.dao.IHapiJpaRepository;
|
import ca.uhn.fhir.jpa.dao.IHapiJpaRepository;
|
||||||
@ -117,7 +118,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResourcePersistentId getValueSetResourcePid(IIdType theIdType) {
|
public ResourcePersistentId getValueSetResourcePid(IIdType theIdType) {
|
||||||
return myIdHelperService.resolveResourcePersistentIds(null, theIdType.getResourceType(), theIdType.getIdPart());
|
return myIdHelperService.resolveResourcePersistentIds(RequestPartitionId.allPartitions(), theIdType.getResourceType(), theIdType.getIdPart());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@ -291,7 +292,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
|
|||||||
Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL");
|
Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL");
|
||||||
|
|
||||||
IIdType csId = myTerminologyVersionAdapterSvc.createOrUpdateCodeSystem(theCodeSystemResource);
|
IIdType csId = myTerminologyVersionAdapterSvc.createOrUpdateCodeSystem(theCodeSystemResource);
|
||||||
ResourcePersistentId codeSystemResourcePid = myIdHelperService.resolveResourcePersistentIds(null, csId.getResourceType(), csId.getIdPart());
|
ResourcePersistentId codeSystemResourcePid = myIdHelperService.resolveResourcePersistentIds(RequestPartitionId.allPartitions(), csId.getResourceType(), csId.getIdPart());
|
||||||
ResourceTable resource = myResourceTableDao.getOne(codeSystemResourcePid.getIdAsLong());
|
ResourceTable resource = myResourceTableDao.getOne(codeSystemResourcePid.getIdAsLong());
|
||||||
|
|
||||||
ourLog.info("CodeSystem resource has ID: {}", csId.getValue());
|
ourLog.info("CodeSystem resource has ID: {}", csId.getValue());
|
||||||
@ -551,7 +552,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ResourcePersistentId getCodeSystemResourcePid(IIdType theIdType) {
|
private ResourcePersistentId getCodeSystemResourcePid(IIdType theIdType) {
|
||||||
return myIdHelperService.resolveResourcePersistentIds(null, theIdType.getResourceType(), theIdType.getIdPart());
|
return myIdHelperService.resolveResourcePersistentIds(RequestPartitionId.allPartitions(), theIdType.getResourceType(), theIdType.getIdPart());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap<TermConcept, Object> theConceptsStack, int theTotalConcepts) {
|
private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap<TermConcept, Object> theConceptsStack, int theTotalConcepts) {
|
||||||
|
@ -36,24 +36,28 @@ public class InterceptorUtil {
|
|||||||
/**
|
/**
|
||||||
* Fires {@link Pointcut#STORAGE_PRESHOW_RESOURCES} interceptor hook, and potentially remove resources
|
* Fires {@link Pointcut#STORAGE_PRESHOW_RESOURCES} interceptor hook, and potentially remove resources
|
||||||
* from the resource list
|
* from the resource list
|
||||||
|
* @return
|
||||||
*/
|
*/
|
||||||
public static void fireStoragePreshowResource(List<IBaseResource> theResources, RequestDetails theRequest, IInterceptorBroadcaster theInterceptorBroadcaster) {
|
public static List<IBaseResource> fireStoragePreshowResource(List<IBaseResource> theResources, RequestDetails theRequest, IInterceptorBroadcaster theInterceptorBroadcaster) {
|
||||||
theResources.removeIf(t -> t == null);
|
List<IBaseResource> retVal = theResources;
|
||||||
|
retVal.removeIf(t -> t == null);
|
||||||
|
|
||||||
// Interceptor call: STORAGE_PRESHOW_RESOURCE
|
// Interceptor call: STORAGE_PRESHOW_RESOURCE
|
||||||
// This can be used to remove results from the search result details before
|
// This can be used to remove results from the search result details before
|
||||||
// the user has a chance to know that they were in the results
|
// the user has a chance to know that they were in the results
|
||||||
if (theResources.size() > 0) {
|
if (retVal.size() > 0) {
|
||||||
SimplePreResourceShowDetails accessDetails = new SimplePreResourceShowDetails(theResources);
|
SimplePreResourceShowDetails accessDetails = new SimplePreResourceShowDetails(retVal);
|
||||||
HookParams params = new HookParams()
|
HookParams params = new HookParams()
|
||||||
.add(IPreResourceShowDetails.class, accessDetails)
|
.add(IPreResourceShowDetails.class, accessDetails)
|
||||||
.add(RequestDetails.class, theRequest)
|
.add(RequestDetails.class, theRequest)
|
||||||
.addIfMatchesType(ServletRequestDetails.class, theRequest);
|
.addIfMatchesType(ServletRequestDetails.class, theRequest);
|
||||||
JpaInterceptorBroadcaster.doCallHooks(theInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PRESHOW_RESOURCES, params);
|
JpaInterceptorBroadcaster.doCallHooks(theInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PRESHOW_RESOURCES, params);
|
||||||
|
|
||||||
theResources.removeIf(t -> t == null);
|
retVal = accessDetails.toList();
|
||||||
|
retVal.removeIf(t -> t == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
package ca.uhn.fhir.jpa.util;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%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.dao.SearchBuilder;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
public class QueryChunker<T> {
|
||||||
|
|
||||||
|
public void chunk(List<T> theInput, Consumer<List<T>> theBatchConsumer) {
|
||||||
|
for (int i = 0; i < theInput.size(); i += SearchBuilder.MAXIMUM_PAGE_SIZE) {
|
||||||
|
int to = i + SearchBuilder.MAXIMUM_PAGE_SIZE;
|
||||||
|
to = Math.min(to, theInput.size());
|
||||||
|
List<T> batch = theInput.subList(i, to);
|
||||||
|
theBatchConsumer.accept(batch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,6 +6,7 @@ import ca.uhn.fhir.interceptor.api.Pointcut;
|
|||||||
import ca.uhn.fhir.interceptor.executor.InterceptorService;
|
import ca.uhn.fhir.interceptor.executor.InterceptorService;
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
|
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
|
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
|
||||||
import ca.uhn.fhir.test.BaseTest;
|
import ca.uhn.fhir.test.BaseTest;
|
||||||
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
|
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
|
||||||
@ -111,6 +112,8 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||||||
protected ISearchCacheSvc mySearchCacheSvc;
|
protected ISearchCacheSvc mySearchCacheSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
protected IPartitionLookupSvc myPartitionConfigSvc;
|
protected IPartitionLookupSvc myPartitionConfigSvc;
|
||||||
|
@Autowired
|
||||||
|
private IdHelperService myIdHelperService;
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void afterPerformCleanup() {
|
public void afterPerformCleanup() {
|
||||||
@ -121,6 +124,10 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||||||
if (myPartitionConfigSvc != null) {
|
if (myPartitionConfigSvc != null) {
|
||||||
myPartitionConfigSvc.clearCaches();
|
myPartitionConfigSvc.clearCaches();
|
||||||
}
|
}
|
||||||
|
if (myIdHelperService != null) {
|
||||||
|
myIdHelperService.clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.dao.dstu2;
|
|||||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
|
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
||||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
||||||
import ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoDstu3Test;
|
import ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoDstu3Test;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
|
||||||
@ -29,10 +30,12 @@ import org.hamcrest.core.StringContains;
|
|||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
@ -1051,6 +1054,9 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
|
|||||||
assertGone(org2Id);
|
assertGone(org2Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IForcedIdDao myForcedIdDao;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testHistoryByForcedId() {
|
public void testHistoryByForcedId() {
|
||||||
IIdType idv1;
|
IIdType idv1;
|
||||||
@ -1067,6 +1073,10 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
|
|||||||
idv2 = myPatientDao.update(patient, mySrd).getId();
|
idv2 = myPatientDao.update(patient, mySrd).getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runInTransaction(()->{
|
||||||
|
ourLog.info("Forced IDs:\n{}", myForcedIdDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n")));
|
||||||
|
});
|
||||||
|
|
||||||
List<Patient> patients = toList(myPatientDao.history(idv1.toVersionless(), null, null, mySrd));
|
List<Patient> patients = toList(myPatientDao.history(idv1.toVersionless(), null, null, mySrd));
|
||||||
assertTrue(patients.size() == 2);
|
assertTrue(patients.size() == 2);
|
||||||
// Newest first
|
// Newest first
|
||||||
@ -1111,7 +1121,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
|
|||||||
for (int i = 0; i < fullSize; i++) {
|
for (int i = 0; i < fullSize; i++) {
|
||||||
String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue();
|
String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue();
|
||||||
String actual = history.getResources(i, i + 1).get(0).getIdElement().getValue();
|
String actual = history.getResources(i, i + 1).get(0).getIdElement().getValue();
|
||||||
assertEquals(expected, actual);
|
assertEquals("Failure at " + i, expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
// By type
|
// By type
|
||||||
|
@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.dao.r4;
|
|||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||||
|
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
|
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
|
||||||
@ -12,14 +13,40 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoStructureDefinition;
|
|||||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSubscription;
|
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSubscription;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
|
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
|
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
|
||||||
|
import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory;
|
||||||
|
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
||||||
import ca.uhn.fhir.jpa.binstore.BinaryAccessProvider;
|
import ca.uhn.fhir.jpa.binstore.BinaryAccessProvider;
|
||||||
import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor;
|
import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor;
|
||||||
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
|
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
|
||||||
import ca.uhn.fhir.jpa.config.TestR4Config;
|
import ca.uhn.fhir.jpa.config.TestR4Config;
|
||||||
import ca.uhn.fhir.jpa.dao.BaseJpaTest;
|
import ca.uhn.fhir.jpa.dao.BaseJpaTest;
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||||
import ca.uhn.fhir.jpa.dao.data.*;
|
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTagDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamDateDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamQuantityDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamStringDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamTokenDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.IResourceReindexJobDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.IResourceTagDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ITagDefinitionDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ITermConceptDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ITermConceptDesignationDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ITermConceptMapDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupElementTargetDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ITermConceptParentChildLinkDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ITermValueSetConceptDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ITermValueSetConceptDesignationDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ITermValueSetDao;
|
||||||
import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest;
|
import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest;
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
import ca.uhn.fhir.jpa.entity.TermCodeSystem;
|
import ca.uhn.fhir.jpa.entity.TermCodeSystem;
|
||||||
@ -33,7 +60,6 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
|||||||
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
|
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
|
||||||
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
|
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
|
||||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||||
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
|
||||||
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
|
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
|
||||||
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
|
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
|
||||||
import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc;
|
import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc;
|
||||||
@ -47,7 +73,6 @@ import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
|
|||||||
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
|
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
|
||||||
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4;
|
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4;
|
||||||
import ca.uhn.fhir.jpa.util.ResourceCountCache;
|
import ca.uhn.fhir.jpa.util.ResourceCountCache;
|
||||||
import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory;
|
|
||||||
import ca.uhn.fhir.parser.IParser;
|
import ca.uhn.fhir.parser.IParser;
|
||||||
import ca.uhn.fhir.parser.StrictErrorHandler;
|
import ca.uhn.fhir.parser.StrictErrorHandler;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
@ -64,11 +89,63 @@ import org.hibernate.search.jpa.Search;
|
|||||||
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
|
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
|
||||||
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
|
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.r4.model.*;
|
import org.hl7.fhir.r4.model.AllergyIntolerance;
|
||||||
|
import org.hl7.fhir.r4.model.Appointment;
|
||||||
|
import org.hl7.fhir.r4.model.AuditEvent;
|
||||||
|
import org.hl7.fhir.r4.model.Binary;
|
||||||
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
|
import org.hl7.fhir.r4.model.CarePlan;
|
||||||
|
import org.hl7.fhir.r4.model.CareTeam;
|
||||||
|
import org.hl7.fhir.r4.model.ChargeItem;
|
||||||
|
import org.hl7.fhir.r4.model.CodeSystem;
|
||||||
|
import org.hl7.fhir.r4.model.CodeableConcept;
|
||||||
|
import org.hl7.fhir.r4.model.Coding;
|
||||||
|
import org.hl7.fhir.r4.model.Communication;
|
||||||
|
import org.hl7.fhir.r4.model.CommunicationRequest;
|
||||||
|
import org.hl7.fhir.r4.model.CompartmentDefinition;
|
||||||
|
import org.hl7.fhir.r4.model.ConceptMap;
|
||||||
import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupComponent;
|
import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupComponent;
|
||||||
import org.hl7.fhir.r4.model.ConceptMap.SourceElementComponent;
|
import org.hl7.fhir.r4.model.ConceptMap.SourceElementComponent;
|
||||||
import org.hl7.fhir.r4.model.ConceptMap.TargetElementComponent;
|
import org.hl7.fhir.r4.model.ConceptMap.TargetElementComponent;
|
||||||
|
import org.hl7.fhir.r4.model.Condition;
|
||||||
|
import org.hl7.fhir.r4.model.Consent;
|
||||||
|
import org.hl7.fhir.r4.model.Coverage;
|
||||||
|
import org.hl7.fhir.r4.model.Device;
|
||||||
|
import org.hl7.fhir.r4.model.DiagnosticReport;
|
||||||
|
import org.hl7.fhir.r4.model.DocumentReference;
|
||||||
|
import org.hl7.fhir.r4.model.Encounter;
|
||||||
import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence;
|
import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence;
|
||||||
|
import org.hl7.fhir.r4.model.EpisodeOfCare;
|
||||||
|
import org.hl7.fhir.r4.model.Group;
|
||||||
|
import org.hl7.fhir.r4.model.Immunization;
|
||||||
|
import org.hl7.fhir.r4.model.ImmunizationRecommendation;
|
||||||
|
import org.hl7.fhir.r4.model.Location;
|
||||||
|
import org.hl7.fhir.r4.model.Media;
|
||||||
|
import org.hl7.fhir.r4.model.Medication;
|
||||||
|
import org.hl7.fhir.r4.model.MedicationAdministration;
|
||||||
|
import org.hl7.fhir.r4.model.MedicationRequest;
|
||||||
|
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.OperationDefinition;
|
||||||
|
import org.hl7.fhir.r4.model.Organization;
|
||||||
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
|
import org.hl7.fhir.r4.model.Practitioner;
|
||||||
|
import org.hl7.fhir.r4.model.PractitionerRole;
|
||||||
|
import org.hl7.fhir.r4.model.Procedure;
|
||||||
|
import org.hl7.fhir.r4.model.Provenance;
|
||||||
|
import org.hl7.fhir.r4.model.Questionnaire;
|
||||||
|
import org.hl7.fhir.r4.model.QuestionnaireResponse;
|
||||||
|
import org.hl7.fhir.r4.model.RiskAssessment;
|
||||||
|
import org.hl7.fhir.r4.model.SearchParameter;
|
||||||
|
import org.hl7.fhir.r4.model.ServiceRequest;
|
||||||
|
import org.hl7.fhir.r4.model.StructureDefinition;
|
||||||
|
import org.hl7.fhir.r4.model.Subscription;
|
||||||
|
import org.hl7.fhir.r4.model.Substance;
|
||||||
|
import org.hl7.fhir.r4.model.Task;
|
||||||
|
import org.hl7.fhir.r4.model.UriType;
|
||||||
|
import org.hl7.fhir.r4.model.ValueSet;
|
||||||
import org.hl7.fhir.r5.utils.IResourceValidator;
|
import org.hl7.fhir.r5.utils.IResourceValidator;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
@ -102,6 +179,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
|
|||||||
private static IValidationSupport ourJpaValidationSupportChainR4;
|
private static IValidationSupport ourJpaValidationSupportChainR4;
|
||||||
private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao;
|
private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao;
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
protected IPartitionLookupSvc myPartitionConfigSvc;
|
protected IPartitionLookupSvc myPartitionConfigSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -375,7 +453,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
|
|||||||
private PerformanceTracingLoggingInterceptor myPerformanceTracingLoggingInterceptor;
|
private PerformanceTracingLoggingInterceptor myPerformanceTracingLoggingInterceptor;
|
||||||
private List<Object> mySystemInterceptors;
|
private List<Object> mySystemInterceptors;
|
||||||
@Autowired
|
@Autowired
|
||||||
private DaoRegistry myDaoRegistry;
|
protected DaoRegistry myDaoRegistry;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IBulkDataExportSvc myBulkDataExportSvc;
|
private IBulkDataExportSvc myBulkDataExportSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -408,8 +486,6 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
|
|||||||
BaseTermReadSvcImpl.clearOurLastResultsFromTranslationWithReverseCache();
|
BaseTermReadSvcImpl.clearOurLastResultsFromTranslationWithReverseCache();
|
||||||
TermDeferredStorageSvcImpl termDeferredStorageSvc = AopTestUtils.getTargetObject(myTerminologyDeferredStorageSvc);
|
TermDeferredStorageSvcImpl termDeferredStorageSvc = AopTestUtils.getTargetObject(myTerminologyDeferredStorageSvc);
|
||||||
termDeferredStorageSvc.clearDeferred();
|
termDeferredStorageSvc.clearDeferred();
|
||||||
|
|
||||||
myIdHelperService.clearCache();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After()
|
@After()
|
||||||
|
@ -316,7 +316,6 @@ public class ConsentEventsDaoR4Test extends BaseJpaR4SystemTest {
|
|||||||
ourLog.info("Search UUID: {}", outcome.getUuid());
|
ourLog.info("Search UUID: {}", outcome.getUuid());
|
||||||
|
|
||||||
// Fetch the first 10 (don't cross a fetch boundary)
|
// Fetch the first 10 (don't cross a fetch boundary)
|
||||||
outcome = myPagingProvider.retrieveResultList(mySrd, outcome.getUuid());
|
|
||||||
List<IBaseResource> resources = outcome.getResources(0, 10);
|
List<IBaseResource> resources = outcome.getResources(0, 10);
|
||||||
List<String> returnedIdValues = toUnqualifiedVersionlessIdValues(resources);
|
List<String> returnedIdValues = toUnqualifiedVersionlessIdValues(resources);
|
||||||
ourLog.info("Returned values: {}", returnedIdValues);
|
ourLog.info("Returned values: {}", returnedIdValues);
|
||||||
|
@ -2,8 +2,11 @@ package ca.uhn.fhir.jpa.dao.r4;
|
|||||||
|
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
|
import ca.uhn.fhir.jpa.util.SqlQuery;
|
||||||
import ca.uhn.fhir.jpa.util.TestUtil;
|
import ca.uhn.fhir.jpa.util.TestUtil;
|
||||||
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.Observation;
|
import org.hl7.fhir.r4.model.Observation;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
@ -12,6 +15,8 @@ import org.junit.AfterClass;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.empty;
|
import static org.hamcrest.Matchers.empty;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
@ -224,6 +229,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||||||
myCaptureQueriesListener.logAllQueriesForCurrentThread();
|
myCaptureQueriesListener.logAllQueriesForCurrentThread();
|
||||||
// select: lookup forced ID
|
// select: lookup forced ID
|
||||||
assertEquals(1, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
assertEquals(1, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||||
|
assertNoPartitionSelectors();
|
||||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||||
// insert to: HFJ_RESOURCE, HFJ_RES_VER, HFJ_RES_LINK
|
// insert to: HFJ_RESOURCE, HFJ_RES_VER, HFJ_RES_LINK
|
||||||
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||||
@ -246,6 +252,127 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void assertNoPartitionSelectors() {
|
||||||
|
List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueriesForCurrentThread();
|
||||||
|
for (SqlQuery next : selectQueries) {
|
||||||
|
assertEquals(0, StringUtils.countMatches(next.getSql(true, true).toLowerCase(), "partition_id is null"));
|
||||||
|
assertEquals(0, StringUtils.countMatches(next.getSql(true, true).toLowerCase(), "partition_id="));
|
||||||
|
assertEquals(0, StringUtils.countMatches(next.getSql(true, true).toLowerCase(), "partition_id ="));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHistory_Server() {
|
||||||
|
runInTransaction(() -> {
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.setId("A");
|
||||||
|
p.addIdentifier().setSystem("urn:system").setValue("1");
|
||||||
|
myPatientDao.update(p).getId().toUnqualified();
|
||||||
|
|
||||||
|
p = new Patient();
|
||||||
|
p.setId("B");
|
||||||
|
p.addIdentifier().setSystem("urn:system").setValue("2");
|
||||||
|
myPatientDao.update(p).getId().toUnqualified();
|
||||||
|
|
||||||
|
p = new Patient();
|
||||||
|
p.addIdentifier().setSystem("urn:system").setValue("2");
|
||||||
|
myPatientDao.create(p).getId().toUnqualified();
|
||||||
|
});
|
||||||
|
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
runInTransaction(() -> {
|
||||||
|
IBundleProvider history = mySystemDao.history(null, null, null);
|
||||||
|
assertEquals(3, history.getResources(0, 99).size());
|
||||||
|
});
|
||||||
|
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||||
|
// Perform count, Search history table, resolve forced IDs
|
||||||
|
assertEquals(3, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
|
||||||
|
assertNoPartitionSelectors();
|
||||||
|
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
|
||||||
|
myCaptureQueriesListener.logInsertQueriesForCurrentThread();
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
|
||||||
|
myCaptureQueriesListener.logDeleteQueriesForCurrentThread();
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
|
||||||
|
|
||||||
|
// Second time should leverage forced ID cache
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
runInTransaction(() -> {
|
||||||
|
IBundleProvider history = mySystemDao.history(null, null, null);
|
||||||
|
assertEquals(3, history.getResources(0, 99).size());
|
||||||
|
});
|
||||||
|
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||||
|
// Perform count, Search history table
|
||||||
|
assertEquals(2, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
|
||||||
|
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
|
||||||
|
myCaptureQueriesListener.logInsertQueriesForCurrentThread();
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
|
||||||
|
myCaptureQueriesListener.logDeleteQueriesForCurrentThread();
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This could definitely stand to be optimized some, since we load tags individually
|
||||||
|
* for each resource
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testHistory_Server_WithTags() {
|
||||||
|
runInTransaction(() -> {
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.getMeta().addTag("system", "code1", "displaY1");
|
||||||
|
p.getMeta().addTag("system", "code2", "displaY2");
|
||||||
|
p.setId("A");
|
||||||
|
p.addIdentifier().setSystem("urn:system").setValue("1");
|
||||||
|
myPatientDao.update(p).getId().toUnqualified();
|
||||||
|
|
||||||
|
p = new Patient();
|
||||||
|
p.getMeta().addTag("system", "code1", "displaY1");
|
||||||
|
p.getMeta().addTag("system", "code2", "displaY2");
|
||||||
|
p.setId("B");
|
||||||
|
p.addIdentifier().setSystem("urn:system").setValue("2");
|
||||||
|
myPatientDao.update(p).getId().toUnqualified();
|
||||||
|
|
||||||
|
p = new Patient();
|
||||||
|
p.getMeta().addTag("system", "code1", "displaY1");
|
||||||
|
p.getMeta().addTag("system", "code2", "displaY2");
|
||||||
|
p.addIdentifier().setSystem("urn:system").setValue("2");
|
||||||
|
myPatientDao.create(p).getId().toUnqualified();
|
||||||
|
});
|
||||||
|
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
runInTransaction(() -> {
|
||||||
|
IBundleProvider history = mySystemDao.history(null, null, null);
|
||||||
|
assertEquals(3, history.getResources(0, 3).size());
|
||||||
|
});
|
||||||
|
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||||
|
// Perform count, Search history table, resolve forced IDs, load tags (x3)
|
||||||
|
assertEquals(6, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
|
||||||
|
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
|
||||||
|
myCaptureQueriesListener.logInsertQueriesForCurrentThread();
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
|
||||||
|
myCaptureQueriesListener.logDeleteQueriesForCurrentThread();
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
|
||||||
|
|
||||||
|
// Second time should leverage forced ID cache
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
runInTransaction(() -> {
|
||||||
|
IBundleProvider history = mySystemDao.history(null, null, null);
|
||||||
|
assertEquals(3, history.getResources(0, 3).size());
|
||||||
|
});
|
||||||
|
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||||
|
// Perform count, Search history table, load tags (x3)
|
||||||
|
assertEquals(5, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
|
||||||
|
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
|
||||||
|
myCaptureQueriesListener.logInsertQueriesForCurrentThread();
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
|
||||||
|
myCaptureQueriesListener.logDeleteQueriesForCurrentThread();
|
||||||
|
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchUsingForcedIdReference() {
|
public void testSearchUsingForcedIdReference() {
|
||||||
@ -267,6 +394,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||||||
assertEquals(1, myObservationDao.search(map).size().intValue());
|
assertEquals(1, myObservationDao.search(map).size().intValue());
|
||||||
// Resolve forced ID, Perform search, load result
|
// Resolve forced ID, Perform search, load result
|
||||||
assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||||
|
assertNoPartitionSelectors();
|
||||||
assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||||
|
@ -28,7 +28,27 @@ import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
|||||||
import ca.uhn.fhir.parser.StrictErrorHandler;
|
import ca.uhn.fhir.parser.StrictErrorHandler;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.param.*;
|
import ca.uhn.fhir.rest.param.CompositeParam;
|
||||||
|
import ca.uhn.fhir.rest.param.DateParam;
|
||||||
|
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||||
|
import ca.uhn.fhir.rest.param.HasAndListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.HasOrListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.HasParam;
|
||||||
|
import ca.uhn.fhir.rest.param.NumberParam;
|
||||||
|
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
||||||
|
import ca.uhn.fhir.rest.param.QuantityParam;
|
||||||
|
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||||
|
import ca.uhn.fhir.rest.param.StringAndListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.StringOrListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.StringParam;
|
||||||
|
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.TokenParam;
|
||||||
|
import ca.uhn.fhir.rest.param.TokenParamModifier;
|
||||||
|
import ca.uhn.fhir.rest.param.UriParam;
|
||||||
|
import ca.uhn.fhir.rest.param.UriParamQualifierEnum;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
@ -38,15 +58,66 @@ import org.hl7.fhir.instance.model.api.IAnyResource;
|
|||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
import org.hl7.fhir.r4.model.*;
|
import org.hl7.fhir.r4.model.Age;
|
||||||
|
import org.hl7.fhir.r4.model.Appointment;
|
||||||
|
import org.hl7.fhir.r4.model.AuditEvent;
|
||||||
|
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.Bundle.BundleEntryComponent;
|
||||||
import org.hl7.fhir.r4.model.Bundle.BundleType;
|
import org.hl7.fhir.r4.model.Bundle.BundleType;
|
||||||
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
|
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
|
||||||
|
import org.hl7.fhir.r4.model.CareTeam;
|
||||||
|
import org.hl7.fhir.r4.model.ChargeItem;
|
||||||
|
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.CommunicationRequest;
|
||||||
|
import org.hl7.fhir.r4.model.Condition;
|
||||||
import org.hl7.fhir.r4.model.ContactPoint.ContactPointSystem;
|
import org.hl7.fhir.r4.model.ContactPoint.ContactPointSystem;
|
||||||
|
import org.hl7.fhir.r4.model.DateTimeType;
|
||||||
|
import org.hl7.fhir.r4.model.DateType;
|
||||||
|
import org.hl7.fhir.r4.model.DecimalType;
|
||||||
|
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;
|
||||||
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
|
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
|
||||||
|
import org.hl7.fhir.r4.model.EpisodeOfCare;
|
||||||
|
import org.hl7.fhir.r4.model.Group;
|
||||||
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
|
import org.hl7.fhir.r4.model.InstantType;
|
||||||
|
import org.hl7.fhir.r4.model.IntegerType;
|
||||||
|
import org.hl7.fhir.r4.model.Location;
|
||||||
|
import org.hl7.fhir.r4.model.Medication;
|
||||||
|
import org.hl7.fhir.r4.model.MedicationAdministration;
|
||||||
|
import org.hl7.fhir.r4.model.MedicationRequest;
|
||||||
|
import org.hl7.fhir.r4.model.MolecularSequence;
|
||||||
|
import org.hl7.fhir.r4.model.Observation;
|
||||||
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
|
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
|
||||||
|
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.Practitioner;
|
||||||
|
import org.hl7.fhir.r4.model.Provenance;
|
||||||
|
import org.hl7.fhir.r4.model.Quantity;
|
||||||
|
import org.hl7.fhir.r4.model.Questionnaire;
|
||||||
|
import org.hl7.fhir.r4.model.QuestionnaireResponse;
|
||||||
|
import org.hl7.fhir.r4.model.Range;
|
||||||
|
import org.hl7.fhir.r4.model.Reference;
|
||||||
|
import org.hl7.fhir.r4.model.RiskAssessment;
|
||||||
|
import org.hl7.fhir.r4.model.SearchParameter;
|
||||||
|
import org.hl7.fhir.r4.model.ServiceRequest;
|
||||||
|
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.Subscription;
|
||||||
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
|
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
|
||||||
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
|
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
|
||||||
|
import org.hl7.fhir.r4.model.Substance;
|
||||||
|
import org.hl7.fhir.r4.model.Task;
|
||||||
|
import org.hl7.fhir.r4.model.Timing;
|
||||||
|
import org.hl7.fhir.r4.model.ValueSet;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
@ -3,7 +3,14 @@ package ca.uhn.fhir.jpa.dao.r4;
|
|||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
||||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
import ca.uhn.fhir.jpa.model.entity.*;
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
|
||||||
|
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.entity.ResourceLink;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
|
||||||
import ca.uhn.fhir.jpa.util.TestUtil;
|
import ca.uhn.fhir.jpa.util.TestUtil;
|
||||||
@ -12,7 +19,27 @@ import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
|||||||
import ca.uhn.fhir.parser.StrictErrorHandler;
|
import ca.uhn.fhir.parser.StrictErrorHandler;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.param.*;
|
import ca.uhn.fhir.rest.param.CompositeParam;
|
||||||
|
import ca.uhn.fhir.rest.param.DateParam;
|
||||||
|
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||||
|
import ca.uhn.fhir.rest.param.HasAndListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.HasOrListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.HasParam;
|
||||||
|
import ca.uhn.fhir.rest.param.NumberParam;
|
||||||
|
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
||||||
|
import ca.uhn.fhir.rest.param.QuantityParam;
|
||||||
|
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||||
|
import ca.uhn.fhir.rest.param.StringAndListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.StringOrListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.StringParam;
|
||||||
|
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.TokenParam;
|
||||||
|
import ca.uhn.fhir.rest.param.TokenParamModifier;
|
||||||
|
import ca.uhn.fhir.rest.param.UriParam;
|
||||||
|
import ca.uhn.fhir.rest.param.UriParamQualifierEnum;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
@ -21,16 +48,56 @@ import org.hl7.fhir.instance.model.api.IAnyResource;
|
|||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
import org.hl7.fhir.r4.model.*;
|
import org.hl7.fhir.r4.model.Appointment;
|
||||||
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
|
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
|
||||||
import org.hl7.fhir.r4.model.Bundle.BundleType;
|
import org.hl7.fhir.r4.model.Bundle.BundleType;
|
||||||
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
|
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
|
||||||
|
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.Condition;
|
||||||
import org.hl7.fhir.r4.model.ContactPoint.ContactPointSystem;
|
import org.hl7.fhir.r4.model.ContactPoint.ContactPointSystem;
|
||||||
|
import org.hl7.fhir.r4.model.DateTimeType;
|
||||||
|
import org.hl7.fhir.r4.model.DateType;
|
||||||
|
import org.hl7.fhir.r4.model.DecimalType;
|
||||||
|
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.AdministrativeGender;
|
||||||
|
import org.hl7.fhir.r4.model.Group;
|
||||||
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
|
import org.hl7.fhir.r4.model.IntegerType;
|
||||||
|
import org.hl7.fhir.r4.model.Location;
|
||||||
|
import org.hl7.fhir.r4.model.Medication;
|
||||||
|
import org.hl7.fhir.r4.model.MedicationAdministration;
|
||||||
|
import org.hl7.fhir.r4.model.MedicationRequest;
|
||||||
|
import org.hl7.fhir.r4.model.MolecularSequence;
|
||||||
|
import org.hl7.fhir.r4.model.Observation;
|
||||||
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
|
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
|
||||||
|
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.Practitioner;
|
||||||
|
import org.hl7.fhir.r4.model.Quantity;
|
||||||
|
import org.hl7.fhir.r4.model.Range;
|
||||||
|
import org.hl7.fhir.r4.model.Reference;
|
||||||
|
import org.hl7.fhir.r4.model.RiskAssessment;
|
||||||
|
import org.hl7.fhir.r4.model.ServiceRequest;
|
||||||
|
import org.hl7.fhir.r4.model.SimpleQuantity;
|
||||||
|
import org.hl7.fhir.r4.model.StringType;
|
||||||
|
import org.hl7.fhir.r4.model.Subscription;
|
||||||
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
|
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
|
||||||
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
|
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
|
||||||
import org.junit.*;
|
import org.hl7.fhir.r4.model.Substance;
|
||||||
|
import org.hl7.fhir.r4.model.Task;
|
||||||
|
import org.hl7.fhir.r4.model.ValueSet;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.transaction.TransactionStatus;
|
import org.springframework.transaction.TransactionStatus;
|
||||||
import org.springframework.transaction.support.TransactionCallback;
|
import org.springframework.transaction.support.TransactionCallback;
|
||||||
@ -41,11 +108,25 @@ import javax.servlet.http.HttpServletRequest;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.contains;
|
||||||
import static org.junit.Assert.*;
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.empty;
|
||||||
|
import static org.hamcrest.Matchers.endsWith;
|
||||||
|
import static org.hamcrest.Matchers.hasItem;
|
||||||
|
import static org.hamcrest.Matchers.hasItems;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "Duplicates"})
|
@SuppressWarnings({"unchecked", "Duplicates"})
|
||||||
|
@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
|||||||
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
||||||
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
|
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
|
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
|
||||||
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,6 @@
|
|||||||
package ca.uhn.fhir.jpa.dao.r4;
|
package ca.uhn.fhir.jpa.dao.r4;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
||||||
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
||||||
import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao;
|
import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao;
|
||||||
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
|
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
|
||||||
@ -7,7 +8,6 @@ import ca.uhn.fhir.jpa.entity.Search;
|
|||||||
import ca.uhn.fhir.jpa.entity.SearchResult;
|
import ca.uhn.fhir.jpa.entity.SearchResult;
|
||||||
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
||||||
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
|
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
|
||||||
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
|
||||||
import ca.uhn.fhir.jpa.search.cache.DatabaseSearchCacheSvcImpl;
|
import ca.uhn.fhir.jpa.search.cache.DatabaseSearchCacheSvcImpl;
|
||||||
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
@ -21,9 +21,7 @@ import java.util.Date;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static ca.uhn.fhir.jpa.search.cache.DatabaseSearchCacheSvcImpl.DEFAULT_MAX_DELETE_CANDIDATES_TO_FIND;
|
import static ca.uhn.fhir.jpa.search.cache.DatabaseSearchCacheSvcImpl.DEFAULT_MAX_DELETE_CANDIDATES_TO_FIND;
|
||||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
|
|
||||||
public class SearchCoordinatorSvcImplTest extends BaseJpaR4Test {
|
public class SearchCoordinatorSvcImplTest extends BaseJpaR4Test {
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
|||||||
import ca.uhn.fhir.jpa.model.util.ProviderConstants;
|
import ca.uhn.fhir.jpa.model.util.ProviderConstants;
|
||||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.test.utilities.server.RestfulServerRule;
|
import ca.uhn.fhir.test.utilities.server.RestfulServerRule;
|
||||||
import org.hl7.fhir.r4.model.CodeType;
|
import org.hl7.fhir.r4.model.CodeType;
|
||||||
import org.hl7.fhir.r4.model.IntegerType;
|
import org.hl7.fhir.r4.model.IntegerType;
|
||||||
@ -27,6 +28,7 @@ import org.springframework.test.context.ContextConfiguration;
|
|||||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
@ -61,19 +63,16 @@ public class PartitionManagementProviderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddPartition() {
|
public void testCreatePartition() {
|
||||||
when(myPartitionConfigSvc.createPartition(any())).thenAnswer(createAnswer());
|
when(myPartitionConfigSvc.createPartition(any())).thenAnswer(createAnswer());
|
||||||
|
|
||||||
Parameters input = new Parameters();
|
Parameters input = createInputPartition();
|
||||||
input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(123));
|
|
||||||
input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("PARTITION-123"));
|
|
||||||
input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, new StringType("a description"));
|
|
||||||
ourLog.info("Input:\n{}", ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));
|
ourLog.info("Input:\n{}", ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));
|
||||||
|
|
||||||
Parameters response = myClient
|
Parameters response = myClient
|
||||||
.operation()
|
.operation()
|
||||||
.onServer()
|
.onServer()
|
||||||
.named(ProviderConstants.PARTITION_MANAGEMENT_ADD_PARTITION)
|
.named(ProviderConstants.PARTITION_MANAGEMENT_CREATE_PARTITION)
|
||||||
.withParameters(input)
|
.withParameters(input)
|
||||||
.encodedXml()
|
.encodedXml()
|
||||||
.execute();
|
.execute();
|
||||||
@ -87,14 +86,80 @@ public class PartitionManagementProviderTest {
|
|||||||
assertEquals("a description", ((StringType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC)).getValue());
|
assertEquals("a description", ((StringType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC)).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@NotNull
|
||||||
public void testUpdatePartition() {
|
private Parameters createInputPartition() {
|
||||||
when(myPartitionConfigSvc.updatePartition(any())).thenAnswer(createAnswer());
|
|
||||||
|
|
||||||
Parameters input = new Parameters();
|
Parameters input = new Parameters();
|
||||||
input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(123));
|
input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(123));
|
||||||
input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("PARTITION-123"));
|
input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("PARTITION-123"));
|
||||||
input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, new StringType("a description"));
|
input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, new StringType("a description"));
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreatePartition_InvalidInput() {
|
||||||
|
try {
|
||||||
|
myClient
|
||||||
|
.operation()
|
||||||
|
.onServer()
|
||||||
|
.named(ProviderConstants.PARTITION_MANAGEMENT_CREATE_PARTITION)
|
||||||
|
.withNoParameters(Parameters.class)
|
||||||
|
.encodedXml()
|
||||||
|
.execute();
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("HTTP 400 Bad Request: No Partition ID supplied", e.getMessage());
|
||||||
|
}
|
||||||
|
verify(myPartitionConfigSvc, times(0)).createPartition(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadPartition() {
|
||||||
|
PartitionEntity partition = new PartitionEntity();
|
||||||
|
partition.setId(123);
|
||||||
|
partition.setName("PARTITION-123");
|
||||||
|
partition.setDescription("a description");
|
||||||
|
when(myPartitionConfigSvc.getPartitionById(eq(123))).thenReturn(partition);
|
||||||
|
|
||||||
|
Parameters response = myClient
|
||||||
|
.operation()
|
||||||
|
.onServer()
|
||||||
|
.named(ProviderConstants.PARTITION_MANAGEMENT_READ_PARTITION)
|
||||||
|
.withParameter(Parameters.class, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(123))
|
||||||
|
.useHttpGet()
|
||||||
|
.encodedXml()
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
ourLog.info("Response:\n{}", ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(response));
|
||||||
|
verify(myPartitionConfigSvc, times(1)).getPartitionById(any());
|
||||||
|
verifyNoMoreInteractions(myPartitionConfigSvc);
|
||||||
|
|
||||||
|
assertEquals(123, ((IntegerType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID)).getValue().intValue());
|
||||||
|
assertEquals("PARTITION-123", ((StringType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME)).getValue());
|
||||||
|
assertEquals("a description", ((StringType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC)).getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadPartition_InvalidInput() {
|
||||||
|
try {
|
||||||
|
myClient
|
||||||
|
.operation()
|
||||||
|
.onServer()
|
||||||
|
.named(ProviderConstants.PARTITION_MANAGEMENT_READ_PARTITION)
|
||||||
|
.withNoParameters(Parameters.class)
|
||||||
|
.encodedXml()
|
||||||
|
.execute();
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("HTTP 400 Bad Request: No Partition ID supplied", e.getMessage());
|
||||||
|
}
|
||||||
|
verify(myPartitionConfigSvc, times(0)).getPartitionById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdatePartition() {
|
||||||
|
when(myPartitionConfigSvc.updatePartition(any())).thenAnswer(createAnswer());
|
||||||
|
|
||||||
|
Parameters input = createInputPartition();
|
||||||
ourLog.info("Input:\n{}", ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));
|
ourLog.info("Input:\n{}", ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));
|
||||||
|
|
||||||
Parameters response = myClient
|
Parameters response = myClient
|
||||||
@ -114,6 +179,23 @@ public class PartitionManagementProviderTest {
|
|||||||
assertEquals("a description", ((StringType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC)).getValue());
|
assertEquals("a description", ((StringType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC)).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdatePartition_InvalidInput() {
|
||||||
|
try {
|
||||||
|
myClient
|
||||||
|
.operation()
|
||||||
|
.onServer()
|
||||||
|
.named(ProviderConstants.PARTITION_MANAGEMENT_UPDATE_PARTITION)
|
||||||
|
.withNoParameters(Parameters.class)
|
||||||
|
.encodedXml()
|
||||||
|
.execute();
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("HTTP 400 Bad Request: No Partition ID supplied", e.getMessage());
|
||||||
|
}
|
||||||
|
verify(myPartitionConfigSvc, times(0)).createPartition(any());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeletePartition() {
|
public void testDeletePartition() {
|
||||||
Parameters input = new Parameters();
|
Parameters input = new Parameters();
|
||||||
@ -133,6 +215,23 @@ public class PartitionManagementProviderTest {
|
|||||||
verifyNoMoreInteractions(myPartitionConfigSvc);
|
verifyNoMoreInteractions(myPartitionConfigSvc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeletePartition_InvalidInput() {
|
||||||
|
try {
|
||||||
|
myClient
|
||||||
|
.operation()
|
||||||
|
.onServer()
|
||||||
|
.named(ProviderConstants.PARTITION_MANAGEMENT_CREATE_PARTITION)
|
||||||
|
.withNoParameters(Parameters.class)
|
||||||
|
.encodedXml()
|
||||||
|
.execute();
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("HTTP 400 Bad Request: No Partition ID supplied", e.getMessage());
|
||||||
|
}
|
||||||
|
verify(myPartitionConfigSvc, times(0)).createPartition(any());
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public static class MyConfig {
|
public static class MyConfig {
|
||||||
|
|
||||||
@ -150,9 +249,7 @@ public class PartitionManagementProviderTest {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private static Answer<Object> createAnswer() {
|
private static Answer<Object> createAnswer() {
|
||||||
return t -> {
|
return t -> t.getArgument(0, PartitionEntity.class);
|
||||||
return t.getArgument(0, PartitionEntity.class);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.partition;
|
|||||||
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
|
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
|
||||||
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@ -43,7 +44,7 @@ public class PartitionSettingsSvcImplTest extends BaseJpaR4Test {
|
|||||||
try {
|
try {
|
||||||
myPartitionConfigSvc.getPartitionById(123);
|
myPartitionConfigSvc.getPartitionById(123);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (ResourceNotFoundException e) {
|
||||||
assertEquals("No partition exists with ID 123", e.getMessage());
|
assertEquals("No partition exists with ID 123", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,11 @@ import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
|
|||||||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
|
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.auth.*;
|
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRuleTester;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.auth.PolicyEnum;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpDelete;
|
import org.apache.http.client.methods.HttpDelete;
|
||||||
@ -19,8 +23,19 @@ import org.apache.http.entity.ContentType;
|
|||||||
import org.apache.http.entity.StringEntity;
|
import org.apache.http.entity.StringEntity;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.*;
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
|
import org.hl7.fhir.r4.model.CodeableConcept;
|
||||||
|
import org.hl7.fhir.r4.model.Coding;
|
||||||
|
import org.hl7.fhir.r4.model.Condition;
|
||||||
|
import org.hl7.fhir.r4.model.Encounter;
|
||||||
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
|
import org.hl7.fhir.r4.model.Identifier;
|
||||||
|
import org.hl7.fhir.r4.model.Observation;
|
||||||
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
|
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
|
||||||
|
import org.hl7.fhir.r4.model.Organization;
|
||||||
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
|
import org.hl7.fhir.r4.model.Practitioner;
|
||||||
|
import org.hl7.fhir.r4.model.Reference;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -31,11 +46,14 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.startsWith;
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
public class AuthorizationInterceptorResourceProviderR4Test extends BaseResourceProviderR4Test {
|
public class AuthorizationInterceptorJpaR4Test extends BaseResourceProviderR4Test {
|
||||||
|
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(AuthorizationInterceptorResourceProviderR4Test.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(AuthorizationInterceptorJpaR4Test.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void before() throws Exception {
|
public void before() throws Exception {
|
@ -0,0 +1,233 @@
|
|||||||
|
package ca.uhn.fhir.jpa.provider.r4;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
|
import ca.uhn.fhir.jpa.util.TestUtil;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder;
|
||||||
|
import ca.uhn.fhir.test.utilities.ITestDataBuilder;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
|
import org.hl7.fhir.r4.model.Observation;
|
||||||
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.contains;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
@SuppressWarnings("Duplicates")
|
||||||
|
public class AuthorizationInterceptorMultitenantJpaR4Test extends BaseMultitenantResourceProviderR4Test implements ITestDataBuilder {
|
||||||
|
|
||||||
|
private static final Logger ourLog = LoggerFactory.getLogger(AuthorizationInterceptorMultitenantJpaR4Test.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateInTenant_Allowed() {
|
||||||
|
enableAuthorizationInterceptor(() -> new RuleBuilder()
|
||||||
|
.allow().create().allResources().withAnyId().forTenantIds(TENANT_A)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
IIdType idA = createPatient(withTenant(TENANT_A), withActiveTrue());
|
||||||
|
|
||||||
|
runInTransaction(() -> {
|
||||||
|
Optional<ResourceTable> patient = myResourceTableDao.findById(idA.getIdPartAsLong());
|
||||||
|
assertTrue(patient.isPresent());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateInTenant_Blocked() {
|
||||||
|
createPatient(withTenant(TENANT_A), withActiveTrue());
|
||||||
|
IIdType idB = createPatient(withTenant(TENANT_B), withActiveFalse());
|
||||||
|
|
||||||
|
enableAuthorizationInterceptor(() -> new RuleBuilder()
|
||||||
|
.allow().create().allResources().withAnyId().forTenantIds(TENANT_A)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
myTenantClientInterceptor.setTenantId(TENANT_B);
|
||||||
|
try {
|
||||||
|
ourClient.read().resource(Patient.class).withId(idB).execute();
|
||||||
|
fail();
|
||||||
|
} catch (ForbiddenOperationException e) {
|
||||||
|
// good
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadInTenant_Allowed() {
|
||||||
|
IIdType idA = createPatient(withTenant(TENANT_A), withActiveTrue());
|
||||||
|
createPatient(withTenant(TENANT_B), withActiveFalse());
|
||||||
|
|
||||||
|
enableAuthorizationInterceptor(() -> new RuleBuilder()
|
||||||
|
.allow().read().allResources().withAnyId().forTenantIds(TENANT_A)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
myTenantClientInterceptor.setTenantId(TENANT_A);
|
||||||
|
Patient p = ourClient.read().resource(Patient.class).withId(idA).execute();
|
||||||
|
assertTrue(p.getActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadInTenant_Blocked() {
|
||||||
|
createPatient(withTenant(TENANT_A), withActiveTrue());
|
||||||
|
IIdType idB = createPatient(withTenant(TENANT_B), withActiveFalse());
|
||||||
|
|
||||||
|
enableAuthorizationInterceptor(() -> new RuleBuilder()
|
||||||
|
.allow().read().allResources().withAnyId().forTenantIds(TENANT_A)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
myTenantClientInterceptor.setTenantId(TENANT_B);
|
||||||
|
try {
|
||||||
|
ourClient.read().resource(Patient.class).withId(idB).execute();
|
||||||
|
fail();
|
||||||
|
} catch (ForbiddenOperationException e) {
|
||||||
|
// good
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadInDefaultTenant_Allowed() {
|
||||||
|
IIdType idA = createPatient(withTenant("DEFAULT"), withActiveTrue());
|
||||||
|
|
||||||
|
enableAuthorizationInterceptor(() -> new RuleBuilder()
|
||||||
|
.allow().read().allResources().withAnyId().forTenantIds("DEFAULT")
|
||||||
|
.build());
|
||||||
|
|
||||||
|
myTenantClientInterceptor.setTenantId("DEFAULT");
|
||||||
|
Patient p = ourClient.read().resource(Patient.class).withId(idA).execute();
|
||||||
|
assertTrue(p.getActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadInDefaultTenant_Blocked() {
|
||||||
|
IIdType idA = createPatient(withTenant(TENANT_A), withActiveTrue());
|
||||||
|
|
||||||
|
enableAuthorizationInterceptor(() -> new RuleBuilder()
|
||||||
|
.allow().read().allResources().withAnyId().forTenantIds("DEFAULT")
|
||||||
|
.build());
|
||||||
|
|
||||||
|
myTenantClientInterceptor.setTenantId(TENANT_A);
|
||||||
|
try {
|
||||||
|
ourClient.read().resource(Patient.class).withId(idA).execute();
|
||||||
|
fail();
|
||||||
|
} catch (ForbiddenOperationException e) {
|
||||||
|
// good
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadAcrossTenants_Allowed() {
|
||||||
|
myPartitionSettings.setAllowReferencesAcrossPartitions(PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED);
|
||||||
|
|
||||||
|
IIdType patientId = createPatient(withTenant(TENANT_A), withActiveTrue());
|
||||||
|
createObservation(withTenant(TENANT_B), withSubject(patientId.toUnqualifiedVersionless()));
|
||||||
|
|
||||||
|
enableAuthorizationInterceptor(() -> new RuleBuilder()
|
||||||
|
.allow().read().allResources().withAnyId().forTenantIds(TENANT_A, TENANT_B)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
myTenantClientInterceptor.setTenantId(TENANT_B);
|
||||||
|
|
||||||
|
Bundle output = ourClient
|
||||||
|
.search()
|
||||||
|
.forResource("Observation")
|
||||||
|
.include(Observation.INCLUDE_ALL)
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.execute();
|
||||||
|
assertEquals(2, output.getEntry().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadAcrossTenants_Blocked() {
|
||||||
|
myPartitionSettings.setAllowReferencesAcrossPartitions(PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED);
|
||||||
|
|
||||||
|
IIdType patientId = createPatient(withTenant(TENANT_A), withActiveTrue());
|
||||||
|
createObservation(withTenant(TENANT_B), withSubject(patientId.toUnqualifiedVersionless()));
|
||||||
|
|
||||||
|
enableAuthorizationInterceptor(() -> new RuleBuilder()
|
||||||
|
.allow().read().allResources().withAnyId().forTenantIds(TENANT_A)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
myTenantClientInterceptor.setTenantId(TENANT_B);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ourClient
|
||||||
|
.search()
|
||||||
|
.forResource("Observation")
|
||||||
|
.include(Observation.INCLUDE_ALL)
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.execute();
|
||||||
|
fail();
|
||||||
|
} catch (ForbiddenOperationException e) {
|
||||||
|
// good
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchPagingAcrossTenants_Blocked() {
|
||||||
|
myPartitionSettings.setAllowReferencesAcrossPartitions(PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED);
|
||||||
|
|
||||||
|
// Create 9 Observations: 1-8 have no subject, 9 has a subject in a different tenant
|
||||||
|
IIdType patientIdA = createPatient(withTenant(TENANT_A), withActiveTrue()).toUnqualifiedVersionless();
|
||||||
|
IIdType patientIdB = createPatient(withTenant(TENANT_B), withActiveTrue()).toUnqualifiedVersionless();
|
||||||
|
List<IIdType> observationIds = Lists.newArrayList();
|
||||||
|
for (int i = 1; i <= 9; i++) {
|
||||||
|
IIdType subject = i == 9 ? patientIdB : patientIdA;
|
||||||
|
IIdType id = createObservation(withTenant(TENANT_A), withIdentifier("foo" + i, "val" + i), withStatus("final"), withSubject(subject)).toUnqualifiedVersionless();
|
||||||
|
observationIds.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
enableAuthorizationInterceptor(() -> new RuleBuilder()
|
||||||
|
.allow().read().allResources().withAnyId().forTenantIds(TENANT_A)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
myTenantClientInterceptor.setTenantId(TENANT_A);
|
||||||
|
|
||||||
|
// Search and fetch the first 3
|
||||||
|
Bundle bundle = ourClient
|
||||||
|
.search()
|
||||||
|
.forResource("Observation")
|
||||||
|
.include(Observation.INCLUDE_ALL)
|
||||||
|
.sort().ascending(Observation.IDENTIFIER)
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.count(3)
|
||||||
|
.execute();
|
||||||
|
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).setEncodeElements(Sets.newHashSet("Bundle.link")).encodeResourceToString(bundle));
|
||||||
|
assertThat(toUnqualifiedVersionlessIds(bundle).toString(), toUnqualifiedVersionlessIds(bundle), contains(observationIds.get(0), observationIds.get(1), observationIds.get(2), patientIdA));
|
||||||
|
|
||||||
|
// Fetch the next 3
|
||||||
|
bundle = ourClient
|
||||||
|
.loadPage()
|
||||||
|
.next(bundle)
|
||||||
|
.execute();
|
||||||
|
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).setEncodeElements(Sets.newHashSet("Bundle.link")).encodeResourceToString(bundle));
|
||||||
|
assertThat(toUnqualifiedVersionlessIds(bundle).toString(), toUnqualifiedVersionlessIds(bundle), contains(observationIds.get(3), observationIds.get(4), observationIds.get(5), patientIdA));
|
||||||
|
|
||||||
|
// Fetch the next 3 - This should fail as the last observation has a cross-partition reference
|
||||||
|
try {
|
||||||
|
bundle = ourClient
|
||||||
|
.loadPage()
|
||||||
|
.next(bundle)
|
||||||
|
.execute();
|
||||||
|
fail();
|
||||||
|
} catch (ForbiddenOperationException e) {
|
||||||
|
// good
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void afterClassClearContext() {
|
||||||
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,141 @@
|
|||||||
|
package ca.uhn.fhir.jpa.provider.r4;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
|
import ca.uhn.fhir.jpa.model.util.ProviderConstants;
|
||||||
|
import ca.uhn.fhir.jpa.partition.PartitionManagementProvider;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor;
|
||||||
|
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||||
|
import ca.uhn.fhir.rest.client.interceptor.UrlTenantSelectionInterceptor;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.auth.PolicyEnum;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor;
|
||||||
|
import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy;
|
||||||
|
import ca.uhn.fhir.test.utilities.ITestDataBuilder;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.hl7.fhir.r4.model.CodeType;
|
||||||
|
import org.hl7.fhir.r4.model.IntegerType;
|
||||||
|
import org.hl7.fhir.r4.model.Parameters;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import static ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.DEFAULT_PERSISTED_PARTITION_NAME;
|
||||||
|
|
||||||
|
public abstract class BaseMultitenantResourceProviderR4Test extends BaseResourceProviderR4Test implements ITestDataBuilder {
|
||||||
|
|
||||||
|
public static final int TENANT_A_ID = 1;
|
||||||
|
public static final int TENANT_B_ID = 2;
|
||||||
|
public static final String TENANT_B = "TENANT-B";
|
||||||
|
public static final String TENANT_A = "TENANT-A";
|
||||||
|
@Autowired
|
||||||
|
private RequestTenantPartitionInterceptor myRequestTenantPartitionInterceptor;
|
||||||
|
@Autowired
|
||||||
|
private PartitionManagementProvider myPartitionManagementProvider;
|
||||||
|
|
||||||
|
protected CapturingInterceptor myCapturingInterceptor;
|
||||||
|
protected UrlTenantSelectionInterceptor myTenantClientInterceptor;
|
||||||
|
protected AuthorizationInterceptor myAuthorizationInterceptor;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Before
|
||||||
|
public void before() throws Exception {
|
||||||
|
super.before();
|
||||||
|
|
||||||
|
myPartitionSettings.setPartitioningEnabled(true);
|
||||||
|
ourRestServer.registerInterceptor(myRequestTenantPartitionInterceptor);
|
||||||
|
ourRestServer.registerProvider(myPartitionManagementProvider);
|
||||||
|
ourRestServer.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy());
|
||||||
|
|
||||||
|
myCapturingInterceptor = new CapturingInterceptor();
|
||||||
|
ourClient.getInterceptorService().registerInterceptor(myCapturingInterceptor);
|
||||||
|
|
||||||
|
myTenantClientInterceptor = new UrlTenantSelectionInterceptor();
|
||||||
|
ourClient.getInterceptorService().registerInterceptor(myTenantClientInterceptor);
|
||||||
|
|
||||||
|
ourClient.getInterceptorService().registerInterceptor(new LoggingInterceptor());
|
||||||
|
|
||||||
|
createTenants();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@After
|
||||||
|
public void after() throws Exception {
|
||||||
|
super.after();
|
||||||
|
|
||||||
|
myPartitionSettings.setPartitioningEnabled(new PartitionSettings().isPartitioningEnabled());
|
||||||
|
ourRestServer.unregisterInterceptor(myRequestTenantPartitionInterceptor);
|
||||||
|
if (myAuthorizationInterceptor != null) {
|
||||||
|
ourRestServer.unregisterInterceptor(myAuthorizationInterceptor);
|
||||||
|
}
|
||||||
|
ourRestServer.unregisterProvider(myPartitionManagementProvider);
|
||||||
|
ourRestServer.setTenantIdentificationStrategy(null);
|
||||||
|
|
||||||
|
ourClient.getInterceptorService().unregisterAllInterceptors();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldLogClient() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void createTenants() {
|
||||||
|
myTenantClientInterceptor.setTenantId(DEFAULT_PERSISTED_PARTITION_NAME);
|
||||||
|
|
||||||
|
ourClient
|
||||||
|
.operation()
|
||||||
|
.onServer()
|
||||||
|
.named(ProviderConstants.PARTITION_MANAGEMENT_CREATE_PARTITION)
|
||||||
|
.withParameter(Parameters.class, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(TENANT_A_ID))
|
||||||
|
.andParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType(TENANT_A))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
ourClient
|
||||||
|
.operation()
|
||||||
|
.onServer()
|
||||||
|
.named(ProviderConstants.PARTITION_MANAGEMENT_CREATE_PARTITION)
|
||||||
|
.withParameter(Parameters.class, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(TENANT_B_ID))
|
||||||
|
.andParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType(TENANT_B))
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableAuthorizationInterceptor(Supplier<List<IAuthRule>> theRuleSupplier) {
|
||||||
|
myAuthorizationInterceptor = new AuthorizationInterceptor() {
|
||||||
|
@Override
|
||||||
|
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||||
|
return theRuleSupplier.get();
|
||||||
|
}
|
||||||
|
}.setDefaultPolicy(PolicyEnum.DENY);
|
||||||
|
ourRestServer.registerInterceptor(myAuthorizationInterceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected Consumer<IBaseResource> withTenant(String theTenantId) {
|
||||||
|
return t -> myTenantClientInterceptor.setTenantId(theTenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIdType doCreateResource(IBaseResource theResource) {
|
||||||
|
return ourClient.create().resource(theResource).execute().getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIdType doUpdateResource(IBaseResource theResource) {
|
||||||
|
return ourClient.update().resource(theResource).execute().getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FhirContext getFhirContext() {
|
||||||
|
return myFhirCtx;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,27 +1,14 @@
|
|||||||
package ca.uhn.fhir.jpa.provider.r4;
|
package ca.uhn.fhir.jpa.provider.r4;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
|
||||||
import ca.uhn.fhir.jpa.model.util.ProviderConstants;
|
|
||||||
import ca.uhn.fhir.jpa.partition.PartitionManagementProvider;
|
|
||||||
import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor;
|
|
||||||
import ca.uhn.fhir.jpa.util.TestUtil;
|
import ca.uhn.fhir.jpa.util.TestUtil;
|
||||||
import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor;
|
|
||||||
import ca.uhn.fhir.rest.client.interceptor.UrlTenantSelectionInterceptor;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy;
|
import ca.uhn.fhir.test.utilities.ITestDataBuilder;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.CapabilityStatement;
|
import org.hl7.fhir.r4.model.CapabilityStatement;
|
||||||
import org.hl7.fhir.r4.model.CodeType;
|
|
||||||
import org.hl7.fhir.r4.model.IntegerType;
|
|
||||||
import org.hl7.fhir.r4.model.Parameters;
|
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
|
|
||||||
import static ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.DEFAULT_PERSISTED_PARTITION_NAME;
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
@ -29,56 +16,11 @@ import static org.junit.Assert.assertTrue;
|
|||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
@SuppressWarnings("Duplicates")
|
@SuppressWarnings("Duplicates")
|
||||||
public class MultitenantServerR4Test extends BaseResourceProviderR4Test {
|
public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Test implements ITestDataBuilder {
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private RequestTenantPartitionInterceptor myRequestTenantPartitionInterceptor;
|
|
||||||
@Autowired
|
|
||||||
private PartitionManagementProvider myPartitionManagementProvider;
|
|
||||||
|
|
||||||
private CapturingInterceptor myCapturingInterceptor;
|
|
||||||
private UrlTenantSelectionInterceptor myTenantInterceptor;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Before
|
|
||||||
public void before() throws Exception {
|
|
||||||
super.before();
|
|
||||||
|
|
||||||
myPartitionSettings.setPartitioningEnabled(true);
|
|
||||||
ourRestServer.registerInterceptor(myRequestTenantPartitionInterceptor);
|
|
||||||
ourRestServer.registerProvider(myPartitionManagementProvider);
|
|
||||||
ourRestServer.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy());
|
|
||||||
|
|
||||||
myCapturingInterceptor = new CapturingInterceptor();
|
|
||||||
ourClient.getInterceptorService().registerInterceptor(myCapturingInterceptor);
|
|
||||||
|
|
||||||
myTenantInterceptor = new UrlTenantSelectionInterceptor();
|
|
||||||
ourClient.getInterceptorService().registerInterceptor(myTenantInterceptor);
|
|
||||||
|
|
||||||
createTenants();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@After
|
|
||||||
public void after() throws Exception {
|
|
||||||
super.after();
|
|
||||||
|
|
||||||
myPartitionSettings.setPartitioningEnabled(new PartitionSettings().isPartitioningEnabled());
|
|
||||||
ourRestServer.unregisterInterceptor(myRequestTenantPartitionInterceptor);
|
|
||||||
ourRestServer.unregisterProvider(myPartitionManagementProvider);
|
|
||||||
ourRestServer.setTenantIdentificationStrategy(null);
|
|
||||||
|
|
||||||
ourClient.getInterceptorService().unregisterAllInterceptors();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean shouldLogClient() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFetchCapabilityStatement() {
|
public void testFetchCapabilityStatement() {
|
||||||
myTenantInterceptor.setTenantId("TENANT-A");
|
myTenantClientInterceptor.setTenantId(TENANT_A);
|
||||||
CapabilityStatement cs = ourClient.capabilities().ofType(CapabilityStatement.class).execute();
|
CapabilityStatement cs = ourClient.capabilities().ofType(CapabilityStatement.class).execute();
|
||||||
|
|
||||||
assertEquals("HAPI FHIR Server", cs.getSoftware().getName());
|
assertEquals("HAPI FHIR Server", cs.getSoftware().getName());
|
||||||
@ -88,23 +30,18 @@ public class MultitenantServerR4Test extends BaseResourceProviderR4Test {
|
|||||||
@Test
|
@Test
|
||||||
public void testCreateAndRead() {
|
public void testCreateAndRead() {
|
||||||
|
|
||||||
myTenantInterceptor.setTenantId("TENANT-A");
|
// Create patients
|
||||||
Patient patientA = new Patient();
|
|
||||||
patientA.setActive(true);
|
|
||||||
IIdType idA = ourClient.create().resource(patientA).execute().getId().toUnqualifiedVersionless();
|
|
||||||
|
|
||||||
myTenantInterceptor.setTenantId("TENANT-B");
|
IIdType idA = createPatient(withTenant(TENANT_A), withActiveTrue());
|
||||||
Patient patientB = new Patient();
|
createPatient(withTenant(TENANT_B), withActiveFalse());
|
||||||
patientB.setActive(true);
|
|
||||||
ourClient.create().resource(patientB).execute();
|
|
||||||
|
|
||||||
// Now read back
|
// Now read back
|
||||||
|
|
||||||
myTenantInterceptor.setTenantId("TENANT-A");
|
myTenantClientInterceptor.setTenantId(TENANT_A);
|
||||||
Patient response = ourClient.read().resource(Patient.class).withId(idA).execute();
|
Patient response = ourClient.read().resource(Patient.class).withId(idA).execute();
|
||||||
assertTrue(response.getActive());
|
assertTrue(response.getActive());
|
||||||
|
|
||||||
myTenantInterceptor.setTenantId("TENANT-B");
|
myTenantClientInterceptor.setTenantId(TENANT_B);
|
||||||
try {
|
try {
|
||||||
ourClient.read().resource(Patient.class).withId(idA).execute();
|
ourClient.read().resource(Patient.class).withId(idA).execute();
|
||||||
fail();
|
fail();
|
||||||
@ -116,37 +53,18 @@ public class MultitenantServerR4Test extends BaseResourceProviderR4Test {
|
|||||||
@Test
|
@Test
|
||||||
public void testCreate_InvalidTenant() {
|
public void testCreate_InvalidTenant() {
|
||||||
|
|
||||||
myTenantInterceptor.setTenantId("TENANT-ZZZ");
|
myTenantClientInterceptor.setTenantId("TENANT-ZZZ");
|
||||||
Patient patientA = new Patient();
|
Patient patientA = new Patient();
|
||||||
patientA.setActive(true);
|
patientA.setActive(true);
|
||||||
try {
|
try {
|
||||||
ourClient.create().resource(patientA).execute();
|
ourClient.create().resource(patientA).execute();
|
||||||
fail();
|
fail();
|
||||||
} catch (ResourceNotFoundException e) {
|
} catch (ResourceNotFoundException e) {
|
||||||
assertThat(e.getMessage(), containsString("Unknown partition name: TENANT-ZZZ"));
|
assertThat(e.getMessage(), containsString("Partition name \"TENANT-ZZZ\" is not valid"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createTenants() {
|
|
||||||
myTenantInterceptor.setTenantId(DEFAULT_PERSISTED_PARTITION_NAME);
|
|
||||||
|
|
||||||
ourClient
|
|
||||||
.operation()
|
|
||||||
.onServer()
|
|
||||||
.named(ProviderConstants.PARTITION_MANAGEMENT_ADD_PARTITION)
|
|
||||||
.withParameter(Parameters.class, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(1))
|
|
||||||
.andParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("TENANT-A"))
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
ourClient
|
|
||||||
.operation()
|
|
||||||
.onServer()
|
|
||||||
.named(ProviderConstants.PARTITION_MANAGEMENT_ADD_PARTITION)
|
|
||||||
.withParameter(Parameters.class, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(2))
|
|
||||||
.andParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("TENANT-B"))
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
|
@ -14,7 +14,7 @@ import ca.uhn.fhir.jpa.entity.Search;
|
|||||||
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
||||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
||||||
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
|
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
|
||||||
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperService;
|
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||||
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
||||||
import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc;
|
import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
@ -110,7 +110,7 @@ public class SearchCoordinatorSvcImplTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
|
private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
|
||||||
@Mock
|
@Mock
|
||||||
private IRequestPartitionHelperService myPartitionHelperSvc;
|
private IRequestPartitionHelperSvc myPartitionHelperSvc;
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void after() {
|
public void after() {
|
||||||
|
@ -7,19 +7,60 @@ import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
|||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult;
|
import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult;
|
||||||
import ca.uhn.fhir.jpa.searchparam.matcher.SearchParamMatcher;
|
import ca.uhn.fhir.jpa.searchparam.matcher.SearchParamMatcher;
|
||||||
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
|
|
||||||
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage;
|
|
||||||
import ca.uhn.fhir.jpa.subscription.match.matcher.matching.InMemorySubscriptionMatcher;
|
import ca.uhn.fhir.jpa.subscription.match.matcher.matching.InMemorySubscriptionMatcher;
|
||||||
import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionMatchingStrategy;
|
import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionMatchingStrategy;
|
||||||
import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionStrategyEvaluator;
|
import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionStrategyEvaluator;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage;
|
||||||
import ca.uhn.fhir.jpa.util.CoordCalculatorTest;
|
import ca.uhn.fhir.jpa.util.CoordCalculatorTest;
|
||||||
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
||||||
import ca.uhn.fhir.rest.param.*;
|
import ca.uhn.fhir.rest.param.CompositeParam;
|
||||||
|
import ca.uhn.fhir.rest.param.DateParam;
|
||||||
|
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||||
|
import ca.uhn.fhir.rest.param.HasParam;
|
||||||
|
import ca.uhn.fhir.rest.param.NumberParam;
|
||||||
|
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
||||||
|
import ca.uhn.fhir.rest.param.QuantityParam;
|
||||||
|
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
|
||||||
|
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||||
|
import ca.uhn.fhir.rest.param.StringOrListParam;
|
||||||
|
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.TokenParamModifier;
|
||||||
|
import ca.uhn.fhir.rest.param.UriParam;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.*;
|
import org.hl7.fhir.r4.model.CodeableConcept;
|
||||||
|
import org.hl7.fhir.r4.model.Coding;
|
||||||
|
import org.hl7.fhir.r4.model.Condition;
|
||||||
|
import org.hl7.fhir.r4.model.ContactPoint;
|
||||||
|
import org.hl7.fhir.r4.model.DateTimeType;
|
||||||
|
import org.hl7.fhir.r4.model.DateType;
|
||||||
|
import org.hl7.fhir.r4.model.DecimalType;
|
||||||
|
import org.hl7.fhir.r4.model.Encounter;
|
||||||
|
import org.hl7.fhir.r4.model.Enumerations;
|
||||||
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
|
import org.hl7.fhir.r4.model.Location;
|
||||||
|
import org.hl7.fhir.r4.model.MedicationAdministration;
|
||||||
|
import org.hl7.fhir.r4.model.MolecularSequence;
|
||||||
|
import org.hl7.fhir.r4.model.Observation;
|
||||||
|
import org.hl7.fhir.r4.model.Organization;
|
||||||
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
|
import org.hl7.fhir.r4.model.Practitioner;
|
||||||
|
import org.hl7.fhir.r4.model.Quantity;
|
||||||
|
import org.hl7.fhir.r4.model.Range;
|
||||||
|
import org.hl7.fhir.r4.model.Reference;
|
||||||
|
import org.hl7.fhir.r4.model.Resource;
|
||||||
|
import org.hl7.fhir.r4.model.RiskAssessment;
|
||||||
|
import org.hl7.fhir.r4.model.SimpleQuantity;
|
||||||
|
import org.hl7.fhir.r4.model.StringType;
|
||||||
|
import org.hl7.fhir.r4.model.Subscription;
|
||||||
|
import org.hl7.fhir.r4.model.Task;
|
||||||
|
import org.hl7.fhir.r4.model.ValueSet;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -31,7 +72,10 @@ import java.util.ArrayList;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
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.junit.Assert.fail;
|
||||||
|
|
||||||
@RunWith(SpringJUnit4ClassRunner.class)
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
@ContextConfiguration(classes = {TestR4Config.class})
|
@ContextConfiguration(classes = {TestR4Config.class})
|
||||||
|
@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
@ -219,8 +220,20 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
|
|||||||
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
|
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
|
||||||
String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml";
|
String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml";
|
||||||
|
|
||||||
|
ourLog.info("Creating 2 subscriptions");
|
||||||
Subscription subscription1 = createSubscription(criteria1, payload, ourListenerServerBase);
|
Subscription subscription1 = createSubscription(criteria1, payload, ourListenerServerBase);
|
||||||
Subscription subscription2 = createSubscription(criteria2, payload, ourListenerServerBase);
|
Subscription subscription2 = createSubscription(criteria2, payload, ourListenerServerBase);
|
||||||
|
|
||||||
|
|
||||||
|
runInTransaction(()->{
|
||||||
|
ourLog.info("All token indexes:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
|
||||||
|
});
|
||||||
|
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
mySubscriptionLoader.doSyncSubscriptionsForUnitTest();
|
||||||
|
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||||
|
|
||||||
|
ourLog.info("Waiting for activation");
|
||||||
waitForActivatedSubscriptionCount(2);
|
waitForActivatedSubscriptionCount(2);
|
||||||
|
|
||||||
Observation observation1 = sendObservation(code, "SNOMED-CT");
|
Observation observation1 = sendObservation(code, "SNOMED-CT");
|
||||||
|
@ -31,7 +31,12 @@ import org.hibernate.engine.jdbc.dialect.internal.StandardDialectResolver;
|
|||||||
import org.hibernate.engine.jdbc.dialect.spi.DatabaseMetaDataDialectResolutionInfoAdapter;
|
import org.hibernate.engine.jdbc.dialect.spi.DatabaseMetaDataDialectResolutionInfoAdapter;
|
||||||
import org.hibernate.engine.jdbc.dialect.spi.DialectResolver;
|
import org.hibernate.engine.jdbc.dialect.spi.DialectResolver;
|
||||||
import org.hibernate.engine.jdbc.env.internal.NormalizingIdentifierHelperImpl;
|
import org.hibernate.engine.jdbc.env.internal.NormalizingIdentifierHelperImpl;
|
||||||
import org.hibernate.engine.jdbc.env.spi.*;
|
import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData;
|
||||||
|
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
|
||||||
|
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
|
||||||
|
import org.hibernate.engine.jdbc.env.spi.LobCreatorBuilder;
|
||||||
|
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
|
||||||
|
import org.hibernate.engine.jdbc.env.spi.QualifiedObjectNameFormatter;
|
||||||
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
|
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
|
||||||
import org.hibernate.engine.jdbc.spi.TypeInfo;
|
import org.hibernate.engine.jdbc.spi.TypeInfo;
|
||||||
import org.hibernate.service.ServiceRegistry;
|
import org.hibernate.service.ServiceRegistry;
|
||||||
@ -44,8 +49,18 @@ import org.springframework.jdbc.core.ColumnMapRowMapper;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
import java.sql.*;
|
import java.sql.Connection;
|
||||||
import java.util.*;
|
import java.sql.DatabaseMetaData;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Types;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.thymeleaf.util.StringUtils.toUpperCase;
|
import static org.thymeleaf.util.StringUtils.toUpperCase;
|
||||||
|
|
||||||
@ -223,6 +238,7 @@ public class JdbcUtils {
|
|||||||
int dataType = indexes.getInt("DATA_TYPE");
|
int dataType = indexes.getInt("DATA_TYPE");
|
||||||
Long length = indexes.getLong("COLUMN_SIZE");
|
Long length = indexes.getLong("COLUMN_SIZE");
|
||||||
switch (dataType) {
|
switch (dataType) {
|
||||||
|
case Types.BIT:
|
||||||
case Types.BOOLEAN:
|
case Types.BOOLEAN:
|
||||||
return new ColumnType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN, length);
|
return new ColumnType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN, length);
|
||||||
case Types.VARCHAR:
|
case Types.VARCHAR:
|
||||||
|
@ -35,6 +35,8 @@ import javax.persistence.Transient;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
|
|
||||||
@MappedSuperclass
|
@MappedSuperclass
|
||||||
public abstract class BaseHasResource extends BasePartitionable implements IBaseResourceEntity, IBasePersistedResource {
|
public abstract class BaseHasResource extends BasePartitionable implements IBaseResourceEntity, IBasePersistedResource {
|
||||||
|
|
||||||
@ -73,6 +75,7 @@ public abstract class BaseHasResource extends BasePartitionable implements IBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setTransientForcedId(String theTransientForcedId) {
|
public void setTransientForcedId(String theTransientForcedId) {
|
||||||
|
assert !defaultString(theTransientForcedId).contains("/") : "Forced ID should not include type: " + theTransientForcedId;
|
||||||
myTransientForcedId = theTransientForcedId;
|
myTransientForcedId = theTransientForcedId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||||||
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import javax.persistence.Embedded;
|
import javax.persistence.Embedded;
|
||||||
@ -41,11 +42,12 @@ public class BasePartitionable implements Serializable {
|
|||||||
@Column(name = PartitionablePartitionId.PARTITION_ID, insertable = false, updatable = false, nullable = true)
|
@Column(name = PartitionablePartitionId.PARTITION_ID, insertable = false, updatable = false, nullable = true)
|
||||||
private Integer myPartitionIdValue;
|
private Integer myPartitionIdValue;
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
public RequestPartitionId getPartitionId() {
|
public RequestPartitionId getPartitionId() {
|
||||||
if (myPartitionId != null) {
|
if (myPartitionId != null) {
|
||||||
return myPartitionId.toPartitionId();
|
return myPartitionId.toPartitionId();
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return RequestPartitionId.defaultPartition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ import com.google.common.hash.Hashing;
|
|||||||
import org.hibernate.search.annotations.ContainedIn;
|
import org.hibernate.search.annotations.ContainedIn;
|
||||||
import org.hibernate.search.annotations.Field;
|
import org.hibernate.search.annotations.Field;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import javax.persistence.FetchType;
|
import javax.persistence.FetchType;
|
||||||
import javax.persistence.JoinColumn;
|
import javax.persistence.JoinColumn;
|
||||||
@ -85,6 +86,9 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
|
|||||||
@Transient
|
@Transient
|
||||||
private transient PartitionSettings myPartitionSettings;
|
private transient PartitionSettings myPartitionSettings;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private transient ModelConfig myModelConfig;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public abstract Long getId();
|
public abstract Long getId();
|
||||||
|
|
||||||
@ -112,6 +116,9 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
|
|||||||
myMissing = source.myMissing;
|
myMissing = source.myMissing;
|
||||||
myParamName = source.myParamName;
|
myParamName = source.myParamName;
|
||||||
myUpdated = source.myUpdated;
|
myUpdated = source.myUpdated;
|
||||||
|
myModelConfig = source.myModelConfig;
|
||||||
|
myPartitionSettings = source.myPartitionSettings;
|
||||||
|
setPartitionId(source.getPartitionId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getResourcePid() {
|
public Long getResourcePid() {
|
||||||
@ -145,6 +152,11 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
|
|||||||
|
|
||||||
public abstract IQueryParameterType toQueryParameterType();
|
public abstract IQueryParameterType toQueryParameterType();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPartitionId(@Nullable RequestPartitionId theRequestPartitionId) {
|
||||||
|
super.setPartitionId(theRequestPartitionId);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
|
public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
|
||||||
throw new UnsupportedOperationException("No parameter matcher for " + theParam);
|
throw new UnsupportedOperationException("No parameter matcher for " + theParam);
|
||||||
}
|
}
|
||||||
@ -158,6 +170,15 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BaseResourceIndexedSearchParam setModelConfig(ModelConfig theModelConfig) {
|
||||||
|
myModelConfig = theModelConfig;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModelConfig getModelConfig() {
|
||||||
|
return myModelConfig;
|
||||||
|
}
|
||||||
|
|
||||||
public static long calculateHashIdentity(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName) {
|
public static long calculateHashIdentity(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName) {
|
||||||
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName);
|
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName);
|
||||||
}
|
}
|
||||||
|
@ -20,9 +20,22 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
|
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
import org.hibernate.annotations.ColumnDefault;
|
||||||
|
|
||||||
import javax.persistence.*;
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.ForeignKey;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.OneToOne;
|
||||||
|
import javax.persistence.SequenceGenerator;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.UniqueConstraint;
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
@Table(name = ForcedId.HFJ_FORCED_ID, uniqueConstraints = {
|
@Table(name = ForcedId.HFJ_FORCED_ID, uniqueConstraints = {
|
||||||
@ -95,4 +108,17 @@ public class ForcedId extends BasePartitionable {
|
|||||||
return myId;
|
return myId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getResourceId() {
|
||||||
|
return myResourcePid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
|
||||||
|
.append("pid", myId)
|
||||||
|
.append("resourceType", myResourceType)
|
||||||
|
.append("forcedId", myForcedId)
|
||||||
|
.append("resourcePid", myResourcePid)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||||
|
|
||||||
@ -49,4 +50,6 @@ public interface IBaseResourceEntity {
|
|||||||
long getVersion();
|
long getVersion();
|
||||||
|
|
||||||
boolean isHasTags();
|
boolean isHasTags();
|
||||||
|
|
||||||
|
RequestPartitionId getPartitionId();
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,25 @@ import ca.uhn.fhir.model.primitive.IdDt;
|
|||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import org.hibernate.annotations.OptimisticLock;
|
import org.hibernate.annotations.OptimisticLock;
|
||||||
|
|
||||||
import javax.persistence.*;
|
import javax.persistence.CascadeType;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.EnumType;
|
||||||
|
import javax.persistence.Enumerated;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
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.Lob;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
|
import javax.persistence.OneToOne;
|
||||||
|
import javax.persistence.SequenceGenerator;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.UniqueConstraint;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -182,14 +200,19 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IdDt getIdDt() {
|
public IdDt getIdDt() {
|
||||||
if (getResourceTable().getForcedId() == null) {
|
// Avoid a join query if possible
|
||||||
Long id = getResourceId();
|
String resourceIdPart;
|
||||||
return new IdDt(getResourceType() + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
if (getTransientForcedId() != null) {
|
||||||
|
resourceIdPart = getTransientForcedId();
|
||||||
} else {
|
} else {
|
||||||
// Avoid a join query if possible
|
if (getResourceTable().getForcedId() == null) {
|
||||||
String forcedId = getTransientForcedId() != null ? getTransientForcedId() : getResourceTable().getForcedId().getForcedId();
|
Long id = getResourceId();
|
||||||
return new IdDt(getResourceType() + '/' + forcedId + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
resourceIdPart = id.toString();
|
||||||
|
} else {
|
||||||
|
resourceIdPart = getResourceTable().getForcedId().getForcedId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return new IdDt(getResourceType() + '/' + resourceIdPart + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -247,10 +247,12 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
|
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
|
||||||
|
b.append("partitionId", getPartitionId());
|
||||||
b.append("paramName", getParamName());
|
b.append("paramName", getParamName());
|
||||||
b.append("resourceId", getResourcePid());
|
b.append("resourceId", getResourcePid());
|
||||||
b.append("valueLow", new InstantDt(getValueLow()));
|
b.append("valueLow", new InstantDt(getValueLow()));
|
||||||
b.append("valueHigh", new InstantDt(getValueHigh()));
|
b.append("valueHigh", new InstantDt(getValueHigh()));
|
||||||
|
b.append("hashIdentity", myHashIdentity);
|
||||||
b.append("missing", isMissing());
|
b.append("missing", isMissing());
|
||||||
return b.build();
|
return b.build();
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,6 @@ import javax.persistence.JoinColumn;
|
|||||||
import javax.persistence.ManyToOne;
|
import javax.persistence.ManyToOne;
|
||||||
import javax.persistence.SequenceGenerator;
|
import javax.persistence.SequenceGenerator;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
import javax.persistence.Transient;
|
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
import static org.apache.commons.lang3.StringUtils.left;
|
import static org.apache.commons.lang3.StringUtils.left;
|
||||||
@ -119,8 +118,6 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
|
|||||||
*/
|
*/
|
||||||
@Column(name = "HASH_EXACT", nullable = true)
|
@Column(name = "HASH_EXACT", nullable = true)
|
||||||
private Long myHashExact;
|
private Long myHashExact;
|
||||||
@Transient
|
|
||||||
private transient ModelConfig myModelConfig;
|
|
||||||
|
|
||||||
public ResourceIndexedSearchParamString() {
|
public ResourceIndexedSearchParamString() {
|
||||||
super();
|
super();
|
||||||
@ -153,7 +150,7 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
|
|||||||
String paramName = getParamName();
|
String paramName = getParamName();
|
||||||
String valueNormalized = getValueNormalized();
|
String valueNormalized = getValueNormalized();
|
||||||
String valueExact = getValueExact();
|
String valueExact = getValueExact();
|
||||||
setHashNormalizedPrefix(calculateHashNormalized(getPartitionSettings(), getPartitionId(), myModelConfig, resourceType, paramName, valueNormalized));
|
setHashNormalizedPrefix(calculateHashNormalized(getPartitionSettings(), getPartitionId(), getModelConfig(), resourceType, paramName, valueNormalized));
|
||||||
setHashExact(calculateHashExact(getPartitionSettings(), getPartitionId(), resourceType, paramName, valueExact));
|
setHashExact(calculateHashExact(getPartitionSettings(), getPartitionId(), resourceType, paramName, valueExact));
|
||||||
setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
|
setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
|
||||||
}
|
}
|
||||||
@ -248,11 +245,6 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
|
|||||||
return b.toHashCode();
|
return b.toHashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseResourceIndexedSearchParam setModelConfig(ModelConfig theModelConfig) {
|
|
||||||
myModelConfig = theModelConfig;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IQueryParameterType toQueryParameterType() {
|
public IQueryParameterType toQueryParameterType() {
|
||||||
return new StringParam(getValueExact());
|
return new StringParam(getValueExact());
|
||||||
|
@ -40,6 +40,7 @@ import javax.persistence.Id;
|
|||||||
import javax.persistence.Index;
|
import javax.persistence.Index;
|
||||||
import javax.persistence.SequenceGenerator;
|
import javax.persistence.SequenceGenerator;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
import static org.apache.commons.lang3.StringUtils.trim;
|
import static org.apache.commons.lang3.StringUtils.trim;
|
||||||
@ -243,6 +244,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
|
|||||||
b.append("paramName", getParamName());
|
b.append("paramName", getParamName());
|
||||||
b.append("system", getSystem());
|
b.append("system", getSystem());
|
||||||
b.append("value", getValue());
|
b.append("value", getValue());
|
||||||
|
b.append("hashIdentity", myHashIdentity);
|
||||||
return b.build();
|
return b.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ public class ProviderConstants {
|
|||||||
/**
|
/**
|
||||||
* Operation name: add partition
|
* Operation name: add partition
|
||||||
*/
|
*/
|
||||||
public static final String PARTITION_MANAGEMENT_ADD_PARTITION = "$partition-management-add-partition";
|
public static final String PARTITION_MANAGEMENT_CREATE_PARTITION = "$partition-management-create-partition";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Operation name: update partition
|
* Operation name: update partition
|
||||||
|
@ -9,7 +9,10 @@ import java.time.Instant;
|
|||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
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.assertNotEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class ResourceIndexedSearchParamDateTest {
|
public class ResourceIndexedSearchParamDateTest {
|
||||||
|
|
||||||
|
@ -22,7 +22,19 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
|
|||||||
|
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
import ca.uhn.fhir.jpa.model.entity.*;
|
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndex;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
|
||||||
|
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.entity.ResourceLink;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||||
|
@ -29,7 +29,17 @@ import ca.uhn.fhir.interceptor.api.Pointcut;
|
|||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
||||||
import ca.uhn.fhir.jpa.model.entity.*;
|
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
|
||||||
|
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.entity.ResourceLink;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||||
@ -45,6 +55,8 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
|
|||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -174,7 +186,7 @@ public class SearchParamExtractorService {
|
|||||||
theEntity.setHasLinks(theParams.myLinks.size() > 0);
|
theEntity.setHasLinks(theParams.myLinks.size() > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void extractResourceLinks(RequestPartitionId theRequestPartitionId, ResourceIndexedSearchParams theParams, ResourceTable theEntity, Date theUpdateTime, RuntimeSearchParam theRuntimeSearchParam, PathAndRef thePathAndRef, boolean theFailOnInvalidReference, RequestDetails theRequest, Map<String, IResourceLookup> theResourceIdToResolvedTarget) {
|
private void extractResourceLinks(@NotNull RequestPartitionId theRequestPartitionId, ResourceIndexedSearchParams theParams, ResourceTable theEntity, Date theUpdateTime, RuntimeSearchParam theRuntimeSearchParam, PathAndRef thePathAndRef, boolean theFailOnInvalidReference, RequestDetails theRequest, Map<String, IResourceLookup> theResourceIdToResolvedTarget) {
|
||||||
IBaseReference nextReference = thePathAndRef.getRef();
|
IBaseReference nextReference = thePathAndRef.getRef();
|
||||||
IIdType nextId = nextReference.getReferenceElement();
|
IIdType nextId = nextReference.getReferenceElement();
|
||||||
String path = thePathAndRef.getPath();
|
String path = thePathAndRef.getPath();
|
||||||
@ -276,7 +288,7 @@ public class SearchParamExtractorService {
|
|||||||
theParams.myLinks.add(resourceLink);
|
theParams.myLinks.add(resourceLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResourceLink resolveTargetAndCreateResourceLinkOrReturnNull(RequestPartitionId theRequestPartitionId, ResourceTable theEntity, Date theUpdateTime, RuntimeSearchParam nextSpDef, String theNextPathsUnsplit, PathAndRef nextPathAndRef, IIdType theNextId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest, Map<String, IResourceLookup> theResourceIdToResolvedTarget) {
|
private ResourceLink resolveTargetAndCreateResourceLinkOrReturnNull(@Nonnull RequestPartitionId theRequestPartitionId, ResourceTable theEntity, Date theUpdateTime, RuntimeSearchParam nextSpDef, String theNextPathsUnsplit, PathAndRef nextPathAndRef, IIdType theNextId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest, Map<String, IResourceLookup> theResourceIdToResolvedTarget) {
|
||||||
/*
|
/*
|
||||||
* We keep a cache of resolved target resources. This is good since for some resource types, there
|
* We keep a cache of resolved target resources. This is good since for some resource types, there
|
||||||
* are multiple search parameters that map to the same element path within a resource (e.g.
|
* are multiple search parameters that map to the same element path within a resource (e.g.
|
||||||
@ -286,7 +298,7 @@ public class SearchParamExtractorService {
|
|||||||
|
|
||||||
RequestPartitionId targetRequestPartitionId = theRequestPartitionId;
|
RequestPartitionId targetRequestPartitionId = theRequestPartitionId;
|
||||||
if (myPartitionSettings.isPartitioningEnabled() && myPartitionSettings.getAllowReferencesAcrossPartitions() == PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED) {
|
if (myPartitionSettings.isPartitioningEnabled() && myPartitionSettings.getAllowReferencesAcrossPartitions() == PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED) {
|
||||||
targetRequestPartitionId = null;
|
targetRequestPartitionId = RequestPartitionId.allPartitions();
|
||||||
}
|
}
|
||||||
|
|
||||||
String key = RequestPartitionId.stringifyForKey(targetRequestPartitionId) + "/" + theNextId.getValue();
|
String key = RequestPartitionId.stringifyForKey(targetRequestPartitionId) + "/" + theNextId.getValue();
|
||||||
|
@ -47,17 +47,19 @@ public class SearchableHashMapResourceProvider<T extends IBaseResource> extends
|
|||||||
mySearchParamMatcher = theSearchParamMatcher;
|
mySearchParamMatcher = theSearchParamMatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<T> searchByCriteria(String theCriteria, RequestDetails theRequest) {
|
public List<IBaseResource> searchByCriteria(String theCriteria, RequestDetails theRequest) {
|
||||||
return searchBy(resource -> mySearchParamMatcher.match(theCriteria, resource, theRequest), theRequest);
|
return searchBy(resource -> mySearchParamMatcher.match(theCriteria, resource, theRequest), theRequest);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<T> searchByParams(SearchParameterMap theSearchParams, RequestDetails theRequest) {
|
public List<IBaseResource> searchByParams(SearchParameterMap theSearchParams, RequestDetails theRequest) {
|
||||||
return searchBy(resource -> mySearchParamMatcher.match(theSearchParams.toNormalizedQueryString(getFhirContext()), resource, theRequest), theRequest);
|
return searchBy(resource -> mySearchParamMatcher.match(theSearchParams.toNormalizedQueryString(getFhirContext()), resource, theRequest), theRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<T> searchBy(Function<IBaseResource, InMemoryMatchResult> theMatcher, RequestDetails theRequest) {
|
private List<IBaseResource> searchBy(Function<IBaseResource, InMemoryMatchResult> theMatcher, RequestDetails theRequest) {
|
||||||
List<T> allEResources = searchAll(theRequest);
|
mySearchCount.incrementAndGet();
|
||||||
|
List<T> allEResources = getAllResources();
|
||||||
|
|
||||||
List<T> matches = new ArrayList<>();
|
List<T> matches = new ArrayList<>();
|
||||||
for (T resource : allEResources) {
|
for (T resource : allEResources) {
|
||||||
InMemoryMatchResult result = theMatcher.apply(resource);
|
InMemoryMatchResult result = theMatcher.apply(resource);
|
||||||
@ -68,6 +70,6 @@ public class SearchableHashMapResourceProvider<T extends IBaseResource> extends
|
|||||||
matches.add(resource);
|
matches.add(resource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return matches;
|
return fireInterceptorsAndFilterAsNeeded(matches, theRequest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,9 @@ import java.time.Duration;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Date;
|
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.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user