From ab710116718beb292486e83374f413bb1362d16a Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Mon, 10 Nov 2025 15:51:48 +0000 Subject: [PATCH 1/7] Initial gradlew build --- .editorconfig | 455 ++++++++++++++++++ .gitattributes | 12 + .gitignore | 51 +- README.md | 2 +- build.gradle.kts | 186 +++++++ config/spotless.license.java | 17 + gradle.properties | 17 + gradle/libs.versions.toml | 89 ++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 45633 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 248 ++++++++++ gradlew.bat | 93 ++++ settings.gradle.kts | 17 + spring-session-data-mongodb.gradle | 49 -- .../data/mongo/AbstractClassLoaderTest.java | 0 .../session/data/mongo/AbstractITest.java | 0 .../mongo/AbstractMongoRepositoryITest.java | 0 ...bDeleteJacksonSessionVerificationTest.java | 0 .../mongo/MongoDbLogoutVerificationTest.java | 0 .../mongo/MongoRepositoryJacksonITest.java | 0 .../MongoRepositoryJdkSerializationITest.java | 0 21 files changed, 1174 insertions(+), 69 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 build.gradle.kts create mode 100644 config/spotless.license.java create mode 100644 gradle.properties create mode 100644 gradle/libs.versions.toml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts delete mode 100644 spring-session-data-mongodb.gradle rename src/{integration-test => integrationTest}/java/org/springframework/session/data/mongo/AbstractClassLoaderTest.java (100%) rename src/{integration-test => integrationTest}/java/org/springframework/session/data/mongo/AbstractITest.java (100%) rename src/{integration-test => integrationTest}/java/org/springframework/session/data/mongo/AbstractMongoRepositoryITest.java (100%) rename src/{integration-test => integrationTest}/java/org/springframework/session/data/mongo/MongoDbDeleteJacksonSessionVerificationTest.java (100%) rename src/{integration-test => integrationTest}/java/org/springframework/session/data/mongo/MongoDbLogoutVerificationTest.java (100%) rename src/{integration-test => integrationTest}/java/org/springframework/session/data/mongo/MongoRepositoryJacksonITest.java (100%) rename src/{integration-test => integrationTest}/java/org/springframework/session/data/mongo/MongoRepositoryJdkSerializationITest.java (100%) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0e84cbd --- /dev/null +++ b/.editorconfig @@ -0,0 +1,455 @@ +# Copyright 2025-present MongoDB, Inc. +# +# 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. + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = 120 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = true +ij_smart_tabs = false +ij_visual_guides = +ij_wrap_on_typing = false + +[*.java] +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_deconstruction_list_components = true +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_align_types_in_multi_catch = true +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_add_space = false +ij_java_block_comment_at_first_column = true +ij_java_builder_methods = +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 = 50 +ij_java_class_names_in_javadoc = 1 +ij_java_deconstruction_list_wrap = normal +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_not_wrap_after_single_annotation_in_parameter = 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_enum_constants_wrap = off +ij_java_enum_field_annotation_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 = +ij_java_field_name_suffix = +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_generate_use_type_annotation_before_type = true +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_builder_methods_indents = false +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_add_space_on_reformat = false +ij_java_line_comment_at_first_column = true +ij_java_local_variable_name_prefix = +ij_java_local_variable_name_suffix = +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_multi_catch_types_wrap = normal +ij_java_names_count_to_use_import_on_demand = 30 +ij_java_new_line_after_lparen_in_annotation = false +ij_java_new_line_after_lparen_in_deconstruction_pattern = true +ij_java_new_line_after_lparen_in_record_header = false +ij_java_new_line_when_body_is_presented = false +ij_java_packages_to_use_import_on_demand = java.awt.*,javax.swing.* +ij_java_parameter_annotation_wrap = off +ij_java_parameter_name_prefix = +ij_java_parameter_name_suffix = +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_annotation = false +ij_java_rparen_on_new_line_in_deconstruction_pattern = true +ij_java_rparen_on_new_line_in_record_header = false +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_deconstruction_list = 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_annotation_eq = 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_inside_block_braces_when_body_is_present = 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_deconstruction_list = 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_record_header = 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 = +ij_java_static_field_name_suffix = +ij_java_subclass_name_prefix = +ij_java_subclass_name_suffix = Impl +ij_java_switch_expressions_wrap = normal +ij_java_ternary_operation_signs_on_next_line = false +ij_java_ternary_operation_wrap = off +ij_java_test_name_prefix = +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 +ij_java_wrap_semicolon_after_call_chain = false + +[*.properties] +ij_properties_align_group_field_declarations = false +ij_properties_keep_blank_lines = false +ij_properties_key_value_delimiter = equals +ij_properties_spaces_around_key_value_delimiter = false + +[.editorconfig] +ij_editorconfig_align_group_field_declarations = false +ij_editorconfig_space_after_colon = false +ij_editorconfig_space_after_comma = true +ij_editorconfig_space_before_colon = false +ij_editorconfig_space_before_comma = false +ij_editorconfig_spaces_around_assignment_operators = true + +[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.jspx,*.pom,*.rng,*.tagx,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] +ij_xml_align_attributes = true +ij_xml_align_text = false +ij_xml_attribute_wrap = normal +ij_xml_block_comment_add_space = false +ij_xml_block_comment_at_first_column = true +ij_xml_keep_blank_lines = 2 +ij_xml_keep_indents_on_empty_lines = false +ij_xml_keep_line_breaks = true +ij_xml_keep_line_breaks_in_text = true +ij_xml_keep_whitespaces = false +ij_xml_keep_whitespaces_around_cdata = preserve +ij_xml_keep_whitespaces_inside_cdata = false +ij_xml_line_comment_at_first_column = true +ij_xml_space_after_tag_name = false +ij_xml_space_around_equals_in_attribute = false +ij_xml_space_inside_empty_tag = false +ij_xml_text_wrap = normal + +[{*.bash,*.sh,*.zsh}] +indent_size = 2 +tab_width = 2 +ij_shell_binary_ops_start_line = false +ij_shell_keep_column_alignment_padding = false +ij_shell_minify_program = false +ij_shell_redirect_followed_by_space = false +ij_shell_switch_cases_indented = false +ij_shell_use_unix_line_separator = true + +[{*.kt,*.kts}] +ij_kotlin_align_in_columns_case_branch = false +ij_kotlin_align_multiline_binary_operation = false +ij_kotlin_align_multiline_extends_list = false +ij_kotlin_align_multiline_method_parentheses = false +ij_kotlin_align_multiline_parameters = true +ij_kotlin_align_multiline_parameters_in_calls = false +ij_kotlin_allow_trailing_comma = false +ij_kotlin_allow_trailing_comma_on_call_site = false +ij_kotlin_assignment_wrap = normal +ij_kotlin_blank_lines_after_class_header = 0 +ij_kotlin_blank_lines_around_block_when_branches = 0 +ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 +ij_kotlin_block_comment_add_space = false +ij_kotlin_block_comment_at_first_column = true +ij_kotlin_call_parameters_new_line_after_left_paren = true +ij_kotlin_call_parameters_right_paren_on_new_line = true +ij_kotlin_call_parameters_wrap = on_every_item +ij_kotlin_catch_on_new_line = false +ij_kotlin_class_annotation_wrap = split_into_lines +ij_kotlin_continuation_indent_for_chained_calls = false +ij_kotlin_continuation_indent_for_expression_bodies = false +ij_kotlin_continuation_indent_in_argument_lists = false +ij_kotlin_continuation_indent_in_elvis = false +ij_kotlin_continuation_indent_in_if_conditions = false +ij_kotlin_continuation_indent_in_parameter_lists = false +ij_kotlin_continuation_indent_in_supertype_lists = false +ij_kotlin_else_on_new_line = false +ij_kotlin_enum_constants_wrap = off +ij_kotlin_extends_list_wrap = normal +ij_kotlin_field_annotation_wrap = split_into_lines +ij_kotlin_finally_on_new_line = false +ij_kotlin_if_rparen_on_new_line = true +ij_kotlin_import_nested_classes = false +ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^ +ij_kotlin_indent_before_arrow_on_new_line = true +ij_kotlin_insert_whitespaces_in_simple_one_line_method = true +ij_kotlin_keep_blank_lines_before_right_brace = 2 +ij_kotlin_keep_blank_lines_in_code = 2 +ij_kotlin_keep_blank_lines_in_declarations = 2 +ij_kotlin_keep_first_column_comment = true +ij_kotlin_keep_indents_on_empty_lines = false +ij_kotlin_keep_line_breaks = true +ij_kotlin_lbrace_on_next_line = false +ij_kotlin_line_break_after_multiline_when_entry = true +ij_kotlin_line_comment_add_space = false +ij_kotlin_line_comment_add_space_on_reformat = false +ij_kotlin_line_comment_at_first_column = true +ij_kotlin_method_annotation_wrap = split_into_lines +ij_kotlin_method_call_chain_wrap = normal +ij_kotlin_method_parameters_new_line_after_left_paren = true +ij_kotlin_method_parameters_right_paren_on_new_line = true +ij_kotlin_method_parameters_wrap = on_every_item +ij_kotlin_name_count_to_use_star_import = 5 +ij_kotlin_name_count_to_use_star_import_for_members = 3 +ij_kotlin_packages_to_use_import_on_demand = java.util.*,kotlinx.android.synthetic.**,io.ktor.** +ij_kotlin_parameter_annotation_wrap = off +ij_kotlin_space_after_comma = true +ij_kotlin_space_after_extend_colon = true +ij_kotlin_space_after_type_colon = true +ij_kotlin_space_before_catch_parentheses = true +ij_kotlin_space_before_comma = false +ij_kotlin_space_before_extend_colon = true +ij_kotlin_space_before_for_parentheses = true +ij_kotlin_space_before_if_parentheses = true +ij_kotlin_space_before_lambda_arrow = true +ij_kotlin_space_before_type_colon = false +ij_kotlin_space_before_when_parentheses = true +ij_kotlin_space_before_while_parentheses = true +ij_kotlin_spaces_around_additive_operators = true +ij_kotlin_spaces_around_assignment_operators = true +ij_kotlin_spaces_around_equality_operators = true +ij_kotlin_spaces_around_function_type_arrow = true +ij_kotlin_spaces_around_logical_operators = true +ij_kotlin_spaces_around_multiplicative_operators = true +ij_kotlin_spaces_around_range = false +ij_kotlin_spaces_around_relational_operators = true +ij_kotlin_spaces_around_unary_operator = false +ij_kotlin_spaces_around_when_arrow = true +ij_kotlin_variable_annotation_wrap = off +ij_kotlin_while_on_new_line = false +ij_kotlin_wrap_elvis_expressions = 1 +ij_kotlin_wrap_expression_body_functions = 1 +ij_kotlin_wrap_first_method_in_call_chain = false + +[{*.markdown,*.md}] +ij_markdown_force_one_space_after_blockquote_symbol = true +ij_markdown_force_one_space_after_header_symbol = true +ij_markdown_force_one_space_after_list_bullet = true +ij_markdown_force_one_space_between_words = true +ij_markdown_format_tables = true +ij_markdown_insert_quote_arrows_on_wrap = true +ij_markdown_keep_indents_on_empty_lines = false +ij_markdown_keep_line_breaks_inside_text_blocks = true +ij_markdown_max_lines_around_block_elements = 1 +ij_markdown_max_lines_around_header = 1 +ij_markdown_max_lines_between_paragraphs = 1 +ij_markdown_min_lines_around_block_elements = 1 +ij_markdown_min_lines_around_header = 1 +ij_markdown_min_lines_between_paragraphs = 1 +ij_markdown_wrap_text_if_long = true +ij_markdown_wrap_text_inside_blockquotes = true + +[{*.yaml,*.yml}] +indent_size = 2 +ij_yaml_align_values_properties = do_not_align +ij_yaml_autoinsert_sequence_marker = true +ij_yaml_block_mapping_on_new_line = false +ij_yaml_indent_sequence_value = true +ij_yaml_keep_indents_on_empty_lines = false +ij_yaml_keep_line_breaks = true +ij_yaml_line_comment_add_space = false +ij_yaml_line_comment_add_space_on_reformat = false +ij_yaml_line_comment_at_first_column = true +ij_yaml_sequence_on_new_line = false +ij_yaml_space_before_colon = false +ij_yaml_spaces_within_braces = true +ij_yaml_spaces_within_brackets = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f91f646 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + +# Binary files should be left untouched +*.jar binary + diff --git a/.gitignore b/.gitignore index 524f096..58a3df9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,37 @@ -# Compiled class file -*.class +# Copyright 2025-present MongoDB, Inc. +# +# 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. -# Log file -*.log +*~ -# BlueJ files -*.ctxt +# Mac +.DS_Store -# Mobile Tools for Java (J2ME) -.mtj.tmp/ +# Eclipse +/.classpath +/.project +/.settings +/bin/ -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar +# Intellij IDEA +/.idea/ +/*.ipr +/*.iws +/*.iml +/out/ +!/.idea/inspectionProfiles/Project_Default.xml -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -replay_pid* +# Gradle +.gradle/ +.kotlin/ +build/ diff --git a/README.md b/README.md index ec6eb66..6f0af1e 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# mongo-spring-session \ No newline at end of file +# mongo-spring-session diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..2edb487 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,186 @@ +/* + * Copyright 2025-present MongoDB, Inc. + * + * 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. + */ + +import org.gradle.api.tasks.testing.logging.TestLogEvent + +buildscript { + repositories { + gradlePluginPortal() + maven(url = "https://repo.spring.io/plugins-release/") + } +} + +plugins { + id("eclipse") + id("idea") + id("java-library") + id("maven-publish") + alias(libs.plugins.spotless) +} + +description = "Spring Session and Spring MongoDB integration" + +repositories { + mavenLocal() + mavenCentral() +} + +java { + toolchain { languageVersion = JavaLanguageVersion.of(17) } // Remember to update javadoc links + withJavadocJar() + withSourcesJar() + registerFeature("optional") { usingSourceSet(sourceSets["main"]) } +} + +// Suppress POM warnings for the optional features (eg: optionalApi, optionalImplementation) +// afterEvaluate { +// configurations +// .filter { it.name.startsWith("optional") } +// .forEach { optional -> +// publishing.publications.named("maven") { suppressPomMetadataWarningsFor(optional.name) } +// } +// } + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Integration Test + +// Added `Action` explicitly due to an intellij 2025.2 false positive: https://youtrack.jetbrains.com/issue/KTIJ-34210 +sourceSets { + create( + "integrationTest", + Action { + compileClasspath += sourceSets.main.get().output + runtimeClasspath += sourceSets.main.get().output + }) +} + +val integrationTestSourceSet: SourceSet = sourceSets["integrationTest"] + +val integrationTestImplementation: Configuration by + configurations.getting { + extendsFrom( + configurations.getByName("api"), + configurations.getByName("optionalApi"), + configurations.getByName("implementation"), + configurations.getByName("optionalImplementation"), + configurations.getByName("testImplementation")) + } +val integrationTestRuntimeOnly: Configuration by + configurations.getting { + extendsFrom(configurations.getByName("runtimeOnly"), configurations.getByName("testRuntimeOnly")) + } + +val integrationTestTask = + tasks.register("integrationTest") { + group = LifecycleBasePlugin.VERIFICATION_GROUP + testClassesDirs = integrationTestSourceSet.output.classesDirs + classpath = integrationTestSourceSet.runtimeClasspath + } + +tasks.check { dependsOn(integrationTestTask) } + +tasks.withType().configureEach { + useJUnitPlatform() + testLogging { events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Dependencies + +dependencies { + api(platform(libs.spring.session.bom)) + api(libs.spring.session.core) + + api(platform(libs.spring.data.bom)) + api(libs.spring.data.mongodb) + + api(platform(libs.jackson.bom)) + api(libs.jackson.databind) + + api(platform(libs.spring.security.bom)) + api(libs.spring.security.core) + + // Optional dependencies + "optionalApi"(platform(libs.project.reactor.bom)) + "optionalApi"(libs.project.reactor.core) + + implementation(platform(libs.mongodb.driver.bom)) + implementation(libs.mongodb.driver.core) + "optionalImplementation"(libs.mongodb.driver.sync) + "optionalImplementation"(libs.mongodb.driver.reactive.streams) + + // We need the `libs.findbugs.jsr` dependency to stop `javadoc` from emitting + // `warning: unknown enum constant When.MAYBE` + // `reason: class file for javax.annotation.meta.When not found`. + compileOnly(libs.findbugs.jsr) + + // add("integrationTestCompile", "org.testcontainers:mongodb") + // + + testImplementation(platform(libs.junit.bom)) + testImplementation(platform(libs.mockito.bom)) + testImplementation(platform(libs.spring.framework.bom)) + + testImplementation(libs.bundles.testing) + testImplementation(libs.bundles.jakarta) + testImplementation(libs.bundles.spring.test) + testImplementation(libs.logback.core) + testImplementation(libs.project.reactor.test) + + testRuntimeOnly(libs.junit.platform.launcher) + + "integrationTestImplementation"(libs.testcontainers.mongodb) +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Static Analysis + +tasks.named("check") { dependsOn("spotlessApply") } + +spotless { + java { + importOrder() + + removeUnusedImports() + + palantirJavaFormat(libs.versions.plugin.palantir.get()).formatJavadoc(true) + + formatAnnotations() + + // need to add license header manually to package-info.java and module-info.java + // due to the bug: https://github.com/diffplug/spotless/issues/532 + licenseHeaderFile(file("config/spotless.license.java")) + + targetExclude("build/generated/sources/buildConfig/**/*.java") + } + + kotlinGradle { + ktfmt(libs.versions.plugin.ktfmt.get()).configure { + it.setMaxWidth(120) + it.setBlockIndent(4) + } + trimTrailingWhitespace() + leadingTabsToSpaces() + endWithNewline() + } + + format("extraneous") { + target("*.xml", "*.yml", "*.md", "*.toml") + trimTrailingWhitespace() + leadingTabsToSpaces() + endWithNewline() + } +} diff --git a/config/spotless.license.java b/config/spotless.license.java new file mode 100644 index 0000000..f401343 --- /dev/null +++ b/config/spotless.license.java @@ -0,0 +1,17 @@ +/* + * Copyright 2025-present MongoDB, Inc. + * Copyright 2014-present the original author or authors. + * + * 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. + */ + diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..2c6f853 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,17 @@ +# Copyright 2025-present MongoDB, Inc. +# +# 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. + +version=0.1.0-SNAPSHOT + +org.gradle.jvmargs=-Duser.country=US -Duser.language=en -Dfile.encoding=UTF-8 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..8fc6f19 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,89 @@ +# Copyright 2025-present MongoDB, Inc. +# +# 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. + +[versions] +spring-session = "4.0.0-RC1" +spring-data = "2025.0.5" +spring-security = "6.5.6" +spring-framework = "6.2.12" +jackson = "2.18.4" +findbugs = "3.0.2" +project-reactor = "2025.0.0" +mongodb-driver = "5.6.1" + +# Test libraries +assertj = "3.27.3" +junit = "5.12.1" +hamcrest = "3.0" +jakarta-websocket = "2.1.1" +jakarta-servlet-api = "6.0.0" +mockito = "5.16.1" +logback = "1.5.18" +testcontainers = "2.0.1" + +plugin-spotless = "7.0.2" +plugin-palantir = "2.58.0" +plugin-ktfmt = "0.54" + +[libraries] +spring-session-bom = { module = "org.springframework.session:spring-session-bom", version.ref = "spring-session" } +spring-session-core = { module = "org.springframework.session:spring-session-core" } +spring-data-bom = { module = "org.springframework.data:spring-data-bom", version.ref = "spring-data" } +spring-data-mongodb = { module = "org.springframework.data:spring-data-mongodb" } +jackson-bom = { module = "com.fasterxml.jackson:jackson-bom", version.ref = "jackson" } +jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind" } +findbugs-jsr = { module = "com.google.code.findbugs:jsr305", version.ref = "findbugs" } + +spring-security-bom = { module = "org.springframework.security:spring-security-bom", version.ref = "spring-security" } +spring-security-core = { module = "org.springframework.security:spring-security-core" } + +project-reactor-bom = { module = "io.projectreactor:reactor-bom", version.ref = "project-reactor" } +project-reactor-core = { module = "io.projectreactor:reactor-core" } +project-reactor-test = { module = "io.projectreactor:reactor-test" } + +mongodb-driver-bom = { module = "org.mongodb:mongodb-driver-bom", version.ref = "mongodb-driver" } +mongodb-driver-core = { module = "org.mongodb:mongodb-driver-core" } +mongodb-driver-sync = { module = "org.mongodb:mongodb-driver-sync" } +mongodb-driver-reactive-streams = { module = "org.mongodb:mongodb-driver-reactivestreams" } + +# Test libraries +junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" } +mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "mockito" } +junit-jupiter = { module = "org.junit.jupiter:junit-jupiter" } +junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" } +mockito-core = { module = "org.mockito:mockito-core" } +mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter" } +assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } +hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" } +logback-core = { module = "ch.qos.logback:logback-core", version.ref = "logback" } + +jakarta-websocket-api = { module = "jakarta.websocket:jakarta.websocket-api", version.ref = "jakarta-websocket" } +jakarta-websocket-client-api = { module = "jakarta.websocket:jakarta.websocket-client-api", version.ref = "jakarta-websocket" } +jakarta-servlet-api = { module = "jakarta.servlet:jakarta.servlet-api", version.ref = "jakarta-servlet-api" } + +spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "spring-framework" } +spring-security-config = { module = "org.springframework.security:spring-security-config" } +spring-security-web = { module = "org.springframework.security:spring-security-web" } +spring-test = { module = "org.springframework:spring-test" } +spring-web = { module = "org.springframework:spring-web" } +spring-webflux = { module = "org.springframework:spring-webflux" } +testcontainers-mongodb = { module = "org.testcontainers:testcontainers-mongodb", version.ref="testcontainers" } + +[bundles] +testing = ["junit-jupiter", "assertj", "hamcrest", "mockito-core", "mockito-junit-jupiter"] +jakarta = ["jakarta-servlet-api", "jakarta-websocket-client-api", "jakarta-websocket-api"] +spring-test = ["spring-security-config", "spring-security-web", "spring-test", "spring-web", "spring-webflux"] + +[plugins] +spotless = { id = "com.diffplug.spotless", version.ref = "plugin-spotless" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f8e1ee3125fe0768e9a76ee977ac089eb657005e GIT binary patch literal 45633 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�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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..bad7c24 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..adff685 --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# 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 +# +# https://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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..c4bdd3a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..8d92201 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,17 @@ +/* + * Copyright 2025-present MongoDB, Inc. + * + * 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. + */ + +rootProject.name = "mongo-spring-session" diff --git a/spring-session-data-mongodb.gradle b/spring-session-data-mongodb.gradle deleted file mode 100644 index 1a82382..0000000 --- a/spring-session-data-mongodb.gradle +++ /dev/null @@ -1,49 +0,0 @@ -apply plugin: 'io.spring.convention.spring-module' - -description = "Spring Session and Spring MongoDB integration" - -dependencies { - management platform(project(":spring-session-dependencies")) - - api project(':spring-session-core') - - // Spring Data MongoDB - - api("org.springframework.data:spring-data-mongodb") { - exclude group: "org.mongodb", module: "mongo-java-driver" - exclude group: "org.slf4j", module: "jcl-over-slf4j" - } - - // MongoDB dependencies - - optional "org.mongodb:mongodb-driver-core" - testImplementation "org.mongodb:mongodb-driver-reactivestreams" - testImplementation "org.mongodb:mongodb-driver-sync" - testImplementation 'jakarta.websocket:jakarta.websocket-api' - testImplementation 'jakarta.websocket:jakarta.websocket-client-api' - integrationTestCompile "org.testcontainers:mongodb" - - // Everything else - - api "com.fasterxml.jackson.core:jackson-databind" - api "com.google.code.findbugs:jsr305" - api "org.springframework.security:spring-security-core" - - optional "io.projectreactor:reactor-core" - - testImplementation "ch.qos.logback:logback-core" - testImplementation "io.projectreactor:reactor-test" - testImplementation "jakarta.servlet:jakarta.servlet-api" - testImplementation "org.assertj:assertj-core" - testImplementation "org.hamcrest:hamcrest" - testImplementation "org.junit.jupiter:junit-jupiter-engine" - testImplementation "org.junit.jupiter:junit-jupiter-params" - testImplementation "org.mockito:mockito-core" - testImplementation "org.mockito:mockito-junit-jupiter" - testImplementation "org.springframework.security:spring-security-config" - testImplementation "org.springframework.security:spring-security-web" - testImplementation "org.springframework:spring-test" - testImplementation "org.springframework:spring-web" - testImplementation "org.springframework:spring-webflux" - testRuntimeOnly "org.junit.platform:junit-platform-launcher" -} diff --git a/src/integration-test/java/org/springframework/session/data/mongo/AbstractClassLoaderTest.java b/src/integrationTest/java/org/springframework/session/data/mongo/AbstractClassLoaderTest.java similarity index 100% rename from src/integration-test/java/org/springframework/session/data/mongo/AbstractClassLoaderTest.java rename to src/integrationTest/java/org/springframework/session/data/mongo/AbstractClassLoaderTest.java diff --git a/src/integration-test/java/org/springframework/session/data/mongo/AbstractITest.java b/src/integrationTest/java/org/springframework/session/data/mongo/AbstractITest.java similarity index 100% rename from src/integration-test/java/org/springframework/session/data/mongo/AbstractITest.java rename to src/integrationTest/java/org/springframework/session/data/mongo/AbstractITest.java diff --git a/src/integration-test/java/org/springframework/session/data/mongo/AbstractMongoRepositoryITest.java b/src/integrationTest/java/org/springframework/session/data/mongo/AbstractMongoRepositoryITest.java similarity index 100% rename from src/integration-test/java/org/springframework/session/data/mongo/AbstractMongoRepositoryITest.java rename to src/integrationTest/java/org/springframework/session/data/mongo/AbstractMongoRepositoryITest.java diff --git a/src/integration-test/java/org/springframework/session/data/mongo/MongoDbDeleteJacksonSessionVerificationTest.java b/src/integrationTest/java/org/springframework/session/data/mongo/MongoDbDeleteJacksonSessionVerificationTest.java similarity index 100% rename from src/integration-test/java/org/springframework/session/data/mongo/MongoDbDeleteJacksonSessionVerificationTest.java rename to src/integrationTest/java/org/springframework/session/data/mongo/MongoDbDeleteJacksonSessionVerificationTest.java diff --git a/src/integration-test/java/org/springframework/session/data/mongo/MongoDbLogoutVerificationTest.java b/src/integrationTest/java/org/springframework/session/data/mongo/MongoDbLogoutVerificationTest.java similarity index 100% rename from src/integration-test/java/org/springframework/session/data/mongo/MongoDbLogoutVerificationTest.java rename to src/integrationTest/java/org/springframework/session/data/mongo/MongoDbLogoutVerificationTest.java diff --git a/src/integration-test/java/org/springframework/session/data/mongo/MongoRepositoryJacksonITest.java b/src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJacksonITest.java similarity index 100% rename from src/integration-test/java/org/springframework/session/data/mongo/MongoRepositoryJacksonITest.java rename to src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJacksonITest.java diff --git a/src/integration-test/java/org/springframework/session/data/mongo/MongoRepositoryJdkSerializationITest.java b/src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJdkSerializationITest.java similarity index 100% rename from src/integration-test/java/org/springframework/session/data/mongo/MongoRepositoryJdkSerializationITest.java rename to src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJdkSerializationITest.java From a711643b057a1f07f5590cad30d123b7ccadf1b0 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Wed, 19 Nov 2025 12:23:54 +0000 Subject: [PATCH 2/7] Spotless auto cleanup --- .git-blame-ignore-revs | 1 + .../data/mongo/AbstractClassLoaderTest.java | 59 +- .../session/data/mongo/AbstractITest.java | 38 +- .../mongo/AbstractMongoRepositoryITest.java | 506 +++++++------ ...bDeleteJacksonSessionVerificationTest.java | 318 ++++---- .../mongo/MongoDbLogoutVerificationTest.java | 314 ++++---- .../mongo/MongoRepositoryJacksonITest.java | 53 +- .../MongoRepositoryJdkSerializationITest.java | 83 +- .../mongo/AbstractMongoSessionConverter.java | 130 ++-- .../mongo/JacksonMongoSessionConverter.java | 199 +++-- .../data/mongo/JdkMongoSessionConverter.java | 182 +++-- .../mongo/MongoIndexedSessionRepository.java | 342 ++++----- .../session/data/mongo/MongoSession.java | 426 +++++------ .../session/data/mongo/MongoSessionUtils.java | 26 +- .../mongo/ReactiveMongoSessionRepository.java | 370 +++++---- .../web/http/EnableMongoHttpSession.java | 34 +- .../http/MongoHttpSessionConfiguration.java | 174 ++--- .../web/reactive/EnableMongoWebSession.java | 36 +- .../ReactiveMongoWebSessionConfiguration.java | 205 +++-- .../AbstractMongoSessionConverterTests.java | 168 ++--- .../JacksonMongoSessionConverterTests.java | 139 ++-- .../mongo/JdkMongoSessionConverterTests.java | 44 +- .../MongoIndexedSessionRepositoryTests.java | 386 +++++----- .../session/data/mongo/MongoSessionTests.java | 23 +- .../ReactiveMongoSessionRepositoryTests.java | 429 ++++++----- .../MongoHttpSessionConfigurationTests.java | 561 +++++++------- ...tiveMongoWebSessionConfigurationTests.java | 709 +++++++++--------- 27 files changed, 2951 insertions(+), 3004 deletions(-) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..a3ad511 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1 @@ +# .git-blame-ignore-revs diff --git a/src/integrationTest/java/org/springframework/session/data/mongo/AbstractClassLoaderTest.java b/src/integrationTest/java/org/springframework/session/data/mongo/AbstractClassLoaderTest.java index 59cb467..c24ec2f 100644 --- a/src/integrationTest/java/org/springframework/session/data/mongo/AbstractClassLoaderTest.java +++ b/src/integrationTest/java/org/springframework/session/data/mongo/AbstractClassLoaderTest.java @@ -1,11 +1,12 @@ /* + * Copyright 2025-present MongoDB, Inc. * Copyright 2014-present the original author or authors. * * 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 * - * https://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -17,10 +18,8 @@ package org.springframework.session.data.mongo; import java.lang.reflect.Field; - import org.assertj.core.api.AssertionsForClassTypes; import org.junit.jupiter.api.Test; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.core.serializer.DefaultDeserializer; @@ -28,46 +27,44 @@ import org.springframework.util.ReflectionUtils; /** - * Verify container's {@link ClassLoader} is injected into session converter (reactive and - * traditional). + * Verify container's {@link ClassLoader} is injected into session converter (reactive and traditional). * * @author Greg Turnquist */ public abstract class AbstractClassLoaderTest extends AbstractITest { - @Autowired - T sessionRepository; - - @Autowired - ApplicationContext applicationContext; + @Autowired + T sessionRepository; - @Test - void verifyContainerClassLoaderLoadedIntoConverter() { + @Autowired + ApplicationContext applicationContext; - Field mongoSessionConverterField = ReflectionUtils.findField(this.sessionRepository.getClass(), - "mongoSessionConverter"); - ReflectionUtils.makeAccessible(mongoSessionConverterField); - AbstractMongoSessionConverter sessionConverter = (AbstractMongoSessionConverter) ReflectionUtils - .getField(mongoSessionConverterField, this.sessionRepository); + @Test + void verifyContainerClassLoaderLoadedIntoConverter() { - AssertionsForClassTypes.assertThat(sessionConverter).isInstanceOf(JdkMongoSessionConverter.class); + Field mongoSessionConverterField = + ReflectionUtils.findField(this.sessionRepository.getClass(), "mongoSessionConverter"); + ReflectionUtils.makeAccessible(mongoSessionConverterField); + AbstractMongoSessionConverter sessionConverter = (AbstractMongoSessionConverter) + ReflectionUtils.getField(mongoSessionConverterField, this.sessionRepository); - JdkMongoSessionConverter jdkMongoSessionConverter = (JdkMongoSessionConverter) sessionConverter; + AssertionsForClassTypes.assertThat(sessionConverter).isInstanceOf(JdkMongoSessionConverter.class); - DeserializingConverter deserializingConverter = (DeserializingConverter) extractField( - JdkMongoSessionConverter.class, "deserializer", jdkMongoSessionConverter); - DefaultDeserializer deserializer = (DefaultDeserializer) extractField(DeserializingConverter.class, - "deserializer", deserializingConverter); - ClassLoader classLoader = (ClassLoader) extractField(DefaultDeserializer.class, "classLoader", deserializer); + JdkMongoSessionConverter jdkMongoSessionConverter = (JdkMongoSessionConverter) sessionConverter; - AssertionsForClassTypes.assertThat(classLoader).isEqualTo(this.applicationContext.getClassLoader()); - } + DeserializingConverter deserializingConverter = (DeserializingConverter) + extractField(JdkMongoSessionConverter.class, "deserializer", jdkMongoSessionConverter); + DefaultDeserializer deserializer = (DefaultDeserializer) + extractField(DeserializingConverter.class, "deserializer", deserializingConverter); + ClassLoader classLoader = (ClassLoader) extractField(DefaultDeserializer.class, "classLoader", deserializer); - private static Object extractField(Class clazz, String fieldName, Object obj) { + AssertionsForClassTypes.assertThat(classLoader).isEqualTo(this.applicationContext.getClassLoader()); + } - Field field = ReflectionUtils.findField(clazz, fieldName); - ReflectionUtils.makeAccessible(field); - return ReflectionUtils.getField(field, obj); - } + private static Object extractField(Class clazz, String fieldName, Object obj) { + Field field = ReflectionUtils.findField(clazz, fieldName); + ReflectionUtils.makeAccessible(field); + return ReflectionUtils.getField(field, obj); + } } diff --git a/src/integrationTest/java/org/springframework/session/data/mongo/AbstractITest.java b/src/integrationTest/java/org/springframework/session/data/mongo/AbstractITest.java index 592fa81..6fe5bcd 100644 --- a/src/integrationTest/java/org/springframework/session/data/mongo/AbstractITest.java +++ b/src/integrationTest/java/org/springframework/session/data/mongo/AbstractITest.java @@ -1,11 +1,12 @@ /* + * Copyright 2025-present MongoDB, Inc. * Copyright 2014-present the original author or authors. * * 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 * - * https://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -17,10 +18,8 @@ package org.springframework.session.data.mongo; import java.util.UUID; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; - import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; @@ -37,27 +36,26 @@ @WebAppConfiguration public abstract class AbstractITest { - protected SecurityContext context; - - protected SecurityContext changedContext; + protected SecurityContext context; - // @Autowired(required = false) - // protected SessionEventRegistry registry; + protected SecurityContext changedContext; - @BeforeEach - void setup() { + // @Autowired(required = false) + // protected SessionEventRegistry registry; - // if (this.registry != null) { - // this.registry.clear(); - // } + @BeforeEach + void setup() { - this.context = SecurityContextHolder.createEmptyContext(); - this.context.setAuthentication(new UsernamePasswordAuthenticationToken("username-" + UUID.randomUUID(), "na", - AuthorityUtils.createAuthorityList("ROLE_USER"))); + // if (this.registry != null) { + // this.registry.clear(); + // } - this.changedContext = SecurityContextHolder.createEmptyContext(); - this.changedContext.setAuthentication(new UsernamePasswordAuthenticationToken( - "changedContext-" + UUID.randomUUID(), "na", AuthorityUtils.createAuthorityList("ROLE_USER"))); - } + this.context = SecurityContextHolder.createEmptyContext(); + this.context.setAuthentication(new UsernamePasswordAuthenticationToken( + "username-" + UUID.randomUUID(), "na", AuthorityUtils.createAuthorityList("ROLE_USER"))); + this.changedContext = SecurityContextHolder.createEmptyContext(); + this.changedContext.setAuthentication(new UsernamePasswordAuthenticationToken( + "changedContext-" + UUID.randomUUID(), "na", AuthorityUtils.createAuthorityList("ROLE_USER"))); + } } diff --git a/src/integrationTest/java/org/springframework/session/data/mongo/AbstractMongoRepositoryITest.java b/src/integrationTest/java/org/springframework/session/data/mongo/AbstractMongoRepositoryITest.java index 7efae90..8f887e7 100644 --- a/src/integrationTest/java/org/springframework/session/data/mongo/AbstractMongoRepositoryITest.java +++ b/src/integrationTest/java/org/springframework/session/data/mongo/AbstractMongoRepositoryITest.java @@ -1,11 +1,12 @@ /* + * Copyright 2025-present MongoDB, Inc. * Copyright 2014-present the original author or authors. * * 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 * - * https://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,16 +17,15 @@ package org.springframework.session.data.mongo; +import static org.assertj.core.api.Assertions.assertThat; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; import java.time.Duration; import java.time.Instant; import java.util.Map; import java.util.UUID; - -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; import org.junit.jupiter.api.Test; -import org.testcontainers.containers.MongoDBContainer; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.data.mongodb.core.MongoOperations; @@ -37,8 +37,7 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.Session; - -import static org.assertj.core.api.Assertions.assertThat; +import org.testcontainers.containers.MongoDBContainer; /** * Abstract base class for {@link MongoIndexedSessionRepository} tests. @@ -49,361 +48,360 @@ */ public abstract class AbstractMongoRepositoryITest extends AbstractITest { - protected static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; - - protected static final String INDEX_NAME = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME; - - @Autowired - protected MongoIndexedSessionRepository repository; + protected static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; - @Test - void saves() { + protected static final String INDEX_NAME = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME; - String username = "saves-" + System.currentTimeMillis(); + @Autowired + protected MongoIndexedSessionRepository repository; - MongoSession toSave = this.repository.createSession(); - String expectedAttributeName = "a"; - String expectedAttributeValue = "b"; - toSave.setAttribute(expectedAttributeName, expectedAttributeValue); - Authentication toSaveToken = new UsernamePasswordAuthenticationToken(username, "password", - AuthorityUtils.createAuthorityList("ROLE_USER")); - SecurityContext toSaveContext = SecurityContextHolder.createEmptyContext(); - toSaveContext.setAuthentication(toSaveToken); - toSave.setAttribute(SPRING_SECURITY_CONTEXT, toSaveContext); - toSave.setAttribute(INDEX_NAME, username); + @Test + void saves() { - this.repository.save(toSave); + String username = "saves-" + System.currentTimeMillis(); - Session session = this.repository.findById(toSave.getId()); + MongoSession toSave = this.repository.createSession(); + String expectedAttributeName = "a"; + String expectedAttributeValue = "b"; + toSave.setAttribute(expectedAttributeName, expectedAttributeValue); + Authentication toSaveToken = new UsernamePasswordAuthenticationToken( + username, "password", AuthorityUtils.createAuthorityList("ROLE_USER")); + SecurityContext toSaveContext = SecurityContextHolder.createEmptyContext(); + toSaveContext.setAuthentication(toSaveToken); + toSave.setAttribute(SPRING_SECURITY_CONTEXT, toSaveContext); + toSave.setAttribute(INDEX_NAME, username); - assertThat(session.getId()).isEqualTo(toSave.getId()); - assertThat(session.getAttributeNames()).isEqualTo(toSave.getAttributeNames()); - assertThat(session.getAttribute(expectedAttributeName)) - .isEqualTo(toSave.getAttribute(expectedAttributeName)); + this.repository.save(toSave); - this.repository.deleteById(toSave.getId()); + Session session = this.repository.findById(toSave.getId()); - String id = toSave.getId(); - assertThat(this.repository.findById(id)).isNull(); - } + assertThat(session.getId()).isEqualTo(toSave.getId()); + assertThat(session.getAttributeNames()).isEqualTo(toSave.getAttributeNames()); + assertThat(session.getAttribute(expectedAttributeName)) + .isEqualTo(toSave.getAttribute(expectedAttributeName)); - @Test - void putAllOnSingleAttrDoesNotRemoveOld() { + this.repository.deleteById(toSave.getId()); - MongoSession toSave = this.repository.createSession(); - toSave.setAttribute("a", "b"); + String id = toSave.getId(); + assertThat(this.repository.findById(id)).isNull(); + } - this.repository.save(toSave); - toSave = this.repository.findById(toSave.getId()); + @Test + void putAllOnSingleAttrDoesNotRemoveOld() { - toSave.setAttribute("1", "2"); + MongoSession toSave = this.repository.createSession(); + toSave.setAttribute("a", "b"); - this.repository.save(toSave); - toSave = this.repository.findById(toSave.getId()); + this.repository.save(toSave); + toSave = this.repository.findById(toSave.getId()); - Session session = this.repository.findById(toSave.getId()); - assertThat(session.getAttributeNames().size()).isEqualTo(2); - assertThat(session.getAttribute("a")).isEqualTo("b"); - assertThat(session.getAttribute("1")).isEqualTo("2"); + toSave.setAttribute("1", "2"); - this.repository.deleteById(toSave.getId()); - } + this.repository.save(toSave); + toSave = this.repository.findById(toSave.getId()); - @Test - void findByPrincipalName() throws Exception { + Session session = this.repository.findById(toSave.getId()); + assertThat(session.getAttributeNames().size()).isEqualTo(2); + assertThat(session.getAttribute("a")).isEqualTo("b"); + assertThat(session.getAttribute("1")).isEqualTo("2"); - String principalName = "findByPrincipalName" + UUID.randomUUID(); - MongoSession toSave = this.repository.createSession(); - toSave.setAttribute(INDEX_NAME, principalName); + this.repository.deleteById(toSave.getId()); + } - this.repository.save(toSave); + @Test + void findByPrincipalName() throws Exception { - Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, - principalName); + String principalName = "findByPrincipalName" + UUID.randomUUID(); + MongoSession toSave = this.repository.createSession(); + toSave.setAttribute(INDEX_NAME, principalName); - assertThat(findByPrincipalName).hasSize(1); - assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); + this.repository.save(toSave); - this.repository.deleteById(toSave.getId()); + Map findByPrincipalName = + this.repository.findByIndexNameAndIndexValue(INDEX_NAME, principalName); - findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, principalName); + assertThat(findByPrincipalName).hasSize(1); + assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); - assertThat(findByPrincipalName).hasSize(0); - assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId()); - } + this.repository.deleteById(toSave.getId()); - @Test - void nonExistentSessionShouldNotBreakMongo() { - this.repository.deleteById("doesn't exist"); - } + findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, principalName); - @Test - void findByPrincipalNameNoPrincipalNameChange() throws Exception { + assertThat(findByPrincipalName).hasSize(0); + assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId()); + } - String principalName = "findByPrincipalNameNoPrincipalNameChange" + UUID.randomUUID(); - MongoSession toSave = this.repository.createSession(); - toSave.setAttribute(INDEX_NAME, principalName); + @Test + void nonExistentSessionShouldNotBreakMongo() { + this.repository.deleteById("doesn't exist"); + } - this.repository.save(toSave); + @Test + void findByPrincipalNameNoPrincipalNameChange() throws Exception { - toSave.setAttribute("other", "value"); - this.repository.save(toSave); + String principalName = "findByPrincipalNameNoPrincipalNameChange" + UUID.randomUUID(); + MongoSession toSave = this.repository.createSession(); + toSave.setAttribute(INDEX_NAME, principalName); - Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, - principalName); + this.repository.save(toSave); - assertThat(findByPrincipalName).hasSize(1); - assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); - } + toSave.setAttribute("other", "value"); + this.repository.save(toSave); - @Test - void findByPrincipalNameNoPrincipalNameChangeReload() throws Exception { + Map findByPrincipalName = + this.repository.findByIndexNameAndIndexValue(INDEX_NAME, principalName); - String principalName = "findByPrincipalNameNoPrincipalNameChangeReload" + UUID.randomUUID(); - MongoSession toSave = this.repository.createSession(); - toSave.setAttribute(INDEX_NAME, principalName); + assertThat(findByPrincipalName).hasSize(1); + assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); + } - this.repository.save(toSave); + @Test + void findByPrincipalNameNoPrincipalNameChangeReload() throws Exception { - toSave = this.repository.findById(toSave.getId()); + String principalName = "findByPrincipalNameNoPrincipalNameChangeReload" + UUID.randomUUID(); + MongoSession toSave = this.repository.createSession(); + toSave.setAttribute(INDEX_NAME, principalName); - toSave.setAttribute("other", "value"); - this.repository.save(toSave); + this.repository.save(toSave); - Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, - principalName); + toSave = this.repository.findById(toSave.getId()); - assertThat(findByPrincipalName).hasSize(1); - assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); - } + toSave.setAttribute("other", "value"); + this.repository.save(toSave); - @Test - void findByDeletedPrincipalName() throws Exception { + Map findByPrincipalName = + this.repository.findByIndexNameAndIndexValue(INDEX_NAME, principalName); - String principalName = "findByDeletedPrincipalName" + UUID.randomUUID(); - MongoSession toSave = this.repository.createSession(); - toSave.setAttribute(INDEX_NAME, principalName); + assertThat(findByPrincipalName).hasSize(1); + assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); + } - this.repository.save(toSave); + @Test + void findByDeletedPrincipalName() throws Exception { - toSave.setAttribute(INDEX_NAME, null); - this.repository.save(toSave); + String principalName = "findByDeletedPrincipalName" + UUID.randomUUID(); + MongoSession toSave = this.repository.createSession(); + toSave.setAttribute(INDEX_NAME, principalName); - Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, - principalName); + this.repository.save(toSave); - assertThat(findByPrincipalName).isEmpty(); - } + toSave.setAttribute(INDEX_NAME, null); + this.repository.save(toSave); - @Test - void findByChangedPrincipalName() throws Exception { + Map findByPrincipalName = + this.repository.findByIndexNameAndIndexValue(INDEX_NAME, principalName); - String principalName = "findByChangedPrincipalName" + UUID.randomUUID(); - String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID(); - MongoSession toSave = this.repository.createSession(); - toSave.setAttribute(INDEX_NAME, principalName); + assertThat(findByPrincipalName).isEmpty(); + } - this.repository.save(toSave); + @Test + void findByChangedPrincipalName() throws Exception { - toSave.setAttribute(INDEX_NAME, principalNameChanged); - this.repository.save(toSave); + String principalName = "findByChangedPrincipalName" + UUID.randomUUID(); + String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID(); + MongoSession toSave = this.repository.createSession(); + toSave.setAttribute(INDEX_NAME, principalName); - Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, - principalName); - assertThat(findByPrincipalName).isEmpty(); + this.repository.save(toSave); - findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, principalNameChanged); + toSave.setAttribute(INDEX_NAME, principalNameChanged); + this.repository.save(toSave); - assertThat(findByPrincipalName).hasSize(1); - assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); - } + Map findByPrincipalName = + this.repository.findByIndexNameAndIndexValue(INDEX_NAME, principalName); + assertThat(findByPrincipalName).isEmpty(); - @Test - void findByDeletedPrincipalNameReload() throws Exception { + findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, principalNameChanged); - String principalName = "findByDeletedPrincipalName" + UUID.randomUUID(); - MongoSession toSave = this.repository.createSession(); - toSave.setAttribute(INDEX_NAME, principalName); + assertThat(findByPrincipalName).hasSize(1); + assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); + } - this.repository.save(toSave); + @Test + void findByDeletedPrincipalNameReload() throws Exception { - MongoSession getSession = this.repository.findById(toSave.getId()); - getSession.setAttribute(INDEX_NAME, null); - this.repository.save(getSession); + String principalName = "findByDeletedPrincipalName" + UUID.randomUUID(); + MongoSession toSave = this.repository.createSession(); + toSave.setAttribute(INDEX_NAME, principalName); - Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, - principalName); + this.repository.save(toSave); - assertThat(findByPrincipalName).isEmpty(); - } + MongoSession getSession = this.repository.findById(toSave.getId()); + getSession.setAttribute(INDEX_NAME, null); + this.repository.save(getSession); - @Test - void findByChangedPrincipalNameReload() throws Exception { + Map findByPrincipalName = + this.repository.findByIndexNameAndIndexValue(INDEX_NAME, principalName); - String principalName = "findByChangedPrincipalName" + UUID.randomUUID(); - String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID(); - MongoSession toSave = this.repository.createSession(); - toSave.setAttribute(INDEX_NAME, principalName); + assertThat(findByPrincipalName).isEmpty(); + } - this.repository.save(toSave); + @Test + void findByChangedPrincipalNameReload() throws Exception { - MongoSession getSession = this.repository.findById(toSave.getId()); + String principalName = "findByChangedPrincipalName" + UUID.randomUUID(); + String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID(); + MongoSession toSave = this.repository.createSession(); + toSave.setAttribute(INDEX_NAME, principalName); - getSession.setAttribute(INDEX_NAME, principalNameChanged); - this.repository.save(getSession); + this.repository.save(toSave); - Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, - principalName); - assertThat(findByPrincipalName).isEmpty(); + MongoSession getSession = this.repository.findById(toSave.getId()); - findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, principalNameChanged); + getSession.setAttribute(INDEX_NAME, principalNameChanged); + this.repository.save(getSession); - assertThat(findByPrincipalName).hasSize(1); - assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); - } + Map findByPrincipalName = + this.repository.findByIndexNameAndIndexValue(INDEX_NAME, principalName); + assertThat(findByPrincipalName).isEmpty(); - @Test - void findBySecurityPrincipalName() throws Exception { + findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, principalNameChanged); - MongoSession toSave = this.repository.createSession(); - toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); + assertThat(findByPrincipalName).hasSize(1); + assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); + } - this.repository.save(toSave); + @Test + void findBySecurityPrincipalName() throws Exception { - Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, - getSecurityName()); + MongoSession toSave = this.repository.createSession(); + toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); - assertThat(findByPrincipalName).hasSize(1); - assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); + this.repository.save(toSave); - this.repository.deleteById(toSave.getId()); + Map findByPrincipalName = + this.repository.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName()); - findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName()); + assertThat(findByPrincipalName).hasSize(1); + assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); - assertThat(findByPrincipalName).hasSize(0); - assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId()); - } + this.repository.deleteById(toSave.getId()); - @Test - void findByPrincipalNameNoSecurityPrincipalNameChange() throws Exception { + findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName()); - MongoSession toSave = this.repository.createSession(); - toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); + assertThat(findByPrincipalName).hasSize(0); + assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId()); + } - this.repository.save(toSave); + @Test + void findByPrincipalNameNoSecurityPrincipalNameChange() throws Exception { - toSave.setAttribute("other", "value"); - this.repository.save(toSave); + MongoSession toSave = this.repository.createSession(); + toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); - Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, - getSecurityName()); + this.repository.save(toSave); - assertThat(findByPrincipalName).hasSize(1); - assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); - } + toSave.setAttribute("other", "value"); + this.repository.save(toSave); - @Test - void findByDeletedSecurityPrincipalName() throws Exception { + Map findByPrincipalName = + this.repository.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName()); - MongoSession toSave = this.repository.createSession(); - toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); + assertThat(findByPrincipalName).hasSize(1); + assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); + } - this.repository.save(toSave); + @Test + void findByDeletedSecurityPrincipalName() throws Exception { - toSave.setAttribute(SPRING_SECURITY_CONTEXT, null); - this.repository.save(toSave); + MongoSession toSave = this.repository.createSession(); + toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); - Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, - getSecurityName()); + this.repository.save(toSave); - assertThat(findByPrincipalName).isEmpty(); - } + toSave.setAttribute(SPRING_SECURITY_CONTEXT, null); + this.repository.save(toSave); - @Test - void findByChangedSecurityPrincipalName() throws Exception { + Map findByPrincipalName = + this.repository.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName()); - MongoSession toSave = this.repository.createSession(); - toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); + assertThat(findByPrincipalName).isEmpty(); + } - this.repository.save(toSave); + @Test + void findByChangedSecurityPrincipalName() throws Exception { - toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.changedContext); - this.repository.save(toSave); + MongoSession toSave = this.repository.createSession(); + toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); - Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, - getSecurityName()); - assertThat(findByPrincipalName).isEmpty(); + this.repository.save(toSave); - findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, getChangedSecurityName()); + toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.changedContext); + this.repository.save(toSave); - assertThat(findByPrincipalName).hasSize(1); - assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); - } + Map findByPrincipalName = + this.repository.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName()); + assertThat(findByPrincipalName).isEmpty(); - @Test - void findByChangedSecurityPrincipalNameReload() throws Exception { + findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, getChangedSecurityName()); - MongoSession toSave = this.repository.createSession(); - toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); + assertThat(findByPrincipalName).hasSize(1); + assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); + } - this.repository.save(toSave); + @Test + void findByChangedSecurityPrincipalNameReload() throws Exception { - MongoSession getSession = this.repository.findById(toSave.getId()); + MongoSession toSave = this.repository.createSession(); + toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); - getSession.setAttribute(SPRING_SECURITY_CONTEXT, this.changedContext); - this.repository.save(getSession); + this.repository.save(toSave); - Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, - getSecurityName()); - assertThat(findByPrincipalName).isEmpty(); + MongoSession getSession = this.repository.findById(toSave.getId()); - findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, getChangedSecurityName()); + getSession.setAttribute(SPRING_SECURITY_CONTEXT, this.changedContext); + this.repository.save(getSession); - assertThat(findByPrincipalName).hasSize(1); - assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); - } + Map findByPrincipalName = + this.repository.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName()); + assertThat(findByPrincipalName).isEmpty(); - @Test - void loadExpiredSession() throws Exception { + findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, getChangedSecurityName()); - // given - MongoSession expiredSession = this.repository.createSession(); - Instant thirtyOneMinutesAgo = Instant.ofEpochMilli(System.currentTimeMillis()).minus(Duration.ofMinutes(31)); - expiredSession.setLastAccessedTime(thirtyOneMinutesAgo); - this.repository.save(expiredSession); + assertThat(findByPrincipalName).hasSize(1); + assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); + } - // then - MongoSession expiredSessionFromDb = this.repository.findById(expiredSession.getId()); - assertThat(expiredSessionFromDb).isNull(); - } + @Test + void loadExpiredSession() throws Exception { - protected String getSecurityName() { - return this.context.getAuthentication().getName(); - } + // given + MongoSession expiredSession = this.repository.createSession(); + Instant thirtyOneMinutesAgo = + Instant.ofEpochMilli(System.currentTimeMillis()).minus(Duration.ofMinutes(31)); + expiredSession.setLastAccessedTime(thirtyOneMinutesAgo); + this.repository.save(expiredSession); - protected String getChangedSecurityName() { - return this.changedContext.getAuthentication().getName(); - } + // then + MongoSession expiredSessionFromDb = this.repository.findById(expiredSession.getId()); + assertThat(expiredSessionFromDb).isNull(); + } - protected static class BaseConfig { + protected String getSecurityName() { + return this.context.getAuthentication().getName(); + } - private static final String DOCKER_IMAGE = "mongo:5.0.11"; + protected String getChangedSecurityName() { + return this.changedContext.getAuthentication().getName(); + } - @Bean - public MongoDBContainer mongoDbContainer() { - MongoDBContainer mongoDbContainer = new MongoDBContainer(DOCKER_IMAGE); - mongoDbContainer.start(); - return mongoDbContainer; - } + protected static class BaseConfig { - @Bean - public MongoOperations mongoOperations(MongoDBContainer mongoContainer) { + private static final String DOCKER_IMAGE = "mongo:5.0.11"; - MongoClient mongo = MongoClients - .create("mongodb://" + mongoContainer.getHost() + ":" + mongoContainer.getFirstMappedPort()); - return new MongoTemplate(mongo, "test"); - } + @Bean + public MongoDBContainer mongoDbContainer() { + MongoDBContainer mongoDbContainer = new MongoDBContainer(DOCKER_IMAGE); + mongoDbContainer.start(); + return mongoDbContainer; + } - } + @Bean + public MongoOperations mongoOperations(MongoDBContainer mongoContainer) { + MongoClient mongo = MongoClients.create( + "mongodb://" + mongoContainer.getHost() + ":" + mongoContainer.getFirstMappedPort()); + return new MongoTemplate(mongo, "test"); + } + } } diff --git a/src/integrationTest/java/org/springframework/session/data/mongo/MongoDbDeleteJacksonSessionVerificationTest.java b/src/integrationTest/java/org/springframework/session/data/mongo/MongoDbDeleteJacksonSessionVerificationTest.java index e8af0a9..d0126a7 100644 --- a/src/integrationTest/java/org/springframework/session/data/mongo/MongoDbDeleteJacksonSessionVerificationTest.java +++ b/src/integrationTest/java/org/springframework/session/data/mongo/MongoDbDeleteJacksonSessionVerificationTest.java @@ -1,11 +1,12 @@ /* + * Copyright 2025-present MongoDB, Inc. * Copyright 2014-present the original author or authors. * * 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 * - * https://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,17 +17,13 @@ package org.springframework.session.data.mongo; -import java.net.URI; - import com.mongodb.reactivestreams.client.MongoClient; import com.mongodb.reactivestreams.client.MongoClients; +import java.net.URI; import org.assertj.core.api.AssertionsForClassTypes; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.testcontainers.containers.MongoDBContainer; -import reactor.test.StepVerifier; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -51,156 +48,171 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.function.BodyInserters; +import org.testcontainers.containers.MongoDBContainer; +import reactor.test.StepVerifier; -/** - * @author Boris Finkelshteyn - */ +/** @author Boris Finkelshteyn */ @ExtendWith(SpringExtension.class) @ContextConfiguration class MongoDbDeleteJacksonSessionVerificationTest { - @Autowired - ApplicationContext ctx; - - WebTestClient client; - - @BeforeEach - void setUp() { - this.client = WebTestClient.bindToApplicationContext(this.ctx).build(); - } - - @Test - void logoutShouldDeleteOldSessionFromMongoDB() { - - // 1. Login and capture the SESSION cookie value. - // @formatter:off - FluxExchangeResult loginResult = this.client.post().uri("/login") - .contentType(MediaType.APPLICATION_FORM_URLENCODED) - .body(BodyInserters - .fromFormData("username", "admin") - .with("password", "password")) - .exchange() - .returnResult(String.class); - // @formatter:on - - AssertionsForClassTypes.assertThat(loginResult.getResponseHeaders().getLocation()).isEqualTo(URI.create("/")); - - String originalSessionId = loginResult.getResponseCookies().getFirst("SESSION").getValue(); - - // 2. Fetch a protected resource using the SESSION cookie. - // @formatter:off - this.client.get().uri("/hello") - .cookie("SESSION", originalSessionId) - .exchange() - .expectStatus().isOk() - .returnResult(String.class).getResponseBody() - .as(StepVerifier::create) - .expectNext("HelloWorld") - .verifyComplete(); - // @formatter:on - - // 3. Logout using the SESSION cookie, and capture the new SESSION cookie. - // @formatter:off - String newSessionId = this.client.post().uri("/logout") - .cookie("SESSION", originalSessionId) - .exchange() - .expectStatus().isFound() - .returnResult(String.class).getResponseCookies().getFirst("SESSION").getValue(); - // @formatter:on - - AssertionsForClassTypes.assertThat(newSessionId).isNotEqualTo(originalSessionId); - - // 4. Verify the new SESSION cookie is not yet authorized. - // @formatter:off - this.client.get().uri("/hello") - .cookie("SESSION", newSessionId) - .exchange() - .expectStatus().isFound() - .expectHeader() - .value(HttpHeaders.LOCATION, (value) -> AssertionsForClassTypes.assertThat(value).isEqualTo("/login")); - // @formatter:on - - // 5. Verify the original SESSION cookie no longer works. - // @formatter:off - this.client.get().uri("/hello") - .cookie("SESSION", originalSessionId) - .exchange() - .expectStatus().isFound() - .expectHeader() - .value(HttpHeaders.LOCATION, (value) -> AssertionsForClassTypes.assertThat(value).isEqualTo("/login")); - // @formatter:on - } - - @RestController - static class TestController { - - @GetMapping("/hello") - ResponseEntity hello() { - return ResponseEntity.ok("HelloWorld"); - } - - } - - @Configuration(proxyBeanMethods = false) - @EnableWebFluxSecurity - static class SecurityConfig { - - @Bean - SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { - // @formatter:off - return http - .logout(Customizer.withDefaults()) - .formLogin(Customizer.withDefaults()) - .csrf((csrf) -> csrf.disable()) - .authorizeExchange((ae) -> ae.anyExchange().authenticated()) - .build(); - // @formatter:on - } - - @Bean - MapReactiveUserDetailsService userDetailsService() { - // @formatter:off - return new MapReactiveUserDetailsService(User.withUsername("admin") - .password("{noop}password") - .roles("USER,ADMIN") - .build()); - // @formatter:on - } - - @Bean - AbstractMongoSessionConverter mongoSessionConverter() { - return new JacksonMongoSessionConverter(); - } - - } - - @Configuration - @EnableWebFlux - @EnableMongoWebSession - static class Config { - - private static final String DOCKER_IMAGE = "mongo:5.0.11"; - - @Bean - MongoDBContainer mongoDbContainer() { - MongoDBContainer mongoDbContainer = new MongoDBContainer(DOCKER_IMAGE); - mongoDbContainer.start(); - return mongoDbContainer; - } - - @Bean - ReactiveMongoOperations mongoOperations(MongoDBContainer mongoContainer) { - - MongoClient mongo = MongoClients - .create("mongodb://" + mongoContainer.getHost() + ":" + mongoContainer.getFirstMappedPort()); - return new ReactiveMongoTemplate(mongo, "DB_Name_DeleteJacksonSessionVerificationTest"); - } - - @Bean - TestController controller() { - return new TestController(); - } - - } - + @Autowired + ApplicationContext ctx; + + WebTestClient client; + + @BeforeEach + void setUp() { + this.client = WebTestClient.bindToApplicationContext(this.ctx).build(); + } + + @Test + void logoutShouldDeleteOldSessionFromMongoDB() { + + // 1. Login and capture the SESSION cookie value. + // @formatter:off + FluxExchangeResult loginResult = this.client + .post() + .uri("/login") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(BodyInserters.fromFormData("username", "admin").with("password", "password")) + .exchange() + .returnResult(String.class); + // @formatter:on + + AssertionsForClassTypes.assertThat(loginResult.getResponseHeaders().getLocation()) + .isEqualTo(URI.create("/")); + + String originalSessionId = + loginResult.getResponseCookies().getFirst("SESSION").getValue(); + + // 2. Fetch a protected resource using the SESSION cookie. + // @formatter:off + this.client + .get() + .uri("/hello") + .cookie("SESSION", originalSessionId) + .exchange() + .expectStatus() + .isOk() + .returnResult(String.class) + .getResponseBody() + .as(StepVerifier::create) + .expectNext("HelloWorld") + .verifyComplete(); + // @formatter:on + + // 3. Logout using the SESSION cookie, and capture the new SESSION cookie. + // @formatter:off + String newSessionId = this.client + .post() + .uri("/logout") + .cookie("SESSION", originalSessionId) + .exchange() + .expectStatus() + .isFound() + .returnResult(String.class) + .getResponseCookies() + .getFirst("SESSION") + .getValue(); + // @formatter:on + + AssertionsForClassTypes.assertThat(newSessionId).isNotEqualTo(originalSessionId); + + // 4. Verify the new SESSION cookie is not yet authorized. + // @formatter:off + this.client + .get() + .uri("/hello") + .cookie("SESSION", newSessionId) + .exchange() + .expectStatus() + .isFound() + .expectHeader() + .value(HttpHeaders.LOCATION, (value) -> AssertionsForClassTypes.assertThat(value) + .isEqualTo("/login")); + // @formatter:on + + // 5. Verify the original SESSION cookie no longer works. + // @formatter:off + this.client + .get() + .uri("/hello") + .cookie("SESSION", originalSessionId) + .exchange() + .expectStatus() + .isFound() + .expectHeader() + .value(HttpHeaders.LOCATION, (value) -> AssertionsForClassTypes.assertThat(value) + .isEqualTo("/login")); + // @formatter:on + } + + @RestController + static class TestController { + + @GetMapping("/hello") + ResponseEntity hello() { + return ResponseEntity.ok("HelloWorld"); + } + } + + @Configuration(proxyBeanMethods = false) + @EnableWebFluxSecurity + static class SecurityConfig { + + @Bean + SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { + // @formatter:off + return http.logout(Customizer.withDefaults()) + .formLogin(Customizer.withDefaults()) + .csrf((csrf) -> csrf.disable()) + .authorizeExchange((ae) -> ae.anyExchange().authenticated()) + .build(); + // @formatter:on + } + + @Bean + MapReactiveUserDetailsService userDetailsService() { + // @formatter:off + return new MapReactiveUserDetailsService(User.withUsername("admin") + .password("{noop}password") + .roles("USER,ADMIN") + .build()); + // @formatter:on + } + + @Bean + AbstractMongoSessionConverter mongoSessionConverter() { + return new JacksonMongoSessionConverter(); + } + } + + @Configuration + @EnableWebFlux + @EnableMongoWebSession + static class Config { + + private static final String DOCKER_IMAGE = "mongo:5.0.11"; + + @Bean + MongoDBContainer mongoDbContainer() { + MongoDBContainer mongoDbContainer = new MongoDBContainer(DOCKER_IMAGE); + mongoDbContainer.start(); + return mongoDbContainer; + } + + @Bean + ReactiveMongoOperations mongoOperations(MongoDBContainer mongoContainer) { + + MongoClient mongo = MongoClients.create( + "mongodb://" + mongoContainer.getHost() + ":" + mongoContainer.getFirstMappedPort()); + return new ReactiveMongoTemplate(mongo, "DB_Name_DeleteJacksonSessionVerificationTest"); + } + + @Bean + TestController controller() { + return new TestController(); + } + } } diff --git a/src/integrationTest/java/org/springframework/session/data/mongo/MongoDbLogoutVerificationTest.java b/src/integrationTest/java/org/springframework/session/data/mongo/MongoDbLogoutVerificationTest.java index 8ce9bdd..25a04b7 100644 --- a/src/integrationTest/java/org/springframework/session/data/mongo/MongoDbLogoutVerificationTest.java +++ b/src/integrationTest/java/org/springframework/session/data/mongo/MongoDbLogoutVerificationTest.java @@ -1,11 +1,12 @@ /* + * Copyright 2025-present MongoDB, Inc. * Copyright 2014-present the original author or authors. * * 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 * - * https://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,17 +17,13 @@ package org.springframework.session.data.mongo; -import java.net.URI; - import com.mongodb.reactivestreams.client.MongoClient; import com.mongodb.reactivestreams.client.MongoClients; +import java.net.URI; import org.assertj.core.api.AssertionsForClassTypes; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.testcontainers.containers.MongoDBContainer; -import reactor.test.StepVerifier; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -51,159 +48,164 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.function.BodyInserters; +import org.testcontainers.containers.MongoDBContainer; +import reactor.test.StepVerifier; -/** - * @author Greg Turnquist - */ +/** @author Greg Turnquist */ @ExtendWith(SpringExtension.class) @ContextConfiguration class MongoDbLogoutVerificationTest { - @Autowired - ApplicationContext ctx; - - WebTestClient client; - - @BeforeEach - void setUp() { - this.client = WebTestClient.bindToApplicationContext(this.ctx).build(); - } - - @Test - void logoutShouldDeleteOldSessionFromMongoDB() { - - // 1. Login and capture the SESSION cookie value. - - FluxExchangeResult loginResult = this.client.post() - .uri("/login") - .contentType(MediaType.APPLICATION_FORM_URLENCODED) // - .body(BodyInserters // - .fromFormData("username", "admin") // - .with("password", "password")) // - .exchange() // - .returnResult(String.class); - - AssertionsForClassTypes.assertThat(loginResult.getResponseHeaders().getLocation()).isEqualTo(URI.create("/")); - - String originalSessionId = loginResult.getResponseCookies().getFirst("SESSION").getValue(); - - // 2. Fetch a protected resource using the SESSION cookie. - - this.client.get() - .uri("/hello") // - .cookie("SESSION", originalSessionId) // - .exchange() // - .expectStatus() - .isOk() // - .returnResult(String.class) - .getResponseBody() // - .as(StepVerifier::create) // - .expectNext("HelloWorld") // - .verifyComplete(); - - // 3. Logout using the SESSION cookie, and capture the new SESSION cookie. - - String newSessionId = this.client.post() - .uri("/logout") // - .cookie("SESSION", originalSessionId) // - .exchange() // - .expectStatus() - .isFound() // - .returnResult(String.class) - .getResponseCookies() - .getFirst("SESSION") - .getValue(); - - AssertionsForClassTypes.assertThat(newSessionId).isNotEqualTo(originalSessionId); - - // 4. Verify the new SESSION cookie is not yet authorized. - - this.client.get() - .uri("/hello") // - .cookie("SESSION", newSessionId) // - .exchange() // - .expectStatus() - .isFound() // - .expectHeader() - .value(HttpHeaders.LOCATION, (value) -> AssertionsForClassTypes.assertThat(value).isEqualTo("/login")); - - // 5. Verify the original SESSION cookie no longer works. - - this.client.get() - .uri("/hello") // - .cookie("SESSION", originalSessionId) // - .exchange() // - .expectStatus() - .isFound() // - .expectHeader() - .value(HttpHeaders.LOCATION, (value) -> AssertionsForClassTypes.assertThat(value).isEqualTo("/login")); - } - - @RestController - static class TestController { - - @GetMapping("/hello") - ResponseEntity hello() { - return ResponseEntity.ok("HelloWorld"); - } - - } - - @Configuration(proxyBeanMethods = false) - @EnableWebFluxSecurity - static class SecurityConfig { - - @Bean - SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { - // @formatter:off - return http - .logout(Customizer.withDefaults()) - .formLogin(Customizer.withDefaults()) - .csrf((csrf) -> csrf.disable()) - .authorizeExchange((ae) -> ae.anyExchange().authenticated()) - .build(); - // @formatter:on - } - - @Bean - MapReactiveUserDetailsService userDetailsService() { - // @formatter:off - return new MapReactiveUserDetailsService(User.withUsername("admin") - .password("{noop}password") - .roles("USER,ADMIN") - .build()); - // @formatter:on - } - - } - - @Configuration - @EnableWebFlux - @EnableMongoWebSession - static class Config { - - private static final String DOCKER_IMAGE = "mongo:5.0.11"; - - @Bean - MongoDBContainer mongoDbContainer() { - MongoDBContainer mongoDbContainer = new MongoDBContainer(DOCKER_IMAGE); - mongoDbContainer.start(); - return mongoDbContainer; - } - - @Bean - ReactiveMongoOperations mongoOperations(MongoDBContainer mongoContainer) { - - MongoClient mongo = MongoClients - .create("mongodb://" + mongoContainer.getHost() + ":" + mongoContainer.getFirstMappedPort()); - return new ReactiveMongoTemplate(mongo, "test"); - } - - @Bean - TestController controller() { - return new TestController(); - } - - } - + @Autowired + ApplicationContext ctx; + + WebTestClient client; + + @BeforeEach + void setUp() { + this.client = WebTestClient.bindToApplicationContext(this.ctx).build(); + } + + @Test + void logoutShouldDeleteOldSessionFromMongoDB() { + + // 1. Login and capture the SESSION cookie value. + + FluxExchangeResult loginResult = this.client + .post() + .uri("/login") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) // + .body( + BodyInserters // + .fromFormData("username", "admin") // + .with("password", "password")) // + .exchange() // + .returnResult(String.class); + + AssertionsForClassTypes.assertThat(loginResult.getResponseHeaders().getLocation()) + .isEqualTo(URI.create("/")); + + String originalSessionId = + loginResult.getResponseCookies().getFirst("SESSION").getValue(); + + // 2. Fetch a protected resource using the SESSION cookie. + + this.client + .get() + .uri("/hello") // + .cookie("SESSION", originalSessionId) // + .exchange() // + .expectStatus() + .isOk() // + .returnResult(String.class) + .getResponseBody() // + .as(StepVerifier::create) // + .expectNext("HelloWorld") // + .verifyComplete(); + + // 3. Logout using the SESSION cookie, and capture the new SESSION cookie. + + String newSessionId = this.client + .post() + .uri("/logout") // + .cookie("SESSION", originalSessionId) // + .exchange() // + .expectStatus() + .isFound() // + .returnResult(String.class) + .getResponseCookies() + .getFirst("SESSION") + .getValue(); + + AssertionsForClassTypes.assertThat(newSessionId).isNotEqualTo(originalSessionId); + + // 4. Verify the new SESSION cookie is not yet authorized. + + this.client + .get() + .uri("/hello") // + .cookie("SESSION", newSessionId) // + .exchange() // + .expectStatus() + .isFound() // + .expectHeader() + .value(HttpHeaders.LOCATION, (value) -> AssertionsForClassTypes.assertThat(value) + .isEqualTo("/login")); + + // 5. Verify the original SESSION cookie no longer works. + + this.client + .get() + .uri("/hello") // + .cookie("SESSION", originalSessionId) // + .exchange() // + .expectStatus() + .isFound() // + .expectHeader() + .value(HttpHeaders.LOCATION, (value) -> AssertionsForClassTypes.assertThat(value) + .isEqualTo("/login")); + } + + @RestController + static class TestController { + + @GetMapping("/hello") + ResponseEntity hello() { + return ResponseEntity.ok("HelloWorld"); + } + } + + @Configuration(proxyBeanMethods = false) + @EnableWebFluxSecurity + static class SecurityConfig { + + @Bean + SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { + // @formatter:off + return http.logout(Customizer.withDefaults()) + .formLogin(Customizer.withDefaults()) + .csrf((csrf) -> csrf.disable()) + .authorizeExchange((ae) -> ae.anyExchange().authenticated()) + .build(); + // @formatter:on + } + + @Bean + MapReactiveUserDetailsService userDetailsService() { + // @formatter:off + return new MapReactiveUserDetailsService(User.withUsername("admin") + .password("{noop}password") + .roles("USER,ADMIN") + .build()); + // @formatter:on + } + } + + @Configuration + @EnableWebFlux + @EnableMongoWebSession + static class Config { + + private static final String DOCKER_IMAGE = "mongo:5.0.11"; + + @Bean + MongoDBContainer mongoDbContainer() { + MongoDBContainer mongoDbContainer = new MongoDBContainer(DOCKER_IMAGE); + mongoDbContainer.start(); + return mongoDbContainer; + } + + @Bean + ReactiveMongoOperations mongoOperations(MongoDBContainer mongoContainer) { + + MongoClient mongo = MongoClients.create( + "mongodb://" + mongoContainer.getHost() + ":" + mongoContainer.getFirstMappedPort()); + return new ReactiveMongoTemplate(mongo, "test"); + } + + @Bean + TestController controller() { + return new TestController(); + } + } } diff --git a/src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJacksonITest.java b/src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJacksonITest.java index 417341b..70e521a 100644 --- a/src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJacksonITest.java +++ b/src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJacksonITest.java @@ -1,11 +1,12 @@ /* + * Copyright 2025-present MongoDB, Inc. * Copyright 2014-present the original author or authors. * * 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 * - * https://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,23 +17,20 @@ package org.springframework.session.data.mongo; +import static org.assertj.core.api.Assertions.assertThat; + import java.util.Collections; import java.util.Map; import java.util.UUID; - import org.junit.jupiter.api.Test; - import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.geo.GeoModule; import org.springframework.session.data.mongo.config.annotation.web.http.EnableMongoHttpSession; import org.springframework.test.context.ContextConfiguration; -import static org.assertj.core.api.Assertions.assertThat; - /** - * Integration tests for - * {@link org.springframework.session.data.mongo.MongoIndexedSessionRepository} that use + * Integration tests for {@link org.springframework.session.data.mongo.MongoIndexedSessionRepository} that use * {@link JacksonMongoSessionConverter} based session serialization. * * @author Jakub Kubrynski @@ -42,32 +40,31 @@ @ContextConfiguration class MongoRepositoryJacksonITest extends AbstractMongoRepositoryITest { - @Test - void findByCustomIndex() throws Exception { - - MongoSession toSave = this.repository.createSession(); - String cartId = "cart-" + UUID.randomUUID(); - toSave.setAttribute("cartId", cartId); + @Test + void findByCustomIndex() throws Exception { - this.repository.save(toSave); + MongoSession toSave = this.repository.createSession(); + String cartId = "cart-" + UUID.randomUUID(); + toSave.setAttribute("cartId", cartId); - Map findByCartId = this.repository.findByIndexNameAndIndexValue("cartId", cartId); + this.repository.save(toSave); - assertThat(findByCartId).hasSize(1); - assertThat(findByCartId.keySet()).containsOnly(toSave.getId()); - } + Map findByCartId = this.repository.findByIndexNameAndIndexValue("cartId", cartId); - // tag::sample[] - @Configuration - @EnableMongoHttpSession - static class Config extends BaseConfig { + assertThat(findByCartId).hasSize(1); + assertThat(findByCartId.keySet()).containsOnly(toSave.getId()); + } - @Bean - AbstractMongoSessionConverter mongoSessionConverter() { - return new JacksonMongoSessionConverter(Collections.singletonList(new GeoModule())); - } + // tag::sample[] + @Configuration + @EnableMongoHttpSession + static class Config extends BaseConfig { - } - // end::sample[] + @Bean + AbstractMongoSessionConverter mongoSessionConverter() { + return new JacksonMongoSessionConverter(Collections.singletonList(new GeoModule())); + } + } + // end::sample[] } diff --git a/src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJdkSerializationITest.java b/src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJdkSerializationITest.java index db242ef..d121a86 100644 --- a/src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJdkSerializationITest.java +++ b/src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJdkSerializationITest.java @@ -1,11 +1,12 @@ /* + * Copyright 2025-present MongoDB, Inc. * Copyright 2014-present the original author or authors. * * 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 * - * https://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,21 +17,18 @@ package org.springframework.session.data.mongo; +import static org.assertj.core.api.Assertions.assertThat; + import java.time.Duration; import java.util.Map; - import org.junit.jupiter.api.Test; - import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.session.data.mongo.config.annotation.web.http.EnableMongoHttpSession; import org.springframework.test.context.ContextConfiguration; -import static org.assertj.core.api.Assertions.assertThat; - /** - * Integration tests for - * {@link org.springframework.session.data.mongo.MongoIndexedSessionRepository} that use + * Integration tests for {@link org.springframework.session.data.mongo.MongoIndexedSessionRepository} that use * {@link JdkMongoSessionConverter} based session serialization. * * @author Jakub Kubrynski @@ -40,55 +38,54 @@ @ContextConfiguration class MongoRepositoryJdkSerializationITest extends AbstractMongoRepositoryITest { - @Test - void findByDeletedSecurityPrincipalNameReload() throws Exception { - - MongoSession toSave = this.repository.createSession(); - toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); + @Test + void findByDeletedSecurityPrincipalNameReload() throws Exception { - this.repository.save(toSave); + MongoSession toSave = this.repository.createSession(); + toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); - MongoSession getSession = this.repository.findById(toSave.getId()); - getSession.setAttribute(INDEX_NAME, null); - this.repository.save(getSession); + this.repository.save(toSave); - Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, - getChangedSecurityName()); + MongoSession getSession = this.repository.findById(toSave.getId()); + getSession.setAttribute(INDEX_NAME, null); + this.repository.save(getSession); - assertThat(findByPrincipalName).isEmpty(); - } + Map findByPrincipalName = + this.repository.findByIndexNameAndIndexValue(INDEX_NAME, getChangedSecurityName()); - @Test - void findByPrincipalNameNoSecurityPrincipalNameChangeReload() throws Exception { + assertThat(findByPrincipalName).isEmpty(); + } - MongoSession toSave = this.repository.createSession(); - toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); + @Test + void findByPrincipalNameNoSecurityPrincipalNameChangeReload() throws Exception { - this.repository.save(toSave); + MongoSession toSave = this.repository.createSession(); + toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context); - toSave = this.repository.findById(toSave.getId()); + this.repository.save(toSave); - toSave.setAttribute("other", "value"); - this.repository.save(toSave); + toSave = this.repository.findById(toSave.getId()); - Map findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, - getSecurityName()); + toSave.setAttribute("other", "value"); + this.repository.save(toSave); - assertThat(findByPrincipalName).hasSize(1); - assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); - } + Map findByPrincipalName = + this.repository.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName()); - // tag::sample[] - @Configuration - @EnableMongoHttpSession - static class Config extends BaseConfig { + assertThat(findByPrincipalName).hasSize(1); + assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId()); + } - @Bean - AbstractMongoSessionConverter mongoSessionConverter() { - return new JdkMongoSessionConverter(Duration.ofMinutes(30)); - } + // tag::sample[] + @Configuration + @EnableMongoHttpSession + static class Config extends BaseConfig { - } - // end::sample[] + @Bean + AbstractMongoSessionConverter mongoSessionConverter() { + return new JdkMongoSessionConverter(Duration.ofMinutes(30)); + } + } + // end::sample[] } diff --git a/src/main/java/org/springframework/session/data/mongo/AbstractMongoSessionConverter.java b/src/main/java/org/springframework/session/data/mongo/AbstractMongoSessionConverter.java index ef7c328..f95e3d9 100644 --- a/src/main/java/org/springframework/session/data/mongo/AbstractMongoSessionConverter.java +++ b/src/main/java/org/springframework/session/data/mongo/AbstractMongoSessionConverter.java @@ -1,11 +1,12 @@ /* + * Copyright 2025-present MongoDB, Inc. * Copyright 2014-present the original author or authors. * * 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 * - * https://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,14 +17,12 @@ package org.springframework.session.data.mongo; +import com.mongodb.DBObject; import java.util.Collections; import java.util.Set; - -import com.mongodb.DBObject; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bson.Document; - import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.GenericConverter; import org.springframework.data.domain.Sort; @@ -40,9 +39,8 @@ import org.springframework.util.Assert; /** - * Base class for serializing and deserializing session objects. To create custom - * serializer you have to implement this interface and simply register your class as a - * bean. + * Base class for serializing and deserializing session objects. To create custom serializer you have to implement this + * interface and simply register your class as a bean. * * @author Jakub Kubrynski * @author Greg Turnquist @@ -50,82 +48,80 @@ */ public abstract class AbstractMongoSessionConverter implements GenericConverter { - static final String EXPIRE_AT_FIELD_NAME = "expireAt"; - - private static final Log LOG = LogFactory.getLog(AbstractMongoSessionConverter.class); + static final String EXPIRE_AT_FIELD_NAME = "expireAt"; - private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; + private static final Log LOG = LogFactory.getLog(AbstractMongoSessionConverter.class); - private IndexResolver indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>()); + private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; - /** - * Returns query to be executed to return sessions based on a particular index. - * @param indexName name of the index - * @param indexValue value to query against - * @return built query or null if indexName is not supported - */ - @Nullable - protected abstract Query getQueryForIndex(String indexName, Object indexValue); + private IndexResolver indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>()); - /** - * Method ensures that there is a TTL index on {@literal expireAt} field. It's has - * {@literal expireAfterSeconds} set to zero seconds, so the expiration time is - * controlled by the application. It can be extended in custom converters when there - * is a need for creating additional custom indexes. - * @param sessionCollectionIndexes {@link IndexOperations} to use - */ - protected void ensureIndexes(IndexOperations sessionCollectionIndexes) { + /** + * Returns query to be executed to return sessions based on a particular index. + * + * @param indexName name of the index + * @param indexValue value to query against + * @return built query or null if indexName is not supported + */ + @Nullable protected abstract Query getQueryForIndex(String indexName, Object indexValue); - for (IndexInfo info : sessionCollectionIndexes.getIndexInfo()) { - if (EXPIRE_AT_FIELD_NAME.equals(info.getName())) { - LOG.debug("TTL index on field " + EXPIRE_AT_FIELD_NAME + " already exists"); - return; - } - } + /** + * Method ensures that there is a TTL index on {@literal expireAt} field. It's has {@literal expireAfterSeconds} set + * to zero seconds, so the expiration time is controlled by the application. It can be extended in custom converters + * when there is a need for creating additional custom indexes. + * + * @param sessionCollectionIndexes {@link IndexOperations} to use + */ + protected void ensureIndexes(IndexOperations sessionCollectionIndexes) { - LOG.info("Creating TTL index on field " + EXPIRE_AT_FIELD_NAME); + for (IndexInfo info : sessionCollectionIndexes.getIndexInfo()) { + if (EXPIRE_AT_FIELD_NAME.equals(info.getName())) { + LOG.debug("TTL index on field " + EXPIRE_AT_FIELD_NAME + " already exists"); + return; + } + } - sessionCollectionIndexes - .ensureIndex(new Index(EXPIRE_AT_FIELD_NAME, Sort.Direction.ASC).named(EXPIRE_AT_FIELD_NAME).expire(0)); - } + LOG.info("Creating TTL index on field " + EXPIRE_AT_FIELD_NAME); - protected String extractPrincipal(MongoSession expiringSession) { + sessionCollectionIndexes.ensureIndex(new Index(EXPIRE_AT_FIELD_NAME, Sort.Direction.ASC) + .named(EXPIRE_AT_FIELD_NAME) + .expire(0)); + } - return this.indexResolver.resolveIndexesFor(expiringSession) - .get(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME); - } + protected String extractPrincipal(MongoSession expiringSession) { - public Set getConvertibleTypes() { + return this.indexResolver + .resolveIndexesFor(expiringSession) + .get(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME); + } - return Collections.singleton(new ConvertiblePair(DBObject.class, MongoSession.class)); - } + public Set getConvertibleTypes() { - @SuppressWarnings("unchecked") - @Nullable - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + return Collections.singleton(new ConvertiblePair(DBObject.class, MongoSession.class)); + } - if (source == null) { - return null; - } + @SuppressWarnings("unchecked") + @Nullable public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { - if (DBObject.class.isAssignableFrom(sourceType.getType())) { - return convert(new Document(((DBObject) source).toMap())); - } - else if (Document.class.isAssignableFrom(sourceType.getType())) { - return convert((Document) source); - } - else { - return convert((MongoSession) source); - } - } + if (source == null) { + return null; + } - protected abstract DBObject convert(MongoSession session); + if (DBObject.class.isAssignableFrom(sourceType.getType())) { + return convert(new Document(((DBObject) source).toMap())); + } else if (Document.class.isAssignableFrom(sourceType.getType())) { + return convert((Document) source); + } else { + return convert((MongoSession) source); + } + } - protected abstract MongoSession convert(Document sessionWrapper); + protected abstract DBObject convert(MongoSession session); - public void setIndexResolver(IndexResolver indexResolver) { - Assert.notNull(indexResolver, "indexResolver must not be null"); - this.indexResolver = indexResolver; - } + protected abstract MongoSession convert(Document sessionWrapper); + public void setIndexResolver(IndexResolver indexResolver) { + Assert.notNull(indexResolver, "indexResolver must not be null"); + this.indexResolver = indexResolver; + } } diff --git a/src/main/java/org/springframework/session/data/mongo/JacksonMongoSessionConverter.java b/src/main/java/org/springframework/session/data/mongo/JacksonMongoSessionConverter.java index f66333c..e208d5f 100644 --- a/src/main/java/org/springframework/session/data/mongo/JacksonMongoSessionConverter.java +++ b/src/main/java/org/springframework/session/data/mongo/JacksonMongoSessionConverter.java @@ -1,11 +1,12 @@ /* + * Copyright 2025-present MongoDB, Inc. * Copyright 2014-present the original author or authors. * * 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 * - * https://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,11 +17,6 @@ package org.springframework.session.data.mongo; -import java.io.IOException; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; - import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; @@ -32,12 +28,15 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; +import java.io.IOException; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bson.Document; import org.bson.json.JsonMode; import org.bson.json.JsonWriterSettings; - import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.lang.Nullable; @@ -55,134 +54,124 @@ */ public class JacksonMongoSessionConverter extends AbstractMongoSessionConverter { - private static final Log LOG = LogFactory.getLog(JacksonMongoSessionConverter.class); - - private static final String ATTRS_FIELD_NAME = "attrs."; - - private static final String PRINCIPAL_FIELD_NAME = "principal"; - - private static final String EXPIRE_AT_FIELD_NAME = "expireAt"; + private static final Log LOG = LogFactory.getLog(JacksonMongoSessionConverter.class); - private final ObjectMapper objectMapper; + private static final String ATTRS_FIELD_NAME = "attrs."; - public JacksonMongoSessionConverter() { - this(Collections.emptyList()); - } + private static final String PRINCIPAL_FIELD_NAME = "principal"; - public JacksonMongoSessionConverter(Iterable modules) { + private static final String EXPIRE_AT_FIELD_NAME = "expireAt"; - this.objectMapper = buildObjectMapper(); - this.objectMapper.registerModules(modules); - } + private final ObjectMapper objectMapper; - public JacksonMongoSessionConverter(ObjectMapper objectMapper) { + public JacksonMongoSessionConverter() { + this(Collections.emptyList()); + } - Assert.notNull(objectMapper, "ObjectMapper can NOT be null!"); - this.objectMapper = objectMapper; - } + public JacksonMongoSessionConverter(Iterable modules) { - @Nullable - protected Query getQueryForIndex(String indexName, Object indexValue) { + this.objectMapper = buildObjectMapper(); + this.objectMapper.registerModules(modules); + } - if (FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) { - return Query.query(Criteria.where(PRINCIPAL_FIELD_NAME).is(indexValue)); - } - else { - return Query.query(Criteria.where(ATTRS_FIELD_NAME + MongoSession.coverDot(indexName)).is(indexValue)); - } - } + public JacksonMongoSessionConverter(ObjectMapper objectMapper) { - private ObjectMapper buildObjectMapper() { + Assert.notNull(objectMapper, "ObjectMapper can NOT be null!"); + this.objectMapper = objectMapper; + } - ObjectMapper objectMapper = new ObjectMapper(); + @Nullable protected Query getQueryForIndex(String indexName, Object indexValue) { - // serialize fields instead of properties - objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); - objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + if (FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) { + return Query.query(Criteria.where(PRINCIPAL_FIELD_NAME).is(indexValue)); + } else { + return Query.query(Criteria.where(ATTRS_FIELD_NAME + MongoSession.coverDot(indexName)) + .is(indexValue)); + } + } - // ignore unresolved fields (mostly 'principal') - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + private ObjectMapper buildObjectMapper() { - objectMapper.setPropertyNamingStrategy(new MongoIdNamingStrategy()); + ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerModules(SecurityJackson2Modules.getModules(getClass().getClassLoader())); - objectMapper.addMixIn(MongoSession.class, MongoSessionMixin.class); - objectMapper.addMixIn(HashMap.class, HashMapMixin.class); + // serialize fields instead of properties + objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); + objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); - return objectMapper; - } + // ignore unresolved fields (mostly 'principal') + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - @Override - protected DBObject convert(MongoSession source) { + objectMapper.setPropertyNamingStrategy(new MongoIdNamingStrategy()); - try { - DBObject dbSession = BasicDBObject.parse(this.objectMapper.writeValueAsString(source)); + objectMapper.registerModules( + SecurityJackson2Modules.getModules(getClass().getClassLoader())); + objectMapper.addMixIn(MongoSession.class, MongoSessionMixin.class); + objectMapper.addMixIn(HashMap.class, HashMapMixin.class); - // Override default serialization with proper values. - dbSession.put(PRINCIPAL_FIELD_NAME, extractPrincipal(source)); - dbSession.put(EXPIRE_AT_FIELD_NAME, source.getExpireAt()); - return dbSession; - } - catch (JsonProcessingException ex) { - throw new IllegalStateException("Cannot convert MongoExpiringSession", ex); - } - } + return objectMapper; + } - @Override - @Nullable - protected MongoSession convert(Document source) { + @Override + protected DBObject convert(MongoSession source) { - Date expireAt = (Date) source.remove(EXPIRE_AT_FIELD_NAME); - source.remove("originalSessionId"); - String json = source.toJson(JsonWriterSettings.builder().outputMode(JsonMode.RELAXED).build()); + try { + DBObject dbSession = BasicDBObject.parse(this.objectMapper.writeValueAsString(source)); - try { - MongoSession mongoSession = this.objectMapper.readValue(json, MongoSession.class); - mongoSession.setExpireAt(expireAt); - return mongoSession; - } - catch (IOException ex) { - LOG.error("Error during Mongo Session deserialization", ex); - return null; - } - } + // Override default serialization with proper values. + dbSession.put(PRINCIPAL_FIELD_NAME, extractPrincipal(source)); + dbSession.put(EXPIRE_AT_FIELD_NAME, source.getExpireAt()); + return dbSession; + } catch (JsonProcessingException ex) { + throw new IllegalStateException("Cannot convert MongoExpiringSession", ex); + } + } - /** - * Used to whitelist {@link MongoSession} for {@link SecurityJackson2Modules}. - */ - private static class MongoSessionMixin { + @Override + @Nullable protected MongoSession convert(Document source) { - @JsonCreator - MongoSessionMixin(@JsonProperty("_id") String id, - @JsonProperty("intervalSeconds") long maxInactiveIntervalInSeconds) { - } + Date expireAt = (Date) source.remove(EXPIRE_AT_FIELD_NAME); + source.remove("originalSessionId"); + String json = source.toJson( + JsonWriterSettings.builder().outputMode(JsonMode.RELAXED).build()); - } + try { + MongoSession mongoSession = this.objectMapper.readValue(json, MongoSession.class); + mongoSession.setExpireAt(expireAt); + return mongoSession; + } catch (IOException ex) { + LOG.error("Error during Mongo Session deserialization", ex); + return null; + } + } - /** - * Used to whitelist {@link HashMap} for {@link SecurityJackson2Modules}. - */ - private static class HashMapMixin { + /** Used to whitelist {@link MongoSession} for {@link SecurityJackson2Modules}. */ + private static class MongoSessionMixin { - // Nothing special + @JsonCreator + MongoSessionMixin( + @JsonProperty("_id") String id, @JsonProperty("intervalSeconds") long maxInactiveIntervalInSeconds) {} + } - } + /** Used to whitelist {@link HashMap} for {@link SecurityJackson2Modules}. */ + private static class HashMapMixin { - private static class MongoIdNamingStrategy extends PropertyNamingStrategies.NamingBase { + // Nothing special - @Override - public String translate(String propertyName) { + } - switch (propertyName) { - case "id": - return "_id"; - case "_id": - return "id"; - default: - return propertyName; - } - } + private static class MongoIdNamingStrategy extends PropertyNamingStrategies.NamingBase { - } + @Override + public String translate(String propertyName) { + switch (propertyName) { + case "id": + return "_id"; + case "_id": + return "id"; + default: + return propertyName; + } + } + } } diff --git a/src/main/java/org/springframework/session/data/mongo/JdkMongoSessionConverter.java b/src/main/java/org/springframework/session/data/mongo/JdkMongoSessionConverter.java index 19a8b56..23c93e1 100644 --- a/src/main/java/org/springframework/session/data/mongo/JdkMongoSessionConverter.java +++ b/src/main/java/org/springframework/session/data/mongo/JdkMongoSessionConverter.java @@ -1,11 +1,12 @@ /* + * Copyright 2025-present MongoDB, Inc. * Copyright 2014-present the original author or authors. * * 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 * - * https://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,17 +17,15 @@ package org.springframework.session.data.mongo; +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; import java.time.Duration; import java.time.Instant; import java.util.Date; import java.util.HashMap; import java.util.Map; - -import com.mongodb.BasicDBObject; -import com.mongodb.DBObject; import org.bson.Document; import org.bson.types.Binary; - import org.springframework.core.convert.converter.Converter; import org.springframework.core.serializer.support.DeserializingConverter; import org.springframework.core.serializer.support.SerializingConverter; @@ -47,128 +46,125 @@ */ public class JdkMongoSessionConverter extends AbstractMongoSessionConverter { - private static final String ID = "_id"; - - private static final String CREATION_TIME = "created"; + private static final String ID = "_id"; - private static final String LAST_ACCESSED_TIME = "accessed"; + private static final String CREATION_TIME = "created"; - private static final String MAX_INTERVAL = "interval"; + private static final String LAST_ACCESSED_TIME = "accessed"; - private static final String ATTRIBUTES = "attr"; + private static final String MAX_INTERVAL = "interval"; - private static final String PRINCIPAL_FIELD_NAME = "principal"; + private static final String ATTRIBUTES = "attr"; - private final Converter serializer; + private static final String PRINCIPAL_FIELD_NAME = "principal"; - private final Converter deserializer; + private final Converter serializer; - private Duration maxInactiveInterval; + private final Converter deserializer; - public JdkMongoSessionConverter(Duration maxInactiveInterval) { - this(new SerializingConverter(), new DeserializingConverter(), maxInactiveInterval); - } + private Duration maxInactiveInterval; - public JdkMongoSessionConverter(Converter serializer, Converter deserializer, - Duration maxInactiveInterval) { + public JdkMongoSessionConverter(Duration maxInactiveInterval) { + this(new SerializingConverter(), new DeserializingConverter(), maxInactiveInterval); + } - Assert.notNull(serializer, "serializer cannot be null"); - Assert.notNull(deserializer, "deserializer cannot be null"); - Assert.notNull(maxInactiveInterval, "maxInactiveInterval cannot be null"); + public JdkMongoSessionConverter( + Converter serializer, + Converter deserializer, + Duration maxInactiveInterval) { - this.serializer = serializer; - this.deserializer = deserializer; - this.maxInactiveInterval = maxInactiveInterval; - } + Assert.notNull(serializer, "serializer cannot be null"); + Assert.notNull(deserializer, "deserializer cannot be null"); + Assert.notNull(maxInactiveInterval, "maxInactiveInterval cannot be null"); - @Override - @Nullable - public Query getQueryForIndex(String indexName, Object indexValue) { + this.serializer = serializer; + this.deserializer = deserializer; + this.maxInactiveInterval = maxInactiveInterval; + } - if (FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) { - return Query.query(Criteria.where(PRINCIPAL_FIELD_NAME).is(indexValue)); - } - else { - return null; - } - } + @Override + @Nullable public Query getQueryForIndex(String indexName, Object indexValue) { - @Override - protected DBObject convert(MongoSession session) { + if (FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) { + return Query.query(Criteria.where(PRINCIPAL_FIELD_NAME).is(indexValue)); + } else { + return null; + } + } - BasicDBObject basicDBObject = new BasicDBObject(); + @Override + protected DBObject convert(MongoSession session) { - basicDBObject.put(ID, session.getId()); - basicDBObject.put(CREATION_TIME, session.getCreationTime()); - basicDBObject.put(LAST_ACCESSED_TIME, session.getLastAccessedTime()); - basicDBObject.put(MAX_INTERVAL, session.getMaxInactiveInterval()); - basicDBObject.put(PRINCIPAL_FIELD_NAME, extractPrincipal(session)); - basicDBObject.put(EXPIRE_AT_FIELD_NAME, session.getExpireAt()); - basicDBObject.put(ATTRIBUTES, serializeAttributes(session)); + BasicDBObject basicDBObject = new BasicDBObject(); - return basicDBObject; - } + basicDBObject.put(ID, session.getId()); + basicDBObject.put(CREATION_TIME, session.getCreationTime()); + basicDBObject.put(LAST_ACCESSED_TIME, session.getLastAccessedTime()); + basicDBObject.put(MAX_INTERVAL, session.getMaxInactiveInterval()); + basicDBObject.put(PRINCIPAL_FIELD_NAME, extractPrincipal(session)); + basicDBObject.put(EXPIRE_AT_FIELD_NAME, session.getExpireAt()); + basicDBObject.put(ATTRIBUTES, serializeAttributes(session)); - @Override - protected MongoSession convert(Document sessionWrapper) { + return basicDBObject; + } - Object maxInterval = sessionWrapper.getOrDefault(MAX_INTERVAL, this.maxInactiveInterval); + @Override + protected MongoSession convert(Document sessionWrapper) { - Duration maxIntervalDuration = (maxInterval instanceof Duration) ? (Duration) maxInterval - : Duration.parse(maxInterval.toString()); + Object maxInterval = sessionWrapper.getOrDefault(MAX_INTERVAL, this.maxInactiveInterval); - MongoSession session = new MongoSession(sessionWrapper.getString(ID), maxIntervalDuration.getSeconds()); + Duration maxIntervalDuration = + (maxInterval instanceof Duration) ? (Duration) maxInterval : Duration.parse(maxInterval.toString()); - Object creationTime = sessionWrapper.get(CREATION_TIME); - if (creationTime instanceof Instant) { - session.setCreationTime(((Instant) creationTime).toEpochMilli()); - } - else if (creationTime instanceof Date) { - session.setCreationTime(((Date) creationTime).getTime()); - } + MongoSession session = new MongoSession(sessionWrapper.getString(ID), maxIntervalDuration.getSeconds()); - Object lastAccessedTime = sessionWrapper.get(LAST_ACCESSED_TIME); - if (lastAccessedTime instanceof Instant) { - session.setLastAccessedTime((Instant) lastAccessedTime); - } - else if (lastAccessedTime instanceof Date) { - session.setLastAccessedTime(Instant.ofEpochMilli(((Date) lastAccessedTime).getTime())); - } + Object creationTime = sessionWrapper.get(CREATION_TIME); + if (creationTime instanceof Instant) { + session.setCreationTime(((Instant) creationTime).toEpochMilli()); + } else if (creationTime instanceof Date) { + session.setCreationTime(((Date) creationTime).getTime()); + } - session.setExpireAt((Date) sessionWrapper.get(EXPIRE_AT_FIELD_NAME)); + Object lastAccessedTime = sessionWrapper.get(LAST_ACCESSED_TIME); + if (lastAccessedTime instanceof Instant) { + session.setLastAccessedTime((Instant) lastAccessedTime); + } else if (lastAccessedTime instanceof Date) { + session.setLastAccessedTime(Instant.ofEpochMilli(((Date) lastAccessedTime).getTime())); + } - deserializeAttributes(sessionWrapper, session); + session.setExpireAt((Date) sessionWrapper.get(EXPIRE_AT_FIELD_NAME)); - return session; - } + deserializeAttributes(sessionWrapper, session); - @Nullable - private byte[] serializeAttributes(Session session) { + return session; + } - Map attributes = new HashMap<>(); + @Nullable private byte[] serializeAttributes(Session session) { - for (String attrName : session.getAttributeNames()) { - attributes.put(attrName, session.getAttribute(attrName)); - } + Map attributes = new HashMap<>(); - return this.serializer.convert(attributes); - } + for (String attrName : session.getAttributeNames()) { + attributes.put(attrName, session.getAttribute(attrName)); + } - @SuppressWarnings("unchecked") - private void deserializeAttributes(Document sessionWrapper, Session session) { + return this.serializer.convert(attributes); + } - Object sessionAttributes = sessionWrapper.get(ATTRIBUTES); + @SuppressWarnings("unchecked") + private void deserializeAttributes(Document sessionWrapper, Session session) { - byte[] attributesBytes = ((sessionAttributes instanceof Binary) ? ((Binary) sessionAttributes).getData() - : (byte[]) sessionAttributes); + Object sessionAttributes = sessionWrapper.get(ATTRIBUTES); - Map attributes = (Map) this.deserializer.convert(attributesBytes); + byte[] attributesBytes = ((sessionAttributes instanceof Binary) + ? ((Binary) sessionAttributes).getData() + : (byte[]) sessionAttributes); - if (attributes != null) { - for (Map.Entry entry : attributes.entrySet()) { - session.setAttribute(entry.getKey(), entry.getValue()); - } - } - } + Map attributes = (Map) this.deserializer.convert(attributesBytes); + if (attributes != null) { + for (Map.Entry entry : attributes.entrySet()) { + session.setAttribute(entry.getKey(), entry.getValue()); + } + } + } } diff --git a/src/main/java/org/springframework/session/data/mongo/MongoIndexedSessionRepository.java b/src/main/java/org/springframework/session/data/mongo/MongoIndexedSessionRepository.java index 2cfea19..a43fa57 100644 --- a/src/main/java/org/springframework/session/data/mongo/MongoIndexedSessionRepository.java +++ b/src/main/java/org/springframework/session/data/mongo/MongoIndexedSessionRepository.java @@ -1,11 +1,12 @@ /* + * Copyright 2025-present MongoDB, Inc. * Copyright 2014-present the original author or authors. * * 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 * - * https://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,17 +17,15 @@ package org.springframework.session.data.mongo; +import com.mongodb.DBObject; import java.time.Duration; import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; - -import com.mongodb.DBObject; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bson.Document; - import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; @@ -44,10 +43,9 @@ import org.springframework.util.Assert; /** - * Session repository implementation which stores sessions in Mongo. Uses - * {@link AbstractMongoSessionConverter} to transform session objects from/to native Mongo - * representation ({@code DBObject}). Repository is also responsible for removing expired - * sessions from database. Cleanup is done every minute. + * Session repository implementation which stores sessions in Mongo. Uses {@link AbstractMongoSessionConverter} to + * transform session objects from/to native Mongo representation ({@code DBObject}). Repository is also responsible for + * removing expired sessions from database. Cleanup is done every minute. * * @author Jakub Kubrynski * @author Greg Turnquist @@ -55,182 +53,174 @@ * @since 2.2.0 */ public class MongoIndexedSessionRepository - implements FindByIndexNameSessionRepository, ApplicationEventPublisherAware, InitializingBean { - - /** - * The default time period in seconds in which a session will expire. - * @deprecated since 3.0.0 in favor of - * {@link MapSession#DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS} - */ - @Deprecated - public static final int DEFAULT_INACTIVE_INTERVAL = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; + implements FindByIndexNameSessionRepository, ApplicationEventPublisherAware, InitializingBean { - /** - * the default collection name for storing session. - */ - public static final String DEFAULT_COLLECTION_NAME = "sessions"; + /** + * The default time period in seconds in which a session will expire. + * + * @deprecated since 3.0.0 in favor of {@link MapSession#DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS} + */ + @Deprecated + public static final int DEFAULT_INACTIVE_INTERVAL = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; - private static final Log logger = LogFactory.getLog(MongoIndexedSessionRepository.class); + /** the default collection name for storing session. */ + public static final String DEFAULT_COLLECTION_NAME = "sessions"; - private final MongoOperations mongoOperations; + private static final Log logger = LogFactory.getLog(MongoIndexedSessionRepository.class); - private Duration defaultMaxInactiveInterval = Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS); + private final MongoOperations mongoOperations; - private String collectionName = DEFAULT_COLLECTION_NAME; + private Duration defaultMaxInactiveInterval = Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS); - private AbstractMongoSessionConverter mongoSessionConverter = new JdkMongoSessionConverter( - this.defaultMaxInactiveInterval); + private String collectionName = DEFAULT_COLLECTION_NAME; - private ApplicationEventPublisher eventPublisher; + private AbstractMongoSessionConverter mongoSessionConverter = + new JdkMongoSessionConverter(this.defaultMaxInactiveInterval); - private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance(); + private ApplicationEventPublisher eventPublisher; - public MongoIndexedSessionRepository(MongoOperations mongoOperations) { - this.mongoOperations = mongoOperations; - } - - @Override - public MongoSession createSession() { - - MongoSession session = new MongoSession(this.sessionIdGenerator, this.defaultMaxInactiveInterval.toSeconds()); - - publishEvent(new SessionCreatedEvent(this, session)); - - return session; - } - - @Override - public void save(MongoSession session) { - DBObject dbObject = MongoSessionUtils.convertToDBObject(this.mongoSessionConverter, session); - Assert.notNull(dbObject, "dbObject must not be null"); - this.mongoOperations.save(dbObject, this.collectionName); - } - - @Override - @Nullable - public MongoSession findById(String id) { - - Document sessionWrapper = findSession(id); - - if (sessionWrapper == null) { - return null; - } - - MongoSession session = MongoSessionUtils.convertToSession(this.mongoSessionConverter, sessionWrapper); - - if (session != null) { - if (session.isExpired()) { - publishEvent(new SessionExpiredEvent(this, session)); - deleteById(id); - return null; - } - session.setSessionIdGenerator(this.sessionIdGenerator); - } - - return session; - } - - /** - * Currently this repository allows only querying against - * {@code PRINCIPAL_NAME_INDEX_NAME}. - * @param indexName the name if the index (i.e. - * {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME}) - * @param indexValue the value of the index to search for. - * @return sessions map - */ - @Override - public Map findByIndexNameAndIndexValue(String indexName, String indexValue) { - - return Optional.ofNullable(this.mongoSessionConverter.getQueryForIndex(indexName, indexValue)) - .map((query) -> this.mongoOperations.find(query, Document.class, this.collectionName)) - .orElse(Collections.emptyList()) - .stream() - .map((dbSession) -> MongoSessionUtils.convertToSession(this.mongoSessionConverter, dbSession)) - .peek((session) -> session.setSessionIdGenerator(this.sessionIdGenerator)) - .collect(Collectors.toMap(MongoSession::getId, (mapSession) -> mapSession)); - } - - @Override - public void deleteById(String id) { - - Optional.ofNullable(findSession(id)).ifPresent((document) -> { - - MongoSession session = MongoSessionUtils.convertToSession(this.mongoSessionConverter, document); - if (session != null) { - publishEvent(new SessionDeletedEvent(this, session)); - } - this.mongoOperations.remove(document, this.collectionName); - }); - } - - @Override - public void afterPropertiesSet() { - - IndexOperations indexOperations = this.mongoOperations.indexOps(this.collectionName); - this.mongoSessionConverter.ensureIndexes(indexOperations); - } - - @Nullable - private Document findSession(String id) { - return this.mongoOperations.findById(id, Document.class, this.collectionName); - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) { - this.eventPublisher = eventPublisher; - } - - private void publishEvent(ApplicationEvent event) { - - try { - this.eventPublisher.publishEvent(event); - } - catch (Throwable ex) { - logger.error("Error publishing " + event + ".", ex); - } - } - - /** - * Set the maximum inactive interval in seconds between requests before newly created - * sessions will be invalidated. A negative time indicates that the session will never - * time out. The default is 30 minutes. - * @param defaultMaxInactiveInterval the default maxInactiveInterval - */ - public void setDefaultMaxInactiveInterval(Duration defaultMaxInactiveInterval) { - org.springframework.util.Assert.notNull(defaultMaxInactiveInterval, - "defaultMaxInactiveInterval must not be null"); - this.defaultMaxInactiveInterval = defaultMaxInactiveInterval; - } - - /** - * Set the maximum inactive interval in seconds between requests before newly created - * sessions will be invalidated. A negative time indicates that the session will never - * time out. The default is 1800 (30 minutes). - * @param defaultMaxInactiveInterval the default maxInactiveInterval in seconds - * @deprecated since 3.0.0, in favor of - * {@link #setDefaultMaxInactiveInterval(Duration)} - */ - @Deprecated(since = "3.0.0") - public void setMaxInactiveIntervalInSeconds(Integer defaultMaxInactiveInterval) { - setDefaultMaxInactiveInterval(Duration.ofSeconds(defaultMaxInactiveInterval)); - } - - public void setCollectionName(final String collectionName) { - this.collectionName = collectionName; - } - - public void setMongoSessionConverter(final AbstractMongoSessionConverter mongoSessionConverter) { - this.mongoSessionConverter = mongoSessionConverter; - } - - /** - * Set the {@link SessionIdGenerator} to use to generate session ids. - * @param sessionIdGenerator the {@link SessionIdGenerator} to use - * @since 3.2 - */ - public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) { - Assert.notNull(sessionIdGenerator, "sessionIdGenerator cannot be null"); - this.sessionIdGenerator = sessionIdGenerator; - } + private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance(); + public MongoIndexedSessionRepository(MongoOperations mongoOperations) { + this.mongoOperations = mongoOperations; + } + + @Override + public MongoSession createSession() { + + MongoSession session = new MongoSession(this.sessionIdGenerator, this.defaultMaxInactiveInterval.toSeconds()); + + publishEvent(new SessionCreatedEvent(this, session)); + + return session; + } + + @Override + public void save(MongoSession session) { + DBObject dbObject = MongoSessionUtils.convertToDBObject(this.mongoSessionConverter, session); + Assert.notNull(dbObject, "dbObject must not be null"); + this.mongoOperations.save(dbObject, this.collectionName); + } + + @Override + @Nullable public MongoSession findById(String id) { + + Document sessionWrapper = findSession(id); + + if (sessionWrapper == null) { + return null; + } + + MongoSession session = MongoSessionUtils.convertToSession(this.mongoSessionConverter, sessionWrapper); + + if (session != null) { + if (session.isExpired()) { + publishEvent(new SessionExpiredEvent(this, session)); + deleteById(id); + return null; + } + session.setSessionIdGenerator(this.sessionIdGenerator); + } + + return session; + } + + /** + * Currently this repository allows only querying against {@code PRINCIPAL_NAME_INDEX_NAME}. + * + * @param indexName the name if the index (i.e. {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME}) + * @param indexValue the value of the index to search for. + * @return sessions map + */ + @Override + public Map findByIndexNameAndIndexValue(String indexName, String indexValue) { + + return Optional.ofNullable(this.mongoSessionConverter.getQueryForIndex(indexName, indexValue)) + .map((query) -> this.mongoOperations.find(query, Document.class, this.collectionName)) + .orElse(Collections.emptyList()) + .stream() + .map((dbSession) -> MongoSessionUtils.convertToSession(this.mongoSessionConverter, dbSession)) + .peek((session) -> session.setSessionIdGenerator(this.sessionIdGenerator)) + .collect(Collectors.toMap(MongoSession::getId, (mapSession) -> mapSession)); + } + + @Override + public void deleteById(String id) { + + Optional.ofNullable(findSession(id)).ifPresent((document) -> { + MongoSession session = MongoSessionUtils.convertToSession(this.mongoSessionConverter, document); + if (session != null) { + publishEvent(new SessionDeletedEvent(this, session)); + } + this.mongoOperations.remove(document, this.collectionName); + }); + } + + @Override + public void afterPropertiesSet() { + + IndexOperations indexOperations = this.mongoOperations.indexOps(this.collectionName); + this.mongoSessionConverter.ensureIndexes(indexOperations); + } + + @Nullable private Document findSession(String id) { + return this.mongoOperations.findById(id, Document.class, this.collectionName); + } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) { + this.eventPublisher = eventPublisher; + } + + private void publishEvent(ApplicationEvent event) { + + try { + this.eventPublisher.publishEvent(event); + } catch (Throwable ex) { + logger.error("Error publishing " + event + ".", ex); + } + } + + /** + * Set the maximum inactive interval in seconds between requests before newly created sessions will be invalidated. + * A negative time indicates that the session will never time out. The default is 30 minutes. + * + * @param defaultMaxInactiveInterval the default maxInactiveInterval + */ + public void setDefaultMaxInactiveInterval(Duration defaultMaxInactiveInterval) { + org.springframework.util.Assert.notNull( + defaultMaxInactiveInterval, "defaultMaxInactiveInterval must not be null"); + this.defaultMaxInactiveInterval = defaultMaxInactiveInterval; + } + + /** + * Set the maximum inactive interval in seconds between requests before newly created sessions will be invalidated. + * A negative time indicates that the session will never time out. The default is 1800 (30 minutes). + * + * @param defaultMaxInactiveInterval the default maxInactiveInterval in seconds + * @deprecated since 3.0.0, in favor of {@link #setDefaultMaxInactiveInterval(Duration)} + */ + @Deprecated(since = "3.0.0") + public void setMaxInactiveIntervalInSeconds(Integer defaultMaxInactiveInterval) { + setDefaultMaxInactiveInterval(Duration.ofSeconds(defaultMaxInactiveInterval)); + } + + public void setCollectionName(final String collectionName) { + this.collectionName = collectionName; + } + + public void setMongoSessionConverter(final AbstractMongoSessionConverter mongoSessionConverter) { + this.mongoSessionConverter = mongoSessionConverter; + } + + /** + * Set the {@link SessionIdGenerator} to use to generate session ids. + * + * @param sessionIdGenerator the {@link SessionIdGenerator} to use + * @since 3.2 + */ + public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) { + Assert.notNull(sessionIdGenerator, "sessionIdGenerator cannot be null"); + this.sessionIdGenerator = sessionIdGenerator; + } } diff --git a/src/main/java/org/springframework/session/data/mongo/MongoSession.java b/src/main/java/org/springframework/session/data/mongo/MongoSession.java index 698f463..86b5e42 100644 --- a/src/main/java/org/springframework/session/data/mongo/MongoSession.java +++ b/src/main/java/org/springframework/session/data/mongo/MongoSession.java @@ -1,11 +1,12 @@ /* + * Copyright 2025-present MongoDB, Inc. * Copyright 2014-present the original author or authors. * * 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 * - * https://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -24,7 +25,6 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; - import org.springframework.lang.Nullable; import org.springframework.session.MapSession; import org.springframework.session.Session; @@ -41,215 +41,215 @@ */ public final class MongoSession implements Session { - /** - * Mongo doesn't support {@literal dot} in field names. We replace it with a unicode - * character from the Private Use Area. - *

NOTE: This was originally stored in unicode format. Delomboking the code caused it to get converted to another + * encoding, which isn't supported on all systems, so we migrated back to unicode. The same character is being + * represented ensuring binary compatibility. See https://www.compart.com/en/unicode/U+F607 + */ + private static final char DOT_COVER_CHAR = '\uF607'; + + private String id; + + private final String originalSessionId; + + private long createdMillis = System.currentTimeMillis(); + + private long accessedMillis; + + private long intervalSeconds; + + private Date expireAt; + + private final Map attrs = new HashMap<>(); + + private transient SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance(); + + /** + * Constructs a new instance using the provided session id. + * + * @param sessionId the session id to use + * @since 3.2 + */ + public MongoSession(String sessionId) { + this(sessionId, MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS); + } + + public MongoSession() { + this(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS); + } + + public MongoSession(long maxInactiveIntervalInSeconds) { + this(UuidSessionIdGenerator.getInstance().generate(), maxInactiveIntervalInSeconds); + } + + public MongoSession(String id, long maxInactiveIntervalInSeconds) { + this.id = id; + this.originalSessionId = id; + this.intervalSeconds = maxInactiveIntervalInSeconds; + setLastAccessedTime(Instant.ofEpochMilli(this.createdMillis)); + } + + /** + * Constructs a new instance using the provided {@link SessionIdGenerator}. + * + * @param sessionIdGenerator the {@link SessionIdGenerator} to use + * @since 3.2 + */ + public MongoSession(SessionIdGenerator sessionIdGenerator) { + this(sessionIdGenerator.generate(), MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS); + this.sessionIdGenerator = sessionIdGenerator; + } + + /** + * Constructs a new instance using the provided {@link SessionIdGenerator} and max inactive interval. + * + * @param sessionIdGenerator the {@link SessionIdGenerator} to use + * @param maxInactiveIntervalInSeconds the max inactive interval in seconds + * @since 3.2 + */ + MongoSession(SessionIdGenerator sessionIdGenerator, long maxInactiveIntervalInSeconds) { + this(sessionIdGenerator.generate(), maxInactiveIntervalInSeconds); + this.sessionIdGenerator = sessionIdGenerator; + } + + static String coverDot(String attributeName) { + return attributeName.replace('.', DOT_COVER_CHAR); + } + + static String uncoverDot(String attributeName) { + return attributeName.replace(DOT_COVER_CHAR, '.'); + } + + @Override + public String changeSessionId() { + + String changedId = this.sessionIdGenerator.generate(); + this.id = changedId; + return changedId; + } + + @Override + @Nullable @SuppressWarnings("unchecked") + public T getAttribute(String attributeName) { + return (T) this.attrs.get(coverDot(attributeName)); + } + + @Override + public Set getAttributeNames() { + return this.attrs.keySet().stream().map(MongoSession::uncoverDot).collect(Collectors.toSet()); + } + + @Override + public void setAttribute(String attributeName, Object attributeValue) { + + if (attributeValue == null) { + removeAttribute(coverDot(attributeName)); + } else { + this.attrs.put(coverDot(attributeName), attributeValue); + } + } + + @Override + public void removeAttribute(String attributeName) { + this.attrs.remove(coverDot(attributeName)); + } + + @Override + public Instant getCreationTime() { + return Instant.ofEpochMilli(this.createdMillis); + } + + void setCreationTime(long created) { + this.createdMillis = created; + } + + @Override + public Instant getLastAccessedTime() { + return Instant.ofEpochMilli(this.accessedMillis); + } + + @Override + public void setLastAccessedTime(Instant lastAccessedTime) { + this.accessedMillis = lastAccessedTime.toEpochMilli(); + this.expireAt = Date.from(lastAccessedTime.plus(Duration.ofSeconds(this.intervalSeconds))); + } + + @Override + public Duration getMaxInactiveInterval() { + return Duration.ofSeconds(this.intervalSeconds); + } + + @Override + public void setMaxInactiveInterval(Duration interval) { + this.intervalSeconds = interval.getSeconds(); + } + + @Override + public boolean isExpired() { + return this.intervalSeconds >= 0 && new Date().after(this.expireAt); + } + + @Override + public boolean equals(Object o) { + + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MongoSession that = (MongoSession) o; + return Objects.equals(this.id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(this.id); + } + + @Override + public String getId() { + return this.id; + } + + Date getExpireAt() { + return this.expireAt; + } + + void setExpireAt(final Date expireAt) { + this.expireAt = expireAt; + } + + boolean hasChangedSessionId() { + return !getId().equals(this.originalSessionId); + } + + String getOriginalSessionId() { + return this.originalSessionId; + } + + /** + * Sets the session id. + * + * @param id the id to set + * @since 3.2 + */ + void setId(String id) { + this.id = id; + } + + /** + * Sets the {@link SessionIdGenerator} to use. + * + * @param sessionIdGenerator the {@link SessionIdGenerator} to use + * @since 3.2 + */ + void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) { + Assert.notNull(sessionIdGenerator, "sessionIdGenerator cannot be null"); + this.sessionIdGenerator = sessionIdGenerator; + } } diff --git a/src/main/java/org/springframework/session/data/mongo/MongoSessionUtils.java b/src/main/java/org/springframework/session/data/mongo/MongoSessionUtils.java index 4021b27..fb352d7 100644 --- a/src/main/java/org/springframework/session/data/mongo/MongoSessionUtils.java +++ b/src/main/java/org/springframework/session/data/mongo/MongoSessionUtils.java @@ -1,11 +1,12 @@ /* + * Copyright 2025-present MongoDB, Inc. * Copyright 2014-present the original author or authors. * * 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 * - * https://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -18,7 +19,6 @@ import com.mongodb.DBObject; import org.bson.Document; - import org.springframework.core.convert.TypeDescriptor; import org.springframework.lang.Nullable; @@ -29,21 +29,17 @@ */ final class MongoSessionUtils { - private MongoSessionUtils() { - } - - @Nullable - static DBObject convertToDBObject(AbstractMongoSessionConverter mongoSessionConverter, MongoSession session) { + private MongoSessionUtils() {} - return (DBObject) mongoSessionConverter.convert(session, TypeDescriptor.valueOf(MongoSession.class), - TypeDescriptor.valueOf(DBObject.class)); - } + @Nullable static DBObject convertToDBObject(AbstractMongoSessionConverter mongoSessionConverter, MongoSession session) { - @Nullable - static MongoSession convertToSession(AbstractMongoSessionConverter mongoSessionConverter, Document session) { + return (DBObject) mongoSessionConverter.convert( + session, TypeDescriptor.valueOf(MongoSession.class), TypeDescriptor.valueOf(DBObject.class)); + } - return (MongoSession) mongoSessionConverter.convert(session, TypeDescriptor.valueOf(Document.class), - TypeDescriptor.valueOf(MongoSession.class)); - } + @Nullable static MongoSession convertToSession(AbstractMongoSessionConverter mongoSessionConverter, Document session) { + return (MongoSession) mongoSessionConverter.convert( + session, TypeDescriptor.valueOf(Document.class), TypeDescriptor.valueOf(MongoSession.class)); + } } diff --git a/src/main/java/org/springframework/session/data/mongo/ReactiveMongoSessionRepository.java b/src/main/java/org/springframework/session/data/mongo/ReactiveMongoSessionRepository.java index a988f3c..814dfa8 100644 --- a/src/main/java/org/springframework/session/data/mongo/ReactiveMongoSessionRepository.java +++ b/src/main/java/org/springframework/session/data/mongo/ReactiveMongoSessionRepository.java @@ -1,11 +1,12 @@ /* + * Copyright 2025-present MongoDB, Inc. * Copyright 2014-present the original author or authors. * * 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 * - * https://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -17,13 +18,9 @@ package org.springframework.session.data.mongo; import java.time.Duration; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bson.Document; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; - import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; @@ -40,6 +37,8 @@ import org.springframework.session.events.SessionCreatedEvent; import org.springframework.session.events.SessionDeletedEvent; import org.springframework.util.Assert; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; /** * A {@link ReactiveSessionRepository} implementation that uses Spring Data MongoDB. @@ -49,189 +48,180 @@ * @since 2.2.0 */ public class ReactiveMongoSessionRepository - implements ReactiveSessionRepository, ApplicationEventPublisherAware, InitializingBean { - - /** - * The default time period in seconds in which a session will expire. - * @deprecated since 3.0.0 in favor of - * {@link MapSession#DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS} - */ - @Deprecated - public static final int DEFAULT_INACTIVE_INTERVAL = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; - - /** - * The default collection name for storing session. - */ - public static final String DEFAULT_COLLECTION_NAME = "sessions"; - - private static final Log logger = LogFactory.getLog(ReactiveMongoSessionRepository.class); - - private final ReactiveMongoOperations mongoOperations; - - private Duration defaultMaxInactiveInterval = Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS); - - private String collectionName = DEFAULT_COLLECTION_NAME; - - private AbstractMongoSessionConverter mongoSessionConverter = new JdkMongoSessionConverter( - this.defaultMaxInactiveInterval); - - private MongoOperations blockingMongoOperations; - - private ApplicationEventPublisher eventPublisher; - - private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance(); - - public ReactiveMongoSessionRepository(ReactiveMongoOperations mongoOperations) { - this.mongoOperations = mongoOperations; - } - - /** - * Creates a new {@link MongoSession} that is capable of being persisted by this - * {@link ReactiveSessionRepository}. - *

- * This allows optimizations and customizations in how the {@link MongoSession} is - * persisted. For example, the implementation returned might keep track of the changes - * ensuring that only the delta needs to be persisted on a save. - *

- * @return a new {@link MongoSession} that is capable of being persisted by this - * {@link ReactiveSessionRepository} - */ - @Override - public Mono createSession() { - // @formatter:off - return Mono.fromSupplier(() -> this.sessionIdGenerator.generate()) - .zipWith(Mono.just(this.defaultMaxInactiveInterval.toSeconds())) - .map((tuple) -> new MongoSession(tuple.getT1(), tuple.getT2())) - .doOnNext((mongoSession) -> mongoSession.setMaxInactiveInterval(this.defaultMaxInactiveInterval)) - .doOnNext( - (mongoSession) -> mongoSession.setSessionIdGenerator(this.sessionIdGenerator)) - .doOnNext((mongoSession) -> publishEvent(new SessionCreatedEvent(this, mongoSession))) - .switchIfEmpty(Mono.just(new MongoSession(this.sessionIdGenerator))) - .subscribeOn(Schedulers.boundedElastic()) - .publishOn(Schedulers.parallel()); - // @formatter:on - } - - @Override - public Mono save(MongoSession session) { - - return Mono // - .justOrEmpty(MongoSessionUtils.convertToDBObject(this.mongoSessionConverter, session)) // - .flatMap((dbObject) -> { - if (session.hasChangedSessionId()) { - - return this.mongoOperations - .remove(Query.query(Criteria.where("_id").is(session.getOriginalSessionId())), - this.collectionName) // - .then(this.mongoOperations.save(dbObject, this.collectionName)); - } - else { - - return this.mongoOperations.save(dbObject, this.collectionName); - } - }) // - .then(); - } - - @Override - public Mono findById(String id) { - - return findSession(id) // - .map((document) -> MongoSessionUtils.convertToSession(this.mongoSessionConverter, document)) // - .filter((mongoSession) -> !mongoSession.isExpired()) // - .doOnNext((mongoSession) -> mongoSession.setSessionIdGenerator(this.sessionIdGenerator)) - .switchIfEmpty(Mono.defer(() -> this.deleteById(id).then(Mono.empty()))); - } - - @Override - public Mono deleteById(String id) { - - return findSession(id) // - .flatMap((document) -> this.mongoOperations.remove(document, this.collectionName) // - .then(Mono.just(document))) // - .map((document) -> MongoSessionUtils.convertToSession(this.mongoSessionConverter, document)) // - .doOnNext((mongoSession) -> publishEvent(new SessionDeletedEvent(this, mongoSession))) // - .then(); - } - - /** - * Do not use - * {@link org.springframework.data.mongodb.core.index.ReactiveIndexOperations} to - * ensure indexes exist. Instead, get a blocking {@link IndexOperations} and use that - * instead, if possible. - */ - @Override - public void afterPropertiesSet() { - - if (this.blockingMongoOperations != null) { - - IndexOperations indexOperations = this.blockingMongoOperations.indexOps(this.collectionName); - this.mongoSessionConverter.ensureIndexes(indexOperations); - } - } - - private Mono findSession(String id) { - return this.mongoOperations.findById(id, Document.class, this.collectionName); - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) { - this.eventPublisher = eventPublisher; - } - - private void publishEvent(ApplicationEvent event) { - - try { - this.eventPublisher.publishEvent(event); - } - catch (Throwable ex) { - logger.error("Error publishing " + event + ".", ex); - } - } - - /** - * Set the maximum inactive interval in seconds between requests before newly created - * sessions will be invalidated. A negative time indicates that the session will never - * time out. The default is 30 minutes. - * @param defaultMaxInactiveInterval the default maxInactiveInterval - */ - public void setDefaultMaxInactiveInterval(Duration defaultMaxInactiveInterval) { - Assert.notNull(defaultMaxInactiveInterval, "defaultMaxInactiveInterval must not be null"); - this.defaultMaxInactiveInterval = defaultMaxInactiveInterval; - } - - /** - * Set the maximum inactive interval in seconds between requests before newly created - * sessions will be invalidated. A negative time indicates that the session will never - * time out. The default is 1800 (30 minutes). - * @param defaultMaxInactiveInterval the default maxInactiveInterval in seconds - * @deprecated since 3.0.0, in favor of - * {@link #setDefaultMaxInactiveInterval(Duration)} - */ - @Deprecated(since = "3.0.0") - public void setMaxInactiveIntervalInSeconds(Integer defaultMaxInactiveInterval) { - setDefaultMaxInactiveInterval(Duration.ofSeconds(defaultMaxInactiveInterval)); - } - - public String getCollectionName() { - return this.collectionName; - } - - public void setCollectionName(final String collectionName) { - this.collectionName = collectionName; - } - - public void setMongoSessionConverter(final AbstractMongoSessionConverter mongoSessionConverter) { - this.mongoSessionConverter = mongoSessionConverter; - } - - public void setBlockingMongoOperations(final MongoOperations blockingMongoOperations) { - this.blockingMongoOperations = blockingMongoOperations; - } - - public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) { - Assert.notNull(sessionIdGenerator, "sessionIdGenerator cannot be null"); - this.sessionIdGenerator = sessionIdGenerator; - } - + implements ReactiveSessionRepository, ApplicationEventPublisherAware, InitializingBean { + + /** + * The default time period in seconds in which a session will expire. + * + * @deprecated since 3.0.0 in favor of {@link MapSession#DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS} + */ + @Deprecated + public static final int DEFAULT_INACTIVE_INTERVAL = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; + + /** The default collection name for storing session. */ + public static final String DEFAULT_COLLECTION_NAME = "sessions"; + + private static final Log logger = LogFactory.getLog(ReactiveMongoSessionRepository.class); + + private final ReactiveMongoOperations mongoOperations; + + private Duration defaultMaxInactiveInterval = Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS); + + private String collectionName = DEFAULT_COLLECTION_NAME; + + private AbstractMongoSessionConverter mongoSessionConverter = + new JdkMongoSessionConverter(this.defaultMaxInactiveInterval); + + private MongoOperations blockingMongoOperations; + + private ApplicationEventPublisher eventPublisher; + + private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance(); + + public ReactiveMongoSessionRepository(ReactiveMongoOperations mongoOperations) { + this.mongoOperations = mongoOperations; + } + + /** + * Creates a new {@link MongoSession} that is capable of being persisted by this {@link ReactiveSessionRepository}. + * + *

This allows optimizations and customizations in how the {@link MongoSession} is persisted. For example, the + * implementation returned might keep track of the changes ensuring that only the delta needs to be persisted on a + * save. + * + * @return a new {@link MongoSession} that is capable of being persisted by this {@link ReactiveSessionRepository} + */ + @Override + public Mono createSession() { + // @formatter:off + return Mono.fromSupplier(() -> this.sessionIdGenerator.generate()) + .zipWith(Mono.just(this.defaultMaxInactiveInterval.toSeconds())) + .map((tuple) -> new MongoSession(tuple.getT1(), tuple.getT2())) + .doOnNext((mongoSession) -> mongoSession.setMaxInactiveInterval(this.defaultMaxInactiveInterval)) + .doOnNext((mongoSession) -> mongoSession.setSessionIdGenerator(this.sessionIdGenerator)) + .doOnNext((mongoSession) -> publishEvent(new SessionCreatedEvent(this, mongoSession))) + .switchIfEmpty(Mono.just(new MongoSession(this.sessionIdGenerator))) + .subscribeOn(Schedulers.boundedElastic()) + .publishOn(Schedulers.parallel()); + // @formatter:on + } + + @Override + public Mono save(MongoSession session) { + + return Mono // + .justOrEmpty(MongoSessionUtils.convertToDBObject(this.mongoSessionConverter, session)) // + .flatMap((dbObject) -> { + if (session.hasChangedSessionId()) { + + return this.mongoOperations + .remove( + Query.query(Criteria.where("_id").is(session.getOriginalSessionId())), + this.collectionName) // + .then(this.mongoOperations.save(dbObject, this.collectionName)); + } else { + + return this.mongoOperations.save(dbObject, this.collectionName); + } + }) // + .then(); + } + + @Override + public Mono findById(String id) { + + return findSession(id) // + .map((document) -> MongoSessionUtils.convertToSession(this.mongoSessionConverter, document)) // + .filter((mongoSession) -> !mongoSession.isExpired()) // + .doOnNext((mongoSession) -> mongoSession.setSessionIdGenerator(this.sessionIdGenerator)) + .switchIfEmpty(Mono.defer(() -> this.deleteById(id).then(Mono.empty()))); + } + + @Override + public Mono deleteById(String id) { + + return findSession(id) // + .flatMap((document) -> this.mongoOperations + .remove(document, this.collectionName) // + .then(Mono.just(document))) // + .map((document) -> MongoSessionUtils.convertToSession(this.mongoSessionConverter, document)) // + .doOnNext((mongoSession) -> publishEvent(new SessionDeletedEvent(this, mongoSession))) // + .then(); + } + + /** + * Do not use {@link org.springframework.data.mongodb.core.index.ReactiveIndexOperations} to ensure indexes exist. + * Instead, get a blocking {@link IndexOperations} and use that instead, if possible. + */ + @Override + public void afterPropertiesSet() { + + if (this.blockingMongoOperations != null) { + + IndexOperations indexOperations = this.blockingMongoOperations.indexOps(this.collectionName); + this.mongoSessionConverter.ensureIndexes(indexOperations); + } + } + + private Mono findSession(String id) { + return this.mongoOperations.findById(id, Document.class, this.collectionName); + } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) { + this.eventPublisher = eventPublisher; + } + + private void publishEvent(ApplicationEvent event) { + + try { + this.eventPublisher.publishEvent(event); + } catch (Throwable ex) { + logger.error("Error publishing " + event + ".", ex); + } + } + + /** + * Set the maximum inactive interval in seconds between requests before newly created sessions will be invalidated. + * A negative time indicates that the session will never time out. The default is 30 minutes. + * + * @param defaultMaxInactiveInterval the default maxInactiveInterval + */ + public void setDefaultMaxInactiveInterval(Duration defaultMaxInactiveInterval) { + Assert.notNull(defaultMaxInactiveInterval, "defaultMaxInactiveInterval must not be null"); + this.defaultMaxInactiveInterval = defaultMaxInactiveInterval; + } + + /** + * Set the maximum inactive interval in seconds between requests before newly created sessions will be invalidated. + * A negative time indicates that the session will never time out. The default is 1800 (30 minutes). + * + * @param defaultMaxInactiveInterval the default maxInactiveInterval in seconds + * @deprecated since 3.0.0, in favor of {@link #setDefaultMaxInactiveInterval(Duration)} + */ + @Deprecated(since = "3.0.0") + public void setMaxInactiveIntervalInSeconds(Integer defaultMaxInactiveInterval) { + setDefaultMaxInactiveInterval(Duration.ofSeconds(defaultMaxInactiveInterval)); + } + + public String getCollectionName() { + return this.collectionName; + } + + public void setCollectionName(final String collectionName) { + this.collectionName = collectionName; + } + + public void setMongoSessionConverter(final AbstractMongoSessionConverter mongoSessionConverter) { + this.mongoSessionConverter = mongoSessionConverter; + } + + public void setBlockingMongoOperations(final MongoOperations blockingMongoOperations) { + this.blockingMongoOperations = blockingMongoOperations; + } + + public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) { + Assert.notNull(sessionIdGenerator, "sessionIdGenerator cannot be null"); + this.sessionIdGenerator = sessionIdGenerator; + } } diff --git a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/http/EnableMongoHttpSession.java b/src/main/java/org/springframework/session/data/mongo/config/annotation/web/http/EnableMongoHttpSession.java index a314592..1552e84 100644 --- a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/http/EnableMongoHttpSession.java +++ b/src/main/java/org/springframework/session/data/mongo/config/annotation/web/http/EnableMongoHttpSession.java @@ -1,11 +1,12 @@ /* + * Copyright 2025-present MongoDB, Inc. * Copyright 2014-present the original author or authors. * * 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 * - * https://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -21,16 +22,14 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - import org.springframework.context.annotation.Import; import org.springframework.session.MapSession; import org.springframework.session.data.mongo.MongoIndexedSessionRepository; /** - * Add this annotation to a {@code @Configuration} class to expose the - * SessionRepositoryFilter as a bean named "springSessionRepositoryFilter" and backed by - * Mongo. Use {@code collectionName} to change default name of the collection used to - * store sessions. + * Add this annotation to a {@code @Configuration} class to expose the SessionRepositoryFilter as a bean named + * "springSessionRepositoryFilter" and backed by Mongo. Use {@code collectionName} to change default name of the + * collection used to store sessions. * *

  * 
@@ -55,16 +54,17 @@
 @Import(MongoHttpSessionConfiguration.class)
 public @interface EnableMongoHttpSession {
 
-	/**
-	 * The maximum time a session will be kept if it is inactive.
-	 * @return default max inactive interval in seconds
-	 */
-	int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
-
-	/**
-	 * The collection name to use.
-	 * @return name of the collection to store session
-	 */
-	String collectionName() default MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME;
+    /**
+     * The maximum time a session will be kept if it is inactive.
+     *
+     * @return default max inactive interval in seconds
+     */
+    int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
 
+    /**
+     * The collection name to use.
+     *
+     * @return name of the collection to store session
+     */
+    String collectionName() default MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME;
 }
diff --git a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/http/MongoHttpSessionConfiguration.java b/src/main/java/org/springframework/session/data/mongo/config/annotation/web/http/MongoHttpSessionConfiguration.java
index a2c7e35..222cd22 100644
--- a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/http/MongoHttpSessionConfiguration.java
+++ b/src/main/java/org/springframework/session/data/mongo/config/annotation/web/http/MongoHttpSessionConfiguration.java
@@ -1,11 +1,12 @@
 /*
+ * Copyright 2025-present MongoDB, Inc.
  * Copyright 2014-present the original author or authors.
  *
  * 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
  *
- *      https://www.apache.org/licenses/LICENSE-2.0
+ *   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,
@@ -19,7 +20,6 @@
 import java.time.Duration;
 import java.util.List;
 import java.util.stream.Collectors;
-
 import org.springframework.beans.factory.BeanClassLoaderAware;
 import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -47,8 +47,8 @@
 import org.springframework.util.StringValueResolver;
 
 /**
- * Configuration class registering {@code MongoSessionRepository} bean. To import this
- * configuration use {@link EnableMongoHttpSession} annotation.
+ * Configuration class registering {@code MongoSessionRepository} bean. To import this configuration use
+ * {@link EnableMongoHttpSession} annotation.
  *
  * @author Jakub Kubrynski
  * @author Eddú Meléndez
@@ -58,116 +58,116 @@
 @Import(SpringHttpSessionConfiguration.class)
 public class MongoHttpSessionConfiguration implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
 
-	private AbstractMongoSessionConverter mongoSessionConverter;
-
-	private Duration maxInactiveInterval = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL;
+    private AbstractMongoSessionConverter mongoSessionConverter;
 
-	private String collectionName;
+    private Duration maxInactiveInterval = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL;
 
-	private StringValueResolver embeddedValueResolver;
+    private String collectionName;
 
-	private List> sessionRepositoryCustomizers;
+    private StringValueResolver embeddedValueResolver;
 
-	private ClassLoader classLoader;
+    private List> sessionRepositoryCustomizers;
 
-	private IndexResolver indexResolver;
+    private ClassLoader classLoader;
 
-	private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance();
+    private IndexResolver indexResolver;
 
-	@Bean
-	public MongoIndexedSessionRepository mongoSessionRepository(MongoOperations mongoOperations) {
+    private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance();
 
-		MongoIndexedSessionRepository repository = new MongoIndexedSessionRepository(mongoOperations);
-		repository.setDefaultMaxInactiveInterval(this.maxInactiveInterval);
+    @Bean
+    public MongoIndexedSessionRepository mongoSessionRepository(MongoOperations mongoOperations) {
 
-		if (this.mongoSessionConverter != null) {
-			repository.setMongoSessionConverter(this.mongoSessionConverter);
+        MongoIndexedSessionRepository repository = new MongoIndexedSessionRepository(mongoOperations);
+        repository.setDefaultMaxInactiveInterval(this.maxInactiveInterval);
 
-			if (this.indexResolver != null) {
-				this.mongoSessionConverter.setIndexResolver(this.indexResolver);
-			}
-		}
-		else {
-			JdkMongoSessionConverter mongoSessionConverter = new JdkMongoSessionConverter(new SerializingConverter(),
-					new DeserializingConverter(this.classLoader),
-					Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS));
+        if (this.mongoSessionConverter != null) {
+            repository.setMongoSessionConverter(this.mongoSessionConverter);
 
-			if (this.indexResolver != null) {
-				mongoSessionConverter.setIndexResolver(this.indexResolver);
-			}
+            if (this.indexResolver != null) {
+                this.mongoSessionConverter.setIndexResolver(this.indexResolver);
+            }
+        } else {
+            JdkMongoSessionConverter mongoSessionConverter = new JdkMongoSessionConverter(
+                    new SerializingConverter(),
+                    new DeserializingConverter(this.classLoader),
+                    Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS));
 
-			repository.setMongoSessionConverter(mongoSessionConverter);
-		}
+            if (this.indexResolver != null) {
+                mongoSessionConverter.setIndexResolver(this.indexResolver);
+            }
 
-		if (StringUtils.hasText(this.collectionName)) {
-			repository.setCollectionName(this.collectionName);
-		}
-		repository.setSessionIdGenerator(this.sessionIdGenerator);
+            repository.setMongoSessionConverter(mongoSessionConverter);
+        }
 
-		this.sessionRepositoryCustomizers
-			.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(repository));
+        if (StringUtils.hasText(this.collectionName)) {
+            repository.setCollectionName(this.collectionName);
+        }
+        repository.setSessionIdGenerator(this.sessionIdGenerator);
 
-		return repository;
-	}
+        this.sessionRepositoryCustomizers.forEach(
+                (sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(repository));
 
-	public void setCollectionName(String collectionName) {
-		this.collectionName = collectionName;
-	}
+        return repository;
+    }
 
-	public void setMaxInactiveInterval(Duration maxInactiveInterval) {
-		this.maxInactiveInterval = maxInactiveInterval;
-	}
+    public void setCollectionName(String collectionName) {
+        this.collectionName = collectionName;
+    }
 
-	@Deprecated
-	public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {
-		setMaxInactiveInterval(Duration.ofSeconds(maxInactiveIntervalInSeconds));
-	}
+    public void setMaxInactiveInterval(Duration maxInactiveInterval) {
+        this.maxInactiveInterval = maxInactiveInterval;
+    }
 
-	public void setImportMetadata(AnnotationMetadata importMetadata) {
+    @Deprecated
+    public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {
+        setMaxInactiveInterval(Duration.ofSeconds(maxInactiveIntervalInSeconds));
+    }
 
-		AnnotationAttributes attributes = AnnotationAttributes
-			.fromMap(importMetadata.getAnnotationAttributes(EnableMongoHttpSession.class.getName()));
+    public void setImportMetadata(AnnotationMetadata importMetadata) {
 
-		if (attributes != null) {
-			this.maxInactiveInterval = Duration
-				.ofSeconds(attributes.getNumber("maxInactiveIntervalInSeconds"));
-		}
+        AnnotationAttributes attributes = AnnotationAttributes.fromMap(
+                importMetadata.getAnnotationAttributes(EnableMongoHttpSession.class.getName()));
 
-		String collectionNameValue = (attributes != null) ? attributes.getString("collectionName") : "";
-		if (StringUtils.hasText(collectionNameValue)) {
-			this.collectionName = this.embeddedValueResolver.resolveStringValue(collectionNameValue);
-		}
-	}
+        if (attributes != null) {
+            this.maxInactiveInterval =
+                    Duration.ofSeconds(attributes.getNumber("maxInactiveIntervalInSeconds"));
+        }
 
-	@Autowired(required = false)
-	public void setMongoSessionConverter(AbstractMongoSessionConverter mongoSessionConverter) {
-		this.mongoSessionConverter = mongoSessionConverter;
-	}
+        String collectionNameValue = (attributes != null) ? attributes.getString("collectionName") : "";
+        if (StringUtils.hasText(collectionNameValue)) {
+            this.collectionName = this.embeddedValueResolver.resolveStringValue(collectionNameValue);
+        }
+    }
 
-	@Autowired(required = false)
-	public void setSessionRepositoryCustomizers(
-			ObjectProvider> sessionRepositoryCustomizers) {
-		this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
-	}
+    @Autowired(required = false)
+    public void setMongoSessionConverter(AbstractMongoSessionConverter mongoSessionConverter) {
+        this.mongoSessionConverter = mongoSessionConverter;
+    }
 
-	@Override
-	public void setBeanClassLoader(ClassLoader classLoader) {
-		this.classLoader = classLoader;
-	}
+    @Autowired(required = false)
+    public void setSessionRepositoryCustomizers(
+            ObjectProvider> sessionRepositoryCustomizers) {
+        this.sessionRepositoryCustomizers =
+                sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
+    }
 
-	@Override
-	public void setEmbeddedValueResolver(StringValueResolver resolver) {
-		this.embeddedValueResolver = resolver;
-	}
+    @Override
+    public void setBeanClassLoader(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
 
-	@Autowired(required = false)
-	public void setIndexResolver(IndexResolver indexResolver) {
-		this.indexResolver = indexResolver;
-	}
+    @Override
+    public void setEmbeddedValueResolver(StringValueResolver resolver) {
+        this.embeddedValueResolver = resolver;
+    }
 
-	@Autowired(required = false)
-	public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
-		this.sessionIdGenerator = sessionIdGenerator;
-	}
+    @Autowired(required = false)
+    public void setIndexResolver(IndexResolver indexResolver) {
+        this.indexResolver = indexResolver;
+    }
 
+    @Autowired(required = false)
+    public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
+        this.sessionIdGenerator = sessionIdGenerator;
+    }
 }
diff --git a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/reactive/EnableMongoWebSession.java b/src/main/java/org/springframework/session/data/mongo/config/annotation/web/reactive/EnableMongoWebSession.java
index 9581e45..2478ee5 100644
--- a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/reactive/EnableMongoWebSession.java
+++ b/src/main/java/org/springframework/session/data/mongo/config/annotation/web/reactive/EnableMongoWebSession.java
@@ -1,11 +1,12 @@
 /*
+ * Copyright 2025-present MongoDB, Inc.
  * Copyright 2014-present the original author or authors.
  *
  * 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
  *
- *      https://www.apache.org/licenses/LICENSE-2.0
+ *   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,
@@ -19,16 +20,14 @@
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
-
 import org.springframework.context.annotation.Import;
 import org.springframework.session.MapSession;
 import org.springframework.session.data.mongo.ReactiveMongoSessionRepository;
 
 /**
- * Add this annotation to a {@code @Configuration} class to configure a MongoDB-based
- * {@code WebSessionManager} for a WebFlux application. This annotation assumes a
- * {@code ReactorMongoOperations} is defined somewhere in the application context. If not,
- * it will fail with a clear error messages. For example:
+ * Add this annotation to a {@code @Configuration} class to configure a MongoDB-based {@code WebSessionManager} for a
+ * WebFlux application. This annotation assumes a {@code ReactorMongoOperations} is defined somewhere in the application
+ * context. If not, it will fail with a clear error messages. For example:
  *
  * 
  * 
@@ -49,21 +48,22 @@
  * @since 2.0
  */
 @Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
-@Target({ java.lang.annotation.ElementType.TYPE })
+@Target({java.lang.annotation.ElementType.TYPE})
 @Documented
 @Import(ReactiveMongoWebSessionConfiguration.class)
 public @interface EnableMongoWebSession {
 
-	/**
-	 * The maximum time a session will be kept if it is inactive.
-	 * @return default max inactive interval in seconds
-	 */
-	int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
-
-	/**
-	 * The collection name to use.
-	 * @return name of the collection to store session
-	 */
-	String collectionName() default ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME;
+    /**
+     * The maximum time a session will be kept if it is inactive.
+     *
+     * @return default max inactive interval in seconds
+     */
+    int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
 
+    /**
+     * The collection name to use.
+     *
+     * @return name of the collection to store session
+     */
+    String collectionName() default ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME;
 }
diff --git a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java b/src/main/java/org/springframework/session/data/mongo/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java
index 08d70bf..e6b263b 100644
--- a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java
+++ b/src/main/java/org/springframework/session/data/mongo/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java
@@ -1,11 +1,12 @@
 /*
+ * Copyright 2025-present MongoDB, Inc.
  * Copyright 2014-present the original author or authors.
  *
  * 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
  *
- *      https://www.apache.org/licenses/LICENSE-2.0
+ *   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,
@@ -19,7 +20,6 @@
 import java.time.Duration;
 import java.util.List;
 import java.util.stream.Collectors;
-
 import org.springframework.beans.factory.BeanClassLoaderAware;
 import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -48,8 +48,7 @@
 import org.springframework.util.StringValueResolver;
 
 /**
- * Configure a {@link ReactiveMongoSessionRepository} using a provided
- * {@link ReactiveMongoOperations}.
+ * Configure a {@link ReactiveMongoSessionRepository} using a provided {@link ReactiveMongoOperations}.
  *
  * @author Greg Turnquist
  * @author Vedran Pavic
@@ -57,138 +56,138 @@
 @Configuration(proxyBeanMethods = false)
 @Import(SpringWebSessionConfiguration.class)
 public class ReactiveMongoWebSessionConfiguration
-		implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
-
-	private AbstractMongoSessionConverter mongoSessionConverter;
-
-	private Duration maxInactiveInterval = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL;
-
-	private String collectionName;
-
-	private StringValueResolver embeddedValueResolver;
+        implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
 
-	private List> sessionRepositoryCustomizers;
+    private AbstractMongoSessionConverter mongoSessionConverter;
 
-	@Autowired(required = false)
-	private MongoOperations mongoOperations;
+    private Duration maxInactiveInterval = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL;
 
-	private ClassLoader classLoader;
+    private String collectionName;
 
-	private IndexResolver indexResolver;
+    private StringValueResolver embeddedValueResolver;
 
-	private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance();
+    private List> sessionRepositoryCustomizers;
 
-	@Bean
-	public ReactiveMongoSessionRepository reactiveMongoSessionRepository(ReactiveMongoOperations operations) {
+    @Autowired(required = false)
+    private MongoOperations mongoOperations;
 
-		ReactiveMongoSessionRepository repository = new ReactiveMongoSessionRepository(operations);
+    private ClassLoader classLoader;
 
-		if (this.mongoSessionConverter != null) {
-			repository.setMongoSessionConverter(this.mongoSessionConverter);
+    private IndexResolver indexResolver;
 
-			if (this.indexResolver != null) {
-				this.mongoSessionConverter.setIndexResolver(this.indexResolver);
-			}
+    private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance();
 
-		}
-		else {
-			JdkMongoSessionConverter mongoSessionConverter = new JdkMongoSessionConverter(new SerializingConverter(),
-					new DeserializingConverter(this.classLoader),
-					Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS));
+    @Bean
+    public ReactiveMongoSessionRepository reactiveMongoSessionRepository(ReactiveMongoOperations operations) {
 
-			if (this.indexResolver != null) {
-				mongoSessionConverter.setIndexResolver(this.indexResolver);
-			}
+        ReactiveMongoSessionRepository repository = new ReactiveMongoSessionRepository(operations);
 
-			repository.setMongoSessionConverter(mongoSessionConverter);
-		}
+        if (this.mongoSessionConverter != null) {
+            repository.setMongoSessionConverter(this.mongoSessionConverter);
 
-		repository.setDefaultMaxInactiveInterval(this.maxInactiveInterval);
+            if (this.indexResolver != null) {
+                this.mongoSessionConverter.setIndexResolver(this.indexResolver);
+            }
 
-		if (this.collectionName != null) {
-			repository.setCollectionName(this.collectionName);
-		}
+        } else {
+            JdkMongoSessionConverter mongoSessionConverter = new JdkMongoSessionConverter(
+                    new SerializingConverter(),
+                    new DeserializingConverter(this.classLoader),
+                    Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS));
 
-		if (this.mongoOperations != null) {
-			repository.setBlockingMongoOperations(this.mongoOperations);
-		}
+            if (this.indexResolver != null) {
+                mongoSessionConverter.setIndexResolver(this.indexResolver);
+            }
 
-		this.sessionRepositoryCustomizers
-			.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(repository));
+            repository.setMongoSessionConverter(mongoSessionConverter);
+        }
 
-		repository.setSessionIdGenerator(this.sessionIdGenerator);
+        repository.setDefaultMaxInactiveInterval(this.maxInactiveInterval);
 
-		return repository;
-	}
+        if (this.collectionName != null) {
+            repository.setCollectionName(this.collectionName);
+        }
 
-	@Autowired(required = false)
-	public void setMongoSessionConverter(AbstractMongoSessionConverter mongoSessionConverter) {
-		this.mongoSessionConverter = mongoSessionConverter;
-	}
+        if (this.mongoOperations != null) {
+            repository.setBlockingMongoOperations(this.mongoOperations);
+        }
 
-	@Override
-	public void setImportMetadata(AnnotationMetadata importMetadata) {
+        this.sessionRepositoryCustomizers.forEach(
+                (sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(repository));
 
-		AnnotationAttributes attributes = AnnotationAttributes
-			.fromMap(importMetadata.getAnnotationAttributes(EnableMongoWebSession.class.getName()));
+        repository.setSessionIdGenerator(this.sessionIdGenerator);
 
-		if (attributes != null) {
-			this.maxInactiveInterval = Duration
-				.ofSeconds(attributes.getNumber("maxInactiveIntervalInSeconds"));
-		}
+        return repository;
+    }
 
-		String collectionNameValue = (attributes != null) ? attributes.getString("collectionName") : "";
-		if (StringUtils.hasText(collectionNameValue)) {
-			this.collectionName = this.embeddedValueResolver.resolveStringValue(collectionNameValue);
-		}
+    @Autowired(required = false)
+    public void setMongoSessionConverter(AbstractMongoSessionConverter mongoSessionConverter) {
+        this.mongoSessionConverter = mongoSessionConverter;
+    }
 
-	}
+    @Override
+    public void setImportMetadata(AnnotationMetadata importMetadata) {
 
-	@Override
-	public void setBeanClassLoader(ClassLoader classLoader) {
-		this.classLoader = classLoader;
-	}
+        AnnotationAttributes attributes = AnnotationAttributes.fromMap(
+                importMetadata.getAnnotationAttributes(EnableMongoWebSession.class.getName()));
 
-	@Override
-	public void setEmbeddedValueResolver(StringValueResolver embeddedValueResolver) {
-		this.embeddedValueResolver = embeddedValueResolver;
-	}
+        if (attributes != null) {
+            this.maxInactiveInterval =
+                    Duration.ofSeconds(attributes.getNumber("maxInactiveIntervalInSeconds"));
+        }
 
-	public Duration getMaxInactiveInterval() {
-		return this.maxInactiveInterval;
-	}
+        String collectionNameValue = (attributes != null) ? attributes.getString("collectionName") : "";
+        if (StringUtils.hasText(collectionNameValue)) {
+            this.collectionName = this.embeddedValueResolver.resolveStringValue(collectionNameValue);
+        }
+    }
 
-	public void setMaxInactiveInterval(Duration maxInactiveInterval) {
-		this.maxInactiveInterval = maxInactiveInterval;
-	}
+    @Override
+    public void setBeanClassLoader(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
 
-	@Deprecated
-	public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {
-		setMaxInactiveInterval(Duration.ofSeconds(maxInactiveIntervalInSeconds));
-	}
+    @Override
+    public void setEmbeddedValueResolver(StringValueResolver embeddedValueResolver) {
+        this.embeddedValueResolver = embeddedValueResolver;
+    }
 
-	public String getCollectionName() {
-		return this.collectionName;
-	}
+    public Duration getMaxInactiveInterval() {
+        return this.maxInactiveInterval;
+    }
 
-	public void setCollectionName(String collectionName) {
-		this.collectionName = collectionName;
-	}
+    public void setMaxInactiveInterval(Duration maxInactiveInterval) {
+        this.maxInactiveInterval = maxInactiveInterval;
+    }
 
-	@Autowired(required = false)
-	public void setSessionRepositoryCustomizers(
-			ObjectProvider> sessionRepositoryCustomizers) {
-		this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
-	}
+    @Deprecated
+    public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {
+        setMaxInactiveInterval(Duration.ofSeconds(maxInactiveIntervalInSeconds));
+    }
 
-	@Autowired(required = false)
-	public void setIndexResolver(IndexResolver indexResolver) {
-		this.indexResolver = indexResolver;
-	}
+    public String getCollectionName() {
+        return this.collectionName;
+    }
 
-	@Autowired(required = false)
-	public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
-		this.sessionIdGenerator = sessionIdGenerator;
-	}
+    public void setCollectionName(String collectionName) {
+        this.collectionName = collectionName;
+    }
 
+    @Autowired(required = false)
+    public void setSessionRepositoryCustomizers(
+            ObjectProvider>
+                    sessionRepositoryCustomizers) {
+        this.sessionRepositoryCustomizers =
+                sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
+    }
+
+    @Autowired(required = false)
+    public void setIndexResolver(IndexResolver indexResolver) {
+        this.indexResolver = indexResolver;
+    }
+
+    @Autowired(required = false)
+    public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
+        this.sessionIdGenerator = sessionIdGenerator;
+    }
 }
diff --git a/src/test/java/org/springframework/session/data/mongo/AbstractMongoSessionConverterTests.java b/src/test/java/org/springframework/session/data/mongo/AbstractMongoSessionConverterTests.java
index 78054f9..ef5413d 100644
--- a/src/test/java/org/springframework/session/data/mongo/AbstractMongoSessionConverterTests.java
+++ b/src/test/java/org/springframework/session/data/mongo/AbstractMongoSessionConverterTests.java
@@ -1,11 +1,12 @@
 /*
+ * Copyright 2025-present MongoDB, Inc.
  * Copyright 2014-present the original author or authors.
  *
  * 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
  *
- *      https://www.apache.org/licenses/LICENSE-2.0
+ *   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,
@@ -16,125 +17,120 @@
 
 package org.springframework.session.data.mongo;
 
-import java.time.Duration;
+import static org.assertj.core.api.Assertions.assertThat;
 
 import com.mongodb.DBObject;
+import java.time.Duration;
 import org.bson.Document;
 import org.junit.jupiter.api.Test;
-
 import org.springframework.core.convert.TypeDescriptor;
 import org.springframework.lang.Nullable;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.context.SecurityContextImpl;
 import org.springframework.session.FindByIndexNameSessionRepository;
 
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * @author Greg Turnquist
- */
+/** @author Greg Turnquist */
 public abstract class AbstractMongoSessionConverterTests {
 
-	abstract AbstractMongoSessionConverter getMongoSessionConverter();
-
-	@Test
-	void verifyRoundTripSerialization() throws Exception {
+    abstract AbstractMongoSessionConverter getMongoSessionConverter();
 
-		// given
-		MongoSession toSerialize = new MongoSession();
-		toSerialize.setAttribute("username", "john_the_springer");
+    @Test
+    void verifyRoundTripSerialization() throws Exception {
 
-		// when
-		DBObject dbObject = convertToDBObject(toSerialize);
-		MongoSession deserialized = convertToSession(dbObject);
+        // given
+        MongoSession toSerialize = new MongoSession();
+        toSerialize.setAttribute("username", "john_the_springer");
 
-		// then
-		assertThat(deserialized).usingRecursiveComparison().isEqualTo(toSerialize);
-	}
+        // when
+        DBObject dbObject = convertToDBObject(toSerialize);
+        MongoSession deserialized = convertToSession(dbObject);
 
-	@Test
-	void verifyRoundTripSecuritySerialization() {
+        // then
+        assertThat(deserialized).usingRecursiveComparison().isEqualTo(toSerialize);
+    }
 
-		// given
-		MongoSession toSerialize = new MongoSession();
-		String principalName = "john_the_springer";
-		SecurityContextImpl context = new SecurityContextImpl();
-		context.setAuthentication(new UsernamePasswordAuthenticationToken(principalName, null));
-		toSerialize.setAttribute("SPRING_SECURITY_CONTEXT", context);
+    @Test
+    void verifyRoundTripSecuritySerialization() {
 
-		// when
-		DBObject serialized = convertToDBObject(toSerialize);
-		MongoSession deserialized = convertToSession(serialized);
+        // given
+        MongoSession toSerialize = new MongoSession();
+        String principalName = "john_the_springer";
+        SecurityContextImpl context = new SecurityContextImpl();
+        context.setAuthentication(new UsernamePasswordAuthenticationToken(principalName, null));
+        toSerialize.setAttribute("SPRING_SECURITY_CONTEXT", context);
 
-		// then
-		assertThat(deserialized).usingRecursiveComparison().isEqualTo(toSerialize);
+        // when
+        DBObject serialized = convertToDBObject(toSerialize);
+        MongoSession deserialized = convertToSession(serialized);
 
-		SecurityContextImpl springSecurityContextBefore = toSerialize.getAttribute("SPRING_SECURITY_CONTEXT");
-		SecurityContextImpl springSecurityContextAfter = deserialized.getAttribute("SPRING_SECURITY_CONTEXT");
+        // then
+        assertThat(deserialized).usingRecursiveComparison().isEqualTo(toSerialize);
 
-		assertThat(springSecurityContextBefore).usingRecursiveComparison().isEqualTo(springSecurityContextAfter);
-		assertThat(springSecurityContextAfter.getAuthentication().getPrincipal()).isEqualTo("john_the_springer");
-		assertThat(springSecurityContextAfter.getAuthentication().getCredentials()).isNull();
-	}
+        SecurityContextImpl springSecurityContextBefore = toSerialize.getAttribute("SPRING_SECURITY_CONTEXT");
+        SecurityContextImpl springSecurityContextAfter = deserialized.getAttribute("SPRING_SECURITY_CONTEXT");
 
-	@Test
-	void shouldExtractPrincipalNameFromAttributes() throws Exception {
+        assertThat(springSecurityContextBefore).usingRecursiveComparison().isEqualTo(springSecurityContextAfter);
+        assertThat(springSecurityContextAfter.getAuthentication().getPrincipal())
+                .isEqualTo("john_the_springer");
+        assertThat(springSecurityContextAfter.getAuthentication().getCredentials())
+                .isNull();
+    }
 
-		// given
-		MongoSession toSerialize = new MongoSession();
-		String principalName = "john_the_springer";
-		toSerialize.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principalName);
+    @Test
+    void shouldExtractPrincipalNameFromAttributes() throws Exception {
 
-		// when
-		DBObject dbObject = convertToDBObject(toSerialize);
+        // given
+        MongoSession toSerialize = new MongoSession();
+        String principalName = "john_the_springer";
+        toSerialize.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principalName);
 
-		// then
-		assertThat(dbObject.get("principal")).isEqualTo(principalName);
-	}
+        // when
+        DBObject dbObject = convertToDBObject(toSerialize);
 
-	@Test
-	void shouldExtractPrincipalNameFromAuthentication() throws Exception {
+        // then
+        assertThat(dbObject.get("principal")).isEqualTo(principalName);
+    }
 
-		// given
-		MongoSession toSerialize = new MongoSession();
-		String principalName = "john_the_springer";
-		SecurityContextImpl context = new SecurityContextImpl();
-		context.setAuthentication(new UsernamePasswordAuthenticationToken(principalName, null));
-		toSerialize.setAttribute("SPRING_SECURITY_CONTEXT", context);
+    @Test
+    void shouldExtractPrincipalNameFromAuthentication() throws Exception {
 
-		// when
-		DBObject dbObject = convertToDBObject(toSerialize);
+        // given
+        MongoSession toSerialize = new MongoSession();
+        String principalName = "john_the_springer";
+        SecurityContextImpl context = new SecurityContextImpl();
+        context.setAuthentication(new UsernamePasswordAuthenticationToken(principalName, null));
+        toSerialize.setAttribute("SPRING_SECURITY_CONTEXT", context);
 
-		// then
-		assertThat(dbObject.get("principal")).isEqualTo(principalName);
-	}
+        // when
+        DBObject dbObject = convertToDBObject(toSerialize);
 
-	@Test
-	void sessionWrapperWithNoMaxIntervalShouldFallbackToDefaultValues() {
+        // then
+        assertThat(dbObject.get("principal")).isEqualTo(principalName);
+    }
 
-		// given
-		MongoSession toSerialize = new MongoSession();
-		DBObject dbObject = convertToDBObject(toSerialize);
-		Document document = new Document(dbObject.toMap());
-		document.remove("interval");
+    @Test
+    void sessionWrapperWithNoMaxIntervalShouldFallbackToDefaultValues() {
 
-		// when
-		MongoSession convertedSession = getMongoSessionConverter().convert(document);
+        // given
+        MongoSession toSerialize = new MongoSession();
+        DBObject dbObject = convertToDBObject(toSerialize);
+        Document document = new Document(dbObject.toMap());
+        document.remove("interval");
 
-		// then
-		assertThat(convertedSession.getMaxInactiveInterval()).isEqualTo(Duration.ofMinutes(30));
-	}
+        // when
+        MongoSession convertedSession = getMongoSessionConverter().convert(document);
 
-	@Nullable
-	MongoSession convertToSession(DBObject session) {
-		return (MongoSession) getMongoSessionConverter().convert(session, TypeDescriptor.valueOf(DBObject.class),
-				TypeDescriptor.valueOf(MongoSession.class));
-	}
+        // then
+        assertThat(convertedSession.getMaxInactiveInterval()).isEqualTo(Duration.ofMinutes(30));
+    }
 
-	@Nullable
-	DBObject convertToDBObject(MongoSession session) {
-		return (DBObject) getMongoSessionConverter().convert(session, TypeDescriptor.valueOf(MongoSession.class),
-				TypeDescriptor.valueOf(DBObject.class));
-	}
+    @Nullable MongoSession convertToSession(DBObject session) {
+        return (MongoSession) getMongoSessionConverter()
+                .convert(session, TypeDescriptor.valueOf(DBObject.class), TypeDescriptor.valueOf(MongoSession.class));
+    }
 
+    @Nullable DBObject convertToDBObject(MongoSession session) {
+        return (DBObject) getMongoSessionConverter()
+                .convert(session, TypeDescriptor.valueOf(MongoSession.class), TypeDescriptor.valueOf(DBObject.class));
+    }
 }
diff --git a/src/test/java/org/springframework/session/data/mongo/JacksonMongoSessionConverterTests.java b/src/test/java/org/springframework/session/data/mongo/JacksonMongoSessionConverterTests.java
index 26efdbe..3af55b6 100644
--- a/src/test/java/org/springframework/session/data/mongo/JacksonMongoSessionConverterTests.java
+++ b/src/test/java/org/springframework/session/data/mongo/JacksonMongoSessionConverterTests.java
@@ -1,11 +1,12 @@
 /*
+ * Copyright 2025-present MongoDB, Inc.
  * Copyright 2014-present the original author or authors.
  *
  * 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
  *
- *      https://www.apache.org/licenses/LICENSE-2.0
+ *   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,
@@ -16,18 +17,16 @@
 
 package org.springframework.session.data.mongo;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mongodb.DBObject;
 import java.lang.reflect.Field;
 import java.util.Date;
 import java.util.HashMap;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.mongodb.DBObject;
 import org.assertj.core.api.Assertions;
 import org.assertj.core.api.AssertionsForClassTypes;
 import org.bson.Document;
 import org.bson.types.ObjectId;
 import org.junit.jupiter.api.Test;
-
 import org.springframework.data.mongodb.core.query.Query;
 import org.springframework.util.ReflectionUtils;
 
@@ -37,94 +36,94 @@
  */
 class JacksonMongoSessionConverterTests extends AbstractMongoSessionConverterTests {
 
-	JacksonMongoSessionConverter mongoSessionConverter = new JacksonMongoSessionConverter();
-
-	@Override
-	AbstractMongoSessionConverter getMongoSessionConverter() {
-		return this.mongoSessionConverter;
-	}
+    JacksonMongoSessionConverter mongoSessionConverter = new JacksonMongoSessionConverter();
 
-	@Test
-	void shouldSaveIdField() {
+    @Override
+    AbstractMongoSessionConverter getMongoSessionConverter() {
+        return this.mongoSessionConverter;
+    }
 
-		// given
-		MongoSession session = new MongoSession();
+    @Test
+    void shouldSaveIdField() {
 
-		// when
-		DBObject convert = this.mongoSessionConverter.convert(session);
+        // given
+        MongoSession session = new MongoSession();
 
-		// then
-		AssertionsForClassTypes.assertThat(convert.get("_id")).isEqualTo(session.getId());
-		AssertionsForClassTypes.assertThat(convert.get("id")).isNull();
-	}
+        // when
+        DBObject convert = this.mongoSessionConverter.convert(session);
 
-	@Test
-	void shouldQueryAgainstAttribute() throws Exception {
+        // then
+        AssertionsForClassTypes.assertThat(convert.get("_id")).isEqualTo(session.getId());
+        AssertionsForClassTypes.assertThat(convert.get("id")).isNull();
+    }
 
-		// when
-		Query cart = this.mongoSessionConverter.getQueryForIndex("cart", "my-cart");
+    @Test
+    void shouldQueryAgainstAttribute() throws Exception {
 
-		// then
-		AssertionsForClassTypes.assertThat(cart.getQueryObject().get("attrs.cart")).isEqualTo("my-cart");
-	}
+        // when
+        Query cart = this.mongoSessionConverter.getQueryForIndex("cart", "my-cart");
 
-	@Test
-	void shouldAllowCustomObjectMapper() {
+        // then
+        AssertionsForClassTypes.assertThat(cart.getQueryObject().get("attrs.cart"))
+                .isEqualTo("my-cart");
+    }
 
-		// given
-		ObjectMapper myMapper = new ObjectMapper();
+    @Test
+    void shouldAllowCustomObjectMapper() {
 
-		// when
-		JacksonMongoSessionConverter converter = new JacksonMongoSessionConverter(myMapper);
+        // given
+        ObjectMapper myMapper = new ObjectMapper();
 
-		// then
-		Field objectMapperField = ReflectionUtils.findField(JacksonMongoSessionConverter.class, "objectMapper");
-		ReflectionUtils.makeAccessible(objectMapperField);
-		ObjectMapper converterMapper = (ObjectMapper) ReflectionUtils.getField(objectMapperField, converter);
+        // when
+        JacksonMongoSessionConverter converter = new JacksonMongoSessionConverter(myMapper);
 
-		AssertionsForClassTypes.assertThat(converterMapper).isEqualTo(myMapper);
-	}
+        // then
+        Field objectMapperField = ReflectionUtils.findField(JacksonMongoSessionConverter.class, "objectMapper");
+        ReflectionUtils.makeAccessible(objectMapperField);
+        ObjectMapper converterMapper = (ObjectMapper) ReflectionUtils.getField(objectMapperField, converter);
 
-	@Test
-	void shouldNotAllowNullObjectMapperToBeInjected() {
+        AssertionsForClassTypes.assertThat(converterMapper).isEqualTo(myMapper);
+    }
 
-		Assertions.assertThatIllegalArgumentException()
-			.isThrownBy(() -> new JacksonMongoSessionConverter((ObjectMapper) null));
-	}
+    @Test
+    void shouldNotAllowNullObjectMapperToBeInjected() {
 
-	@Test
-	void shouldSaveExpireAtAsDate() {
+        Assertions.assertThatIllegalArgumentException()
+                .isThrownBy(() -> new JacksonMongoSessionConverter((ObjectMapper) null));
+    }
 
-		// given
-		MongoSession session = new MongoSession();
+    @Test
+    void shouldSaveExpireAtAsDate() {
 
-		// when
-		DBObject convert = this.mongoSessionConverter.convert(session);
+        // given
+        MongoSession session = new MongoSession();
 
-		// then
-		AssertionsForClassTypes.assertThat(convert.get("expireAt")).isInstanceOf(Date.class);
-		AssertionsForClassTypes.assertThat(convert.get("expireAt")).isEqualTo(session.getExpireAt());
-	}
+        // when
+        DBObject convert = this.mongoSessionConverter.convert(session);
 
-	@Test
-	void shouldLoadExpireAtFromDocument() {
+        // then
+        AssertionsForClassTypes.assertThat(convert.get("expireAt")).isInstanceOf(Date.class);
+        AssertionsForClassTypes.assertThat(convert.get("expireAt")).isEqualTo(session.getExpireAt());
+    }
 
-		// given
-		Date now = new Date();
-		HashMap data = new HashMap<>();
+    @Test
+    void shouldLoadExpireAtFromDocument() {
 
-		data.put("expireAt", now);
-		data.put("@class", MongoSession.class.getName());
-		data.put("_id", new ObjectId().toString());
+        // given
+        Date now = new Date();
+        HashMap data = new HashMap<>();
 
-		Document document = new Document(data);
+        data.put("expireAt", now);
+        data.put("@class", MongoSession.class.getName());
+        data.put("_id", new ObjectId().toString());
 
-		// when
-		MongoSession convertedSession = this.mongoSessionConverter.convert(document);
+        Document document = new Document(data);
 
-		// then
-		AssertionsForClassTypes.assertThat(convertedSession).isNotNull();
-		AssertionsForClassTypes.assertThat(convertedSession.getExpireAt()).isEqualTo(now);
-	}
+        // when
+        MongoSession convertedSession = this.mongoSessionConverter.convert(document);
 
+        // then
+        AssertionsForClassTypes.assertThat(convertedSession).isNotNull();
+        AssertionsForClassTypes.assertThat(convertedSession.getExpireAt()).isEqualTo(now);
+    }
 }
diff --git a/src/test/java/org/springframework/session/data/mongo/JdkMongoSessionConverterTests.java b/src/test/java/org/springframework/session/data/mongo/JdkMongoSessionConverterTests.java
index af4b5de..260671f 100644
--- a/src/test/java/org/springframework/session/data/mongo/JdkMongoSessionConverterTests.java
+++ b/src/test/java/org/springframework/session/data/mongo/JdkMongoSessionConverterTests.java
@@ -1,11 +1,12 @@
 /*
+ * Copyright 2025-present MongoDB, Inc.
  * Copyright 2014-present the original author or authors.
  *
  * 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
  *
- *      https://www.apache.org/licenses/LICENSE-2.0
+ *   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,
@@ -16,15 +17,13 @@
 
 package org.springframework.session.data.mongo;
 
-import java.time.Duration;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
 
+import java.time.Duration;
 import org.junit.jupiter.api.Test;
-
 import org.springframework.core.serializer.support.DeserializingConverter;
 import org.springframework.core.serializer.support.SerializingConverter;
 
-import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
-
 /**
  * @author Jakub Kubrynski
  * @author Rob Winch
@@ -32,25 +31,26 @@
  */
 class JdkMongoSessionConverterTests extends AbstractMongoSessionConverterTests {
 
-	Duration inactiveInterval = Duration.ofMinutes(30);
-
-	JdkMongoSessionConverter mongoSessionConverter = new JdkMongoSessionConverter(this.inactiveInterval);
+    Duration inactiveInterval = Duration.ofMinutes(30);
 
-	@Override
-	AbstractMongoSessionConverter getMongoSessionConverter() {
-		return this.mongoSessionConverter;
-	}
+    JdkMongoSessionConverter mongoSessionConverter = new JdkMongoSessionConverter(this.inactiveInterval);
 
-	@Test
-	void constructorNullSerializer() {
-		assertThatIllegalArgumentException()
-			.isThrownBy(() -> new JdkMongoSessionConverter(null, new DeserializingConverter(), this.inactiveInterval));
-	}
+    @Override
+    AbstractMongoSessionConverter getMongoSessionConverter() {
+        return this.mongoSessionConverter;
+    }
 
-	@Test
-	void constructorNullDeserializer() {
-		assertThatIllegalArgumentException()
-			.isThrownBy(() -> new JdkMongoSessionConverter(new SerializingConverter(), null, this.inactiveInterval));
-	}
+    @Test
+    void constructorNullSerializer() {
+        assertThatIllegalArgumentException()
+                .isThrownBy(
+                        () -> new JdkMongoSessionConverter(null, new DeserializingConverter(), this.inactiveInterval));
+    }
 
+    @Test
+    void constructorNullDeserializer() {
+        assertThatIllegalArgumentException()
+                .isThrownBy(
+                        () -> new JdkMongoSessionConverter(new SerializingConverter(), null, this.inactiveInterval));
+    }
 }
diff --git a/src/test/java/org/springframework/session/data/mongo/MongoIndexedSessionRepositoryTests.java b/src/test/java/org/springframework/session/data/mongo/MongoIndexedSessionRepositoryTests.java
index cb86171..4183bab 100644
--- a/src/test/java/org/springframework/session/data/mongo/MongoIndexedSessionRepositoryTests.java
+++ b/src/test/java/org/springframework/session/data/mongo/MongoIndexedSessionRepositoryTests.java
@@ -1,11 +1,12 @@
 /*
+ * Copyright 2025-present MongoDB, Inc.
  * Copyright 2014-present the original author or authors.
  *
  * 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
  *
- *      https://www.apache.org/licenses/LICENSE-2.0
+ *   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,
@@ -16,21 +17,28 @@
 
 package org.springframework.session.data.mongo;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.mock;
+import static org.mockito.BDDMockito.verify;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DBObject;
 import java.time.Duration;
 import java.time.Instant;
 import java.util.Collections;
 import java.util.Map;
 import java.util.UUID;
-
-import com.mongodb.BasicDBObject;
-import com.mongodb.DBObject;
 import org.bson.Document;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
-
 import org.springframework.core.convert.TypeDescriptor;
 import org.springframework.data.mongodb.core.MongoOperations;
 import org.springframework.data.mongodb.core.query.Query;
@@ -38,15 +46,6 @@
 import org.springframework.session.MapSession;
 import org.springframework.session.SessionIdGenerator;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.BDDMockito.mock;
-import static org.mockito.BDDMockito.verify;
-
 /**
  * Tests for {@link MongoIndexedSessionRepository}.
  *
@@ -57,226 +56,235 @@
 @ExtendWith(MockitoExtension.class)
 class MongoIndexedSessionRepositoryTests {
 
-	@Mock
-	private AbstractMongoSessionConverter converter;
-
-	@Mock
-	private MongoOperations mongoOperations;
-
-	private MongoIndexedSessionRepository repository;
-
-	@BeforeEach
-	void setUp() {
-
-		this.repository = new MongoIndexedSessionRepository(this.mongoOperations);
-		this.repository.setMongoSessionConverter(this.converter);
-	}
-
-	@Test
-	void shouldCreateSession() {
-
-		// when
-		MongoSession session = this.repository.createSession();
-
-		// then
-		assertThat(session.getId()).isNotEmpty();
-		assertThat(session.getMaxInactiveInterval().getSeconds())
-			.isEqualTo(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
-	}
-
-	@Test
-	void shouldSaveSession() {
-
-		// given
-		MongoSession session = new MongoSession();
-		BasicDBObject dbSession = new BasicDBObject();
-
-		given(this.converter.convert(session, TypeDescriptor.valueOf(MongoSession.class),
-				TypeDescriptor.valueOf(DBObject.class)))
-			.willReturn(dbSession);
-		// when
-		this.repository.save(session);
-
-		// then
-		verify(this.mongoOperations).save(dbSession, MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME);
-	}
-
-	@Test
-	void shouldGetSession() {
-
-		// given
-		String sessionId = UUID.randomUUID().toString();
-		Document sessionDocument = new Document();
+    @Mock
+    private AbstractMongoSessionConverter converter;
 
-		given(this.mongoOperations.findById(sessionId, Document.class,
-				MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME))
-			.willReturn(sessionDocument);
+    @Mock
+    private MongoOperations mongoOperations;
 
-		MongoSession session = new MongoSession();
+    private MongoIndexedSessionRepository repository;
 
-		given(this.converter.convert(sessionDocument, TypeDescriptor.valueOf(Document.class),
-				TypeDescriptor.valueOf(MongoSession.class)))
-			.willReturn(session);
+    @BeforeEach
+    void setUp() {
 
-		// when
-		MongoSession retrievedSession = this.repository.findById(sessionId);
+        this.repository = new MongoIndexedSessionRepository(this.mongoOperations);
+        this.repository.setMongoSessionConverter(this.converter);
+    }
 
-		// then
-		assertThat(retrievedSession).isEqualTo(session);
-	}
+    @Test
+    void shouldCreateSession() {
 
-	@Test
-	void shouldHandleExpiredSession() {
+        // when
+        MongoSession session = this.repository.createSession();
 
-		// given
-		String sessionId = UUID.randomUUID().toString();
-		Document sessionDocument = new Document();
+        // then
+        assertThat(session.getId()).isNotEmpty();
+        assertThat(session.getMaxInactiveInterval().getSeconds())
+                .isEqualTo(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
+    }
 
-		given(this.mongoOperations.findById(sessionId, Document.class,
-				MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME))
-			.willReturn(sessionDocument);
+    @Test
+    void shouldSaveSession() {
 
-		MongoSession session = mock(MongoSession.class);
+        // given
+        MongoSession session = new MongoSession();
+        BasicDBObject dbSession = new BasicDBObject();
 
-		given(session.isExpired()).willReturn(true);
-		given(this.converter.convert(sessionDocument, TypeDescriptor.valueOf(Document.class),
-				TypeDescriptor.valueOf(MongoSession.class)))
-			.willReturn(session);
-		given(session.getId()).willReturn("sessionId");
+        given(this.converter.convert(
+                        session, TypeDescriptor.valueOf(MongoSession.class), TypeDescriptor.valueOf(DBObject.class)))
+                .willReturn(dbSession);
+        // when
+        this.repository.save(session);
 
-		// when
-		this.repository.findById(sessionId);
+        // then
+        verify(this.mongoOperations).save(dbSession, MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME);
+    }
 
-		// then
-		verify(this.mongoOperations).remove(any(Document.class),
-				eq(MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME));
-	}
+    @Test
+    void shouldGetSession() {
 
-	@Test
-	void shouldDeleteSession() {
+        // given
+        String sessionId = UUID.randomUUID().toString();
+        Document sessionDocument = new Document();
 
-		// given
-		String sessionId = UUID.randomUUID().toString();
+        given(this.mongoOperations.findById(
+                        sessionId, Document.class, MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME))
+                .willReturn(sessionDocument);
 
-		Document sessionDocument = new Document();
-		sessionDocument.put("id", sessionId);
+        MongoSession session = new MongoSession();
 
-		MongoSession mongoSession = new MongoSession(sessionId, MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
+        given(this.converter.convert(
+                        sessionDocument,
+                        TypeDescriptor.valueOf(Document.class),
+                        TypeDescriptor.valueOf(MongoSession.class)))
+                .willReturn(session);
 
-		given(this.converter.convert(sessionDocument, TypeDescriptor.valueOf(Document.class),
-				TypeDescriptor.valueOf(MongoSession.class)))
-			.willReturn(mongoSession);
-		given(this.mongoOperations.findById(eq(sessionId), eq(Document.class),
-				eq(MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME)))
-			.willReturn(sessionDocument);
+        // when
+        MongoSession retrievedSession = this.repository.findById(sessionId);
 
-		// when
-		this.repository.deleteById(sessionId);
+        // then
+        assertThat(retrievedSession).isEqualTo(session);
+    }
 
-		// then
-		verify(this.mongoOperations).remove(any(Document.class),
-				eq(MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME));
-	}
+    @Test
+    void shouldHandleExpiredSession() {
 
-	@Test
-	void shouldGetSessionsMapByPrincipal() {
+        // given
+        String sessionId = UUID.randomUUID().toString();
+        Document sessionDocument = new Document();
 
-		// given
-		String principalNameIndexName = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
+        given(this.mongoOperations.findById(
+                        sessionId, Document.class, MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME))
+                .willReturn(sessionDocument);
 
-		Document document = new Document();
+        MongoSession session = mock(MongoSession.class);
 
-		given(this.converter.getQueryForIndex(anyString(), any(Object.class))).willReturn(mock(Query.class));
-		given(this.mongoOperations.find(any(Query.class), eq(Document.class),
-				eq(MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME)))
-			.willReturn(Collections.singletonList(document));
+        given(session.isExpired()).willReturn(true);
+        given(this.converter.convert(
+                        sessionDocument,
+                        TypeDescriptor.valueOf(Document.class),
+                        TypeDescriptor.valueOf(MongoSession.class)))
+                .willReturn(session);
+        given(session.getId()).willReturn("sessionId");
 
-		String sessionId = UUID.randomUUID().toString();
+        // when
+        this.repository.findById(sessionId);
 
-		MongoSession session = new MongoSession(sessionId, 1800);
+        // then
+        verify(this.mongoOperations)
+                .remove(any(Document.class), eq(MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME));
+    }
 
-		given(this.converter.convert(document, TypeDescriptor.valueOf(Document.class),
-				TypeDescriptor.valueOf(MongoSession.class)))
-			.willReturn(session);
+    @Test
+    void shouldDeleteSession() {
 
-		// when
-		Map sessionsMap = this.repository.findByIndexNameAndIndexValue(principalNameIndexName,
-				"john");
+        // given
+        String sessionId = UUID.randomUUID().toString();
 
-		// then
-		assertThat(sessionsMap).containsOnlyKeys(sessionId);
-		assertThat(sessionsMap).containsValues(session);
-	}
+        Document sessionDocument = new Document();
+        sessionDocument.put("id", sessionId);
 
-	@Test
-	void shouldReturnEmptyMapForNotSupportedIndex() {
+        MongoSession mongoSession = new MongoSession(sessionId, MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
 
-		// given
-		String index = "some_not_supported_index_name";
+        given(this.converter.convert(
+                        sessionDocument,
+                        TypeDescriptor.valueOf(Document.class),
+                        TypeDescriptor.valueOf(MongoSession.class)))
+                .willReturn(mongoSession);
+        given(this.mongoOperations.findById(
+                        eq(sessionId), eq(Document.class), eq(MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME)))
+                .willReturn(sessionDocument);
+
+        // when
+        this.repository.deleteById(sessionId);
 
-		// when
-		Map sessionsMap = this.repository.findByIndexNameAndIndexValue(index, "some_value");
+        // then
+        verify(this.mongoOperations)
+                .remove(any(Document.class), eq(MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME));
+    }
 
-		// then
-		assertThat(sessionsMap).isEmpty();
-	}
+    @Test
+    void shouldGetSessionsMapByPrincipal() {
 
-	@Test
-	void createSessionWhenSessionIdGeneratorThenUses() {
-		this.repository.setSessionIdGenerator(new FixedSessionIdGenerator("123"));
-		MongoSession session = this.repository.createSession();
-		assertThat(session.getId()).isEqualTo("123");
-		assertThat(session.changeSessionId()).isEqualTo("123");
-	}
+        // given
+        String principalNameIndexName = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
 
-	@Test
-	void setSessionIdGeneratorWhenNullThenThrowsException() {
-		assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setSessionIdGenerator(null));
-	}
+        Document document = new Document();
+
+        given(this.converter.getQueryForIndex(anyString(), any(Object.class))).willReturn(mock(Query.class));
+        given(this.mongoOperations.find(
+                        any(Query.class),
+                        eq(Document.class),
+                        eq(MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME)))
+                .willReturn(Collections.singletonList(document));
 
-	@Test
-	void findByIdWhenChangeSessionIdThenUsesSessionIdGenerator() {
-		this.repository.setSessionIdGenerator(new FixedSessionIdGenerator("456"));
+        String sessionId = UUID.randomUUID().toString();
 
-		Document sessionDocument = new Document();
+        MongoSession session = new MongoSession(sessionId, 1800);
 
-		given(this.mongoOperations.findById("123", Document.class,
-				MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME))
-			.willReturn(sessionDocument);
+        given(this.converter.convert(
+                        document, TypeDescriptor.valueOf(Document.class), TypeDescriptor.valueOf(MongoSession.class)))
+                .willReturn(session);
 
-		MongoSession session = new MongoSession("123");
+        // when
+        Map sessionsMap =
+                this.repository.findByIndexNameAndIndexValue(principalNameIndexName, "john");
 
-		given(this.converter.convert(sessionDocument, TypeDescriptor.valueOf(Document.class),
-				TypeDescriptor.valueOf(MongoSession.class)))
-			.willReturn(session);
+        // then
+        assertThat(sessionsMap).containsOnlyKeys(sessionId);
+        assertThat(sessionsMap).containsValues(session);
+    }
+
+    @Test
+    void shouldReturnEmptyMapForNotSupportedIndex() {
 
-		MongoSession retrievedSession = this.repository.findById("123");
-		assertThat(retrievedSession.getId()).isEqualTo("123");
-		String newSessionId = retrievedSession.changeSessionId();
-		assertThat(newSessionId).isEqualTo("456");
-	}
+        // given
+        String index = "some_not_supported_index_name";
 
-	@Test
-	void createSessionWhenMaxInactiveIntervalSetThenUse() {
-		this.repository.setDefaultMaxInactiveInterval(Duration.ofSeconds(60));
-		MongoSession session = this.repository.createSession();
-		Instant now = Instant.now();
-		assertThat(session.getExpireAt()).isBetween(now.plusSeconds(59), Instant.now().plusSeconds(61));
-	}
+        // when
+        Map sessionsMap = this.repository.findByIndexNameAndIndexValue(index, "some_value");
+
+        // then
+        assertThat(sessionsMap).isEmpty();
+    }
+
+    @Test
+    void createSessionWhenSessionIdGeneratorThenUses() {
+        this.repository.setSessionIdGenerator(new FixedSessionIdGenerator("123"));
+        MongoSession session = this.repository.createSession();
+        assertThat(session.getId()).isEqualTo("123");
+        assertThat(session.changeSessionId()).isEqualTo("123");
+    }
+
+    @Test
+    void setSessionIdGeneratorWhenNullThenThrowsException() {
+        assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setSessionIdGenerator(null));
+    }
+
+    @Test
+    void findByIdWhenChangeSessionIdThenUsesSessionIdGenerator() {
+        this.repository.setSessionIdGenerator(new FixedSessionIdGenerator("456"));
 
-	static class FixedSessionIdGenerator implements SessionIdGenerator {
+        Document sessionDocument = new Document();
 
-		private final String id;
+        given(this.mongoOperations.findById(
+                        "123", Document.class, MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME))
+                .willReturn(sessionDocument);
+
+        MongoSession session = new MongoSession("123");
 
-		FixedSessionIdGenerator(String id) {
-			this.id = id;
-		}
+        given(this.converter.convert(
+                        sessionDocument,
+                        TypeDescriptor.valueOf(Document.class),
+                        TypeDescriptor.valueOf(MongoSession.class)))
+                .willReturn(session);
+
+        MongoSession retrievedSession = this.repository.findById("123");
+        assertThat(retrievedSession.getId()).isEqualTo("123");
+        String newSessionId = retrievedSession.changeSessionId();
+        assertThat(newSessionId).isEqualTo("456");
+    }
+
+    @Test
+    void createSessionWhenMaxInactiveIntervalSetThenUse() {
+        this.repository.setDefaultMaxInactiveInterval(Duration.ofSeconds(60));
+        MongoSession session = this.repository.createSession();
+        Instant now = Instant.now();
+        assertThat(session.getExpireAt())
+                .isBetween(now.plusSeconds(59), Instant.now().plusSeconds(61));
+    }
 
-		@Override
-		public String generate() {
-			return this.id;
-		}
+    static class FixedSessionIdGenerator implements SessionIdGenerator {
+
+        private final String id;
 
-	}
+        FixedSessionIdGenerator(String id) {
+            this.id = id;
+        }
 
+        @Override
+        public String generate() {
+            return this.id;
+        }
+    }
 }
diff --git a/src/test/java/org/springframework/session/data/mongo/MongoSessionTests.java b/src/test/java/org/springframework/session/data/mongo/MongoSessionTests.java
index 6992fce..c23a322 100644
--- a/src/test/java/org/springframework/session/data/mongo/MongoSessionTests.java
+++ b/src/test/java/org/springframework/session/data/mongo/MongoSessionTests.java
@@ -1,11 +1,12 @@
 /*
+ * Copyright 2025-present MongoDB, Inc.
  * Copyright 2014-present the original author or authors.
  *
  * 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
  *
- *      https://www.apache.org/licenses/LICENSE-2.0
+ *   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,
@@ -16,27 +17,25 @@
 
 package org.springframework.session.data.mongo;
 
+import static org.assertj.core.api.Assertions.assertThat;
+
 import java.time.Duration;
 import java.time.Instant;
-
 import org.junit.jupiter.api.Test;
 
-import static org.assertj.core.api.Assertions.assertThat;
-
 /**
  * @author Rob Winch
  * @author Greg Turnquist
  */
 class MongoSessionTests {
 
-	@Test
-	void isExpiredWhenIntervalNegativeThenFalse() {
-
-		MongoSession session = new MongoSession();
-		session.setMaxInactiveInterval(Duration.ofSeconds(-1));
-		session.setLastAccessedTime(Instant.ofEpochMilli(0L));
+    @Test
+    void isExpiredWhenIntervalNegativeThenFalse() {
 
-		assertThat(session.isExpired()).isFalse();
-	}
+        MongoSession session = new MongoSession();
+        session.setMaxInactiveInterval(Duration.ofSeconds(-1));
+        session.setLastAccessedTime(Instant.ofEpochMilli(0L));
 
+        assertThat(session.isExpired()).isFalse();
+    }
 }
diff --git a/src/test/java/org/springframework/session/data/mongo/ReactiveMongoSessionRepositoryTests.java b/src/test/java/org/springframework/session/data/mongo/ReactiveMongoSessionRepositoryTests.java
index 313f64c..2e59af8 100644
--- a/src/test/java/org/springframework/session/data/mongo/ReactiveMongoSessionRepositoryTests.java
+++ b/src/test/java/org/springframework/session/data/mongo/ReactiveMongoSessionRepositoryTests.java
@@ -1,11 +1,12 @@
 /*
+ * Copyright 2025-present MongoDB, Inc.
  * Copyright 2014-present the original author or authors.
  *
  * 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
  *
- *      https://www.apache.org/licenses/LICENSE-2.0
+ *   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,
@@ -16,22 +17,27 @@
 
 package org.springframework.session.data.mongo;
 
-import java.time.Duration;
-import java.time.Instant;
-import java.util.UUID;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.BDDMockito.any;
+import static org.mockito.BDDMockito.eq;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.mock;
+import static org.mockito.BDDMockito.times;
+import static org.mockito.BDDMockito.verify;
 
 import com.mongodb.BasicDBObject;
 import com.mongodb.DBObject;
 import com.mongodb.client.result.DeleteResult;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.UUID;
 import org.bson.Document;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
-import reactor.core.publisher.Mono;
-import reactor.test.StepVerifier;
-
 import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.core.convert.TypeDescriptor;
 import org.springframework.data.mongodb.core.MongoOperations;
@@ -39,15 +45,8 @@
 import org.springframework.data.mongodb.core.index.IndexOperations;
 import org.springframework.session.MapSession;
 import org.springframework.session.events.SessionDeletedEvent;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
-import static org.mockito.BDDMockito.any;
-import static org.mockito.BDDMockito.eq;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.BDDMockito.mock;
-import static org.mockito.BDDMockito.times;
-import static org.mockito.BDDMockito.verify;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
 
 /**
  * Tests for {@link ReactiveMongoSessionRepository}.
@@ -59,213 +58,235 @@
 @ExtendWith(MockitoExtension.class)
 class ReactiveMongoSessionRepositoryTests {
 
-	@Mock
-	private AbstractMongoSessionConverter converter;
-
-	@Mock
-	private ReactiveMongoOperations mongoOperations;
-
-	@Mock
-	private MongoOperations blockingMongoOperations;
-
-	@Mock
-	private ApplicationEventPublisher eventPublisher;
-
-	private ReactiveMongoSessionRepository repository;
-
-	@BeforeEach
-	void setUp() {
-
-		this.repository = new ReactiveMongoSessionRepository(this.mongoOperations);
-		this.repository.setMongoSessionConverter(this.converter);
-		this.repository.setApplicationEventPublisher(this.eventPublisher);
-	}
-
-	@Test
-	void shouldCreateSession() {
-
-		this.repository.createSession() //
-			.as(StepVerifier::create) //
-			.expectNextMatches((mongoSession) -> {
-				assertThat(mongoSession.getId()).isNotEmpty();
-				assertThat(mongoSession.getMaxInactiveInterval())
-					.isEqualTo(Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS));
-				return true;
-			}) //
-			.verifyComplete();
-	}
-
-	@Test
-	void shouldSaveSession() {
-
-		// given
-		MongoSession session = new MongoSession();
-		BasicDBObject dbSession = new BasicDBObject();
-
-		given(this.converter.convert(session, TypeDescriptor.valueOf(MongoSession.class),
-				TypeDescriptor.valueOf(DBObject.class)))
-			.willReturn(dbSession);
-
-		given(this.mongoOperations.save(dbSession, "sessions")).willReturn(Mono.just(dbSession));
-
-		// when
-		this.repository.save(session) //
-			.as(StepVerifier::create) //
-			.verifyComplete();
-
-		verify(this.mongoOperations).save(dbSession, ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME);
-	}
-
-	@Test
-	void shouldGetSession() {
-
-		// given
-		String sessionId = UUID.randomUUID().toString();
-		Document sessionDocument = new Document();
-
-		given(this.mongoOperations.findById(sessionId, Document.class,
-				ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME))
-			.willReturn(Mono.just(sessionDocument));
-
-		MongoSession session = new MongoSession();
-
-		given(this.converter.convert(sessionDocument, TypeDescriptor.valueOf(Document.class),
-				TypeDescriptor.valueOf(MongoSession.class)))
-			.willReturn(session);
-
-		// when
-		this.repository.findById(sessionId) //
-			.as(StepVerifier::create) //
-			.expectNext(session) //
-			.verifyComplete();
-	}
-
-	@Test
-	void shouldHandleExpiredSession() {
-
-		// given
-		String sessionId = UUID.randomUUID().toString();
-		Document sessionDocument = new Document();
-
-		given(this.mongoOperations.findById(sessionId, Document.class,
-				ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME))
-			.willReturn(Mono.just(sessionDocument));
-
-		given(this.mongoOperations.remove(sessionDocument, ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME))
-			.willReturn(Mono.just(DeleteResult.acknowledged(1)));
-
-		MongoSession session = mock(MongoSession.class);
-
-		given(session.isExpired()).willReturn(true);
-		given(this.converter.convert(sessionDocument, TypeDescriptor.valueOf(Document.class),
-				TypeDescriptor.valueOf(MongoSession.class)))
-			.willReturn(session);
-
-		// when
-		this.repository.findById(sessionId) //
-			.as(StepVerifier::create) //
-			.verifyComplete();
-
-		// then
-		verify(this.mongoOperations).remove(any(Document.class),
-				eq(ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME));
-	}
-
-	@Test
-	void shouldDeleteSession() {
-
-		// given
-		String sessionId = UUID.randomUUID().toString();
-		Document sessionDocument = new Document();
-
-		given(this.mongoOperations.findById(sessionId, Document.class,
-				ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME))
-			.willReturn(Mono.just(sessionDocument));
+    @Mock
+    private AbstractMongoSessionConverter converter;
 
-		given(this.mongoOperations.remove(sessionDocument, "sessions"))
-			.willReturn(Mono.just(DeleteResult.acknowledged(1)));
-
-		MongoSession session = mock(MongoSession.class);
+    @Mock
+    private ReactiveMongoOperations mongoOperations;
 
-		given(this.converter.convert(sessionDocument, TypeDescriptor.valueOf(Document.class),
-				TypeDescriptor.valueOf(MongoSession.class)))
-			.willReturn(session);
+    @Mock
+    private MongoOperations blockingMongoOperations;
 
-		// when
-		this.repository.deleteById(sessionId) //
-			.as(StepVerifier::create) //
-			.verifyComplete();
+    @Mock
+    private ApplicationEventPublisher eventPublisher;
 
-		verify(this.mongoOperations).remove(any(Document.class),
-				eq(ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME));
+    private ReactiveMongoSessionRepository repository;
 
-		verify(this.eventPublisher).publishEvent(any(SessionDeletedEvent.class));
-	}
+    @BeforeEach
+    void setUp() {
 
-	@Test
-	void shouldInvokeMethodToCreateIndexesImperatively() {
+        this.repository = new ReactiveMongoSessionRepository(this.mongoOperations);
+        this.repository.setMongoSessionConverter(this.converter);
+        this.repository.setApplicationEventPublisher(this.eventPublisher);
+    }
 
-		// given
-		IndexOperations indexOperations = mock(IndexOperations.class);
-		given(this.blockingMongoOperations.indexOps((String) any())).willReturn(indexOperations);
+    @Test
+    void shouldCreateSession() {
 
-		this.repository.setBlockingMongoOperations(this.blockingMongoOperations);
+        this.repository
+                .createSession() //
+                .as(StepVerifier::create) //
+                .expectNextMatches((mongoSession) -> {
+                    assertThat(mongoSession.getId()).isNotEmpty();
+                    assertThat(mongoSession.getMaxInactiveInterval())
+                            .isEqualTo(Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS));
+                    return true;
+                }) //
+                .verifyComplete();
+    }
 
-		// when
-		this.repository.afterPropertiesSet();
+    @Test
+    void shouldSaveSession() {
 
-		// then
-		verify(this.blockingMongoOperations, times(1)).indexOps((String) any());
-		verify(this.converter, times(1)).ensureIndexes(indexOperations);
-	}
+        // given
+        MongoSession session = new MongoSession();
+        BasicDBObject dbSession = new BasicDBObject();
 
-	@Test
-	void createSessionWhenSessionIdGeneratorThenUses() {
-		this.repository.setSessionIdGenerator(() -> "test");
+        given(this.converter.convert(
+                        session, TypeDescriptor.valueOf(MongoSession.class), TypeDescriptor.valueOf(DBObject.class)))
+                .willReturn(dbSession);
 
-		this.repository.createSession().as(StepVerifier::create).assertNext((mongoSession) -> {
-			assertThat(mongoSession.getId()).isEqualTo("test");
-			assertThat(mongoSession.changeSessionId()).isEqualTo("test");
-		}).verifyComplete();
-	}
+        given(this.mongoOperations.save(dbSession, "sessions")).willReturn(Mono.just(dbSession));
 
-	@Test
-	void setSessionIdGeneratorWhenNullThenThrowsException() {
-		assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setSessionIdGenerator(null))
-			.withMessage("sessionIdGenerator cannot be null");
-	}
+        // when
+        this.repository
+                .save(session) //
+                .as(StepVerifier::create) //
+                .verifyComplete();
 
-	@Test
-	void findByIdWhenChangeSessionIdThenUsesSessionIdGenerator() {
-		this.repository.setSessionIdGenerator(() -> "test");
+        verify(this.mongoOperations).save(dbSession, ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME);
+    }
 
-		String sessionId = UUID.randomUUID().toString();
-		Document sessionDocument = new Document();
+    @Test
+    void shouldGetSession() {
 
-		given(this.mongoOperations.findById(sessionId, Document.class,
-				ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME))
-			.willReturn(Mono.just(sessionDocument));
+        // given
+        String sessionId = UUID.randomUUID().toString();
+        Document sessionDocument = new Document();
 
-		MongoSession session = new MongoSession(sessionId);
+        given(this.mongoOperations.findById(
+                        sessionId, Document.class, ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME))
+                .willReturn(Mono.just(sessionDocument));
 
-		given(this.converter.convert(sessionDocument, TypeDescriptor.valueOf(Document.class),
-				TypeDescriptor.valueOf(MongoSession.class)))
-			.willReturn(session);
+        MongoSession session = new MongoSession();
 
-		this.repository.findById(sessionId).as(StepVerifier::create).assertNext((mongoSession) -> {
-			String oldId = mongoSession.getId();
-			String newId = mongoSession.changeSessionId();
-			assertThat(oldId).isEqualTo(sessionId);
-			assertThat(newId).isEqualTo("test");
-		}).verifyComplete();
-	}
+        given(this.converter.convert(
+                        sessionDocument,
+                        TypeDescriptor.valueOf(Document.class),
+                        TypeDescriptor.valueOf(MongoSession.class)))
+                .willReturn(session);
 
-	@Test
-	void createSessionWhenMaxInactiveIntervalSetThenUse() {
-		this.repository.setDefaultMaxInactiveInterval(Duration.ofSeconds(60));
-		MongoSession session = this.repository.createSession().block();
-		Instant now = Instant.now();
-		assertThat(session.getExpireAt()).isBetween(now.plusSeconds(59), Instant.now().plusSeconds(61));
-	}
+        // when
+        this.repository
+                .findById(sessionId) //
+                .as(StepVerifier::create) //
+                .expectNext(session) //
+                .verifyComplete();
+    }
 
+    @Test
+    void shouldHandleExpiredSession() {
+
+        // given
+        String sessionId = UUID.randomUUID().toString();
+        Document sessionDocument = new Document();
+
+        given(this.mongoOperations.findById(
+                        sessionId, Document.class, ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME))
+                .willReturn(Mono.just(sessionDocument));
+
+        given(this.mongoOperations.remove(sessionDocument, ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME))
+                .willReturn(Mono.just(DeleteResult.acknowledged(1)));
+
+        MongoSession session = mock(MongoSession.class);
+
+        given(session.isExpired()).willReturn(true);
+        given(this.converter.convert(
+                        sessionDocument,
+                        TypeDescriptor.valueOf(Document.class),
+                        TypeDescriptor.valueOf(MongoSession.class)))
+                .willReturn(session);
+
+        // when
+        this.repository
+                .findById(sessionId) //
+                .as(StepVerifier::create) //
+                .verifyComplete();
+
+        // then
+        verify(this.mongoOperations)
+                .remove(any(Document.class), eq(ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME));
+    }
+
+    @Test
+    void shouldDeleteSession() {
+
+        // given
+        String sessionId = UUID.randomUUID().toString();
+        Document sessionDocument = new Document();
+
+        given(this.mongoOperations.findById(
+                        sessionId, Document.class, ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME))
+                .willReturn(Mono.just(sessionDocument));
+
+        given(this.mongoOperations.remove(sessionDocument, "sessions"))
+                .willReturn(Mono.just(DeleteResult.acknowledged(1)));
+
+        MongoSession session = mock(MongoSession.class);
+
+        given(this.converter.convert(
+                        sessionDocument,
+                        TypeDescriptor.valueOf(Document.class),
+                        TypeDescriptor.valueOf(MongoSession.class)))
+                .willReturn(session);
+
+        // when
+        this.repository
+                .deleteById(sessionId) //
+                .as(StepVerifier::create) //
+                .verifyComplete();
+
+        verify(this.mongoOperations)
+                .remove(any(Document.class), eq(ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME));
+
+        verify(this.eventPublisher).publishEvent(any(SessionDeletedEvent.class));
+    }
+
+    @Test
+    void shouldInvokeMethodToCreateIndexesImperatively() {
+
+        // given
+        IndexOperations indexOperations = mock(IndexOperations.class);
+        given(this.blockingMongoOperations.indexOps((String) any())).willReturn(indexOperations);
+
+        this.repository.setBlockingMongoOperations(this.blockingMongoOperations);
+
+        // when
+        this.repository.afterPropertiesSet();
+
+        // then
+        verify(this.blockingMongoOperations, times(1)).indexOps((String) any());
+        verify(this.converter, times(1)).ensureIndexes(indexOperations);
+    }
+
+    @Test
+    void createSessionWhenSessionIdGeneratorThenUses() {
+        this.repository.setSessionIdGenerator(() -> "test");
+
+        this.repository
+                .createSession()
+                .as(StepVerifier::create)
+                .assertNext((mongoSession) -> {
+                    assertThat(mongoSession.getId()).isEqualTo("test");
+                    assertThat(mongoSession.changeSessionId()).isEqualTo("test");
+                })
+                .verifyComplete();
+    }
+
+    @Test
+    void setSessionIdGeneratorWhenNullThenThrowsException() {
+        assertThatIllegalArgumentException()
+                .isThrownBy(() -> this.repository.setSessionIdGenerator(null))
+                .withMessage("sessionIdGenerator cannot be null");
+    }
+
+    @Test
+    void findByIdWhenChangeSessionIdThenUsesSessionIdGenerator() {
+        this.repository.setSessionIdGenerator(() -> "test");
+
+        String sessionId = UUID.randomUUID().toString();
+        Document sessionDocument = new Document();
+
+        given(this.mongoOperations.findById(
+                        sessionId, Document.class, ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME))
+                .willReturn(Mono.just(sessionDocument));
+
+        MongoSession session = new MongoSession(sessionId);
+
+        given(this.converter.convert(
+                        sessionDocument,
+                        TypeDescriptor.valueOf(Document.class),
+                        TypeDescriptor.valueOf(MongoSession.class)))
+                .willReturn(session);
+
+        this.repository
+                .findById(sessionId)
+                .as(StepVerifier::create)
+                .assertNext((mongoSession) -> {
+                    String oldId = mongoSession.getId();
+                    String newId = mongoSession.changeSessionId();
+                    assertThat(oldId).isEqualTo(sessionId);
+                    assertThat(newId).isEqualTo("test");
+                })
+                .verifyComplete();
+    }
+
+    @Test
+    void createSessionWhenMaxInactiveIntervalSetThenUse() {
+        this.repository.setDefaultMaxInactiveInterval(Duration.ofSeconds(60));
+        MongoSession session = this.repository.createSession().block();
+        Instant now = Instant.now();
+        assertThat(session.getExpireAt())
+                .isBetween(now.plusSeconds(59), Instant.now().plusSeconds(61));
+    }
 }
diff --git a/src/test/java/org/springframework/session/data/mongo/config/annotation/web/http/MongoHttpSessionConfigurationTests.java b/src/test/java/org/springframework/session/data/mongo/config/annotation/web/http/MongoHttpSessionConfigurationTests.java
index eb46469..300641e 100644
--- a/src/test/java/org/springframework/session/data/mongo/config/annotation/web/http/MongoHttpSessionConfigurationTests.java
+++ b/src/test/java/org/springframework/session/data/mongo/config/annotation/web/http/MongoHttpSessionConfigurationTests.java
@@ -1,11 +1,12 @@
 /*
+ * Copyright 2025-present MongoDB, Inc.
  * Copyright 2014-present the original author or authors.
  *
  * 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
  *
- *      https://www.apache.org/licenses/LICENSE-2.0
+ *   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,
@@ -16,13 +17,17 @@
 
 package org.springframework.session.data.mongo.config.annotation.web.http;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.mock;
+
 import java.net.UnknownHostException;
 import java.time.Duration;
-
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Order;
 import org.junit.jupiter.api.Test;
-
 import org.springframework.beans.factory.UnsatisfiedDependencyException;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 import org.springframework.context.annotation.Bean;
@@ -42,12 +47,6 @@
 import org.springframework.session.data.mongo.MongoIndexedSessionRepository;
 import org.springframework.test.util.ReflectionTestUtils;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.BDDMockito.mock;
-
 /**
  * Tests for {@link MongoHttpSessionConfiguration}.
  *
@@ -56,335 +55,319 @@
  */
 class MongoHttpSessionConfigurationTests {
 
-	private static final String COLLECTION_NAME = "testSessions";
-
-	private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600;
-
-	private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
-
-	@AfterEach
-	void after() {
-
-		if (this.context != null) {
-			this.context.close();
-		}
-	}
-
-	@Test
-	void noMongoOperationsConfiguration() {
-
-		assertThatExceptionOfType(UnsatisfiedDependencyException.class)
-			.isThrownBy(() -> registerAndRefresh(EmptyConfiguration.class))
-			.withMessageContaining("mongoSessionRepository");
-	}
-
-	@Test
-	void defaultConfiguration() {
-
-		registerAndRefresh(DefaultConfiguration.class);
-
-		assertThat(this.context.getBean(MongoIndexedSessionRepository.class)).isNotNull();
-	}
-
-	@Test
-	void customCollectionName() {
-
-		registerAndRefresh(CustomCollectionNameConfiguration.class);
-
-		MongoIndexedSessionRepository repository = this.context.getBean(MongoIndexedSessionRepository.class);
-
-		assertThat(repository).isNotNull();
-		assertThat(ReflectionTestUtils.getField(repository, "collectionName")).isEqualTo(COLLECTION_NAME);
-	}
-
-	@Test
-	void setCustomCollectionName() {
-
-		registerAndRefresh(CustomCollectionNameSetConfiguration.class);
-
-		MongoHttpSessionConfiguration session = this.context.getBean(MongoHttpSessionConfiguration.class);
-
-		assertThat(session).isNotNull();
-		assertThat(ReflectionTestUtils.getField(session, "collectionName")).isEqualTo(COLLECTION_NAME);
-	}
-
-	@Test
-	void customMaxInactiveIntervalInSeconds() {
-
-		registerAndRefresh(CustomMaxInactiveIntervalInSecondsConfiguration.class);
-
-		MongoIndexedSessionRepository repository = this.context.getBean(MongoIndexedSessionRepository.class);
-
-		assertThat(repository).extracting("defaultMaxInactiveInterval")
-			.isEqualTo(Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS));
-	}
-
-	@Test
-	void setCustomMaxInactiveIntervalInSeconds() {
-
-		registerAndRefresh(CustomMaxInactiveIntervalInSecondsSetConfiguration.class);
-
-		MongoIndexedSessionRepository repository = this.context.getBean(MongoIndexedSessionRepository.class);
-
-		assertThat(repository).extracting("defaultMaxInactiveInterval")
-			.isEqualTo(Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS));
-	}
-
-	@Test
-	void setCustomSessionConverterConfiguration() {
-
-		registerAndRefresh(CustomSessionConverterConfiguration.class);
-
-		MongoIndexedSessionRepository repository = this.context.getBean(MongoIndexedSessionRepository.class);
-		AbstractMongoSessionConverter mongoSessionConverter = this.context.getBean(AbstractMongoSessionConverter.class);
-
-		assertThat(repository).isNotNull();
-		assertThat(mongoSessionConverter).isNotNull();
-		assertThat(ReflectionTestUtils.getField(repository, "mongoSessionConverter")).isEqualTo(mongoSessionConverter);
-	}
-
-	@Test
-	void resolveCollectionNameByPropertyPlaceholder() {
-
-		this.context
-			.setEnvironment(new MockEnvironment().withProperty("session.mongo.collectionName", COLLECTION_NAME));
-		registerAndRefresh(CustomMongoJdbcSessionConfiguration.class);
-
-		MongoHttpSessionConfiguration configuration = this.context.getBean(MongoHttpSessionConfiguration.class);
-
-		assertThat(ReflectionTestUtils.getField(configuration, "collectionName")).isEqualTo(COLLECTION_NAME);
-	}
-
-	@Test
-	void sessionRepositoryCustomizer() {
-
-		registerAndRefresh(MongoConfiguration.class, SessionRepositoryCustomizerConfiguration.class);
-
-		MongoIndexedSessionRepository sessionRepository = this.context.getBean(MongoIndexedSessionRepository.class);
-
-		assertThat(sessionRepository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ofSeconds(10000));
-	}
-
-	@Test
-	void customIndexResolverConfigurationWithDefaultMongoSessionConverter() {
-
-		registerAndRefresh(MongoConfiguration.class,
-				CustomIndexResolverConfigurationWithDefaultMongoSessionConverter.class);
-
-		MongoIndexedSessionRepository repository = this.context.getBean(MongoIndexedSessionRepository.class);
-		IndexResolver indexResolver = this.context.getBean(IndexResolver.class);
-
-		assertThat(repository).isNotNull();
-		assertThat(indexResolver).isNotNull();
-		assertThat(repository).extracting("mongoSessionConverter")
-			.hasFieldOrPropertyWithValue("indexResolver", indexResolver);
-	}
-
-	@Test
-	void customIndexResolverConfigurationWithProvidedMongoSessionConverter() {
-
-		registerAndRefresh(MongoConfiguration.class,
-				CustomIndexResolverConfigurationWithProvidedMongoSessionConverter.class);
-
-		MongoIndexedSessionRepository repository = this.context.getBean(MongoIndexedSessionRepository.class);
-		IndexResolver indexResolver = this.context.getBean(IndexResolver.class);
-
-		assertThat(repository).isNotNull();
-		assertThat(indexResolver).isNotNull();
-		assertThat(repository).extracting("mongoSessionConverter")
-			.hasFieldOrPropertyWithValue("indexResolver", indexResolver);
-	}
-
-	@Test
-	void importConfigAndCustomize() {
-		registerAndRefresh(ImportConfigAndCustomizeConfiguration.class);
-		MongoIndexedSessionRepository sessionRepository = this.context.getBean(MongoIndexedSessionRepository.class);
-		assertThat(sessionRepository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ZERO);
-	}
-
-	@Test
-	void registerWhenSessionIdGeneratorBeanThenUses() {
-		registerAndRefresh(SessionIdGeneratorConfiguration.class);
-		MongoIndexedSessionRepository sessionRepository = this.context.getBean(MongoIndexedSessionRepository.class);
-		assertThat(sessionRepository).extracting("sessionIdGenerator").isInstanceOf(TestSessionIdGenerator.class);
-	}
-
-	@Test
-	void registerWhenNoSessionIdGeneratorBeanThenDefault() {
-		registerAndRefresh(DefaultConfiguration.class);
-		MongoIndexedSessionRepository sessionRepository = this.context.getBean(MongoIndexedSessionRepository.class);
-		assertThat(sessionRepository).extracting("sessionIdGenerator").isInstanceOf(UuidSessionIdGenerator.class);
-	}
+    private static final String COLLECTION_NAME = "testSessions";
 
-	private void registerAndRefresh(Class... annotatedClasses) {
+    private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600;
 
-		this.context.register(annotatedClasses);
-		this.context.refresh();
-	}
+    private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
 
-	@Configuration
-	@EnableMongoHttpSession
-	static class EmptyConfiguration {
+    @AfterEach
+    void after() {
 
-	}
+        if (this.context != null) {
+            this.context.close();
+        }
+    }
 
-	static class BaseConfiguration {
+    @Test
+    void noMongoOperationsConfiguration() {
 
-		@Bean
-		MongoOperations mongoOperations() throws UnknownHostException {
+        assertThatExceptionOfType(UnsatisfiedDependencyException.class)
+                .isThrownBy(() -> registerAndRefresh(EmptyConfiguration.class))
+                .withMessageContaining("mongoSessionRepository");
+    }
 
-			MongoOperations mongoOperations = mock(MongoOperations.class);
-			IndexOperations indexOperations = mock(IndexOperations.class);
+    @Test
+    void defaultConfiguration() {
 
-			given(mongoOperations.indexOps(anyString())).willReturn(indexOperations);
+        registerAndRefresh(DefaultConfiguration.class);
 
-			return mongoOperations;
-		}
+        assertThat(this.context.getBean(MongoIndexedSessionRepository.class)).isNotNull();
+    }
 
-	}
+    @Test
+    void customCollectionName() {
 
-	@Configuration
-	@EnableMongoHttpSession
-	static class DefaultConfiguration extends BaseConfiguration {
+        registerAndRefresh(CustomCollectionNameConfiguration.class);
 
-	}
+        MongoIndexedSessionRepository repository = this.context.getBean(MongoIndexedSessionRepository.class);
 
-	@Configuration
-	static class MongoConfiguration extends BaseConfiguration {
+        assertThat(repository).isNotNull();
+        assertThat(ReflectionTestUtils.getField(repository, "collectionName")).isEqualTo(COLLECTION_NAME);
+    }
 
-	}
+    @Test
+    void setCustomCollectionName() {
 
-	@Configuration
-	@EnableMongoHttpSession(collectionName = COLLECTION_NAME)
-	static class CustomCollectionNameConfiguration extends BaseConfiguration {
+        registerAndRefresh(CustomCollectionNameSetConfiguration.class);
 
-	}
+        MongoHttpSessionConfiguration session = this.context.getBean(MongoHttpSessionConfiguration.class);
 
-	@Configuration
-	@Import(MongoConfiguration.class)
-	static class CustomCollectionNameSetConfiguration extends MongoHttpSessionConfiguration {
+        assertThat(session).isNotNull();
+        assertThat(ReflectionTestUtils.getField(session, "collectionName")).isEqualTo(COLLECTION_NAME);
+    }
 
-		CustomCollectionNameSetConfiguration() {
-			setCollectionName(COLLECTION_NAME);
-		}
+    @Test
+    void customMaxInactiveIntervalInSeconds() {
 
-	}
+        registerAndRefresh(CustomMaxInactiveIntervalInSecondsConfiguration.class);
 
-	@Configuration
-	@EnableMongoHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
-	static class CustomMaxInactiveIntervalInSecondsConfiguration extends BaseConfiguration {
+        MongoIndexedSessionRepository repository = this.context.getBean(MongoIndexedSessionRepository.class);
 
-	}
+        assertThat(repository)
+                .extracting("defaultMaxInactiveInterval")
+                .isEqualTo(Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS));
+    }
 
-	@Configuration
-	@Import(MongoConfiguration.class)
-	static class CustomMaxInactiveIntervalInSecondsSetConfiguration extends MongoHttpSessionConfiguration {
+    @Test
+    void setCustomMaxInactiveIntervalInSeconds() {
 
-		CustomMaxInactiveIntervalInSecondsSetConfiguration() {
-			setMaxInactiveInterval(Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS));
-		}
+        registerAndRefresh(CustomMaxInactiveIntervalInSecondsSetConfiguration.class);
 
-	}
+        MongoIndexedSessionRepository repository = this.context.getBean(MongoIndexedSessionRepository.class);
 
-	@Configuration
-	@Import(MongoConfiguration.class)
-	static class CustomSessionConverterConfiguration extends MongoHttpSessionConfiguration {
+        assertThat(repository)
+                .extracting("defaultMaxInactiveInterval")
+                .isEqualTo(Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS));
+    }
 
-		@Bean
-		AbstractMongoSessionConverter mongoSessionConverter() {
-			return mock(AbstractMongoSessionConverter.class);
-		}
+    @Test
+    void setCustomSessionConverterConfiguration() {
 
-	}
+        registerAndRefresh(CustomSessionConverterConfiguration.class);
 
-	@Configuration
-	@EnableMongoHttpSession(collectionName = "${session.mongo.collectionName}")
-	static class CustomMongoJdbcSessionConfiguration extends BaseConfiguration {
+        MongoIndexedSessionRepository repository = this.context.getBean(MongoIndexedSessionRepository.class);
+        AbstractMongoSessionConverter mongoSessionConverter = this.context.getBean(AbstractMongoSessionConverter.class);
 
-		@Bean
-		PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
-			return new PropertySourcesPlaceholderConfigurer();
-		}
+        assertThat(repository).isNotNull();
+        assertThat(mongoSessionConverter).isNotNull();
+        assertThat(ReflectionTestUtils.getField(repository, "mongoSessionConverter"))
+                .isEqualTo(mongoSessionConverter);
+    }
 
-	}
+    @Test
+    void resolveCollectionNameByPropertyPlaceholder() {
 
-	@Configuration(proxyBeanMethods = false)
-	@EnableMongoHttpSession
-	static class SessionRepositoryCustomizerConfiguration {
+        this.context.setEnvironment(
+                new MockEnvironment().withProperty("session.mongo.collectionName", COLLECTION_NAME));
+        registerAndRefresh(CustomMongoJdbcSessionConfiguration.class);
 
-		@Bean
-		@Order(0)
-		SessionRepositoryCustomizer sessionRepositoryCustomizerOne() {
-			return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(Duration.ZERO);
-		}
+        MongoHttpSessionConfiguration configuration = this.context.getBean(MongoHttpSessionConfiguration.class);
 
-		@Bean
-		@Order(1)
-		SessionRepositoryCustomizer sessionRepositoryCustomizerTwo() {
-			return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(Duration.ofSeconds(10000));
-		}
+        assertThat(ReflectionTestUtils.getField(configuration, "collectionName"))
+                .isEqualTo(COLLECTION_NAME);
+    }
 
-	}
+    @Test
+    void sessionRepositoryCustomizer() {
 
-	@Configuration
-	@EnableMongoHttpSession
-	static class CustomIndexResolverConfigurationWithDefaultMongoSessionConverter {
+        registerAndRefresh(MongoConfiguration.class, SessionRepositoryCustomizerConfiguration.class);
 
-		@Bean
-		@SuppressWarnings("unchecked")
-		IndexResolver indexResolver() {
-			return mock(IndexResolver.class);
-		}
+        MongoIndexedSessionRepository sessionRepository = this.context.getBean(MongoIndexedSessionRepository.class);
 
-	}
+        assertThat(sessionRepository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ofSeconds(10000));
+    }
 
-	@Configuration
-	@EnableMongoHttpSession
-	static class CustomIndexResolverConfigurationWithProvidedMongoSessionConverter {
+    @Test
+    void customIndexResolverConfigurationWithDefaultMongoSessionConverter() {
 
-		@Bean
-		AbstractMongoSessionConverter mongoSessionConverter() {
-			return new JacksonMongoSessionConverter();
-		}
+        registerAndRefresh(
+                MongoConfiguration.class, CustomIndexResolverConfigurationWithDefaultMongoSessionConverter.class);
 
-		@Bean
-		@SuppressWarnings("unchecked")
-		IndexResolver indexResolver() {
-			return mock(IndexResolver.class);
-		}
+        MongoIndexedSessionRepository repository = this.context.getBean(MongoIndexedSessionRepository.class);
+        IndexResolver indexResolver = this.context.getBean(IndexResolver.class);
 
-	}
+        assertThat(repository).isNotNull();
+        assertThat(indexResolver).isNotNull();
+        assertThat(repository)
+                .extracting("mongoSessionConverter")
+                .hasFieldOrPropertyWithValue("indexResolver", indexResolver);
+    }
 
-	@Configuration(proxyBeanMethods = false)
-	@Import(MongoHttpSessionConfiguration.class)
-	static class ImportConfigAndCustomizeConfiguration extends BaseConfiguration {
+    @Test
+    void customIndexResolverConfigurationWithProvidedMongoSessionConverter() {
 
-		@Bean
-		SessionRepositoryCustomizer sessionRepositoryCustomizer() {
-			return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(Duration.ZERO);
-		}
+        registerAndRefresh(
+                MongoConfiguration.class, CustomIndexResolverConfigurationWithProvidedMongoSessionConverter.class);
 
-	}
+        MongoIndexedSessionRepository repository = this.context.getBean(MongoIndexedSessionRepository.class);
+        IndexResolver indexResolver = this.context.getBean(IndexResolver.class);
 
-	@Configuration(proxyBeanMethods = false)
-	@EnableMongoHttpSession
-	@Import(MongoConfiguration.class)
-	static class SessionIdGeneratorConfiguration {
+        assertThat(repository).isNotNull();
+        assertThat(indexResolver).isNotNull();
+        assertThat(repository)
+                .extracting("mongoSessionConverter")
+                .hasFieldOrPropertyWithValue("indexResolver", indexResolver);
+    }
 
-		@Bean
-		SessionIdGenerator sessionIdGenerator() {
-			return new TestSessionIdGenerator();
-		}
+    @Test
+    void importConfigAndCustomize() {
+        registerAndRefresh(ImportConfigAndCustomizeConfiguration.class);
+        MongoIndexedSessionRepository sessionRepository = this.context.getBean(MongoIndexedSessionRepository.class);
+        assertThat(sessionRepository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ZERO);
+    }
 
-	}
+    @Test
+    void registerWhenSessionIdGeneratorBeanThenUses() {
+        registerAndRefresh(SessionIdGeneratorConfiguration.class);
+        MongoIndexedSessionRepository sessionRepository = this.context.getBean(MongoIndexedSessionRepository.class);
+        assertThat(sessionRepository).extracting("sessionIdGenerator").isInstanceOf(TestSessionIdGenerator.class);
+    }
 
-	static class TestSessionIdGenerator implements SessionIdGenerator {
+    @Test
+    void registerWhenNoSessionIdGeneratorBeanThenDefault() {
+        registerAndRefresh(DefaultConfiguration.class);
+        MongoIndexedSessionRepository sessionRepository = this.context.getBean(MongoIndexedSessionRepository.class);
+        assertThat(sessionRepository).extracting("sessionIdGenerator").isInstanceOf(UuidSessionIdGenerator.class);
+    }
 
-		@Override
-		public String generate() {
-			return "test";
-		}
+    private void registerAndRefresh(Class... annotatedClasses) {
 
-	}
+        this.context.register(annotatedClasses);
+        this.context.refresh();
+    }
+
+    @Configuration
+    @EnableMongoHttpSession
+    static class EmptyConfiguration {}
 
+    static class BaseConfiguration {
+
+        @Bean
+        MongoOperations mongoOperations() throws UnknownHostException {
+
+            MongoOperations mongoOperations = mock(MongoOperations.class);
+            IndexOperations indexOperations = mock(IndexOperations.class);
+
+            given(mongoOperations.indexOps(anyString())).willReturn(indexOperations);
+
+            return mongoOperations;
+        }
+    }
+
+    @Configuration
+    @EnableMongoHttpSession
+    static class DefaultConfiguration extends BaseConfiguration {}
+
+    @Configuration
+    static class MongoConfiguration extends BaseConfiguration {}
+
+    @Configuration
+    @EnableMongoHttpSession(collectionName = COLLECTION_NAME)
+    static class CustomCollectionNameConfiguration extends BaseConfiguration {}
+
+    @Configuration
+    @Import(MongoConfiguration.class)
+    static class CustomCollectionNameSetConfiguration extends MongoHttpSessionConfiguration {
+
+        CustomCollectionNameSetConfiguration() {
+            setCollectionName(COLLECTION_NAME);
+        }
+    }
+
+    @Configuration
+    @EnableMongoHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
+    static class CustomMaxInactiveIntervalInSecondsConfiguration extends BaseConfiguration {}
+
+    @Configuration
+    @Import(MongoConfiguration.class)
+    static class CustomMaxInactiveIntervalInSecondsSetConfiguration extends MongoHttpSessionConfiguration {
+
+        CustomMaxInactiveIntervalInSecondsSetConfiguration() {
+            setMaxInactiveInterval(Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS));
+        }
+    }
+
+    @Configuration
+    @Import(MongoConfiguration.class)
+    static class CustomSessionConverterConfiguration extends MongoHttpSessionConfiguration {
+
+        @Bean
+        AbstractMongoSessionConverter mongoSessionConverter() {
+            return mock(AbstractMongoSessionConverter.class);
+        }
+    }
+
+    @Configuration
+    @EnableMongoHttpSession(collectionName = "${session.mongo.collectionName}")
+    static class CustomMongoJdbcSessionConfiguration extends BaseConfiguration {
+
+        @Bean
+        PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
+            return new PropertySourcesPlaceholderConfigurer();
+        }
+    }
+
+    @Configuration(proxyBeanMethods = false)
+    @EnableMongoHttpSession
+    static class SessionRepositoryCustomizerConfiguration {
+
+        @Bean
+        @Order(0)
+        SessionRepositoryCustomizer sessionRepositoryCustomizerOne() {
+            return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(Duration.ZERO);
+        }
+
+        @Bean
+        @Order(1)
+        SessionRepositoryCustomizer sessionRepositoryCustomizerTwo() {
+            return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(Duration.ofSeconds(10000));
+        }
+    }
+
+    @Configuration
+    @EnableMongoHttpSession
+    static class CustomIndexResolverConfigurationWithDefaultMongoSessionConverter {
+
+        @Bean
+        @SuppressWarnings("unchecked")
+        IndexResolver indexResolver() {
+            return mock(IndexResolver.class);
+        }
+    }
+
+    @Configuration
+    @EnableMongoHttpSession
+    static class CustomIndexResolverConfigurationWithProvidedMongoSessionConverter {
+
+        @Bean
+        AbstractMongoSessionConverter mongoSessionConverter() {
+            return new JacksonMongoSessionConverter();
+        }
+
+        @Bean
+        @SuppressWarnings("unchecked")
+        IndexResolver indexResolver() {
+            return mock(IndexResolver.class);
+        }
+    }
+
+    @Configuration(proxyBeanMethods = false)
+    @Import(MongoHttpSessionConfiguration.class)
+    static class ImportConfigAndCustomizeConfiguration extends BaseConfiguration {
+
+        @Bean
+        SessionRepositoryCustomizer sessionRepositoryCustomizer() {
+            return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(Duration.ZERO);
+        }
+    }
+
+    @Configuration(proxyBeanMethods = false)
+    @EnableMongoHttpSession
+    @Import(MongoConfiguration.class)
+    static class SessionIdGeneratorConfiguration {
+
+        @Bean
+        SessionIdGenerator sessionIdGenerator() {
+            return new TestSessionIdGenerator();
+        }
+    }
+
+    static class TestSessionIdGenerator implements SessionIdGenerator {
+
+        @Override
+        public String generate() {
+            return "test";
+        }
+    }
 }
diff --git a/src/test/java/org/springframework/session/data/mongo/config/annotation/web/reactive/ReactiveMongoWebSessionConfigurationTests.java b/src/test/java/org/springframework/session/data/mongo/config/annotation/web/reactive/ReactiveMongoWebSessionConfigurationTests.java
index 1a18bfe..5e87274 100644
--- a/src/test/java/org/springframework/session/data/mongo/config/annotation/web/reactive/ReactiveMongoWebSessionConfigurationTests.java
+++ b/src/test/java/org/springframework/session/data/mongo/config/annotation/web/reactive/ReactiveMongoWebSessionConfigurationTests.java
@@ -1,11 +1,12 @@
 /*
+ * Copyright 2025-present MongoDB, Inc.
  * Copyright 2014-present the original author or authors.
  *
  * 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
  *
- *      https://www.apache.org/licenses/LICENSE-2.0
+ *   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,
@@ -16,14 +17,20 @@
 
 package org.springframework.session.data.mongo.config.annotation.web.reactive;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.BDDMockito.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.mock;
+import static org.mockito.BDDMockito.times;
+import static org.mockito.BDDMockito.verify;
+
 import java.lang.reflect.Field;
 import java.time.Duration;
 import java.util.Collections;
-
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Order;
 import org.junit.jupiter.api.Test;
-
 import org.springframework.beans.factory.UnsatisfiedDependencyException;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 import org.springframework.context.annotation.Bean;
@@ -47,14 +54,6 @@
 import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
 import org.springframework.web.server.session.WebSessionManager;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import static org.mockito.BDDMockito.any;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.BDDMockito.mock;
-import static org.mockito.BDDMockito.times;
-import static org.mockito.BDDMockito.verify;
-
 /**
  * Verify various configurations through {@link EnableSpringWebSession}.
  *
@@ -63,392 +62,376 @@
  */
 class ReactiveMongoWebSessionConfigurationTests {
 
-	private AnnotationConfigApplicationContext context;
-
-	@AfterEach
-	void tearDown() {
-
-		if (this.context != null) {
-			this.context.close();
-		}
-	}
-
-	@Test
-	void enableSpringWebSessionConfiguresThings() {
-
-		this.context = new AnnotationConfigApplicationContext();
-		this.context.register(GoodConfig.class);
-		this.context.refresh();
-
-		WebSessionManager webSessionManagerFoundByType = this.context.getBean(WebSessionManager.class);
-		Object webSessionManagerFoundByName = this.context.getBean(WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME);
-
-		assertThat(webSessionManagerFoundByType).isNotNull();
-		assertThat(webSessionManagerFoundByName).isNotNull();
-		assertThat(webSessionManagerFoundByType).isEqualTo(webSessionManagerFoundByName);
-
-		assertThat(this.context.getBean(ReactiveSessionRepository.class)).isNotNull();
-	}
-
-	@Test
-	void missingReactorSessionRepositoryBreaksAppContext() {
-
-		this.context = new AnnotationConfigApplicationContext();
-		this.context.register(BadConfig.class);
-
-		assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(this.context::refresh)
-			.withMessageContaining("Error creating bean with name 'reactiveMongoSessionRepository'")
-			.withMessageContaining("No qualifying bean of type '" + ReactiveMongoOperations.class.getCanonicalName());
-	}
-
-	@Test
-	void defaultSessionConverterShouldBeJdkWhenOnClasspath() throws IllegalAccessException {
-
-		this.context = new AnnotationConfigApplicationContext();
-		this.context.register(GoodConfig.class);
-		this.context.refresh();
-
-		ReactiveMongoSessionRepository repository = this.context.getBean(ReactiveMongoSessionRepository.class);
-
-		AbstractMongoSessionConverter converter = findMongoSessionConverter(repository);
-
-		assertThat(converter).isOfAnyClassIn(JdkMongoSessionConverter.class);
-	}
-
-	@Test
-	void overridingMongoSessionConverterWithBeanShouldWork() throws IllegalAccessException {
-
-		this.context = new AnnotationConfigApplicationContext();
-		this.context.register(OverrideSessionConverterConfig.class);
-		this.context.refresh();
-
-		ReactiveMongoSessionRepository repository = this.context.getBean(ReactiveMongoSessionRepository.class);
-
-		AbstractMongoSessionConverter converter = findMongoSessionConverter(repository);
-
-		assertThat(converter).isOfAnyClassIn(JacksonMongoSessionConverter.class);
-	}
-
-	@Test
-	void overridingIntervalAndCollectionNameThroughAnnotationShouldWork() {
-
-		this.context = new AnnotationConfigApplicationContext();
-		this.context.register(OverrideMongoParametersConfig.class);
-		this.context.refresh();
-
-		ReactiveMongoSessionRepository repository = this.context.getBean(ReactiveMongoSessionRepository.class);
-
-		assertThat(repository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ofSeconds(123));
-		assertThat(repository).extracting("collectionName").isEqualTo("test-case");
-	}
-
-	@Test
-	void reactiveAndBlockingMongoOperationsShouldEnsureIndexing() {
-
-		this.context = new AnnotationConfigApplicationContext();
-		this.context.register(ConfigWithReactiveAndImperativeMongoOperations.class);
-		this.context.refresh();
-
-		MongoOperations operations = this.context.getBean(MongoOperations.class);
-		IndexOperations indexOperations = this.context.getBean(IndexOperations.class);
-
-		verify(operations, times(1)).indexOps((String) any());
-		verify(indexOperations, times(1)).getIndexInfo();
-		verify(indexOperations, times(1)).ensureIndex(any());
-	}
-
-	@Test
-	void overrideCollectionAndInactiveIntervalThroughConfigurationOptions() {
-
-		this.context = new AnnotationConfigApplicationContext();
-		this.context.register(CustomizedReactiveConfiguration.class);
-		this.context.refresh();
-
-		ReactiveMongoSessionRepository repository = this.context.getBean(ReactiveMongoSessionRepository.class);
+    private AnnotationConfigApplicationContext context;
 
-		assertThat(repository.getCollectionName()).isEqualTo("custom-collection");
-		assertThat(repository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ofSeconds(123));
-	}
+    @AfterEach
+    void tearDown() {
 
-	@Test
-	void sessionRepositoryCustomizer() {
+        if (this.context != null) {
+            this.context.close();
+        }
+    }
 
-		this.context = new AnnotationConfigApplicationContext();
-		this.context.register(SessionRepositoryCustomizerConfiguration.class);
-		this.context.refresh();
+    @Test
+    void enableSpringWebSessionConfiguresThings() {
 
-		ReactiveMongoSessionRepository repository = this.context.getBean(ReactiveMongoSessionRepository.class);
+        this.context = new AnnotationConfigApplicationContext();
+        this.context.register(GoodConfig.class);
+        this.context.refresh();
 
-		assertThat(repository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ofSeconds(10000));
-	}
+        WebSessionManager webSessionManagerFoundByType = this.context.getBean(WebSessionManager.class);
+        Object webSessionManagerFoundByName = this.context.getBean(WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME);
 
-	@Test
-	void customIndexResolverConfigurationWithDefaultMongoSessionConverter() {
+        assertThat(webSessionManagerFoundByType).isNotNull();
+        assertThat(webSessionManagerFoundByName).isNotNull();
+        assertThat(webSessionManagerFoundByType).isEqualTo(webSessionManagerFoundByName);
 
-		this.context = new AnnotationConfigApplicationContext();
-		this.context.register(CustomIndexResolverConfigurationWithDefaultMongoSessionConverter.class);
-		this.context.refresh();
+        assertThat(this.context.getBean(ReactiveSessionRepository.class)).isNotNull();
+    }
 
-		ReactiveMongoSessionRepository repository = this.context.getBean(ReactiveMongoSessionRepository.class);
-		IndexResolver indexResolver = this.context.getBean(IndexResolver.class);
+    @Test
+    void missingReactorSessionRepositoryBreaksAppContext() {
 
-		assertThat(repository).isNotNull();
-		assertThat(indexResolver).isNotNull();
-		assertThat(repository).extracting("mongoSessionConverter")
-			.hasFieldOrPropertyWithValue("indexResolver", indexResolver);
-	}
-
-	@Test
-	void customIndexResolverConfigurationWithProvidedMongoSessionConverter() {
+        this.context = new AnnotationConfigApplicationContext();
+        this.context.register(BadConfig.class);
 
-		this.context = new AnnotationConfigApplicationContext();
-		this.context.register(CustomIndexResolverConfigurationWithProvidedtMongoSessionConverter.class);
-		this.context.refresh();
-
-		ReactiveMongoSessionRepository repository = this.context.getBean(ReactiveMongoSessionRepository.class);
-		IndexResolver indexResolver = this.context.getBean(IndexResolver.class);
+        assertThatExceptionOfType(UnsatisfiedDependencyException.class)
+                .isThrownBy(this.context::refresh)
+                .withMessageContaining("Error creating bean with name 'reactiveMongoSessionRepository'")
+                .withMessageContaining(
+                        "No qualifying bean of type '" + ReactiveMongoOperations.class.getCanonicalName());
+    }
 
-		assertThat(repository).isNotNull();
-		assertThat(indexResolver).isNotNull();
-		assertThat(repository).extracting("mongoSessionConverter")
-			.hasFieldOrPropertyWithValue("indexResolver", indexResolver);
-	}
-
-	@Test
-	void importConfigAndCustomize() {
-		this.context = new AnnotationConfigApplicationContext();
-		this.context.register(ImportConfigAndCustomizeConfiguration.class);
-		this.context.refresh();
-		ReactiveMongoSessionRepository sessionRepository = this.context.getBean(ReactiveMongoSessionRepository.class);
-		assertThat(sessionRepository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ZERO);
-	}
-
-	@Test
-	void registerWhenSessionIdGeneratorBeanThenUses() {
-		registerAndRefresh(GoodConfig.class, SessionIdGeneratorConfiguration.class);
-		ReactiveMongoSessionRepository sessionRepository = this.context.getBean(ReactiveMongoSessionRepository.class);
-		assertThat(sessionRepository).extracting("sessionIdGenerator").isInstanceOf(TestSessionIdGenerator.class);
-	}
-
-	@Test
-	void registerWhenNoSessionIdGeneratorBeanThenDefault() {
-		registerAndRefresh(GoodConfig.class);
-		ReactiveMongoSessionRepository sessionRepository = this.context.getBean(ReactiveMongoSessionRepository.class);
-		assertThat(sessionRepository).extracting("sessionIdGenerator").isInstanceOf(UuidSessionIdGenerator.class);
-	}
-
-	private void registerAndRefresh(Class... annotatedClasses) {
-		this.context = new AnnotationConfigApplicationContext();
-		this.context.register(annotatedClasses);
-		this.context.refresh();
-	}
-
-	/**
-	 * Reflectively extract the {@link AbstractMongoSessionConverter} from the
-	 * {@link ReactiveMongoSessionRepository}. This is to avoid expanding the surface area
-	 * of the API.
-	 */
-	private AbstractMongoSessionConverter findMongoSessionConverter(ReactiveMongoSessionRepository repository) {
-
-		Field field = ReflectionUtils.findField(ReactiveMongoSessionRepository.class, "mongoSessionConverter");
-		ReflectionUtils.makeAccessible(field);
-		try {
-			return (AbstractMongoSessionConverter) field.get(repository);
-		}
-		catch (IllegalAccessException ex) {
-			throw new RuntimeException(ex);
-		}
-	}
-
-	/**
-	 * A configuration with all the right parts.
-	 */
-	@Configuration(proxyBeanMethods = false)
-	@EnableMongoWebSession
-	static class GoodConfig {
-
-		@Bean
-		ReactiveMongoOperations operations() {
-			return mock(ReactiveMongoOperations.class);
-		}
-
-	}
-
-	/**
-	 * A configuration where no {@link ReactiveMongoOperations} is defined. It's BAD!
-	 */
-	@Configuration(proxyBeanMethods = false)
-	@EnableMongoWebSession
-	static class BadConfig {
-
-	}
-
-	@Configuration(proxyBeanMethods = false)
-	@EnableMongoWebSession
-	static class OverrideSessionConverterConfig {
-
-		@Bean
-		ReactiveMongoOperations operations() {
-			return mock(ReactiveMongoOperations.class);
-		}
-
-		@Bean
-		AbstractMongoSessionConverter mongoSessionConverter() {
-			return new JacksonMongoSessionConverter();
-		}
-
-	}
-
-	@Configuration(proxyBeanMethods = false)
-	@EnableMongoWebSession(maxInactiveIntervalInSeconds = 123, collectionName = "test-case")
-	static class OverrideMongoParametersConfig {
-
-		@Bean
-		ReactiveMongoOperations operations() {
-			return mock(ReactiveMongoOperations.class);
-		}
-
-	}
-
-	@Configuration(proxyBeanMethods = false)
-	@EnableMongoWebSession
-	static class ConfigWithReactiveAndImperativeMongoOperations {
-
-		@Bean
-		ReactiveMongoOperations reactiveMongoOperations() {
-			return mock(ReactiveMongoOperations.class);
-		}
-
-		@Bean
-		IndexOperations indexOperations() {
-
-			IndexOperations indexOperations = mock(IndexOperations.class);
-			given(indexOperations.getIndexInfo()).willReturn(Collections.emptyList());
-			return indexOperations;
-		}
-
-		@Bean
-		MongoOperations mongoOperations(IndexOperations indexOperations) {
-
-			MongoOperations mongoOperations = mock(MongoOperations.class);
-			given(mongoOperations.indexOps((String) any())).willReturn(indexOperations);
-			return mongoOperations;
-		}
-
-	}
-
-	@Configuration(proxyBeanMethods = false)
-	@EnableSpringWebSession
-	static class CustomizedReactiveConfiguration extends ReactiveMongoWebSessionConfiguration {
-
-		CustomizedReactiveConfiguration() {
-
-			this.setCollectionName("custom-collection");
-			this.setMaxInactiveInterval(Duration.ofSeconds(123));
-		}
-
-		@Bean
-		ReactiveMongoOperations reactiveMongoOperations() {
-			return mock(ReactiveMongoOperations.class);
-		}
-
-	}
+    @Test
+    void defaultSessionConverterShouldBeJdkWhenOnClasspath() throws IllegalAccessException {
 
-	@Configuration(proxyBeanMethods = false)
-	@EnableMongoWebSession
-	static class SessionRepositoryCustomizerConfiguration {
+        this.context = new AnnotationConfigApplicationContext();
+        this.context.register(GoodConfig.class);
+        this.context.refresh();
 
-		@Bean
-		ReactiveMongoOperations operations() {
-			return mock(ReactiveMongoOperations.class);
-		}
+        ReactiveMongoSessionRepository repository = this.context.getBean(ReactiveMongoSessionRepository.class);
 
-		@Bean
-		@Order(0)
-		ReactiveSessionRepositoryCustomizer sessionRepositoryCustomizerOne() {
-			return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(Duration.ZERO);
-		}
+        AbstractMongoSessionConverter converter = findMongoSessionConverter(repository);
 
-		@Bean
-		@Order(1)
-		ReactiveSessionRepositoryCustomizer sessionRepositoryCustomizerTwo() {
-			return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(Duration.ofSeconds(10000));
-		}
+        assertThat(converter).isOfAnyClassIn(JdkMongoSessionConverter.class);
+    }
 
-	}
+    @Test
+    void overridingMongoSessionConverterWithBeanShouldWork() throws IllegalAccessException {
 
-	@Configuration(proxyBeanMethods = false)
-	@EnableMongoWebSession
-	static class CustomIndexResolverConfigurationWithDefaultMongoSessionConverter {
+        this.context = new AnnotationConfigApplicationContext();
+        this.context.register(OverrideSessionConverterConfig.class);
+        this.context.refresh();
 
-		@Bean
-		ReactiveMongoOperations operations() {
-			return mock(ReactiveMongoOperations.class);
-		}
+        ReactiveMongoSessionRepository repository = this.context.getBean(ReactiveMongoSessionRepository.class);
 
-		@Bean
-		@SuppressWarnings("unchecked")
-		IndexResolver indexResolver() {
-			return mock(IndexResolver.class);
-		}
+        AbstractMongoSessionConverter converter = findMongoSessionConverter(repository);
 
-	}
+        assertThat(converter).isOfAnyClassIn(JacksonMongoSessionConverter.class);
+    }
 
-	@Configuration(proxyBeanMethods = false)
-	@EnableMongoWebSession
-	static class CustomIndexResolverConfigurationWithProvidedtMongoSessionConverter {
+    @Test
+    void overridingIntervalAndCollectionNameThroughAnnotationShouldWork() {
 
-		@Bean
-		ReactiveMongoOperations operations() {
-			return mock(ReactiveMongoOperations.class);
-		}
+        this.context = new AnnotationConfigApplicationContext();
+        this.context.register(OverrideMongoParametersConfig.class);
+        this.context.refresh();
 
-		@Bean
-		JacksonMongoSessionConverter jacksonMongoSessionConverter() {
-			return new JacksonMongoSessionConverter();
-		}
+        ReactiveMongoSessionRepository repository = this.context.getBean(ReactiveMongoSessionRepository.class);
 
-		@Bean
-		@SuppressWarnings("unchecked")
-		IndexResolver indexResolver() {
-			return mock(IndexResolver.class);
-		}
+        assertThat(repository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ofSeconds(123));
+        assertThat(repository).extracting("collectionName").isEqualTo("test-case");
+    }
 
-	}
+    @Test
+    void reactiveAndBlockingMongoOperationsShouldEnsureIndexing() {
 
-	@Configuration(proxyBeanMethods = false)
-	@Import(ReactiveMongoWebSessionConfiguration.class)
-	static class ImportConfigAndCustomizeConfiguration {
+        this.context = new AnnotationConfigApplicationContext();
+        this.context.register(ConfigWithReactiveAndImperativeMongoOperations.class);
+        this.context.refresh();
 
-		@Bean
-		ReactiveMongoOperations operations() {
-			return mock(ReactiveMongoOperations.class);
-		}
+        MongoOperations operations = this.context.getBean(MongoOperations.class);
+        IndexOperations indexOperations = this.context.getBean(IndexOperations.class);
 
-		@Bean
-		ReactiveSessionRepositoryCustomizer sessionRepositoryCustomizer() {
-			return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(Duration.ZERO);
-		}
+        verify(operations, times(1)).indexOps((String) any());
+        verify(indexOperations, times(1)).getIndexInfo();
+        verify(indexOperations, times(1)).ensureIndex(any());
+    }
 
-	}
+    @Test
+    void overrideCollectionAndInactiveIntervalThroughConfigurationOptions() {
 
-	@Configuration(proxyBeanMethods = false)
-	static class SessionIdGeneratorConfiguration {
+        this.context = new AnnotationConfigApplicationContext();
+        this.context.register(CustomizedReactiveConfiguration.class);
+        this.context.refresh();
 
-		@Bean
-		SessionIdGenerator sessionIdGenerator() {
-			return new TestSessionIdGenerator();
-		}
+        ReactiveMongoSessionRepository repository = this.context.getBean(ReactiveMongoSessionRepository.class);
 
-	}
+        assertThat(repository.getCollectionName()).isEqualTo("custom-collection");
+        assertThat(repository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ofSeconds(123));
+    }
 
-	static class TestSessionIdGenerator implements SessionIdGenerator {
+    @Test
+    void sessionRepositoryCustomizer() {
 
-		@Override
-		public String generate() {
-			return "test";
-		}
+        this.context = new AnnotationConfigApplicationContext();
+        this.context.register(SessionRepositoryCustomizerConfiguration.class);
+        this.context.refresh();
 
-	}
+        ReactiveMongoSessionRepository repository = this.context.getBean(ReactiveMongoSessionRepository.class);
+
+        assertThat(repository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ofSeconds(10000));
+    }
+
+    @Test
+    void customIndexResolverConfigurationWithDefaultMongoSessionConverter() {
 
+        this.context = new AnnotationConfigApplicationContext();
+        this.context.register(CustomIndexResolverConfigurationWithDefaultMongoSessionConverter.class);
+        this.context.refresh();
+
+        ReactiveMongoSessionRepository repository = this.context.getBean(ReactiveMongoSessionRepository.class);
+        IndexResolver indexResolver = this.context.getBean(IndexResolver.class);
+
+        assertThat(repository).isNotNull();
+        assertThat(indexResolver).isNotNull();
+        assertThat(repository)
+                .extracting("mongoSessionConverter")
+                .hasFieldOrPropertyWithValue("indexResolver", indexResolver);
+    }
+
+    @Test
+    void customIndexResolverConfigurationWithProvidedMongoSessionConverter() {
+
+        this.context = new AnnotationConfigApplicationContext();
+        this.context.register(CustomIndexResolverConfigurationWithProvidedtMongoSessionConverter.class);
+        this.context.refresh();
+
+        ReactiveMongoSessionRepository repository = this.context.getBean(ReactiveMongoSessionRepository.class);
+        IndexResolver indexResolver = this.context.getBean(IndexResolver.class);
+
+        assertThat(repository).isNotNull();
+        assertThat(indexResolver).isNotNull();
+        assertThat(repository)
+                .extracting("mongoSessionConverter")
+                .hasFieldOrPropertyWithValue("indexResolver", indexResolver);
+    }
+
+    @Test
+    void importConfigAndCustomize() {
+        this.context = new AnnotationConfigApplicationContext();
+        this.context.register(ImportConfigAndCustomizeConfiguration.class);
+        this.context.refresh();
+        ReactiveMongoSessionRepository sessionRepository = this.context.getBean(ReactiveMongoSessionRepository.class);
+        assertThat(sessionRepository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ZERO);
+    }
+
+    @Test
+    void registerWhenSessionIdGeneratorBeanThenUses() {
+        registerAndRefresh(GoodConfig.class, SessionIdGeneratorConfiguration.class);
+        ReactiveMongoSessionRepository sessionRepository = this.context.getBean(ReactiveMongoSessionRepository.class);
+        assertThat(sessionRepository).extracting("sessionIdGenerator").isInstanceOf(TestSessionIdGenerator.class);
+    }
+
+    @Test
+    void registerWhenNoSessionIdGeneratorBeanThenDefault() {
+        registerAndRefresh(GoodConfig.class);
+        ReactiveMongoSessionRepository sessionRepository = this.context.getBean(ReactiveMongoSessionRepository.class);
+        assertThat(sessionRepository).extracting("sessionIdGenerator").isInstanceOf(UuidSessionIdGenerator.class);
+    }
+
+    private void registerAndRefresh(Class... annotatedClasses) {
+        this.context = new AnnotationConfigApplicationContext();
+        this.context.register(annotatedClasses);
+        this.context.refresh();
+    }
+
+    /**
+     * Reflectively extract the {@link AbstractMongoSessionConverter} from the {@link ReactiveMongoSessionRepository}.
+     * This is to avoid expanding the surface area of the API.
+     */
+    private AbstractMongoSessionConverter findMongoSessionConverter(ReactiveMongoSessionRepository repository) {
+
+        Field field = ReflectionUtils.findField(ReactiveMongoSessionRepository.class, "mongoSessionConverter");
+        ReflectionUtils.makeAccessible(field);
+        try {
+            return (AbstractMongoSessionConverter) field.get(repository);
+        } catch (IllegalAccessException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    /** A configuration with all the right parts. */
+    @Configuration(proxyBeanMethods = false)
+    @EnableMongoWebSession
+    static class GoodConfig {
+
+        @Bean
+        ReactiveMongoOperations operations() {
+            return mock(ReactiveMongoOperations.class);
+        }
+    }
+
+    /** A configuration where no {@link ReactiveMongoOperations} is defined. It's BAD! */
+    @Configuration(proxyBeanMethods = false)
+    @EnableMongoWebSession
+    static class BadConfig {}
+
+    @Configuration(proxyBeanMethods = false)
+    @EnableMongoWebSession
+    static class OverrideSessionConverterConfig {
+
+        @Bean
+        ReactiveMongoOperations operations() {
+            return mock(ReactiveMongoOperations.class);
+        }
+
+        @Bean
+        AbstractMongoSessionConverter mongoSessionConverter() {
+            return new JacksonMongoSessionConverter();
+        }
+    }
+
+    @Configuration(proxyBeanMethods = false)
+    @EnableMongoWebSession(maxInactiveIntervalInSeconds = 123, collectionName = "test-case")
+    static class OverrideMongoParametersConfig {
+
+        @Bean
+        ReactiveMongoOperations operations() {
+            return mock(ReactiveMongoOperations.class);
+        }
+    }
+
+    @Configuration(proxyBeanMethods = false)
+    @EnableMongoWebSession
+    static class ConfigWithReactiveAndImperativeMongoOperations {
+
+        @Bean
+        ReactiveMongoOperations reactiveMongoOperations() {
+            return mock(ReactiveMongoOperations.class);
+        }
+
+        @Bean
+        IndexOperations indexOperations() {
+
+            IndexOperations indexOperations = mock(IndexOperations.class);
+            given(indexOperations.getIndexInfo()).willReturn(Collections.emptyList());
+            return indexOperations;
+        }
+
+        @Bean
+        MongoOperations mongoOperations(IndexOperations indexOperations) {
+
+            MongoOperations mongoOperations = mock(MongoOperations.class);
+            given(mongoOperations.indexOps((String) any())).willReturn(indexOperations);
+            return mongoOperations;
+        }
+    }
+
+    @Configuration(proxyBeanMethods = false)
+    @EnableSpringWebSession
+    static class CustomizedReactiveConfiguration extends ReactiveMongoWebSessionConfiguration {
+
+        CustomizedReactiveConfiguration() {
+
+            this.setCollectionName("custom-collection");
+            this.setMaxInactiveInterval(Duration.ofSeconds(123));
+        }
+
+        @Bean
+        ReactiveMongoOperations reactiveMongoOperations() {
+            return mock(ReactiveMongoOperations.class);
+        }
+    }
+
+    @Configuration(proxyBeanMethods = false)
+    @EnableMongoWebSession
+    static class SessionRepositoryCustomizerConfiguration {
+
+        @Bean
+        ReactiveMongoOperations operations() {
+            return mock(ReactiveMongoOperations.class);
+        }
+
+        @Bean
+        @Order(0)
+        ReactiveSessionRepositoryCustomizer sessionRepositoryCustomizerOne() {
+            return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(Duration.ZERO);
+        }
+
+        @Bean
+        @Order(1)
+        ReactiveSessionRepositoryCustomizer sessionRepositoryCustomizerTwo() {
+            return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(Duration.ofSeconds(10000));
+        }
+    }
+
+    @Configuration(proxyBeanMethods = false)
+    @EnableMongoWebSession
+    static class CustomIndexResolverConfigurationWithDefaultMongoSessionConverter {
+
+        @Bean
+        ReactiveMongoOperations operations() {
+            return mock(ReactiveMongoOperations.class);
+        }
+
+        @Bean
+        @SuppressWarnings("unchecked")
+        IndexResolver indexResolver() {
+            return mock(IndexResolver.class);
+        }
+    }
+
+    @Configuration(proxyBeanMethods = false)
+    @EnableMongoWebSession
+    static class CustomIndexResolverConfigurationWithProvidedtMongoSessionConverter {
+
+        @Bean
+        ReactiveMongoOperations operations() {
+            return mock(ReactiveMongoOperations.class);
+        }
+
+        @Bean
+        JacksonMongoSessionConverter jacksonMongoSessionConverter() {
+            return new JacksonMongoSessionConverter();
+        }
+
+        @Bean
+        @SuppressWarnings("unchecked")
+        IndexResolver indexResolver() {
+            return mock(IndexResolver.class);
+        }
+    }
+
+    @Configuration(proxyBeanMethods = false)
+    @Import(ReactiveMongoWebSessionConfiguration.class)
+    static class ImportConfigAndCustomizeConfiguration {
+
+        @Bean
+        ReactiveMongoOperations operations() {
+            return mock(ReactiveMongoOperations.class);
+        }
+
+        @Bean
+        ReactiveSessionRepositoryCustomizer sessionRepositoryCustomizer() {
+            return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(Duration.ZERO);
+        }
+    }
+
+    @Configuration(proxyBeanMethods = false)
+    static class SessionIdGeneratorConfiguration {
+
+        @Bean
+        SessionIdGenerator sessionIdGenerator() {
+            return new TestSessionIdGenerator();
+        }
+    }
+
+    static class TestSessionIdGenerator implements SessionIdGenerator {
+
+        @Override
+        public String generate() {
+            return "test";
+        }
+    }
 }

From a4382b92fc74ba26e3a888626cfbbbc7fc1a03da Mon Sep 17 00:00:00 2001
From: Ross Lawley 
Date: Thu, 13 Nov 2025 10:05:02 +0000
Subject: [PATCH 3/7] Moved package namespace

From: org.springframework.session.data.mongo
To: org.mongodb.spring.session
---
 .../spring/session}/AbstractClassLoaderTest.java       |  2 +-
 .../spring/session}/AbstractITest.java                 |  2 +-
 .../spring/session}/AbstractMongoRepositoryITest.java  |  2 +-
 .../MongoDbDeleteJacksonSessionVerificationTest.java   |  4 ++--
 .../spring/session}/MongoDbLogoutVerificationTest.java |  4 ++--
 .../spring/session}/MongoRepositoryJacksonITest.java   |  6 +++---
 .../session}/MongoRepositoryJdkSerializationITest.java |  6 +++---
 .../spring/session}/AbstractMongoSessionConverter.java |  2 +-
 .../spring/session}/JacksonMongoSessionConverter.java  |  2 +-
 .../spring/session}/JdkMongoSessionConverter.java      |  2 +-
 .../spring/session}/MongoIndexedSessionRepository.java |  2 +-
 .../mongo => mongodb/spring/session}/MongoSession.java |  2 +-
 .../spring/session}/MongoSessionUtils.java             |  2 +-
 .../session}/ReactiveMongoSessionRepository.java       |  2 +-
 .../annotation/web/http/EnableMongoHttpSession.java    |  4 ++--
 .../web/http/MongoHttpSessionConfiguration.java        |  8 ++++----
 .../annotation/web/reactive/EnableMongoWebSession.java |  4 ++--
 .../reactive/ReactiveMongoWebSessionConfiguration.java |  8 ++++----
 .../mongo => mongodb/spring/session}/package-info.java |  2 +-
 .../session}/AbstractMongoSessionConverterTests.java   |  2 +-
 .../session}/JacksonMongoSessionConverterTests.java    |  2 +-
 .../spring/session}/JdkMongoSessionConverterTests.java |  2 +-
 .../session}/MongoIndexedSessionRepositoryTests.java   |  2 +-
 .../spring/session}/MongoSessionTests.java             |  2 +-
 .../session}/ReactiveMongoSessionRepositoryTests.java  |  2 +-
 .../web/http/MongoHttpSessionConfigurationTests.java   |  8 ++++----
 .../ReactiveMongoWebSessionConfigurationTests.java     | 10 +++++-----
 27 files changed, 48 insertions(+), 48 deletions(-)
 rename src/integrationTest/java/org/{springframework/session/data/mongo => mongodb/spring/session}/AbstractClassLoaderTest.java (98%)
 rename src/integrationTest/java/org/{springframework/session/data/mongo => mongodb/spring/session}/AbstractITest.java (97%)
 rename src/integrationTest/java/org/{springframework/session/data/mongo => mongodb/spring/session}/AbstractMongoRepositoryITest.java (99%)
 rename src/integrationTest/java/org/{springframework/session/data/mongo => mongodb/spring/session}/MongoDbDeleteJacksonSessionVerificationTest.java (98%)
 rename src/integrationTest/java/org/{springframework/session/data/mongo => mongodb/spring/session}/MongoDbLogoutVerificationTest.java (98%)
 rename src/integrationTest/java/org/{springframework/session/data/mongo => mongodb/spring/session}/MongoRepositoryJacksonITest.java (89%)
 rename src/integrationTest/java/org/{springframework/session/data/mongo => mongodb/spring/session}/MongoRepositoryJdkSerializationITest.java (91%)
 rename src/main/java/org/{springframework/session/data/mongo => mongodb/spring/session}/AbstractMongoSessionConverter.java (99%)
 rename src/main/java/org/{springframework/session/data/mongo => mongodb/spring/session}/JacksonMongoSessionConverter.java (99%)
 rename src/main/java/org/{springframework/session/data/mongo => mongodb/spring/session}/JdkMongoSessionConverter.java (99%)
 rename src/main/java/org/{springframework/session/data/mongo => mongodb/spring/session}/MongoIndexedSessionRepository.java (99%)
 rename src/main/java/org/{springframework/session/data/mongo => mongodb/spring/session}/MongoSession.java (99%)
 rename src/main/java/org/{springframework/session/data/mongo => mongodb/spring/session}/MongoSessionUtils.java (97%)
 rename src/main/java/org/{springframework/session/data/mongo => mongodb/spring/session}/ReactiveMongoSessionRepository.java (99%)
 rename src/main/java/org/{springframework/session/data/mongo => mongodb/spring/session}/config/annotation/web/http/EnableMongoHttpSession.java (93%)
 rename src/main/java/org/{springframework/session/data/mongo => mongodb/spring/session}/config/annotation/web/http/MongoHttpSessionConfiguration.java (95%)
 rename src/main/java/org/{springframework/session/data/mongo => mongodb/spring/session}/config/annotation/web/reactive/EnableMongoWebSession.java (93%)
 rename src/main/java/org/{springframework/session/data/mongo => mongodb/spring/session}/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java (95%)
 rename src/main/java/org/{springframework/session/data/mongo => mongodb/spring/session}/package-info.java (94%)
 rename src/test/java/org/{springframework/session/data/mongo => mongodb/spring/session}/AbstractMongoSessionConverterTests.java (99%)
 rename src/test/java/org/{springframework/session/data/mongo => mongodb/spring/session}/JacksonMongoSessionConverterTests.java (98%)
 rename src/test/java/org/{springframework/session/data/mongo => mongodb/spring/session}/JdkMongoSessionConverterTests.java (97%)
 rename src/test/java/org/{springframework/session/data/mongo => mongodb/spring/session}/MongoIndexedSessionRepositoryTests.java (99%)
 rename src/test/java/org/{springframework/session/data/mongo => mongodb/spring/session}/MongoSessionTests.java (96%)
 rename src/test/java/org/{springframework/session/data/mongo => mongodb/spring/session}/ReactiveMongoSessionRepositoryTests.java (99%)
 rename src/test/java/org/{springframework/session/data/mongo => mongodb/spring/session}/config/annotation/web/http/MongoHttpSessionConfigurationTests.java (97%)
 rename src/test/java/org/{springframework/session/data/mongo => mongodb/spring/session}/config/annotation/web/reactive/ReactiveMongoWebSessionConfigurationTests.java (97%)

diff --git a/src/integrationTest/java/org/springframework/session/data/mongo/AbstractClassLoaderTest.java b/src/integrationTest/java/org/mongodb/spring/session/AbstractClassLoaderTest.java
similarity index 98%
rename from src/integrationTest/java/org/springframework/session/data/mongo/AbstractClassLoaderTest.java
rename to src/integrationTest/java/org/mongodb/spring/session/AbstractClassLoaderTest.java
index c24ec2f..2ea0766 100644
--- a/src/integrationTest/java/org/springframework/session/data/mongo/AbstractClassLoaderTest.java
+++ b/src/integrationTest/java/org/mongodb/spring/session/AbstractClassLoaderTest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import java.lang.reflect.Field;
 import org.assertj.core.api.AssertionsForClassTypes;
diff --git a/src/integrationTest/java/org/springframework/session/data/mongo/AbstractITest.java b/src/integrationTest/java/org/mongodb/spring/session/AbstractITest.java
similarity index 97%
rename from src/integrationTest/java/org/springframework/session/data/mongo/AbstractITest.java
rename to src/integrationTest/java/org/mongodb/spring/session/AbstractITest.java
index 6fe5bcd..a95a737 100644
--- a/src/integrationTest/java/org/springframework/session/data/mongo/AbstractITest.java
+++ b/src/integrationTest/java/org/mongodb/spring/session/AbstractITest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import java.util.UUID;
 import org.junit.jupiter.api.BeforeEach;
diff --git a/src/integrationTest/java/org/springframework/session/data/mongo/AbstractMongoRepositoryITest.java b/src/integrationTest/java/org/mongodb/spring/session/AbstractMongoRepositoryITest.java
similarity index 99%
rename from src/integrationTest/java/org/springframework/session/data/mongo/AbstractMongoRepositoryITest.java
rename to src/integrationTest/java/org/mongodb/spring/session/AbstractMongoRepositoryITest.java
index 8f887e7..b7f0704 100644
--- a/src/integrationTest/java/org/springframework/session/data/mongo/AbstractMongoRepositoryITest.java
+++ b/src/integrationTest/java/org/mongodb/spring/session/AbstractMongoRepositoryITest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
diff --git a/src/integrationTest/java/org/springframework/session/data/mongo/MongoDbDeleteJacksonSessionVerificationTest.java b/src/integrationTest/java/org/mongodb/spring/session/MongoDbDeleteJacksonSessionVerificationTest.java
similarity index 98%
rename from src/integrationTest/java/org/springframework/session/data/mongo/MongoDbDeleteJacksonSessionVerificationTest.java
rename to src/integrationTest/java/org/mongodb/spring/session/MongoDbDeleteJacksonSessionVerificationTest.java
index d0126a7..7e40624 100644
--- a/src/integrationTest/java/org/springframework/session/data/mongo/MongoDbDeleteJacksonSessionVerificationTest.java
+++ b/src/integrationTest/java/org/mongodb/spring/session/MongoDbDeleteJacksonSessionVerificationTest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import com.mongodb.reactivestreams.client.MongoClient;
 import com.mongodb.reactivestreams.client.MongoClients;
@@ -24,6 +24,7 @@
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.mongodb.spring.session.config.annotation.web.reactive.EnableMongoWebSession;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
@@ -39,7 +40,6 @@
 import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.web.server.SecurityWebFilterChain;
-import org.springframework.session.data.mongo.config.annotation.web.reactive.EnableMongoWebSession;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 import org.springframework.test.web.reactive.server.FluxExchangeResult;
diff --git a/src/integrationTest/java/org/springframework/session/data/mongo/MongoDbLogoutVerificationTest.java b/src/integrationTest/java/org/mongodb/spring/session/MongoDbLogoutVerificationTest.java
similarity index 98%
rename from src/integrationTest/java/org/springframework/session/data/mongo/MongoDbLogoutVerificationTest.java
rename to src/integrationTest/java/org/mongodb/spring/session/MongoDbLogoutVerificationTest.java
index 25a04b7..128e6b2 100644
--- a/src/integrationTest/java/org/springframework/session/data/mongo/MongoDbLogoutVerificationTest.java
+++ b/src/integrationTest/java/org/mongodb/spring/session/MongoDbLogoutVerificationTest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import com.mongodb.reactivestreams.client.MongoClient;
 import com.mongodb.reactivestreams.client.MongoClients;
@@ -24,6 +24,7 @@
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.mongodb.spring.session.config.annotation.web.reactive.EnableMongoWebSession;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
@@ -39,7 +40,6 @@
 import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.web.server.SecurityWebFilterChain;
-import org.springframework.session.data.mongo.config.annotation.web.reactive.EnableMongoWebSession;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 import org.springframework.test.web.reactive.server.FluxExchangeResult;
diff --git a/src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJacksonITest.java b/src/integrationTest/java/org/mongodb/spring/session/MongoRepositoryJacksonITest.java
similarity index 89%
rename from src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJacksonITest.java
rename to src/integrationTest/java/org/mongodb/spring/session/MongoRepositoryJacksonITest.java
index 70e521a..3eb01c4 100644
--- a/src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJacksonITest.java
+++ b/src/integrationTest/java/org/mongodb/spring/session/MongoRepositoryJacksonITest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -23,14 +23,14 @@
 import java.util.Map;
 import java.util.UUID;
 import org.junit.jupiter.api.Test;
+import org.mongodb.spring.session.config.annotation.web.http.EnableMongoHttpSession;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.data.geo.GeoModule;
-import org.springframework.session.data.mongo.config.annotation.web.http.EnableMongoHttpSession;
 import org.springframework.test.context.ContextConfiguration;
 
 /**
- * Integration tests for {@link org.springframework.session.data.mongo.MongoIndexedSessionRepository} that use
+ * Integration tests for {@link org.mongodb.spring.session.MongoIndexedSessionRepository} that use
  * {@link JacksonMongoSessionConverter} based session serialization.
  *
  * @author Jakub Kubrynski
diff --git a/src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJdkSerializationITest.java b/src/integrationTest/java/org/mongodb/spring/session/MongoRepositoryJdkSerializationITest.java
similarity index 91%
rename from src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJdkSerializationITest.java
rename to src/integrationTest/java/org/mongodb/spring/session/MongoRepositoryJdkSerializationITest.java
index d121a86..85d8943 100644
--- a/src/integrationTest/java/org/springframework/session/data/mongo/MongoRepositoryJdkSerializationITest.java
+++ b/src/integrationTest/java/org/mongodb/spring/session/MongoRepositoryJdkSerializationITest.java
@@ -15,20 +15,20 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.time.Duration;
 import java.util.Map;
 import org.junit.jupiter.api.Test;
+import org.mongodb.spring.session.config.annotation.web.http.EnableMongoHttpSession;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.session.data.mongo.config.annotation.web.http.EnableMongoHttpSession;
 import org.springframework.test.context.ContextConfiguration;
 
 /**
- * Integration tests for {@link org.springframework.session.data.mongo.MongoIndexedSessionRepository} that use
+ * Integration tests for {@link org.mongodb.spring.session.MongoIndexedSessionRepository} that use
  * {@link JdkMongoSessionConverter} based session serialization.
  *
  * @author Jakub Kubrynski
diff --git a/src/main/java/org/springframework/session/data/mongo/AbstractMongoSessionConverter.java b/src/main/java/org/mongodb/spring/session/AbstractMongoSessionConverter.java
similarity index 99%
rename from src/main/java/org/springframework/session/data/mongo/AbstractMongoSessionConverter.java
rename to src/main/java/org/mongodb/spring/session/AbstractMongoSessionConverter.java
index f95e3d9..a3ecc89 100644
--- a/src/main/java/org/springframework/session/data/mongo/AbstractMongoSessionConverter.java
+++ b/src/main/java/org/mongodb/spring/session/AbstractMongoSessionConverter.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import com.mongodb.DBObject;
 import java.util.Collections;
diff --git a/src/main/java/org/springframework/session/data/mongo/JacksonMongoSessionConverter.java b/src/main/java/org/mongodb/spring/session/JacksonMongoSessionConverter.java
similarity index 99%
rename from src/main/java/org/springframework/session/data/mongo/JacksonMongoSessionConverter.java
rename to src/main/java/org/mongodb/spring/session/JacksonMongoSessionConverter.java
index e208d5f..c741ebb 100644
--- a/src/main/java/org/springframework/session/data/mongo/JacksonMongoSessionConverter.java
+++ b/src/main/java/org/mongodb/spring/session/JacksonMongoSessionConverter.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.annotation.JsonCreator;
diff --git a/src/main/java/org/springframework/session/data/mongo/JdkMongoSessionConverter.java b/src/main/java/org/mongodb/spring/session/JdkMongoSessionConverter.java
similarity index 99%
rename from src/main/java/org/springframework/session/data/mongo/JdkMongoSessionConverter.java
rename to src/main/java/org/mongodb/spring/session/JdkMongoSessionConverter.java
index 23c93e1..d26dc81 100644
--- a/src/main/java/org/springframework/session/data/mongo/JdkMongoSessionConverter.java
+++ b/src/main/java/org/mongodb/spring/session/JdkMongoSessionConverter.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import com.mongodb.BasicDBObject;
 import com.mongodb.DBObject;
diff --git a/src/main/java/org/springframework/session/data/mongo/MongoIndexedSessionRepository.java b/src/main/java/org/mongodb/spring/session/MongoIndexedSessionRepository.java
similarity index 99%
rename from src/main/java/org/springframework/session/data/mongo/MongoIndexedSessionRepository.java
rename to src/main/java/org/mongodb/spring/session/MongoIndexedSessionRepository.java
index a43fa57..0b5b026 100644
--- a/src/main/java/org/springframework/session/data/mongo/MongoIndexedSessionRepository.java
+++ b/src/main/java/org/mongodb/spring/session/MongoIndexedSessionRepository.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import com.mongodb.DBObject;
 import java.time.Duration;
diff --git a/src/main/java/org/springframework/session/data/mongo/MongoSession.java b/src/main/java/org/mongodb/spring/session/MongoSession.java
similarity index 99%
rename from src/main/java/org/springframework/session/data/mongo/MongoSession.java
rename to src/main/java/org/mongodb/spring/session/MongoSession.java
index 86b5e42..284c595 100644
--- a/src/main/java/org/springframework/session/data/mongo/MongoSession.java
+++ b/src/main/java/org/mongodb/spring/session/MongoSession.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import java.time.Duration;
 import java.time.Instant;
diff --git a/src/main/java/org/springframework/session/data/mongo/MongoSessionUtils.java b/src/main/java/org/mongodb/spring/session/MongoSessionUtils.java
similarity index 97%
rename from src/main/java/org/springframework/session/data/mongo/MongoSessionUtils.java
rename to src/main/java/org/mongodb/spring/session/MongoSessionUtils.java
index fb352d7..f1f2f67 100644
--- a/src/main/java/org/springframework/session/data/mongo/MongoSessionUtils.java
+++ b/src/main/java/org/mongodb/spring/session/MongoSessionUtils.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import com.mongodb.DBObject;
 import org.bson.Document;
diff --git a/src/main/java/org/springframework/session/data/mongo/ReactiveMongoSessionRepository.java b/src/main/java/org/mongodb/spring/session/ReactiveMongoSessionRepository.java
similarity index 99%
rename from src/main/java/org/springframework/session/data/mongo/ReactiveMongoSessionRepository.java
rename to src/main/java/org/mongodb/spring/session/ReactiveMongoSessionRepository.java
index 814dfa8..9c756f8 100644
--- a/src/main/java/org/springframework/session/data/mongo/ReactiveMongoSessionRepository.java
+++ b/src/main/java/org/mongodb/spring/session/ReactiveMongoSessionRepository.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import java.time.Duration;
 import org.apache.commons.logging.Log;
diff --git a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/http/EnableMongoHttpSession.java b/src/main/java/org/mongodb/spring/session/config/annotation/web/http/EnableMongoHttpSession.java
similarity index 93%
rename from src/main/java/org/springframework/session/data/mongo/config/annotation/web/http/EnableMongoHttpSession.java
rename to src/main/java/org/mongodb/spring/session/config/annotation/web/http/EnableMongoHttpSession.java
index 1552e84..35f1ed7 100644
--- a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/http/EnableMongoHttpSession.java
+++ b/src/main/java/org/mongodb/spring/session/config/annotation/web/http/EnableMongoHttpSession.java
@@ -15,16 +15,16 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo.config.annotation.web.http;
+package org.mongodb.spring.session.config.annotation.web.http;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import org.mongodb.spring.session.MongoIndexedSessionRepository;
 import org.springframework.context.annotation.Import;
 import org.springframework.session.MapSession;
-import org.springframework.session.data.mongo.MongoIndexedSessionRepository;
 
 /**
  * Add this annotation to a {@code @Configuration} class to expose the SessionRepositoryFilter as a bean named
diff --git a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/http/MongoHttpSessionConfiguration.java b/src/main/java/org/mongodb/spring/session/config/annotation/web/http/MongoHttpSessionConfiguration.java
similarity index 95%
rename from src/main/java/org/springframework/session/data/mongo/config/annotation/web/http/MongoHttpSessionConfiguration.java
rename to src/main/java/org/mongodb/spring/session/config/annotation/web/http/MongoHttpSessionConfiguration.java
index 222cd22..13522bd 100644
--- a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/http/MongoHttpSessionConfiguration.java
+++ b/src/main/java/org/mongodb/spring/session/config/annotation/web/http/MongoHttpSessionConfiguration.java
@@ -15,11 +15,14 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo.config.annotation.web.http;
+package org.mongodb.spring.session.config.annotation.web.http;
 
 import java.time.Duration;
 import java.util.List;
 import java.util.stream.Collectors;
+import org.mongodb.spring.session.AbstractMongoSessionConverter;
+import org.mongodb.spring.session.JdkMongoSessionConverter;
+import org.mongodb.spring.session.MongoIndexedSessionRepository;
 import org.springframework.beans.factory.BeanClassLoaderAware;
 import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -40,9 +43,6 @@
 import org.springframework.session.UuidSessionIdGenerator;
 import org.springframework.session.config.SessionRepositoryCustomizer;
 import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
-import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
-import org.springframework.session.data.mongo.JdkMongoSessionConverter;
-import org.springframework.session.data.mongo.MongoIndexedSessionRepository;
 import org.springframework.util.StringUtils;
 import org.springframework.util.StringValueResolver;
 
diff --git a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/reactive/EnableMongoWebSession.java b/src/main/java/org/mongodb/spring/session/config/annotation/web/reactive/EnableMongoWebSession.java
similarity index 93%
rename from src/main/java/org/springframework/session/data/mongo/config/annotation/web/reactive/EnableMongoWebSession.java
rename to src/main/java/org/mongodb/spring/session/config/annotation/web/reactive/EnableMongoWebSession.java
index 2478ee5..07b543f 100644
--- a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/reactive/EnableMongoWebSession.java
+++ b/src/main/java/org/mongodb/spring/session/config/annotation/web/reactive/EnableMongoWebSession.java
@@ -15,14 +15,14 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo.config.annotation.web.reactive;
+package org.mongodb.spring.session.config.annotation.web.reactive;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
+import org.mongodb.spring.session.ReactiveMongoSessionRepository;
 import org.springframework.context.annotation.Import;
 import org.springframework.session.MapSession;
-import org.springframework.session.data.mongo.ReactiveMongoSessionRepository;
 
 /**
  * Add this annotation to a {@code @Configuration} class to configure a MongoDB-based {@code WebSessionManager} for a
diff --git a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java b/src/main/java/org/mongodb/spring/session/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java
similarity index 95%
rename from src/main/java/org/springframework/session/data/mongo/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java
rename to src/main/java/org/mongodb/spring/session/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java
index e6b263b..170308b 100644
--- a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java
+++ b/src/main/java/org/mongodb/spring/session/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java
@@ -15,11 +15,14 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo.config.annotation.web.reactive;
+package org.mongodb.spring.session.config.annotation.web.reactive;
 
 import java.time.Duration;
 import java.util.List;
 import java.util.stream.Collectors;
+import org.mongodb.spring.session.AbstractMongoSessionConverter;
+import org.mongodb.spring.session.JdkMongoSessionConverter;
+import org.mongodb.spring.session.ReactiveMongoSessionRepository;
 import org.springframework.beans.factory.BeanClassLoaderAware;
 import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -41,9 +44,6 @@
 import org.springframework.session.UuidSessionIdGenerator;
 import org.springframework.session.config.ReactiveSessionRepositoryCustomizer;
 import org.springframework.session.config.annotation.web.server.SpringWebSessionConfiguration;
-import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
-import org.springframework.session.data.mongo.JdkMongoSessionConverter;
-import org.springframework.session.data.mongo.ReactiveMongoSessionRepository;
 import org.springframework.util.StringUtils;
 import org.springframework.util.StringValueResolver;
 
diff --git a/src/main/java/org/springframework/session/data/mongo/package-info.java b/src/main/java/org/mongodb/spring/session/package-info.java
similarity index 94%
rename from src/main/java/org/springframework/session/data/mongo/package-info.java
rename to src/main/java/org/mongodb/spring/session/package-info.java
index f7881e7..c4c4138 100644
--- a/src/main/java/org/springframework/session/data/mongo/package-info.java
+++ b/src/main/java/org/mongodb/spring/session/package-info.java
@@ -20,6 +20,6 @@
  * @author Greg Turnquist
  */
 @NonNullApi
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import org.springframework.lang.NonNullApi;
diff --git a/src/test/java/org/springframework/session/data/mongo/AbstractMongoSessionConverterTests.java b/src/test/java/org/mongodb/spring/session/AbstractMongoSessionConverterTests.java
similarity index 99%
rename from src/test/java/org/springframework/session/data/mongo/AbstractMongoSessionConverterTests.java
rename to src/test/java/org/mongodb/spring/session/AbstractMongoSessionConverterTests.java
index ef5413d..767682f 100644
--- a/src/test/java/org/springframework/session/data/mongo/AbstractMongoSessionConverterTests.java
+++ b/src/test/java/org/mongodb/spring/session/AbstractMongoSessionConverterTests.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
diff --git a/src/test/java/org/springframework/session/data/mongo/JacksonMongoSessionConverterTests.java b/src/test/java/org/mongodb/spring/session/JacksonMongoSessionConverterTests.java
similarity index 98%
rename from src/test/java/org/springframework/session/data/mongo/JacksonMongoSessionConverterTests.java
rename to src/test/java/org/mongodb/spring/session/JacksonMongoSessionConverterTests.java
index 3af55b6..107ba4a 100644
--- a/src/test/java/org/springframework/session/data/mongo/JacksonMongoSessionConverterTests.java
+++ b/src/test/java/org/mongodb/spring/session/JacksonMongoSessionConverterTests.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.mongodb.DBObject;
diff --git a/src/test/java/org/springframework/session/data/mongo/JdkMongoSessionConverterTests.java b/src/test/java/org/mongodb/spring/session/JdkMongoSessionConverterTests.java
similarity index 97%
rename from src/test/java/org/springframework/session/data/mongo/JdkMongoSessionConverterTests.java
rename to src/test/java/org/mongodb/spring/session/JdkMongoSessionConverterTests.java
index 260671f..6415cf0 100644
--- a/src/test/java/org/springframework/session/data/mongo/JdkMongoSessionConverterTests.java
+++ b/src/test/java/org/mongodb/spring/session/JdkMongoSessionConverterTests.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
 
diff --git a/src/test/java/org/springframework/session/data/mongo/MongoIndexedSessionRepositoryTests.java b/src/test/java/org/mongodb/spring/session/MongoIndexedSessionRepositoryTests.java
similarity index 99%
rename from src/test/java/org/springframework/session/data/mongo/MongoIndexedSessionRepositoryTests.java
rename to src/test/java/org/mongodb/spring/session/MongoIndexedSessionRepositoryTests.java
index 4183bab..084da6e 100644
--- a/src/test/java/org/springframework/session/data/mongo/MongoIndexedSessionRepositoryTests.java
+++ b/src/test/java/org/mongodb/spring/session/MongoIndexedSessionRepositoryTests.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
diff --git a/src/test/java/org/springframework/session/data/mongo/MongoSessionTests.java b/src/test/java/org/mongodb/spring/session/MongoSessionTests.java
similarity index 96%
rename from src/test/java/org/springframework/session/data/mongo/MongoSessionTests.java
rename to src/test/java/org/mongodb/spring/session/MongoSessionTests.java
index c23a322..6ca08bd 100644
--- a/src/test/java/org/springframework/session/data/mongo/MongoSessionTests.java
+++ b/src/test/java/org/mongodb/spring/session/MongoSessionTests.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
diff --git a/src/test/java/org/springframework/session/data/mongo/ReactiveMongoSessionRepositoryTests.java b/src/test/java/org/mongodb/spring/session/ReactiveMongoSessionRepositoryTests.java
similarity index 99%
rename from src/test/java/org/springframework/session/data/mongo/ReactiveMongoSessionRepositoryTests.java
rename to src/test/java/org/mongodb/spring/session/ReactiveMongoSessionRepositoryTests.java
index 2e59af8..b006674 100644
--- a/src/test/java/org/springframework/session/data/mongo/ReactiveMongoSessionRepositoryTests.java
+++ b/src/test/java/org/mongodb/spring/session/ReactiveMongoSessionRepositoryTests.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
diff --git a/src/test/java/org/springframework/session/data/mongo/config/annotation/web/http/MongoHttpSessionConfigurationTests.java b/src/test/java/org/mongodb/spring/session/config/annotation/web/http/MongoHttpSessionConfigurationTests.java
similarity index 97%
rename from src/test/java/org/springframework/session/data/mongo/config/annotation/web/http/MongoHttpSessionConfigurationTests.java
rename to src/test/java/org/mongodb/spring/session/config/annotation/web/http/MongoHttpSessionConfigurationTests.java
index 300641e..6a09df6 100644
--- a/src/test/java/org/springframework/session/data/mongo/config/annotation/web/http/MongoHttpSessionConfigurationTests.java
+++ b/src/test/java/org/mongodb/spring/session/config/annotation/web/http/MongoHttpSessionConfigurationTests.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo.config.annotation.web.http;
+package org.mongodb.spring.session.config.annotation.web.http;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -28,6 +28,9 @@
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Order;
 import org.junit.jupiter.api.Test;
+import org.mongodb.spring.session.AbstractMongoSessionConverter;
+import org.mongodb.spring.session.JacksonMongoSessionConverter;
+import org.mongodb.spring.session.MongoIndexedSessionRepository;
 import org.springframework.beans.factory.UnsatisfiedDependencyException;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 import org.springframework.context.annotation.Bean;
@@ -42,9 +45,6 @@
 import org.springframework.session.SessionIdGenerator;
 import org.springframework.session.UuidSessionIdGenerator;
 import org.springframework.session.config.SessionRepositoryCustomizer;
-import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
-import org.springframework.session.data.mongo.JacksonMongoSessionConverter;
-import org.springframework.session.data.mongo.MongoIndexedSessionRepository;
 import org.springframework.test.util.ReflectionTestUtils;
 
 /**
diff --git a/src/test/java/org/springframework/session/data/mongo/config/annotation/web/reactive/ReactiveMongoWebSessionConfigurationTests.java b/src/test/java/org/mongodb/spring/session/config/annotation/web/reactive/ReactiveMongoWebSessionConfigurationTests.java
similarity index 97%
rename from src/test/java/org/springframework/session/data/mongo/config/annotation/web/reactive/ReactiveMongoWebSessionConfigurationTests.java
rename to src/test/java/org/mongodb/spring/session/config/annotation/web/reactive/ReactiveMongoWebSessionConfigurationTests.java
index 5e87274..9a2222d 100644
--- a/src/test/java/org/springframework/session/data/mongo/config/annotation/web/reactive/ReactiveMongoWebSessionConfigurationTests.java
+++ b/src/test/java/org/mongodb/spring/session/config/annotation/web/reactive/ReactiveMongoWebSessionConfigurationTests.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.springframework.session.data.mongo.config.annotation.web.reactive;
+package org.mongodb.spring.session.config.annotation.web.reactive;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -31,6 +31,10 @@
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Order;
 import org.junit.jupiter.api.Test;
+import org.mongodb.spring.session.AbstractMongoSessionConverter;
+import org.mongodb.spring.session.JacksonMongoSessionConverter;
+import org.mongodb.spring.session.JdkMongoSessionConverter;
+import org.mongodb.spring.session.ReactiveMongoSessionRepository;
 import org.springframework.beans.factory.UnsatisfiedDependencyException;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 import org.springframework.context.annotation.Bean;
@@ -46,10 +50,6 @@
 import org.springframework.session.UuidSessionIdGenerator;
 import org.springframework.session.config.ReactiveSessionRepositoryCustomizer;
 import org.springframework.session.config.annotation.web.server.EnableSpringWebSession;
-import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
-import org.springframework.session.data.mongo.JacksonMongoSessionConverter;
-import org.springframework.session.data.mongo.JdkMongoSessionConverter;
-import org.springframework.session.data.mongo.ReactiveMongoSessionRepository;
 import org.springframework.util.ReflectionUtils;
 import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
 import org.springframework.web.server.session.WebSessionManager;

From d0a33a2b7d131219d5f3a6b479eeace6f0f62da6 Mon Sep 17 00:00:00 2001
From: Ross Lawley 
Date: Wed, 12 Nov 2025 16:32:42 +0000
Subject: [PATCH 4/7] Initial README.md

---
 CONTRIBUTING.md |  36 +++++++++++++++
 README.md       | 113 +++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 148 insertions(+), 1 deletion(-)
 create mode 100644 CONTRIBUTING.md

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..b521482
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,36 @@
+## Contributing to the MongoDB Spring Session extension
+
+Thank you for your interest in contributing to the MongoDB Spring Session extension.
+
+We are building this software together and strongly encourage contributions from the community that are within the guidelines set forth
+below.
+
+Bug Fixes and New Features
+--------------------------
+
+Before starting to write code, look for existing [tickets](https://jira.mongodb.org/browse/JAVAF) or
+[create one](https://jira.mongodb.org/secure/CreateIssue!default.jspa) for your bug, issue, or feature request. This helps the community
+avoid working on something that might not be of interest or which has already been addressed.
+
+Pull Requests
+-------------
+
+Pull requests should generally be made against the main (default) branch and include relevant tests, if applicable.
+
+Code should compile with the Java 17 compiler and tests should pass under all Java versions which the driver currently
+supports.
+
+The results of pull request testing will be appended to the request. If any tests do not pass, or relevant tests are not included, the
+pull request will not be considered.
+
+To run all checks locally run:
+
+```console
+./gradlew clean check
+```
+
+Talk To Us
+----------
+
+If you want to work on something or have questions / complaints please reach out to us by creating a Question issue at
+(https://jira.mongodb.org/secure/CreateIssue!default.jspa).
diff --git a/README.md b/README.md
index 6f0af1e..38fe49e 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,112 @@
-# mongo-spring-session
+# MongoDB Spring Session extension
+
+This product enables applications to use [Spring Session](https://spring.io/projects/spring-session)
+with [MongoDB](https://www.mongodb.com/) and provides a `SessionRepository` implementation backed
+by MongoDB using [Spring Data MongoDB](https://spring.io/projects/spring-data-mongodb).
+
+## Overview
+
+Spring Session provides an API and implementations for managing a user's session information,
+while also making it trivial to support clustered sessions without being tied to an application container
+specific solution. It also provides transparent integration with:
+
+ * `HttpSession` - allows replacing the `HttpSession` in an application container (i.e. Tomcat) neutral way, with support for providing session IDs in headers to work with RESTful APIs.
+ * `WebSocket` - provides the ability to keep the `HttpSession` alive when receiving WebSocket messages
+ * `WebSession` - allows replacing the Spring WebFlux's `WebSession` in an application container neutral way.
+
+## Migrating from `spring-session-data-mongodb`
+Todo..
+
+## Support / Feedback
+
+For issues with, questions about, or feedback for the MongoDB Java, Kotlin, and Scala drivers, please look into
+our [support channels](https://www.mongodb.com/docs/manual/support/). Please
+do not email any of the driver developers directly with issues or
+questions - you're more likely to get an answer on [StackOverflow](https://stackoverflow.com/questions/tagged/mongodb+java).
+
+At a minimum, please include in your description the exact version of the driver that you are using.  If you are having
+connectivity issues, it's often also useful to paste in the line of code where you construct the MongoClient instance,
+along with the values of all parameters that you pass to the constructor. You should also check your application logs for
+any connectivity-related exceptions and post those as well.
+
+## Bugs / Feature Requests
+
+Think you’ve found a bug? Want to see a new feature in the drivers? Please open a
+case in our issue management tool, JIRA:
+
+- [Create an account and login](https://jira.mongodb.org).
+- Navigate to [the JAVA Frameworks project](https://jira.mongodb.org/browse/JAVAF).
+- Click **Create Issue** - Please provide as much information as possible about the issue type, which driver you are using, and how to reproduce your issue.
+
+Bug reports in JIRA for the extension and the Core Server (i.e. SERVER) project are **public**.
+
+If you’ve identified a security vulnerability in a driver or any other
+MongoDB project, please report it according to the [instructions here](https://www.mongodb.com/docs/manual/tutorial/create-a-vulnerability-report).
+
+## Versioning
+
+We follow [semantic versioning](https://semver.org/spec/v2.0.0.html) when releasing.
+
+## Binaries
+
+Binaries and dependency information for Maven, Gradle, Ivy and others can be found at
+[https://central.sonatype.com/search](https://central.sonatype.com/search?namespace=org.mongodb&name=mongodb-driver-sync).
+
+Example for Maven:
+
+```xml
+
+    org.mongodb
+    mongo-spring-session
+    x.y.z
+
+```
+Snapshot builds are also published regulary via Sonatype.
+
+Example for Maven:
+
+```xml
+
+    
+        Central Portal Snapshots
+        central-portal-snapshots
+        https://central.sonatype.com/repository/maven-snapshots/
+        
+            false
+        
+        
+            true
+        
+    
+
+```
+
+### Testing
+
+This project uses separate directories for unit and integration tests:
+
+- [unit test](src/test)
+- [integration test](src/integrationTest)
+
+#### Gradle Tasks
+
+##### All checks
+```console
+./gradlew clean check
+```
+
+##### Unit Tests only
+```console
+./gradlew clean test
+```
+
+##### Integration Tests only
+```console
+./gradlew clean integrationTest
+```
+
+Integration tests require a MongoDB deployment to be available
+
+### CI/CD
+This project uses [evergreen](https://github.com/evergreen-ci/evergreen), a distributed continuous integration system from MongoDB.
+The evergreen configuration is in the [.evergreen](/.evergreen) directory.

From 963739e7adb3bf8b6a73c1fdb3b75cd5c461ab52 Mon Sep 17 00:00:00 2001
From: Ross Lawley 
Date: Thu, 13 Nov 2025 10:29:07 +0000
Subject: [PATCH 5/7] Removed test containers dependency

---
 build.gradle.kts                              | 37 +++++++++++++++----
 gradle/libs.versions.toml                     | 16 ++++++--
 .../session/AbstractMongoRepositoryITest.java | 20 +++-------
 ...bDeleteJacksonSessionVerificationTest.java | 22 +++--------
 .../MongoDbLogoutVerificationTest.java        | 21 +++--------
 .../spring/session/MongoSessionUtils.java     | 10 +++++
 6 files changed, 69 insertions(+), 57 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 2edb487..4b96dec 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-import org.gradle.api.tasks.testing.logging.TestLogEvent
+import com.adarshr.gradle.testlogger.theme.ThemeType
 
 buildscript {
     repositories {
@@ -29,6 +29,7 @@ plugins {
     id("java-library")
     id("maven-publish")
     alias(libs.plugins.spotless)
+    alias(libs.plugins.test.logger)
 }
 
 description = "Spring Session and Spring MongoDB integration"
@@ -94,7 +95,34 @@ tasks.check { dependsOn(integrationTestTask) }
 
 tasks.withType().configureEach {
     useJUnitPlatform()
-    testLogging { events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) }
+
+    // Pass any `org.mongodb.*` system settings
+    systemProperties =
+        System.getProperties()
+            .map { (key, value) -> Pair(key.toString(), value) }
+            .filter { it.first.startsWith("org.mongodb.") }
+            .toMap()
+}
+
+// Pretty test output
+testlogger {
+    theme = ThemeType.STANDARD
+    showExceptions = true
+    showStackTraces = true
+    showFullStackTraces = false
+    showCauses = true
+    slowThreshold = 2000
+    showSummary = true
+    showSimpleNames = false
+    showPassed = true
+    showSkipped = true
+    showFailed = true
+    showOnlySlow = false
+    showStandardStreams = false
+    showPassedStandardStreams = true
+    showSkippedStandardStreams = true
+    showFailedStandardStreams = true
+    logLevel = LogLevel.LIFECYCLE
 }
 
 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -127,9 +155,6 @@ dependencies {
     //   `reason: class file for javax.annotation.meta.When not found`.
     compileOnly(libs.findbugs.jsr)
 
-    //    add("integrationTestCompile", "org.testcontainers:mongodb")
-    //
-
     testImplementation(platform(libs.junit.bom))
     testImplementation(platform(libs.mockito.bom))
     testImplementation(platform(libs.spring.framework.bom))
@@ -141,8 +166,6 @@ dependencies {
     testImplementation(libs.project.reactor.test)
 
     testRuntimeOnly(libs.junit.platform.launcher)
-
-    "integrationTestImplementation"(libs.testcontainers.mongodb)
 }
 
 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 8fc6f19..d3174fd 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -18,10 +18,14 @@ spring-data = "2025.0.5"
 spring-security = "6.5.6"
 spring-framework = "6.2.12"
 jackson = "2.18.4"
-findbugs = "3.0.2"
 project-reactor = "2025.0.0"
 mongodb-driver = "5.6.1"
 
+# Code verification libraries
+findbugs = "3.0.2"
+nullaway = "0.12.4"
+google-errorprone-core = "2.36.0"
+
 # Test libraries
 assertj = "3.27.3"
 junit = "5.12.1"
@@ -30,11 +34,11 @@ jakarta-websocket = "2.1.1"
 jakarta-servlet-api = "6.0.0"
 mockito = "5.16.1"
 logback = "1.5.18"
-testcontainers = "2.0.1"
 
 plugin-spotless = "7.0.2"
 plugin-palantir = "2.58.0"
 plugin-ktfmt = "0.54"
+plugin-test-logger = "4.0.0"
 
 [libraries]
 spring-session-bom = { module = "org.springframework.session:spring-session-bom", version.ref = "spring-session" }
@@ -43,7 +47,6 @@ spring-data-bom = { module = "org.springframework.data:spring-data-bom", version
 spring-data-mongodb = { module = "org.springframework.data:spring-data-mongodb" }
 jackson-bom =  { module = "com.fasterxml.jackson:jackson-bom", version.ref = "jackson" }
 jackson-databind =  { module = "com.fasterxml.jackson.core:jackson-databind" }
-findbugs-jsr = { module = "com.google.code.findbugs:jsr305", version.ref = "findbugs" }
 
 spring-security-bom = { module = "org.springframework.security:spring-security-bom", version.ref = "spring-security" }
 spring-security-core = { module = "org.springframework.security:spring-security-core" }
@@ -57,6 +60,11 @@ mongodb-driver-core = { module = "org.mongodb:mongodb-driver-core" }
 mongodb-driver-sync = { module = "org.mongodb:mongodb-driver-sync" }
 mongodb-driver-reactive-streams = { module = "org.mongodb:mongodb-driver-reactivestreams" }
 
+# Code verification libraries
+findbugs-jsr = { module = "com.google.code.findbugs:jsr305", version.ref = "findbugs" }
+nullaway = { module = "com.uber.nullaway:nullaway", version.ref = "nullaway" }
+google-errorprone-core = { module = "com.google.errorprone:error_prone_core", version.ref = "google-errorprone-core" }
+
 # Test libraries
 junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
 mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "mockito" }
@@ -78,7 +86,6 @@ spring-security-web =  { module = "org.springframework.security:spring-security-
 spring-test =  { module = "org.springframework:spring-test" }
 spring-web =  { module = "org.springframework:spring-web" }
 spring-webflux =  { module = "org.springframework:spring-webflux" }
-testcontainers-mongodb = { module = "org.testcontainers:testcontainers-mongodb",  version.ref="testcontainers"  }
 
 [bundles]
 testing = ["junit-jupiter", "assertj", "hamcrest", "mockito-core", "mockito-junit-jupiter"]
@@ -87,3 +94,4 @@ spring-test = ["spring-security-config", "spring-security-web", "spring-test", "
 
 [plugins]
 spotless = { id = "com.diffplug.spotless", version.ref = "plugin-spotless" }
+test-logger = { id = "com.adarshr.test-logger", version.ref = "plugin-test-logger" }
diff --git a/src/integrationTest/java/org/mongodb/spring/session/AbstractMongoRepositoryITest.java b/src/integrationTest/java/org/mongodb/spring/session/AbstractMongoRepositoryITest.java
index b7f0704..01063f2 100644
--- a/src/integrationTest/java/org/mongodb/spring/session/AbstractMongoRepositoryITest.java
+++ b/src/integrationTest/java/org/mongodb/spring/session/AbstractMongoRepositoryITest.java
@@ -18,6 +18,8 @@
 package org.mongodb.spring.session;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mongodb.spring.session.MongoSessionUtils.DEFAULT_DATABASE_NAME;
+import static org.mongodb.spring.session.MongoSessionUtils.getConnectionString;
 
 import com.mongodb.client.MongoClient;
 import com.mongodb.client.MongoClients;
@@ -37,7 +39,6 @@
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.session.FindByIndexNameSessionRepository;
 import org.springframework.session.Session;
-import org.testcontainers.containers.MongoDBContainer;
 
 /**
  * Abstract base class for {@link MongoIndexedSessionRepository} tests.
@@ -387,21 +388,10 @@ protected String getChangedSecurityName() {
 
     protected static class BaseConfig {
 
-        private static final String DOCKER_IMAGE = "mongo:5.0.11";
-
         @Bean
-        public MongoDBContainer mongoDbContainer() {
-            MongoDBContainer mongoDbContainer = new MongoDBContainer(DOCKER_IMAGE);
-            mongoDbContainer.start();
-            return mongoDbContainer;
-        }
-
-        @Bean
-        public MongoOperations mongoOperations(MongoDBContainer mongoContainer) {
-
-            MongoClient mongo = MongoClients.create(
-                    "mongodb://" + mongoContainer.getHost() + ":" + mongoContainer.getFirstMappedPort());
-            return new MongoTemplate(mongo, "test");
+        public MongoOperations mongoOperations() {
+            MongoClient mongo = MongoClients.create(getConnectionString());
+            return new MongoTemplate(mongo, DEFAULT_DATABASE_NAME);
         }
     }
 }
diff --git a/src/integrationTest/java/org/mongodb/spring/session/MongoDbDeleteJacksonSessionVerificationTest.java b/src/integrationTest/java/org/mongodb/spring/session/MongoDbDeleteJacksonSessionVerificationTest.java
index 7e40624..b4fc809 100644
--- a/src/integrationTest/java/org/mongodb/spring/session/MongoDbDeleteJacksonSessionVerificationTest.java
+++ b/src/integrationTest/java/org/mongodb/spring/session/MongoDbDeleteJacksonSessionVerificationTest.java
@@ -17,6 +17,9 @@
 
 package org.mongodb.spring.session;
 
+import static org.mongodb.spring.session.MongoSessionUtils.DEFAULT_DATABASE_NAME;
+import static org.mongodb.spring.session.MongoSessionUtils.getConnectionString;
+
 import com.mongodb.reactivestreams.client.MongoClient;
 import com.mongodb.reactivestreams.client.MongoClients;
 import java.net.URI;
@@ -48,7 +51,6 @@
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.reactive.config.EnableWebFlux;
 import org.springframework.web.reactive.function.BodyInserters;
-import org.testcontainers.containers.MongoDBContainer;
 import reactor.test.StepVerifier;
 
 /** @author Boris Finkelshteyn */
@@ -192,22 +194,10 @@ AbstractMongoSessionConverter mongoSessionConverter() {
     @EnableWebFlux
     @EnableMongoWebSession
     static class Config {
-
-        private static final String DOCKER_IMAGE = "mongo:5.0.11";
-
         @Bean
-        MongoDBContainer mongoDbContainer() {
-            MongoDBContainer mongoDbContainer = new MongoDBContainer(DOCKER_IMAGE);
-            mongoDbContainer.start();
-            return mongoDbContainer;
-        }
-
-        @Bean
-        ReactiveMongoOperations mongoOperations(MongoDBContainer mongoContainer) {
-
-            MongoClient mongo = MongoClients.create(
-                    "mongodb://" + mongoContainer.getHost() + ":" + mongoContainer.getFirstMappedPort());
-            return new ReactiveMongoTemplate(mongo, "DB_Name_DeleteJacksonSessionVerificationTest");
+        ReactiveMongoOperations mongoOperations() {
+            MongoClient mongo = MongoClients.create(getConnectionString());
+            return new ReactiveMongoTemplate(mongo, DEFAULT_DATABASE_NAME);
         }
 
         @Bean
diff --git a/src/integrationTest/java/org/mongodb/spring/session/MongoDbLogoutVerificationTest.java b/src/integrationTest/java/org/mongodb/spring/session/MongoDbLogoutVerificationTest.java
index 128e6b2..a0d61ba 100644
--- a/src/integrationTest/java/org/mongodb/spring/session/MongoDbLogoutVerificationTest.java
+++ b/src/integrationTest/java/org/mongodb/spring/session/MongoDbLogoutVerificationTest.java
@@ -17,6 +17,9 @@
 
 package org.mongodb.spring.session;
 
+import static org.mongodb.spring.session.MongoSessionUtils.DEFAULT_DATABASE_NAME;
+import static org.mongodb.spring.session.MongoSessionUtils.getConnectionString;
+
 import com.mongodb.reactivestreams.client.MongoClient;
 import com.mongodb.reactivestreams.client.MongoClients;
 import java.net.URI;
@@ -48,7 +51,6 @@
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.reactive.config.EnableWebFlux;
 import org.springframework.web.reactive.function.BodyInserters;
-import org.testcontainers.containers.MongoDBContainer;
 import reactor.test.StepVerifier;
 
 /** @author Greg Turnquist */
@@ -186,21 +188,10 @@ MapReactiveUserDetailsService userDetailsService() {
     @EnableMongoWebSession
     static class Config {
 
-        private static final String DOCKER_IMAGE = "mongo:5.0.11";
-
-        @Bean
-        MongoDBContainer mongoDbContainer() {
-            MongoDBContainer mongoDbContainer = new MongoDBContainer(DOCKER_IMAGE);
-            mongoDbContainer.start();
-            return mongoDbContainer;
-        }
-
         @Bean
-        ReactiveMongoOperations mongoOperations(MongoDBContainer mongoContainer) {
-
-            MongoClient mongo = MongoClients.create(
-                    "mongodb://" + mongoContainer.getHost() + ":" + mongoContainer.getFirstMappedPort());
-            return new ReactiveMongoTemplate(mongo, "test");
+        ReactiveMongoOperations mongoOperations() {
+            MongoClient mongo = MongoClients.create(getConnectionString());
+            return new ReactiveMongoTemplate(mongo, DEFAULT_DATABASE_NAME);
         }
 
         @Bean
diff --git a/src/main/java/org/mongodb/spring/session/MongoSessionUtils.java b/src/main/java/org/mongodb/spring/session/MongoSessionUtils.java
index f1f2f67..415dc4b 100644
--- a/src/main/java/org/mongodb/spring/session/MongoSessionUtils.java
+++ b/src/main/java/org/mongodb/spring/session/MongoSessionUtils.java
@@ -17,6 +17,7 @@
 
 package org.mongodb.spring.session;
 
+import com.mongodb.ConnectionString;
 import com.mongodb.DBObject;
 import org.bson.Document;
 import org.springframework.core.convert.TypeDescriptor;
@@ -29,6 +30,10 @@
  */
 final class MongoSessionUtils {
 
+    private static final String DEFAULT_URI = "mongodb://localhost:27017";
+    private static final String URI_SYSTEM_PROPERTY_NAME = "org.mongodb.test.uri";
+    public static final String DEFAULT_DATABASE_NAME = "MongoSpringSessionTest";
+
     private MongoSessionUtils() {}
 
     @Nullable static DBObject convertToDBObject(AbstractMongoSessionConverter mongoSessionConverter, MongoSession session) {
@@ -42,4 +47,9 @@ private MongoSessionUtils() {}
         return (MongoSession) mongoSessionConverter.convert(
                 session, TypeDescriptor.valueOf(Document.class), TypeDescriptor.valueOf(MongoSession.class));
     }
+
+    static ConnectionString getConnectionString() {
+        String connectionString = System.getProperty(URI_SYSTEM_PROPERTY_NAME, DEFAULT_URI);
+        return new ConnectionString(connectionString);
+    }
 }

From f77c7eb65350dafe9b67249a514c7a9eb8ce4f6a Mon Sep 17 00:00:00 2001
From: Ross Lawley 
Date: Thu, 13 Nov 2025 14:04:32 +0000
Subject: [PATCH 6/7] Updated to use errorprone and nullAway

---
 build.gradle.kts                              | 18 +++++++++++
 gradle/libs.versions.toml                     |  2 ++
 .../AbstractMongoSessionConverter.java        | 11 +++----
 .../session/JacksonMongoSessionConverter.java |  3 ++
 .../session/JdkMongoSessionConverter.java     | 13 +++++---
 .../MongoIndexedSessionRepository.java        | 31 ++++++++++++-------
 .../mongodb/spring/session/MongoSession.java  |  6 ++--
 .../ReactiveMongoSessionRepository.java       | 19 +++++++-----
 .../http/MongoHttpSessionConfiguration.java   | 18 ++++++++---
 .../ReactiveMongoWebSessionConfiguration.java | 19 +++++++-----
 ...tiveMongoWebSessionConfigurationTests.java |  2 +-
 11 files changed, 97 insertions(+), 45 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 4b96dec..7abe876 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -15,6 +15,7 @@
  */
 
 import com.adarshr.gradle.testlogger.theme.ThemeType
+import net.ltgt.gradle.errorprone.errorprone
 
 buildscript {
     repositories {
@@ -30,6 +31,7 @@ plugins {
     id("maven-publish")
     alias(libs.plugins.spotless)
     alias(libs.plugins.test.logger)
+    alias(libs.plugins.errorprone)
 }
 
 description = "Spring Session and Spring MongoDB integration"
@@ -154,6 +156,8 @@ dependencies {
     // `warning: unknown enum constant When.MAYBE`
     //   `reason: class file for javax.annotation.meta.When not found`.
     compileOnly(libs.findbugs.jsr)
+    errorprone(libs.nullaway)
+    errorprone(libs.google.errorprone.core)
 
     testImplementation(platform(libs.junit.bom))
     testImplementation(platform(libs.mockito.bom))
@@ -207,3 +211,17 @@ spotless {
         endWithNewline()
     }
 }
+
+// Configure errorprone
+tasks.withType().configureEach {
+    if (name.endsWith("TestJava")) {
+        options.errorprone.isEnabled = false
+    } else {
+        options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror"))
+        options.errorprone {
+            disableWarningsInGeneratedCode = true
+            option("NullAway:AnnotatedPackages", "org.mongodb.spring.session")
+            error("NullAway")
+        }
+    }
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index d3174fd..16dfa3c 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -39,6 +39,7 @@ plugin-spotless = "7.0.2"
 plugin-palantir = "2.58.0"
 plugin-ktfmt = "0.54"
 plugin-test-logger = "4.0.0"
+plugin-errorprone = "4.1.0"
 
 [libraries]
 spring-session-bom = { module = "org.springframework.session:spring-session-bom", version.ref = "spring-session" }
@@ -95,3 +96,4 @@ spring-test = ["spring-security-config", "spring-security-web", "spring-test", "
 [plugins]
 spotless = { id = "com.diffplug.spotless", version.ref = "plugin-spotless" }
 test-logger = { id = "com.adarshr.test-logger", version.ref = "plugin-test-logger" }
+errorprone = { id = "net.ltgt.errorprone", version.ref = "plugin-errorprone" }
diff --git a/src/main/java/org/mongodb/spring/session/AbstractMongoSessionConverter.java b/src/main/java/org/mongodb/spring/session/AbstractMongoSessionConverter.java
index a3ecc89..70f1c60 100644
--- a/src/main/java/org/mongodb/spring/session/AbstractMongoSessionConverter.java
+++ b/src/main/java/org/mongodb/spring/session/AbstractMongoSessionConverter.java
@@ -52,8 +52,6 @@ public abstract class AbstractMongoSessionConverter implements GenericConverter
 
     private static final Log LOG = LogFactory.getLog(AbstractMongoSessionConverter.class);
 
-    private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
-
     private IndexResolver indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>());
 
     /**
@@ -83,24 +81,25 @@ protected void ensureIndexes(IndexOperations sessionCollectionIndexes) {
 
         LOG.info("Creating TTL index on field " + EXPIRE_AT_FIELD_NAME);
 
-        sessionCollectionIndexes.ensureIndex(new Index(EXPIRE_AT_FIELD_NAME, Sort.Direction.ASC)
+        sessionCollectionIndexes.createIndex(new Index(EXPIRE_AT_FIELD_NAME, Sort.Direction.ASC)
                 .named(EXPIRE_AT_FIELD_NAME)
                 .expire(0));
     }
 
-    protected String extractPrincipal(MongoSession expiringSession) {
+    @Nullable protected String extractPrincipal(MongoSession expiringSession) {
 
         return this.indexResolver
                 .resolveIndexesFor(expiringSession)
                 .get(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
     }
 
+    @Override
     public Set getConvertibleTypes() {
-
         return Collections.singleton(new ConvertiblePair(DBObject.class, MongoSession.class));
     }
 
     @SuppressWarnings("unchecked")
+    @Override
     @Nullable public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
 
         if (source == null) {
@@ -118,7 +117,7 @@ public Set getConvertibleTypes() {
 
     protected abstract DBObject convert(MongoSession session);
 
-    protected abstract MongoSession convert(Document sessionWrapper);
+    @Nullable protected abstract MongoSession convert(Document sessionWrapper);
 
     public void setIndexResolver(IndexResolver indexResolver) {
         Assert.notNull(indexResolver, "indexResolver must not be null");
diff --git a/src/main/java/org/mongodb/spring/session/JacksonMongoSessionConverter.java b/src/main/java/org/mongodb/spring/session/JacksonMongoSessionConverter.java
index c741ebb..c605bf5 100644
--- a/src/main/java/org/mongodb/spring/session/JacksonMongoSessionConverter.java
+++ b/src/main/java/org/mongodb/spring/session/JacksonMongoSessionConverter.java
@@ -80,6 +80,7 @@ public JacksonMongoSessionConverter(ObjectMapper objectMapper) {
         this.objectMapper = objectMapper;
     }
 
+    @Override
     @Nullable protected Query getQueryForIndex(String indexName, Object indexValue) {
 
         if (FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) {
@@ -145,6 +146,7 @@ protected DBObject convert(MongoSession source) {
     }
 
     /** Used to whitelist {@link MongoSession} for {@link SecurityJackson2Modules}. */
+    @SuppressWarnings("unused")
     private static class MongoSessionMixin {
 
         @JsonCreator
@@ -160,6 +162,7 @@ private static class HashMapMixin {
     }
 
     private static class MongoIdNamingStrategy extends PropertyNamingStrategies.NamingBase {
+        private static final long serialVersionUID = 2L;
 
         @Override
         public String translate(String propertyName) {
diff --git a/src/main/java/org/mongodb/spring/session/JdkMongoSessionConverter.java b/src/main/java/org/mongodb/spring/session/JdkMongoSessionConverter.java
index d26dc81..4692cd5 100644
--- a/src/main/java/org/mongodb/spring/session/JdkMongoSessionConverter.java
+++ b/src/main/java/org/mongodb/spring/session/JdkMongoSessionConverter.java
@@ -17,6 +17,8 @@
 
 package org.mongodb.spring.session;
 
+import static java.lang.String.format;
+
 import com.mongodb.BasicDBObject;
 import com.mongodb.DBObject;
 import java.time.Duration;
@@ -109,6 +111,7 @@ protected DBObject convert(MongoSession session) {
     }
 
     @Override
+    @SuppressWarnings("NullAway")
     protected MongoSession convert(Document sessionWrapper) {
 
         Object maxInterval = sessionWrapper.getOrDefault(MAX_INTERVAL, this.maxInactiveInterval);
@@ -116,23 +119,25 @@ protected MongoSession convert(Document sessionWrapper) {
         Duration maxIntervalDuration =
                 (maxInterval instanceof Duration) ? (Duration) maxInterval : Duration.parse(maxInterval.toString());
 
-        MongoSession session = new MongoSession(sessionWrapper.getString(ID), maxIntervalDuration.getSeconds());
+        MongoSession session = new MongoSession(sessionWrapper.getString(ID), maxIntervalDuration.toSeconds());
 
         Object creationTime = sessionWrapper.get(CREATION_TIME);
         if (creationTime instanceof Instant) {
             session.setCreationTime(((Instant) creationTime).toEpochMilli());
         } else if (creationTime instanceof Date) {
-            session.setCreationTime(((Date) creationTime).getTime());
+            session.setCreationTime(((Date) creationTime).toInstant().toEpochMilli());
         }
 
         Object lastAccessedTime = sessionWrapper.get(LAST_ACCESSED_TIME);
         if (lastAccessedTime instanceof Instant) {
             session.setLastAccessedTime((Instant) lastAccessedTime);
         } else if (lastAccessedTime instanceof Date) {
-            session.setLastAccessedTime(Instant.ofEpochMilli(((Date) lastAccessedTime).getTime()));
+            session.setLastAccessedTime(((Date) lastAccessedTime).toInstant());
         }
 
-        session.setExpireAt((Date) sessionWrapper.get(EXPIRE_AT_FIELD_NAME));
+        Object expires = sessionWrapper.get(EXPIRE_AT_FIELD_NAME);
+        Assert.notNull(expires, () -> format("%s missing from session.", EXPIRE_AT_FIELD_NAME));
+        session.setExpireAt((Date) expires);
 
         deserializeAttributes(sessionWrapper, session);
 
diff --git a/src/main/java/org/mongodb/spring/session/MongoIndexedSessionRepository.java b/src/main/java/org/mongodb/spring/session/MongoIndexedSessionRepository.java
index 0b5b026..d188864 100644
--- a/src/main/java/org/mongodb/spring/session/MongoIndexedSessionRepository.java
+++ b/src/main/java/org/mongodb/spring/session/MongoIndexedSessionRepository.java
@@ -77,17 +77,18 @@ public class MongoIndexedSessionRepository
     private AbstractMongoSessionConverter mongoSessionConverter =
             new JdkMongoSessionConverter(this.defaultMaxInactiveInterval);
 
-    private ApplicationEventPublisher eventPublisher;
+    @Nullable private ApplicationEventPublisher eventPublisher;
 
-    private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance();
+    @Nullable private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance();
 
     public MongoIndexedSessionRepository(MongoOperations mongoOperations) {
         this.mongoOperations = mongoOperations;
     }
 
     @Override
+    @SuppressWarnings("NullAway")
     public MongoSession createSession() {
-
+        Assert.notNull(this.sessionIdGenerator, "sessionIdGenerator not initialized.");
         MongoSession session = new MongoSession(this.sessionIdGenerator, this.defaultMaxInactiveInterval.toSeconds());
 
         publishEvent(new SessionCreatedEvent(this, session));
@@ -103,7 +104,8 @@ public void save(MongoSession session) {
     }
 
     @Override
-    @Nullable public MongoSession findById(String id) {
+    @Nullable @SuppressWarnings("NullAway")
+    public MongoSession findById(String id) {
 
         Document sessionWrapper = findSession(id);
 
@@ -119,6 +121,7 @@ public void save(MongoSession session) {
                 deleteById(id);
                 return null;
             }
+            Assert.notNull(this.sessionIdGenerator, "sessionIdGenerator not initialized.");
             session.setSessionIdGenerator(this.sessionIdGenerator);
         }
 
@@ -133,8 +136,9 @@ public void save(MongoSession session) {
      * @return sessions map
      */
     @Override
+    @SuppressWarnings("NullAway")
     public Map findByIndexNameAndIndexValue(String indexName, String indexValue) {
-
+        Assert.notNull(this.sessionIdGenerator, "sessionIdGenerator not initialized.");
         return Optional.ofNullable(this.mongoSessionConverter.getQueryForIndex(indexName, indexValue))
                 .map((query) -> this.mongoOperations.find(query, Document.class, this.collectionName))
                 .orElse(Collections.emptyList())
@@ -173,11 +177,14 @@ public void setApplicationEventPublisher(ApplicationEventPublisher eventPublishe
     }
 
     private void publishEvent(ApplicationEvent event) {
-
-        try {
-            this.eventPublisher.publishEvent(event);
-        } catch (Throwable ex) {
-            logger.error("Error publishing " + event + ".", ex);
+        if (this.eventPublisher == null) {
+            logger.error("Error publishing " + event + ". No event publisher set.");
+        } else {
+            try {
+                this.eventPublisher.publishEvent(event);
+            } catch (Throwable ex) {
+                logger.error("Error publishing " + event + ".", ex);
+            }
         }
     }
 
@@ -188,8 +195,7 @@ private void publishEvent(ApplicationEvent event) {
      * @param defaultMaxInactiveInterval the default maxInactiveInterval
      */
     public void setDefaultMaxInactiveInterval(Duration defaultMaxInactiveInterval) {
-        org.springframework.util.Assert.notNull(
-                defaultMaxInactiveInterval, "defaultMaxInactiveInterval must not be null");
+        Assert.notNull(defaultMaxInactiveInterval, "defaultMaxInactiveInterval must not be null");
         this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
     }
 
@@ -201,6 +207,7 @@ public void setDefaultMaxInactiveInterval(Duration defaultMaxInactiveInterval) {
      * @deprecated since 3.0.0, in favor of {@link #setDefaultMaxInactiveInterval(Duration)}
      */
     @Deprecated(since = "3.0.0")
+    @SuppressWarnings("InlineMeSuggester")
     public void setMaxInactiveIntervalInSeconds(Integer defaultMaxInactiveInterval) {
         setDefaultMaxInactiveInterval(Duration.ofSeconds(defaultMaxInactiveInterval));
     }
diff --git a/src/main/java/org/mongodb/spring/session/MongoSession.java b/src/main/java/org/mongodb/spring/session/MongoSession.java
index 284c595..4ce9dbb 100644
--- a/src/main/java/org/mongodb/spring/session/MongoSession.java
+++ b/src/main/java/org/mongodb/spring/session/MongoSession.java
@@ -133,7 +133,7 @@ public String changeSessionId() {
     }
 
     @Override
-    @Nullable @SuppressWarnings("unchecked")
+    @Nullable @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
     public  T getAttribute(String attributeName) {
         return (T) this.attrs.get(coverDot(attributeName));
     }
@@ -185,12 +185,12 @@ public Duration getMaxInactiveInterval() {
 
     @Override
     public void setMaxInactiveInterval(Duration interval) {
-        this.intervalSeconds = interval.getSeconds();
+        this.intervalSeconds = interval.toSeconds();
     }
 
     @Override
     public boolean isExpired() {
-        return this.intervalSeconds >= 0 && new Date().after(this.expireAt);
+        return this.intervalSeconds >= 0 && Instant.now().isAfter(expireAt.toInstant());
     }
 
     @Override
diff --git a/src/main/java/org/mongodb/spring/session/ReactiveMongoSessionRepository.java b/src/main/java/org/mongodb/spring/session/ReactiveMongoSessionRepository.java
index 9c756f8..e52cad8 100644
--- a/src/main/java/org/mongodb/spring/session/ReactiveMongoSessionRepository.java
+++ b/src/main/java/org/mongodb/spring/session/ReactiveMongoSessionRepository.java
@@ -30,6 +30,7 @@
 import org.springframework.data.mongodb.core.index.IndexOperations;
 import org.springframework.data.mongodb.core.query.Criteria;
 import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.lang.Nullable;
 import org.springframework.session.MapSession;
 import org.springframework.session.ReactiveSessionRepository;
 import org.springframework.session.SessionIdGenerator;
@@ -72,9 +73,9 @@ public class ReactiveMongoSessionRepository
     private AbstractMongoSessionConverter mongoSessionConverter =
             new JdkMongoSessionConverter(this.defaultMaxInactiveInterval);
 
-    private MongoOperations blockingMongoOperations;
+    @Nullable private MongoOperations blockingMongoOperations;
 
-    private ApplicationEventPublisher eventPublisher;
+    @Nullable private ApplicationEventPublisher eventPublisher;
 
     private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance();
 
@@ -173,11 +174,14 @@ public void setApplicationEventPublisher(ApplicationEventPublisher eventPublishe
     }
 
     private void publishEvent(ApplicationEvent event) {
-
-        try {
-            this.eventPublisher.publishEvent(event);
-        } catch (Throwable ex) {
-            logger.error("Error publishing " + event + ".", ex);
+        if (this.eventPublisher == null) {
+            logger.error("Error publishing " + event + ". No event publisher set.");
+        } else {
+            try {
+                this.eventPublisher.publishEvent(event);
+            } catch (Throwable ex) {
+                logger.error("Error publishing " + event + ".", ex);
+            }
         }
     }
 
@@ -200,6 +204,7 @@ public void setDefaultMaxInactiveInterval(Duration defaultMaxInactiveInterval) {
      * @deprecated since 3.0.0, in favor of {@link #setDefaultMaxInactiveInterval(Duration)}
      */
     @Deprecated(since = "3.0.0")
+    @SuppressWarnings("InlineMeSuggester")
     public void setMaxInactiveIntervalInSeconds(Integer defaultMaxInactiveInterval) {
         setDefaultMaxInactiveInterval(Duration.ofSeconds(defaultMaxInactiveInterval));
     }
diff --git a/src/main/java/org/mongodb/spring/session/config/annotation/web/http/MongoHttpSessionConfiguration.java b/src/main/java/org/mongodb/spring/session/config/annotation/web/http/MongoHttpSessionConfiguration.java
index 13522bd..041f472 100644
--- a/src/main/java/org/mongodb/spring/session/config/annotation/web/http/MongoHttpSessionConfiguration.java
+++ b/src/main/java/org/mongodb/spring/session/config/annotation/web/http/MongoHttpSessionConfiguration.java
@@ -36,6 +36,7 @@
 import org.springframework.core.serializer.support.SerializingConverter;
 import org.springframework.core.type.AnnotationMetadata;
 import org.springframework.data.mongodb.core.MongoOperations;
+import org.springframework.lang.Nullable;
 import org.springframework.session.IndexResolver;
 import org.springframework.session.MapSession;
 import org.springframework.session.Session;
@@ -43,6 +44,7 @@
 import org.springframework.session.UuidSessionIdGenerator;
 import org.springframework.session.config.SessionRepositoryCustomizer;
 import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
+import org.springframework.util.Assert;
 import org.springframework.util.StringUtils;
 import org.springframework.util.StringValueResolver;
 
@@ -62,19 +64,20 @@ public class MongoHttpSessionConfiguration implements BeanClassLoaderAware, Embe
 
     private Duration maxInactiveInterval = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL;
 
-    private String collectionName;
+    @Nullable private String collectionName;
 
-    private StringValueResolver embeddedValueResolver;
+    @Nullable private StringValueResolver embeddedValueResolver;
 
-    private List> sessionRepositoryCustomizers;
+    @Nullable private List> sessionRepositoryCustomizers;
 
-    private ClassLoader classLoader;
+    @Nullable private ClassLoader classLoader;
 
-    private IndexResolver indexResolver;
+    @Nullable private IndexResolver indexResolver;
 
     private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance();
 
     @Bean
+    @SuppressWarnings("NullAway")
     public MongoIndexedSessionRepository mongoSessionRepository(MongoOperations mongoOperations) {
 
         MongoIndexedSessionRepository repository = new MongoIndexedSessionRepository(mongoOperations);
@@ -104,6 +107,7 @@ public MongoIndexedSessionRepository mongoSessionRepository(MongoOperations mong
         }
         repository.setSessionIdGenerator(this.sessionIdGenerator);
 
+        Assert.notNull(this.sessionRepositoryCustomizers, "SessionRepositoryCustomizers not initialized.");
         this.sessionRepositoryCustomizers.forEach(
                 (sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(repository));
 
@@ -119,10 +123,13 @@ public void setMaxInactiveInterval(Duration maxInactiveInterval) {
     }
 
     @Deprecated
+    @SuppressWarnings("InlineMeSuggester")
     public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {
         setMaxInactiveInterval(Duration.ofSeconds(maxInactiveIntervalInSeconds));
     }
 
+    @Override
+    @SuppressWarnings("NullAway")
     public void setImportMetadata(AnnotationMetadata importMetadata) {
 
         AnnotationAttributes attributes = AnnotationAttributes.fromMap(
@@ -135,6 +142,7 @@ public void setImportMetadata(AnnotationMetadata importMetadata) {
 
         String collectionNameValue = (attributes != null) ? attributes.getString("collectionName") : "";
         if (StringUtils.hasText(collectionNameValue)) {
+            Assert.notNull(this.embeddedValueResolver, "EmbeddedValueResolver not initialized.");
             this.collectionName = this.embeddedValueResolver.resolveStringValue(collectionNameValue);
         }
     }
diff --git a/src/main/java/org/mongodb/spring/session/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java b/src/main/java/org/mongodb/spring/session/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java
index 170308b..43f4972 100644
--- a/src/main/java/org/mongodb/spring/session/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java
+++ b/src/main/java/org/mongodb/spring/session/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java
@@ -37,6 +37,7 @@
 import org.springframework.core.type.AnnotationMetadata;
 import org.springframework.data.mongodb.core.MongoOperations;
 import org.springframework.data.mongodb.core.ReactiveMongoOperations;
+import org.springframework.lang.Nullable;
 import org.springframework.session.IndexResolver;
 import org.springframework.session.MapSession;
 import org.springframework.session.Session;
@@ -44,6 +45,7 @@
 import org.springframework.session.UuidSessionIdGenerator;
 import org.springframework.session.config.ReactiveSessionRepositoryCustomizer;
 import org.springframework.session.config.annotation.web.server.SpringWebSessionConfiguration;
+import org.springframework.util.Assert;
 import org.springframework.util.StringUtils;
 import org.springframework.util.StringValueResolver;
 
@@ -58,22 +60,22 @@
 public class ReactiveMongoWebSessionConfiguration
         implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
 
-    private AbstractMongoSessionConverter mongoSessionConverter;
+    @Nullable private AbstractMongoSessionConverter mongoSessionConverter;
 
     private Duration maxInactiveInterval = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL;
 
-    private String collectionName;
+    @Nullable private String collectionName;
 
-    private StringValueResolver embeddedValueResolver;
+    @Nullable private StringValueResolver embeddedValueResolver;
 
     private List> sessionRepositoryCustomizers;
 
     @Autowired(required = false)
-    private MongoOperations mongoOperations;
+    @Nullable private MongoOperations mongoOperations;
 
-    private ClassLoader classLoader;
+    @Nullable private ClassLoader classLoader;
 
-    private IndexResolver indexResolver;
+    @Nullable private IndexResolver indexResolver;
 
     private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance();
 
@@ -126,6 +128,7 @@ public void setMongoSessionConverter(AbstractMongoSessionConverter mongoSessionC
     }
 
     @Override
+    @SuppressWarnings("NullAway")
     public void setImportMetadata(AnnotationMetadata importMetadata) {
 
         AnnotationAttributes attributes = AnnotationAttributes.fromMap(
@@ -138,6 +141,7 @@ public void setImportMetadata(AnnotationMetadata importMetadata) {
 
         String collectionNameValue = (attributes != null) ? attributes.getString("collectionName") : "";
         if (StringUtils.hasText(collectionNameValue)) {
+            Assert.notNull(this.embeddedValueResolver, "EmbeddedValueResolver not initialized.");
             this.collectionName = this.embeddedValueResolver.resolveStringValue(collectionNameValue);
         }
     }
@@ -161,11 +165,12 @@ public void setMaxInactiveInterval(Duration maxInactiveInterval) {
     }
 
     @Deprecated
+    @SuppressWarnings("InlineMeSuggester")
     public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {
         setMaxInactiveInterval(Duration.ofSeconds(maxInactiveIntervalInSeconds));
     }
 
-    public String getCollectionName() {
+    @Nullable public String getCollectionName() {
         return this.collectionName;
     }
 
diff --git a/src/test/java/org/mongodb/spring/session/config/annotation/web/reactive/ReactiveMongoWebSessionConfigurationTests.java b/src/test/java/org/mongodb/spring/session/config/annotation/web/reactive/ReactiveMongoWebSessionConfigurationTests.java
index 9a2222d..e949861 100644
--- a/src/test/java/org/mongodb/spring/session/config/annotation/web/reactive/ReactiveMongoWebSessionConfigurationTests.java
+++ b/src/test/java/org/mongodb/spring/session/config/annotation/web/reactive/ReactiveMongoWebSessionConfigurationTests.java
@@ -155,7 +155,7 @@ void reactiveAndBlockingMongoOperationsShouldEnsureIndexing() {
 
         verify(operations, times(1)).indexOps((String) any());
         verify(indexOperations, times(1)).getIndexInfo();
-        verify(indexOperations, times(1)).ensureIndex(any());
+        verify(indexOperations, times(1)).createIndex(any());
     }
 
     @Test

From 52c5063239982d9275abf1ffe51abda2eff6d209 Mon Sep 17 00:00:00 2001
From: Ross Lawley 
Date: Wed, 19 Nov 2025 12:47:03 +0000
Subject: [PATCH 7/7] Updated .git-blame-ignore-revs

---
 .git-blame-ignore-revs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index a3ad511..6407dae 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -1 +1,3 @@
 # .git-blame-ignore-revs
+# Spotless initial cleanup
+a711643b057a1f07f5590cad30d123b7ccadf1b0

- * NOTE: This was originally stored in unicode format. Delomboking the code caused it - * to get converted to another encoding, which isn't supported on all systems, so we - * migrated back to unicode. The same character is being represented ensuring binary - * compatibility. See https://www.compart.com/en/unicode/U+F607 - */ - private static final char DOT_COVER_CHAR = '\uF607'; - - private String id; - - private final String originalSessionId; - - private long createdMillis = System.currentTimeMillis(); - - private long accessedMillis; - - private long intervalSeconds; - - private Date expireAt; - - private final Map attrs = new HashMap<>(); - - private transient SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance(); - - /** - * Constructs a new instance using the provided session id. - * @param sessionId the session id to use - * @since 3.2 - */ - public MongoSession(String sessionId) { - this(sessionId, MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS); - } - - public MongoSession() { - this(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS); - } - - public MongoSession(long maxInactiveIntervalInSeconds) { - this(UuidSessionIdGenerator.getInstance().generate(), maxInactiveIntervalInSeconds); - } - - public MongoSession(String id, long maxInactiveIntervalInSeconds) { - this.id = id; - this.originalSessionId = id; - this.intervalSeconds = maxInactiveIntervalInSeconds; - setLastAccessedTime(Instant.ofEpochMilli(this.createdMillis)); - } - - /** - * Constructs a new instance using the provided {@link SessionIdGenerator}. - * @param sessionIdGenerator the {@link SessionIdGenerator} to use - * @since 3.2 - */ - public MongoSession(SessionIdGenerator sessionIdGenerator) { - this(sessionIdGenerator.generate(), MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS); - this.sessionIdGenerator = sessionIdGenerator; - } - - /** - * Constructs a new instance using the provided {@link SessionIdGenerator} and max - * inactive interval. - * @param sessionIdGenerator the {@link SessionIdGenerator} to use - * @param maxInactiveIntervalInSeconds the max inactive interval in seconds - * @since 3.2 - */ - MongoSession(SessionIdGenerator sessionIdGenerator, long maxInactiveIntervalInSeconds) { - this(sessionIdGenerator.generate(), maxInactiveIntervalInSeconds); - this.sessionIdGenerator = sessionIdGenerator; - } - - static String coverDot(String attributeName) { - return attributeName.replace('.', DOT_COVER_CHAR); - } - - static String uncoverDot(String attributeName) { - return attributeName.replace(DOT_COVER_CHAR, '.'); - } - - @Override - public String changeSessionId() { - - String changedId = this.sessionIdGenerator.generate(); - this.id = changedId; - return changedId; - } - - @Override - @Nullable - @SuppressWarnings("unchecked") - public T getAttribute(String attributeName) { - return (T) this.attrs.get(coverDot(attributeName)); - } - - @Override - public Set getAttributeNames() { - return this.attrs.keySet().stream().map(MongoSession::uncoverDot).collect(Collectors.toSet()); - } - - @Override - public void setAttribute(String attributeName, Object attributeValue) { - - if (attributeValue == null) { - removeAttribute(coverDot(attributeName)); - } - else { - this.attrs.put(coverDot(attributeName), attributeValue); - } - } - - @Override - public void removeAttribute(String attributeName) { - this.attrs.remove(coverDot(attributeName)); - } - - @Override - public Instant getCreationTime() { - return Instant.ofEpochMilli(this.createdMillis); - } - - void setCreationTime(long created) { - this.createdMillis = created; - } - - @Override - public Instant getLastAccessedTime() { - return Instant.ofEpochMilli(this.accessedMillis); - } - - @Override - public void setLastAccessedTime(Instant lastAccessedTime) { - this.accessedMillis = lastAccessedTime.toEpochMilli(); - this.expireAt = Date.from(lastAccessedTime.plus(Duration.ofSeconds(this.intervalSeconds))); - } - - @Override - public Duration getMaxInactiveInterval() { - return Duration.ofSeconds(this.intervalSeconds); - } - - @Override - public void setMaxInactiveInterval(Duration interval) { - this.intervalSeconds = interval.getSeconds(); - } - - @Override - public boolean isExpired() { - return this.intervalSeconds >= 0 && new Date().after(this.expireAt); - } - - @Override - public boolean equals(Object o) { - - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - MongoSession that = (MongoSession) o; - return Objects.equals(this.id, that.id); - } - - @Override - public int hashCode() { - return Objects.hash(this.id); - } - - @Override - public String getId() { - return this.id; - } - - Date getExpireAt() { - return this.expireAt; - } - - void setExpireAt(final Date expireAt) { - this.expireAt = expireAt; - } - - boolean hasChangedSessionId() { - return !getId().equals(this.originalSessionId); - } - - String getOriginalSessionId() { - return this.originalSessionId; - } - - /** - * Sets the session id. - * @param id the id to set - * @since 3.2 - */ - void setId(String id) { - this.id = id; - } - - /** - * Sets the {@link SessionIdGenerator} to use. - * @param sessionIdGenerator the {@link SessionIdGenerator} to use - * @since 3.2 - */ - void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) { - Assert.notNull(sessionIdGenerator, "sessionIdGenerator cannot be null"); - this.sessionIdGenerator = sessionIdGenerator; - } - + /** + * Mongo doesn't support {@literal dot} in field names. We replace it with a unicode character from the Private Use + * Area. + * + *