├── .coveralls.yml ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .java-version ├── .sdkmanrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.gradle ├── codequality └── HEADER ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── license.gradle ├── release.gradle └── src ├── main └── java │ └── net │ └── saliman │ └── spring │ └── request │ └── correlation │ ├── api │ ├── CorrelationIdGenerator.java │ ├── EnableRequestCorrelation.java │ ├── RequestCorrelation.java │ └── RequestCorrelationInterceptor.java │ ├── feign │ ├── FeignCorrelationConfiguration.java │ └── FeignCorrelationInterceptor.java │ ├── filter │ ├── DefaultRequestCorrelation.java │ ├── RequestCorrelationConfiguration.java │ └── RequestCorrelationFilter.java │ ├── generator │ └── DefaultIdGenerator.java │ ├── http │ ├── ClientHttpCorrelationConfiguration.java │ └── ClientHttpRequestCorrelationInterceptor.java │ ├── support │ ├── FilterOrderOffset.java │ ├── RequestCorrelationConsts.java │ ├── RequestCorrelationProperties.java │ └── RequestCorrelationUtils.java │ └── webclient │ ├── WebClientCorrelationConfiguration.java │ └── WebClientCorrelationInterceptor.java └── test ├── java └── net │ └── saliman │ └── spring │ └── request │ └── correlation │ ├── CorrelationTestUtils.java │ ├── RequestCorrelationIT.java │ ├── demo │ ├── DemoApplication.java │ ├── DemoConfiguration.java │ └── DemoFeignClient.java │ ├── feign │ └── FeignCorrelationInterceptorTest.java │ ├── filter │ └── RequestCorrelationFilterTest.java │ ├── generator │ └── DefaultIdGeneratorTest.java │ ├── http │ └── ClientHttpRequestCorrelationInterceptorTest.java │ └── support │ └── RequestCorrelationUtilsTest.java └── resources └── application.yml /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: GePGL0UeMhfAkKaGHuXqYCipRrslPcj6T -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # 4 | # properties that start with ij_ are specific to IntelliJ, and will probably 5 | # be ignored in other editors. 6 | # 7 | # This file was initially generated by exporting Intellij editor settings, so 8 | # most of the items in it are just the default values for IntelliJ, and can 9 | # probably go away. Where we deviate from the standard, there will be a comment 10 | # explaining why. 11 | # 12 | # See editorconfig.org for more information. 13 | 14 | root = true 15 | 16 | # Default values for all file types 17 | [*] 18 | charset = utf-8 19 | end_of_line = lf 20 | indent_size = 4 21 | indent_style = space 22 | insert_final_newline = true 23 | max_line_length = 100 24 | tab_width = 4 25 | trim_trailing_whitespace = true 26 | ij_continuation_indent_size = 8 27 | # Allow formatting to be turned off for certain code blocks. This is most 28 | # useful when chaining methods with the Spring Security DSL to make sense of 29 | # what it is doing. 30 | ij_formatter_tags_enabled = true 31 | ij_formatter_off_tag = @formatter:off 32 | ij_formatter_on_tag = @formatter:on 33 | ij_wrap_on_typing = false 34 | 35 | # CSS Style Sheets 36 | [*.css] 37 | ij_css_align_closing_brace_with_properties = false 38 | ij_css_blank_lines_around_nested_selector = 1 39 | ij_css_blank_lines_between_blocks = 1 40 | ij_css_brace_placement = end_of_line 41 | ij_css_enforce_quotes_on_format = false 42 | ij_css_hex_color_long_format = false 43 | ij_css_hex_color_lower_case = false 44 | ij_css_hex_color_short_format = false 45 | ij_css_hex_color_upper_case = false 46 | ij_css_keep_blank_lines_in_code = 2 47 | ij_css_keep_indents_on_empty_lines = false 48 | ij_css_keep_single_line_blocks = false 49 | ij_css_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow 50 | ij_css_space_after_colon = true 51 | ij_css_space_before_opening_brace = true 52 | ij_css_use_double_quotes = true 53 | ij_css_value_alignment = do_not_align 54 | 55 | # Java files 56 | [*.java] 57 | # aligning chained methods can get really ugly when we have long lines, or we're 58 | # using nested builders of classes of different name lengths. We want to use a 59 | # standard continuation indent for consistency. 60 | ij_java_align_multiline_chained_methods = false 61 | # There are several reasons why wildcard imports are a bad idea, so we'll 62 | # effectively disable them by setting the threshold really high. 63 | ij_java_class_count_to_use_import_on_demand = 99 64 | ij_java_names_count_to_use_import_on_demand = 99 65 | # It is more space efficient to use continuation indents when param descriptions 66 | # get long, especially if the param name is long. 67 | ij_java_doc_align_exception_comments = false 68 | ij_java_doc_align_param_comments = false 69 | # most standards don't put spaces within "if" parentheses, but we like spaces better. 70 | ij_java_spaces_within_for_parentheses = true 71 | ij_java_spaces_within_if_parentheses = true 72 | ij_java_spaces_within_switch_parentheses = true 73 | ij_java_spaces_within_while_parentheses = true 74 | 75 | # The rest are defaults. 76 | ij_java_align_consecutive_assignments = false 77 | ij_java_align_consecutive_variable_declarations = false 78 | ij_java_align_group_field_declarations = false 79 | ij_java_align_multiline_annotation_parameters = false 80 | ij_java_align_multiline_array_initializer_expression = false 81 | ij_java_align_multiline_assignment = false 82 | ij_java_align_multiline_binary_operation = false 83 | ij_java_align_multiline_extends_list = false 84 | ij_java_align_multiline_for = true 85 | ij_java_align_multiline_method_parentheses = false 86 | ij_java_align_multiline_parameters = true 87 | ij_java_align_multiline_parameters_in_calls = false 88 | ij_java_align_multiline_parenthesized_expression = false 89 | ij_java_align_multiline_records = true 90 | ij_java_align_multiline_resources = true 91 | ij_java_align_multiline_ternary_operation = false 92 | ij_java_align_multiline_text_blocks = false 93 | ij_java_align_multiline_throws_list = false 94 | ij_java_align_subsequent_simple_methods = false 95 | ij_java_align_throws_keyword = false 96 | ij_java_annotation_parameter_wrap = off 97 | ij_java_array_initializer_new_line_after_left_brace = false 98 | ij_java_array_initializer_right_brace_on_new_line = false 99 | ij_java_array_initializer_wrap = off 100 | ij_java_assert_statement_colon_on_next_line = false 101 | ij_java_assert_statement_wrap = off 102 | ij_java_assignment_wrap = off 103 | ij_java_binary_operation_sign_on_next_line = false 104 | ij_java_binary_operation_wrap = off 105 | ij_java_blank_lines_after_anonymous_class_header = 0 106 | ij_java_blank_lines_after_class_header = 0 107 | ij_java_blank_lines_after_imports = 1 108 | ij_java_blank_lines_after_package = 1 109 | ij_java_blank_lines_around_class = 1 110 | ij_java_blank_lines_around_field = 0 111 | ij_java_blank_lines_around_field_in_interface = 0 112 | ij_java_blank_lines_around_initializer = 1 113 | ij_java_blank_lines_around_method = 1 114 | ij_java_blank_lines_around_method_in_interface = 1 115 | ij_java_blank_lines_before_class_end = 0 116 | ij_java_blank_lines_before_imports = 1 117 | ij_java_blank_lines_before_method_body = 0 118 | ij_java_blank_lines_before_package = 0 119 | ij_java_block_brace_style = end_of_line 120 | ij_java_block_comment_at_first_column = true 121 | ij_java_call_parameters_new_line_after_left_paren = false 122 | ij_java_call_parameters_right_paren_on_new_line = false 123 | ij_java_call_parameters_wrap = off 124 | ij_java_case_statement_on_separate_line = true 125 | ij_java_catch_on_new_line = false 126 | ij_java_class_annotation_wrap = split_into_lines 127 | ij_java_class_brace_style = end_of_line 128 | ij_java_class_names_in_javadoc = 1 129 | ij_java_do_not_indent_top_level_class_members = false 130 | ij_java_do_not_wrap_after_single_annotation = false 131 | ij_java_do_while_brace_force = never 132 | ij_java_doc_add_blank_line_after_description = true 133 | ij_java_doc_add_blank_line_after_param_comments = false 134 | ij_java_doc_add_blank_line_after_return = false 135 | ij_java_doc_add_p_tag_on_empty_lines = true 136 | ij_java_doc_do_not_wrap_if_one_line = true 137 | ij_java_doc_enable_formatting = true 138 | ij_java_doc_enable_leading_asterisks = true 139 | ij_java_doc_indent_on_continuation = true 140 | ij_java_doc_keep_empty_lines = true 141 | ij_java_doc_keep_empty_parameter_tag = true 142 | ij_java_doc_keep_empty_return_tag = true 143 | ij_java_doc_keep_empty_throws_tag = true 144 | ij_java_doc_keep_invalid_tags = true 145 | ij_java_doc_param_description_on_new_line = false 146 | ij_java_doc_preserve_line_breaks = false 147 | ij_java_doc_use_throws_not_exception_tag = true 148 | ij_java_else_on_new_line = false 149 | ij_java_entity_dd_suffix = EJB 150 | ij_java_entity_eb_suffix = Bean 151 | ij_java_entity_hi_suffix = Home 152 | ij_java_entity_lhi_prefix = Local 153 | ij_java_entity_lhi_suffix = Home 154 | ij_java_entity_li_prefix = Local 155 | ij_java_entity_pk_class = java.lang.String 156 | ij_java_entity_vo_suffix = VO 157 | ij_java_enum_constants_wrap = off 158 | ij_java_extends_keyword_wrap = off 159 | ij_java_extends_list_wrap = off 160 | ij_java_field_annotation_wrap = split_into_lines 161 | ij_java_finally_on_new_line = false 162 | ij_java_for_brace_force = never 163 | ij_java_for_statement_new_line_after_left_paren = false 164 | ij_java_for_statement_right_paren_on_new_line = false 165 | ij_java_for_statement_wrap = off 166 | ij_java_generate_final_locals = false 167 | ij_java_generate_final_parameters = false 168 | ij_java_if_brace_force = never 169 | ij_java_imports_layout = *,|,javax.**,java.**,|,$* 170 | ij_java_indent_case_from_switch = true 171 | ij_java_insert_inner_class_imports = false 172 | ij_java_insert_override_annotation = true 173 | ij_java_keep_blank_lines_before_right_brace = 2 174 | ij_java_keep_blank_lines_between_package_declaration_and_header = 2 175 | ij_java_keep_blank_lines_in_code = 2 176 | ij_java_keep_blank_lines_in_declarations = 2 177 | ij_java_keep_control_statement_in_one_line = true 178 | ij_java_keep_first_column_comment = true 179 | ij_java_keep_indents_on_empty_lines = false 180 | ij_java_keep_line_breaks = true 181 | ij_java_keep_multiple_expressions_in_one_line = false 182 | ij_java_keep_simple_blocks_in_one_line = false 183 | ij_java_keep_simple_classes_in_one_line = false 184 | ij_java_keep_simple_lambdas_in_one_line = false 185 | ij_java_keep_simple_methods_in_one_line = false 186 | ij_java_label_indent_absolute = false 187 | ij_java_label_indent_size = 0 188 | ij_java_lambda_brace_style = end_of_line 189 | ij_java_layout_static_imports_separately = true 190 | ij_java_line_comment_add_space = false 191 | ij_java_line_comment_at_first_column = true 192 | ij_java_message_dd_suffix = EJB 193 | ij_java_message_eb_suffix = Bean 194 | ij_java_method_annotation_wrap = split_into_lines 195 | ij_java_method_brace_style = end_of_line 196 | ij_java_method_call_chain_wrap = off 197 | ij_java_method_parameters_new_line_after_left_paren = false 198 | ij_java_method_parameters_right_paren_on_new_line = false 199 | ij_java_method_parameters_wrap = off 200 | ij_java_modifier_list_wrap = false 201 | ij_java_new_line_after_lparen_in_record_header = false 202 | ij_java_packages_to_use_import_on_demand = java.awt.*,javax.swing.* 203 | ij_java_parameter_annotation_wrap = off 204 | ij_java_parentheses_expression_new_line_after_left_paren = false 205 | ij_java_parentheses_expression_right_paren_on_new_line = false 206 | ij_java_place_assignment_sign_on_next_line = false 207 | ij_java_prefer_longer_names = true 208 | ij_java_prefer_parameters_wrap = false 209 | ij_java_record_components_wrap = normal 210 | ij_java_repeat_synchronized = true 211 | ij_java_replace_instanceof_and_cast = false 212 | ij_java_replace_null_check = true 213 | ij_java_replace_sum_lambda_with_method_ref = true 214 | ij_java_resource_list_new_line_after_left_paren = false 215 | ij_java_resource_list_right_paren_on_new_line = false 216 | ij_java_resource_list_wrap = off 217 | ij_java_rparen_on_new_line_in_record_header = false 218 | ij_java_session_dd_suffix = EJB 219 | ij_java_session_eb_suffix = Bean 220 | ij_java_session_hi_suffix = Home 221 | ij_java_session_lhi_prefix = Local 222 | ij_java_session_lhi_suffix = Home 223 | ij_java_session_li_prefix = Local 224 | ij_java_session_si_suffix = Service 225 | ij_java_space_after_closing_angle_bracket_in_type_argument = false 226 | ij_java_space_after_colon = true 227 | ij_java_space_after_comma = true 228 | ij_java_space_after_comma_in_type_arguments = true 229 | ij_java_space_after_for_semicolon = true 230 | ij_java_space_after_quest = true 231 | ij_java_space_after_type_cast = true 232 | ij_java_space_before_annotation_array_initializer_left_brace = false 233 | ij_java_space_before_annotation_parameter_list = false 234 | ij_java_space_before_array_initializer_left_brace = false 235 | ij_java_space_before_catch_keyword = true 236 | ij_java_space_before_catch_left_brace = true 237 | ij_java_space_before_catch_parentheses = true 238 | ij_java_space_before_class_left_brace = true 239 | ij_java_space_before_colon = true 240 | ij_java_space_before_colon_in_foreach = true 241 | ij_java_space_before_comma = false 242 | ij_java_space_before_do_left_brace = true 243 | ij_java_space_before_else_keyword = true 244 | ij_java_space_before_else_left_brace = true 245 | ij_java_space_before_finally_keyword = true 246 | ij_java_space_before_finally_left_brace = true 247 | ij_java_space_before_for_left_brace = true 248 | ij_java_space_before_for_parentheses = true 249 | ij_java_space_before_for_semicolon = false 250 | ij_java_space_before_if_left_brace = true 251 | ij_java_space_before_if_parentheses = true 252 | ij_java_space_before_method_call_parentheses = false 253 | ij_java_space_before_method_left_brace = true 254 | ij_java_space_before_method_parentheses = false 255 | ij_java_space_before_opening_angle_bracket_in_type_parameter = false 256 | ij_java_space_before_quest = true 257 | ij_java_space_before_switch_left_brace = true 258 | ij_java_space_before_switch_parentheses = true 259 | ij_java_space_before_synchronized_left_brace = true 260 | ij_java_space_before_synchronized_parentheses = true 261 | ij_java_space_before_try_left_brace = true 262 | ij_java_space_before_try_parentheses = true 263 | ij_java_space_before_type_parameter_list = false 264 | ij_java_space_before_while_keyword = true 265 | ij_java_space_before_while_left_brace = true 266 | ij_java_space_before_while_parentheses = true 267 | ij_java_space_inside_one_line_enum_braces = false 268 | ij_java_space_within_empty_array_initializer_braces = false 269 | ij_java_space_within_empty_method_call_parentheses = false 270 | ij_java_space_within_empty_method_parentheses = false 271 | ij_java_spaces_around_additive_operators = true 272 | ij_java_spaces_around_assignment_operators = true 273 | ij_java_spaces_around_bitwise_operators = true 274 | ij_java_spaces_around_equality_operators = true 275 | ij_java_spaces_around_lambda_arrow = true 276 | ij_java_spaces_around_logical_operators = true 277 | ij_java_spaces_around_method_ref_dbl_colon = false 278 | ij_java_spaces_around_multiplicative_operators = true 279 | ij_java_spaces_around_relational_operators = true 280 | ij_java_spaces_around_shift_operators = true 281 | ij_java_spaces_around_type_bounds_in_type_parameters = true 282 | ij_java_spaces_around_unary_operator = false 283 | ij_java_spaces_within_angle_brackets = false 284 | ij_java_spaces_within_annotation_parentheses = false 285 | ij_java_spaces_within_array_initializer_braces = true 286 | ij_java_spaces_within_braces = false 287 | ij_java_spaces_within_brackets = false 288 | ij_java_spaces_within_cast_parentheses = false 289 | ij_java_spaces_within_catch_parentheses = false 290 | ij_java_spaces_within_method_call_parentheses = false 291 | ij_java_spaces_within_method_parentheses = false 292 | ij_java_spaces_within_parentheses = false 293 | ij_java_spaces_within_synchronized_parentheses = false 294 | ij_java_spaces_within_try_parentheses = false 295 | ij_java_special_else_if_treatment = true 296 | ij_java_subclass_name_suffix = Impl 297 | ij_java_ternary_operation_signs_on_next_line = false 298 | ij_java_ternary_operation_wrap = off 299 | ij_java_test_name_suffix = Test 300 | ij_java_throws_keyword_wrap = off 301 | ij_java_throws_list_wrap = off 302 | ij_java_use_external_annotations = false 303 | ij_java_use_fq_class_names = false 304 | ij_java_use_relative_indents = false 305 | ij_java_use_single_class_imports = true 306 | ij_java_variable_annotation_wrap = off 307 | ij_java_visibility = public 308 | ij_java_while_brace_force = never 309 | ij_java_while_on_new_line = false 310 | ij_java_wrap_comments = true 311 | ij_java_wrap_first_method_in_call_chain = false 312 | ij_java_wrap_long_lines = false 313 | 314 | # Less style sheets 315 | [*.less] 316 | # 2 spaces seems to be the norm in HTML based languages. 317 | indent_size = 2 318 | ij_continuation_indent_size = 4 319 | ij_less_align_closing_brace_with_properties = false 320 | ij_less_blank_lines_around_nested_selector = 1 321 | ij_less_blank_lines_between_blocks = 1 322 | ij_less_brace_placement = 0 323 | ij_less_enforce_quotes_on_format = false 324 | ij_less_hex_color_long_format = false 325 | ij_less_hex_color_lower_case = false 326 | ij_less_hex_color_short_format = false 327 | ij_less_hex_color_upper_case = false 328 | ij_less_keep_blank_lines_in_code = 2 329 | ij_less_keep_indents_on_empty_lines = false 330 | ij_less_keep_single_line_blocks = false 331 | ij_less_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow 332 | ij_less_space_after_colon = true 333 | ij_less_space_before_opening_brace = true 334 | ij_less_use_double_quotes = true 335 | ij_less_value_alignment = 0 336 | 337 | # Sass style sheets 338 | [*.sass] 339 | # 2 spaces seems to be the norm in HTML based languages. 340 | indent_size = 2 341 | ij_continuation_indent_size = 4 342 | ij_sass_align_closing_brace_with_properties = false 343 | ij_sass_blank_lines_around_nested_selector = 1 344 | ij_sass_blank_lines_between_blocks = 1 345 | ij_sass_brace_placement = 0 346 | ij_sass_enforce_quotes_on_format = false 347 | ij_sass_hex_color_long_format = false 348 | ij_sass_hex_color_lower_case = false 349 | ij_sass_hex_color_short_format = false 350 | ij_sass_hex_color_upper_case = false 351 | ij_sass_keep_blank_lines_in_code = 2 352 | ij_sass_keep_indents_on_empty_lines = false 353 | ij_sass_keep_single_line_blocks = false 354 | ij_sass_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow 355 | ij_sass_space_after_colon = true 356 | ij_sass_space_before_opening_brace = true 357 | ij_sass_use_double_quotes = true 358 | ij_sass_value_alignment = 0 359 | 360 | # Scss style sheets 361 | [*.scss] 362 | # 2 spaces seems to be the norm in HTML based languages. 363 | indent_size = 2 364 | ij_continuation_indent_size = 4 365 | ij_scss_align_closing_brace_with_properties = false 366 | ij_scss_blank_lines_around_nested_selector = 1 367 | ij_scss_blank_lines_between_blocks = 1 368 | ij_scss_brace_placement = 0 369 | ij_scss_enforce_quotes_on_format = false 370 | ij_scss_hex_color_long_format = false 371 | ij_scss_hex_color_lower_case = false 372 | ij_scss_hex_color_short_format = false 373 | ij_scss_hex_color_upper_case = false 374 | ij_scss_keep_blank_lines_in_code = 2 375 | ij_scss_keep_indents_on_empty_lines = false 376 | ij_scss_keep_single_line_blocks = false 377 | ij_scss_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow 378 | ij_scss_space_after_colon = true 379 | ij_scss_space_before_opening_brace = true 380 | ij_scss_use_double_quotes = true 381 | ij_scss_value_alignment = 0 382 | 383 | [*.styl] 384 | indent_size = 2 385 | ij_continuation_indent_size = 4 386 | ij_stylus_align_closing_brace_with_properties = false 387 | ij_stylus_blank_lines_around_nested_selector = 1 388 | ij_stylus_blank_lines_between_blocks = 1 389 | ij_stylus_brace_placement = 0 390 | ij_stylus_enforce_quotes_on_format = false 391 | ij_stylus_hex_color_long_format = false 392 | ij_stylus_hex_color_lower_case = false 393 | ij_stylus_hex_color_short_format = false 394 | ij_stylus_hex_color_upper_case = false 395 | ij_stylus_keep_blank_lines_in_code = 2 396 | ij_stylus_keep_indents_on_empty_lines = false 397 | ij_stylus_keep_single_line_blocks = false 398 | ij_stylus_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow 399 | ij_stylus_space_after_colon = true 400 | ij_stylus_space_before_opening_brace = true 401 | ij_stylus_use_double_quotes = true 402 | ij_stylus_value_alignment = 0 403 | 404 | # Yaml files 405 | [{*.yaml, *.yml}] 406 | # 2 spaces seems to be the standard for Yaml files, and tabs are also a problem. 407 | indent_style = space 408 | indent_size = 2 409 | ij_continuation_indent_size = 4 410 | ij_yaml_keep_indents_on_empty_lines = false 411 | ij_yaml_keep_line_breaks = true 412 | ij_yaml_space_before_colon = false 413 | ij_yaml_spaces_within_braces = true 414 | ij_yaml_spaces_within_brackets = true 415 | 416 | [.editorconfig] 417 | ij_editorconfig_align_group_field_declarations = false 418 | ij_editorconfig_space_after_colon = false 419 | ij_editorconfig_space_after_comma = true 420 | ij_editorconfig_space_before_colon = false 421 | ij_editorconfig_space_before_comma = false 422 | ij_editorconfig_spaces_around_assignment_operators = true 423 | 424 | [{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.pom,*.qrc,*.rng,*.tld,*.wadl,*.wsdd,*.wsdl,*.xjb,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] 425 | indent_size = 2 426 | tab_width = 2 427 | ij_continuation_indent_size = 4 428 | ij_xml_align_attributes = true 429 | ij_xml_align_text = false 430 | ij_xml_attribute_wrap = normal 431 | ij_xml_block_comment_at_first_column = true 432 | ij_xml_keep_blank_lines = 2 433 | ij_xml_keep_indents_on_empty_lines = false 434 | ij_xml_keep_line_breaks = true 435 | ij_xml_keep_line_breaks_in_text = true 436 | ij_xml_keep_whitespaces = false 437 | ij_xml_keep_whitespaces_around_cdata = preserve 438 | ij_xml_keep_whitespaces_inside_cdata = false 439 | ij_xml_line_comment_at_first_column = true 440 | ij_xml_space_after_tag_name = false 441 | ij_xml_space_around_equals_in_attribute = false 442 | ij_xml_space_inside_empty_tag = false 443 | ij_xml_text_wrap = normal 444 | ij_xml_use_custom_settings = false 445 | 446 | # TypeScript files for Angular code 447 | [{*.ats,*.ts}] 448 | # Most TypeScript style guides, including all the Angular tutorials use single 449 | # quotes instead of double quotes, so we'll do the same. 450 | quote_type = single 451 | ij_typescript_use_double_quotes = false 452 | ij_typescript_force_quote_style = true 453 | # Google's style guide for TypeScript says that we should always use a 454 | # semicolon at the end of a line. 455 | ij_typescript_force_semicolon_style = false 456 | # Google's tutorials all use spaces within imports. 457 | ij_typescript_spaces_within_imports = true 458 | # most standards don't put spaces within "if" parentheses, but we like spaces better. 459 | ij_typescript_spaces_within_for_parentheses = true 460 | ij_typescript_spaces_within_if_parentheses = true 461 | ij_typescript_spaces_within_switch_parentheses = true 462 | ij_typescript_spaces_within_while_parentheses = true 463 | 464 | # The rest are defaults. 465 | ij_typescript_align_imports = false 466 | ij_typescript_align_multiline_array_initializer_expression = false 467 | ij_typescript_align_multiline_binary_operation = false 468 | ij_typescript_align_multiline_chained_methods = false 469 | ij_typescript_align_multiline_extends_list = false 470 | ij_typescript_align_multiline_for = true 471 | ij_typescript_align_multiline_parameters = true 472 | ij_typescript_align_multiline_parameters_in_calls = false 473 | ij_typescript_align_multiline_ternary_operation = false 474 | ij_typescript_align_object_properties = 0 475 | ij_typescript_align_union_types = false 476 | ij_typescript_align_var_statements = 0 477 | ij_typescript_array_initializer_new_line_after_left_brace = false 478 | ij_typescript_array_initializer_right_brace_on_new_line = false 479 | ij_typescript_array_initializer_wrap = off 480 | ij_typescript_assignment_wrap = off 481 | ij_typescript_binary_operation_sign_on_next_line = false 482 | ij_typescript_binary_operation_wrap = off 483 | ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** 484 | ij_typescript_blank_lines_after_imports = 1 485 | ij_typescript_blank_lines_around_class = 1 486 | ij_typescript_blank_lines_around_field = 0 487 | ij_typescript_blank_lines_around_field_in_interface = 0 488 | ij_typescript_blank_lines_around_function = 1 489 | ij_typescript_blank_lines_around_method = 1 490 | ij_typescript_blank_lines_around_method_in_interface = 1 491 | ij_typescript_block_brace_style = end_of_line 492 | ij_typescript_call_parameters_new_line_after_left_paren = false 493 | ij_typescript_call_parameters_right_paren_on_new_line = false 494 | ij_typescript_call_parameters_wrap = off 495 | ij_typescript_catch_on_new_line = false 496 | ij_typescript_chained_call_dot_on_new_line = true 497 | ij_typescript_class_brace_style = end_of_line 498 | ij_typescript_comma_on_new_line = false 499 | ij_typescript_do_while_brace_force = never 500 | ij_typescript_else_on_new_line = false 501 | ij_typescript_enforce_trailing_comma = keep 502 | ij_typescript_extends_keyword_wrap = off 503 | ij_typescript_extends_list_wrap = off 504 | ij_typescript_field_prefix = _ 505 | ij_typescript_file_name_style = relaxed 506 | ij_typescript_finally_on_new_line = false 507 | ij_typescript_for_brace_force = never 508 | ij_typescript_for_statement_new_line_after_left_paren = false 509 | ij_typescript_for_statement_right_paren_on_new_line = false 510 | ij_typescript_for_statement_wrap = off 511 | ij_typescript_function_expression_brace_style = end_of_line 512 | ij_typescript_if_brace_force = never 513 | ij_typescript_import_merge_members = global 514 | ij_typescript_import_prefer_absolute_path = global 515 | ij_typescript_import_sort_members = true 516 | ij_typescript_import_sort_module_name = false 517 | ij_typescript_import_use_node_resolution = true 518 | ij_typescript_imports_wrap = on_every_item 519 | ij_typescript_indent_case_from_switch = true 520 | ij_typescript_indent_chained_calls = true 521 | ij_typescript_indent_package_children = 0 522 | ij_typescript_jsdoc_include_types = false 523 | ij_typescript_jsx_attribute_value = braces 524 | ij_typescript_keep_blank_lines_in_code = 2 525 | ij_typescript_keep_first_column_comment = true 526 | ij_typescript_keep_indents_on_empty_lines = false 527 | ij_typescript_keep_line_breaks = true 528 | ij_typescript_keep_simple_blocks_in_one_line = false 529 | ij_typescript_keep_simple_methods_in_one_line = false 530 | ij_typescript_line_comment_add_space = true 531 | ij_typescript_line_comment_at_first_column = false 532 | ij_typescript_method_brace_style = end_of_line 533 | ij_typescript_method_call_chain_wrap = off 534 | ij_typescript_method_parameters_new_line_after_left_paren = false 535 | ij_typescript_method_parameters_right_paren_on_new_line = false 536 | ij_typescript_method_parameters_wrap = off 537 | ij_typescript_object_literal_wrap = on_every_item 538 | ij_typescript_parentheses_expression_new_line_after_left_paren = false 539 | ij_typescript_parentheses_expression_right_paren_on_new_line = false 540 | ij_typescript_place_assignment_sign_on_next_line = false 541 | ij_typescript_prefer_as_type_cast = false 542 | ij_typescript_prefer_explicit_types_function_expression_returns = false 543 | ij_typescript_prefer_explicit_types_function_returns = false 544 | ij_typescript_prefer_explicit_types_vars_fields = false 545 | ij_typescript_prefer_parameters_wrap = false 546 | ij_typescript_reformat_c_style_comments = false 547 | ij_typescript_space_after_colon = true 548 | ij_typescript_space_after_comma = true 549 | ij_typescript_space_after_dots_in_rest_parameter = false 550 | ij_typescript_space_after_generator_mult = true 551 | ij_typescript_space_after_property_colon = true 552 | ij_typescript_space_after_quest = true 553 | ij_typescript_space_after_type_colon = true 554 | ij_typescript_space_after_unary_not = false 555 | ij_typescript_space_before_async_arrow_lparen = true 556 | ij_typescript_space_before_catch_keyword = true 557 | ij_typescript_space_before_catch_left_brace = true 558 | ij_typescript_space_before_catch_parentheses = true 559 | ij_typescript_space_before_class_lbrace = true 560 | ij_typescript_space_before_class_left_brace = true 561 | ij_typescript_space_before_colon = true 562 | ij_typescript_space_before_comma = false 563 | ij_typescript_space_before_do_left_brace = true 564 | ij_typescript_space_before_else_keyword = true 565 | ij_typescript_space_before_else_left_brace = true 566 | ij_typescript_space_before_finally_keyword = true 567 | ij_typescript_space_before_finally_left_brace = true 568 | ij_typescript_space_before_for_left_brace = true 569 | ij_typescript_space_before_for_parentheses = true 570 | ij_typescript_space_before_for_semicolon = false 571 | ij_typescript_space_before_function_left_parenth = true 572 | ij_typescript_space_before_generator_mult = false 573 | ij_typescript_space_before_if_left_brace = true 574 | ij_typescript_space_before_if_parentheses = true 575 | ij_typescript_space_before_method_call_parentheses = false 576 | ij_typescript_space_before_method_left_brace = true 577 | ij_typescript_space_before_method_parentheses = false 578 | ij_typescript_space_before_property_colon = false 579 | ij_typescript_space_before_quest = true 580 | ij_typescript_space_before_switch_left_brace = true 581 | ij_typescript_space_before_switch_parentheses = true 582 | ij_typescript_space_before_try_left_brace = true 583 | ij_typescript_space_before_type_colon = false 584 | ij_typescript_space_before_unary_not = false 585 | ij_typescript_space_before_while_keyword = true 586 | ij_typescript_space_before_while_left_brace = true 587 | ij_typescript_space_before_while_parentheses = true 588 | ij_typescript_spaces_around_additive_operators = true 589 | ij_typescript_spaces_around_arrow_function_operator = true 590 | ij_typescript_spaces_around_assignment_operators = true 591 | ij_typescript_spaces_around_bitwise_operators = true 592 | ij_typescript_spaces_around_equality_operators = true 593 | ij_typescript_spaces_around_logical_operators = true 594 | ij_typescript_spaces_around_multiplicative_operators = true 595 | ij_typescript_spaces_around_relational_operators = true 596 | ij_typescript_spaces_around_shift_operators = true 597 | ij_typescript_spaces_around_unary_operator = false 598 | ij_typescript_spaces_within_array_initializer_brackets = false 599 | ij_typescript_spaces_within_brackets = false 600 | ij_typescript_spaces_within_catch_parentheses = false 601 | ij_typescript_spaces_within_interpolation_expressions = false 602 | ij_typescript_spaces_within_method_call_parentheses = false 603 | ij_typescript_spaces_within_method_parentheses = false 604 | ij_typescript_spaces_within_object_literal_braces = false 605 | ij_typescript_spaces_within_object_type_braces = true 606 | ij_typescript_spaces_within_parentheses = false 607 | ij_typescript_spaces_within_type_assertion = false 608 | ij_typescript_spaces_within_union_types = true 609 | ij_typescript_special_else_if_treatment = true 610 | ij_typescript_ternary_operation_signs_on_next_line = false 611 | ij_typescript_ternary_operation_wrap = off 612 | ij_typescript_union_types_wrap = on_every_item 613 | ij_typescript_use_chained_calls_group_indents = false 614 | ij_typescript_use_explicit_js_extension = global 615 | ij_typescript_use_path_mapping = always 616 | ij_typescript_use_public_modifier = false 617 | ij_typescript_use_semicolon_after_statement = true 618 | ij_typescript_var_declaration_wrap = normal 619 | ij_typescript_while_brace_force = never 620 | ij_typescript_while_on_new_line = false 621 | ij_typescript_wrap_comments = false 622 | 623 | # Shell scripts 624 | [{*.bash,*.sh,*.zsh}] 625 | ij_shell_binary_ops_start_line = false 626 | ij_shell_keep_column_alignment_padding = false 627 | ij_shell_minify_program = false 628 | ij_shell_redirect_followed_by_space = false 629 | ij_shell_switch_cases_indented = false 630 | 631 | # Javascript 632 | [{*.cjs,*.js}] 633 | # Most style guides for JavaScript use 2 space indents. 634 | indent_size = 2 635 | tab_width = 2 636 | ij_continuation_indent_size = 4 637 | # Most JavaScript style guides generally prefer single quotes in js. 638 | ij_javascript_use_double_quotes = false 639 | ij_javascript_force_quote_style = true 640 | # Google's style guide for JavaScript says that we should always use a 641 | # semicolon at the end of a line. 642 | ij_javascript_force_semicolon_style = true 643 | # We don't need lots of blank lines. Condense to 1 644 | ij_javascript_keep_blank_lines_in_code = 1 645 | # Google's tutorials all use spaces within imports. 646 | ij_javascript_spaces_within_imports = true 647 | # most standards don't put spaces within "if" parentheses, but we like spaces better. 648 | ij_javascript_spaces_within_for_parentheses = true 649 | ij_javascript_spaces_within_if_parentheses = true 650 | ij_javascript_spaces_within_switch_parentheses = true 651 | ij_javascript_spaces_within_while_parentheses = true 652 | 653 | # The rest are defaults. 654 | ij_javascript_align_imports = false 655 | ij_javascript_align_multiline_array_initializer_expression = false 656 | ij_javascript_align_multiline_binary_operation = false 657 | ij_javascript_align_multiline_chained_methods = false 658 | ij_javascript_align_multiline_extends_list = false 659 | ij_javascript_align_multiline_for = true 660 | ij_javascript_align_multiline_parameters = true 661 | ij_javascript_align_multiline_parameters_in_calls = false 662 | ij_javascript_align_multiline_ternary_operation = false 663 | ij_javascript_align_object_properties = 0 664 | ij_javascript_align_union_types = false 665 | ij_javascript_align_var_statements = 0 666 | ij_javascript_array_initializer_new_line_after_left_brace = false 667 | ij_javascript_array_initializer_right_brace_on_new_line = false 668 | ij_javascript_array_initializer_wrap = off 669 | ij_javascript_assignment_wrap = off 670 | ij_javascript_binary_operation_sign_on_next_line = false 671 | ij_javascript_binary_operation_wrap = off 672 | ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** 673 | ij_javascript_blank_lines_after_imports = 1 674 | ij_javascript_blank_lines_around_class = 1 675 | ij_javascript_blank_lines_around_field = 0 676 | ij_javascript_blank_lines_around_function = 1 677 | ij_javascript_blank_lines_around_method = 1 678 | ij_javascript_block_brace_style = end_of_line 679 | ij_javascript_call_parameters_new_line_after_left_paren = false 680 | ij_javascript_call_parameters_right_paren_on_new_line = false 681 | ij_javascript_call_parameters_wrap = off 682 | ij_javascript_catch_on_new_line = false 683 | ij_javascript_chained_call_dot_on_new_line = true 684 | ij_javascript_class_brace_style = end_of_line 685 | ij_javascript_comma_on_new_line = false 686 | ij_javascript_do_while_brace_force = never 687 | ij_javascript_else_on_new_line = false 688 | ij_javascript_enforce_trailing_comma = keep 689 | ij_javascript_extends_keyword_wrap = off 690 | ij_javascript_extends_list_wrap = off 691 | ij_javascript_field_prefix = _ 692 | ij_javascript_file_name_style = relaxed 693 | ij_javascript_finally_on_new_line = false 694 | ij_javascript_for_brace_force = never 695 | ij_javascript_for_statement_new_line_after_left_paren = false 696 | ij_javascript_for_statement_right_paren_on_new_line = false 697 | ij_javascript_for_statement_wrap = off 698 | ij_javascript_function_expression_brace_style = end_of_line 699 | ij_javascript_if_brace_force = never 700 | ij_javascript_import_merge_members = global 701 | ij_javascript_import_prefer_absolute_path = global 702 | ij_javascript_import_sort_members = true 703 | ij_javascript_import_sort_module_name = false 704 | ij_javascript_import_use_node_resolution = true 705 | ij_javascript_imports_wrap = on_every_item 706 | ij_javascript_indent_case_from_switch = true 707 | ij_javascript_indent_chained_calls = true 708 | ij_javascript_indent_package_children = 0 709 | ij_javascript_jsx_attribute_value = braces 710 | ij_javascript_keep_first_column_comment = true 711 | ij_javascript_keep_indents_on_empty_lines = false 712 | ij_javascript_keep_line_breaks = true 713 | ij_javascript_keep_simple_blocks_in_one_line = false 714 | ij_javascript_keep_simple_methods_in_one_line = false 715 | ij_javascript_line_comment_add_space = true 716 | ij_javascript_line_comment_at_first_column = false 717 | ij_javascript_method_brace_style = end_of_line 718 | ij_javascript_method_call_chain_wrap = off 719 | ij_javascript_method_parameters_new_line_after_left_paren = false 720 | ij_javascript_method_parameters_right_paren_on_new_line = false 721 | ij_javascript_method_parameters_wrap = off 722 | ij_javascript_object_literal_wrap = on_every_item 723 | ij_javascript_parentheses_expression_new_line_after_left_paren = false 724 | ij_javascript_parentheses_expression_right_paren_on_new_line = false 725 | ij_javascript_place_assignment_sign_on_next_line = false 726 | ij_javascript_prefer_as_type_cast = false 727 | ij_javascript_prefer_explicit_types_function_expression_returns = false 728 | ij_javascript_prefer_explicit_types_function_returns = false 729 | ij_javascript_prefer_explicit_types_vars_fields = false 730 | ij_javascript_prefer_parameters_wrap = false 731 | ij_javascript_reformat_c_style_comments = false 732 | ij_javascript_space_after_colon = true 733 | ij_javascript_space_after_comma = true 734 | ij_javascript_space_after_dots_in_rest_parameter = false 735 | ij_javascript_space_after_generator_mult = true 736 | ij_javascript_space_after_property_colon = true 737 | ij_javascript_space_after_quest = true 738 | ij_javascript_space_after_type_colon = true 739 | ij_javascript_space_after_unary_not = false 740 | ij_javascript_space_before_async_arrow_lparen = true 741 | ij_javascript_space_before_catch_keyword = true 742 | ij_javascript_space_before_catch_left_brace = true 743 | ij_javascript_space_before_catch_parentheses = true 744 | ij_javascript_space_before_class_lbrace = true 745 | ij_javascript_space_before_class_left_brace = true 746 | ij_javascript_space_before_colon = true 747 | ij_javascript_space_before_comma = false 748 | ij_javascript_space_before_do_left_brace = true 749 | ij_javascript_space_before_else_keyword = true 750 | ij_javascript_space_before_else_left_brace = true 751 | ij_javascript_space_before_finally_keyword = true 752 | ij_javascript_space_before_finally_left_brace = true 753 | ij_javascript_space_before_for_left_brace = true 754 | ij_javascript_space_before_for_parentheses = true 755 | ij_javascript_space_before_for_semicolon = false 756 | ij_javascript_space_before_function_left_parenth = true 757 | ij_javascript_space_before_generator_mult = false 758 | ij_javascript_space_before_if_left_brace = true 759 | ij_javascript_space_before_if_parentheses = true 760 | ij_javascript_space_before_method_call_parentheses = false 761 | ij_javascript_space_before_method_left_brace = true 762 | ij_javascript_space_before_method_parentheses = false 763 | ij_javascript_space_before_property_colon = false 764 | ij_javascript_space_before_quest = true 765 | ij_javascript_space_before_switch_left_brace = true 766 | ij_javascript_space_before_switch_parentheses = true 767 | ij_javascript_space_before_try_left_brace = true 768 | ij_javascript_space_before_type_colon = false 769 | ij_javascript_space_before_unary_not = false 770 | ij_javascript_space_before_while_keyword = true 771 | ij_javascript_space_before_while_left_brace = true 772 | ij_javascript_space_before_while_parentheses = true 773 | ij_javascript_spaces_around_additive_operators = true 774 | ij_javascript_spaces_around_arrow_function_operator = true 775 | ij_javascript_spaces_around_assignment_operators = true 776 | ij_javascript_spaces_around_bitwise_operators = true 777 | ij_javascript_spaces_around_equality_operators = true 778 | ij_javascript_spaces_around_logical_operators = true 779 | ij_javascript_spaces_around_multiplicative_operators = true 780 | ij_javascript_spaces_around_relational_operators = true 781 | ij_javascript_spaces_around_shift_operators = true 782 | ij_javascript_spaces_around_unary_operator = false 783 | ij_javascript_spaces_within_array_initializer_brackets = false 784 | ij_javascript_spaces_within_brackets = false 785 | ij_javascript_spaces_within_catch_parentheses = false 786 | ij_javascript_spaces_within_interpolation_expressions = false 787 | ij_javascript_spaces_within_method_call_parentheses = false 788 | ij_javascript_spaces_within_method_parentheses = false 789 | ij_javascript_spaces_within_object_literal_braces = false 790 | ij_javascript_spaces_within_object_type_braces = true 791 | ij_javascript_spaces_within_parentheses = false 792 | ij_javascript_spaces_within_type_assertion = false 793 | ij_javascript_spaces_within_union_types = true 794 | ij_javascript_special_else_if_treatment = true 795 | ij_javascript_ternary_operation_signs_on_next_line = false 796 | ij_javascript_ternary_operation_wrap = off 797 | ij_javascript_union_types_wrap = on_every_item 798 | ij_javascript_use_chained_calls_group_indents = false 799 | ij_javascript_use_explicit_js_extension = global 800 | ij_javascript_use_path_mapping = always 801 | ij_javascript_use_public_modifier = false 802 | ij_javascript_use_semicolon_after_statement = true 803 | ij_javascript_var_declaration_wrap = normal 804 | ij_javascript_while_brace_force = never 805 | ij_javascript_while_on_new_line = false 806 | ij_javascript_wrap_comments = false 807 | 808 | # CoffeeScript 809 | [{*.cjsx,*.coffee}] 810 | # Since it is a JavaScript based language, we'll use JavaScript's 2 space 811 | # indentation. 812 | 813 | indent_size = 2 814 | tab_width = 2 815 | ij_continuation_indent_size = 2 816 | ij_coffeescript_align_function_body = false 817 | ij_coffeescript_align_imports = false 818 | ij_coffeescript_align_multiline_array_initializer_expression = true 819 | ij_coffeescript_align_multiline_parameters = true 820 | ij_coffeescript_align_multiline_parameters_in_calls = false 821 | ij_coffeescript_align_object_properties = 0 822 | ij_coffeescript_align_union_types = false 823 | ij_coffeescript_align_var_statements = 0 824 | ij_coffeescript_array_initializer_new_line_after_left_brace = false 825 | ij_coffeescript_array_initializer_right_brace_on_new_line = false 826 | ij_coffeescript_array_initializer_wrap = normal 827 | ij_coffeescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** 828 | ij_coffeescript_blank_lines_around_function = 1 829 | ij_coffeescript_call_parameters_new_line_after_left_paren = false 830 | ij_coffeescript_call_parameters_right_paren_on_new_line = false 831 | ij_coffeescript_call_parameters_wrap = normal 832 | ij_coffeescript_chained_call_dot_on_new_line = true 833 | ij_coffeescript_comma_on_new_line = false 834 | ij_coffeescript_enforce_trailing_comma = keep 835 | ij_coffeescript_field_prefix = _ 836 | ij_coffeescript_file_name_style = relaxed 837 | ij_coffeescript_force_quote_style = false 838 | ij_coffeescript_force_semicolon_style = false 839 | ij_coffeescript_function_expression_brace_style = end_of_line 840 | ij_coffeescript_import_merge_members = global 841 | ij_coffeescript_import_prefer_absolute_path = global 842 | ij_coffeescript_import_sort_members = true 843 | ij_coffeescript_import_sort_module_name = false 844 | ij_coffeescript_import_use_node_resolution = true 845 | ij_coffeescript_imports_wrap = on_every_item 846 | ij_coffeescript_indent_chained_calls = true 847 | ij_coffeescript_indent_package_children = 0 848 | ij_coffeescript_jsx_attribute_value = braces 849 | ij_coffeescript_keep_blank_lines_in_code = 2 850 | ij_coffeescript_keep_first_column_comment = true 851 | ij_coffeescript_keep_indents_on_empty_lines = false 852 | ij_coffeescript_keep_line_breaks = true 853 | ij_coffeescript_keep_simple_methods_in_one_line = false 854 | ij_coffeescript_method_parameters_new_line_after_left_paren = false 855 | ij_coffeescript_method_parameters_right_paren_on_new_line = false 856 | ij_coffeescript_method_parameters_wrap = off 857 | ij_coffeescript_object_literal_wrap = on_every_item 858 | ij_coffeescript_prefer_as_type_cast = false 859 | ij_coffeescript_prefer_explicit_types_function_expression_returns = false 860 | ij_coffeescript_prefer_explicit_types_function_returns = false 861 | ij_coffeescript_prefer_explicit_types_vars_fields = false 862 | ij_coffeescript_reformat_c_style_comments = false 863 | ij_coffeescript_space_after_comma = true 864 | ij_coffeescript_space_after_dots_in_rest_parameter = false 865 | ij_coffeescript_space_after_generator_mult = true 866 | ij_coffeescript_space_after_property_colon = true 867 | ij_coffeescript_space_after_type_colon = true 868 | ij_coffeescript_space_after_unary_not = false 869 | ij_coffeescript_space_before_async_arrow_lparen = true 870 | ij_coffeescript_space_before_class_lbrace = true 871 | ij_coffeescript_space_before_comma = false 872 | ij_coffeescript_space_before_function_left_parenth = true 873 | ij_coffeescript_space_before_generator_mult = false 874 | ij_coffeescript_space_before_property_colon = false 875 | ij_coffeescript_space_before_type_colon = false 876 | ij_coffeescript_space_before_unary_not = false 877 | ij_coffeescript_spaces_around_additive_operators = true 878 | ij_coffeescript_spaces_around_arrow_function_operator = true 879 | ij_coffeescript_spaces_around_assignment_operators = true 880 | ij_coffeescript_spaces_around_bitwise_operators = true 881 | ij_coffeescript_spaces_around_equality_operators = true 882 | ij_coffeescript_spaces_around_logical_operators = true 883 | ij_coffeescript_spaces_around_multiplicative_operators = true 884 | ij_coffeescript_spaces_around_relational_operators = true 885 | ij_coffeescript_spaces_around_shift_operators = true 886 | ij_coffeescript_spaces_around_unary_operator = false 887 | ij_coffeescript_spaces_within_array_initializer_braces = false 888 | ij_coffeescript_spaces_within_array_initializer_brackets = false 889 | ij_coffeescript_spaces_within_imports = false 890 | ij_coffeescript_spaces_within_index_brackets = false 891 | ij_coffeescript_spaces_within_interpolation_expressions = false 892 | ij_coffeescript_spaces_within_method_call_parentheses = false 893 | ij_coffeescript_spaces_within_method_parentheses = false 894 | ij_coffeescript_spaces_within_object_braces = false 895 | ij_coffeescript_spaces_within_object_literal_braces = false 896 | ij_coffeescript_spaces_within_object_type_braces = true 897 | ij_coffeescript_spaces_within_range_brackets = false 898 | ij_coffeescript_spaces_within_type_assertion = false 899 | ij_coffeescript_spaces_within_union_types = true 900 | ij_coffeescript_union_types_wrap = on_every_item 901 | ij_coffeescript_use_chained_calls_group_indents = false 902 | ij_coffeescript_use_double_quotes = true 903 | ij_coffeescript_use_explicit_js_extension = global 904 | ij_coffeescript_use_path_mapping = always 905 | ij_coffeescript_use_public_modifier = false 906 | ij_coffeescript_use_semicolon_after_statement = false 907 | ij_coffeescript_var_declaration_wrap = normal 908 | 909 | [{*.ft,*.vm,*.vsl}] 910 | ij_vtl_keep_indents_on_empty_lines = false 911 | 912 | # Groovy files, which include build.gradle 913 | [{*.gant,*.gradle,*.groovy,*.gson,*.gy,Jenkinsfile}] 914 | # There are several reasons why wildcard imports are a bad idea, so we'll 915 | # effectively disable them by setting the threshold really high. 916 | ij_groovy_class_count_to_use_import_on_demand = 99 917 | ij_groovy_names_count_to_use_import_on_demand = 99 918 | # most standards don't put spaces within "if" parentheses, but we like spaces better. 919 | ij_groovy_spaces_within_for_parentheses = true 920 | ij_groovy_spaces_within_if_parentheses = true 921 | ij_groovy_spaces_within_while_parentheses = true 922 | ij_groovy_spaces_within_switch_parentheses = true 923 | 924 | # The rest are defaults. 925 | ij_groovy_align_group_field_declarations = false 926 | ij_groovy_align_multiline_array_initializer_expression = false 927 | ij_groovy_align_multiline_assignment = false 928 | ij_groovy_align_multiline_binary_operation = false 929 | ij_groovy_align_multiline_chained_methods = false 930 | ij_groovy_align_multiline_extends_list = false 931 | ij_groovy_align_multiline_for = true 932 | ij_groovy_align_multiline_list_or_map = true 933 | ij_groovy_align_multiline_method_parentheses = false 934 | ij_groovy_align_multiline_parameters = true 935 | ij_groovy_align_multiline_parameters_in_calls = false 936 | ij_groovy_align_multiline_resources = true 937 | ij_groovy_align_multiline_ternary_operation = false 938 | ij_groovy_align_multiline_throws_list = false 939 | ij_groovy_align_named_args_in_map = true 940 | ij_groovy_align_throws_keyword = false 941 | ij_groovy_array_initializer_new_line_after_left_brace = false 942 | ij_groovy_array_initializer_right_brace_on_new_line = false 943 | ij_groovy_array_initializer_wrap = off 944 | ij_groovy_assert_statement_wrap = off 945 | ij_groovy_assignment_wrap = off 946 | ij_groovy_binary_operation_wrap = off 947 | ij_groovy_blank_lines_after_class_header = 0 948 | ij_groovy_blank_lines_after_imports = 1 949 | ij_groovy_blank_lines_after_package = 1 950 | ij_groovy_blank_lines_around_class = 1 951 | ij_groovy_blank_lines_around_field = 0 952 | ij_groovy_blank_lines_around_field_in_interface = 0 953 | ij_groovy_blank_lines_around_method = 1 954 | ij_groovy_blank_lines_around_method_in_interface = 1 955 | ij_groovy_blank_lines_before_imports = 1 956 | ij_groovy_blank_lines_before_method_body = 0 957 | ij_groovy_blank_lines_before_package = 0 958 | ij_groovy_block_brace_style = end_of_line 959 | ij_groovy_block_comment_at_first_column = true 960 | ij_groovy_call_parameters_new_line_after_left_paren = false 961 | ij_groovy_call_parameters_right_paren_on_new_line = false 962 | ij_groovy_call_parameters_wrap = off 963 | ij_groovy_catch_on_new_line = false 964 | ij_groovy_class_annotation_wrap = split_into_lines 965 | ij_groovy_class_brace_style = end_of_line 966 | ij_groovy_do_while_brace_force = never 967 | ij_groovy_else_on_new_line = false 968 | ij_groovy_enum_constants_wrap = off 969 | ij_groovy_extends_keyword_wrap = off 970 | ij_groovy_extends_list_wrap = off 971 | ij_groovy_field_annotation_wrap = split_into_lines 972 | ij_groovy_finally_on_new_line = false 973 | ij_groovy_for_brace_force = never 974 | ij_groovy_for_statement_new_line_after_left_paren = false 975 | ij_groovy_for_statement_right_paren_on_new_line = false 976 | ij_groovy_for_statement_wrap = off 977 | ij_groovy_if_brace_force = never 978 | ij_groovy_import_annotation_wrap = 2 979 | ij_groovy_imports_layout = *,|,javax.**,java.**,|,$* 980 | ij_groovy_indent_case_from_switch = true 981 | ij_groovy_indent_label_blocks = true 982 | ij_groovy_insert_inner_class_imports = false 983 | ij_groovy_keep_blank_lines_before_right_brace = 2 984 | ij_groovy_keep_blank_lines_in_code = 2 985 | ij_groovy_keep_blank_lines_in_declarations = 2 986 | ij_groovy_keep_control_statement_in_one_line = true 987 | ij_groovy_keep_first_column_comment = true 988 | ij_groovy_keep_indents_on_empty_lines = false 989 | ij_groovy_keep_line_breaks = true 990 | ij_groovy_keep_multiple_expressions_in_one_line = false 991 | ij_groovy_keep_simple_blocks_in_one_line = false 992 | ij_groovy_keep_simple_classes_in_one_line = true 993 | ij_groovy_keep_simple_lambdas_in_one_line = true 994 | ij_groovy_keep_simple_methods_in_one_line = true 995 | ij_groovy_label_indent_absolute = false 996 | ij_groovy_label_indent_size = 0 997 | ij_groovy_lambda_brace_style = end_of_line 998 | ij_groovy_layout_static_imports_separately = true 999 | ij_groovy_line_comment_add_space = false 1000 | ij_groovy_line_comment_at_first_column = true 1001 | ij_groovy_method_annotation_wrap = split_into_lines 1002 | ij_groovy_method_brace_style = end_of_line 1003 | ij_groovy_method_call_chain_wrap = off 1004 | ij_groovy_method_parameters_new_line_after_left_paren = false 1005 | ij_groovy_method_parameters_right_paren_on_new_line = false 1006 | ij_groovy_method_parameters_wrap = off 1007 | ij_groovy_modifier_list_wrap = false 1008 | ij_groovy_parameter_annotation_wrap = off 1009 | ij_groovy_parentheses_expression_new_line_after_left_paren = false 1010 | ij_groovy_parentheses_expression_right_paren_on_new_line = false 1011 | ij_groovy_prefer_parameters_wrap = false 1012 | ij_groovy_resource_list_new_line_after_left_paren = false 1013 | ij_groovy_resource_list_right_paren_on_new_line = false 1014 | ij_groovy_resource_list_wrap = off 1015 | ij_groovy_space_after_assert_separator = true 1016 | ij_groovy_space_after_colon = true 1017 | ij_groovy_space_after_comma = true 1018 | ij_groovy_space_after_comma_in_type_arguments = true 1019 | ij_groovy_space_after_for_semicolon = true 1020 | ij_groovy_space_after_quest = true 1021 | ij_groovy_space_after_type_cast = true 1022 | ij_groovy_space_before_annotation_parameter_list = false 1023 | ij_groovy_space_before_array_initializer_left_brace = false 1024 | ij_groovy_space_before_assert_separator = false 1025 | ij_groovy_space_before_catch_keyword = true 1026 | ij_groovy_space_before_catch_left_brace = true 1027 | ij_groovy_space_before_catch_parentheses = true 1028 | ij_groovy_space_before_class_left_brace = true 1029 | ij_groovy_space_before_closure_left_brace = true 1030 | ij_groovy_space_before_colon = true 1031 | ij_groovy_space_before_comma = false 1032 | ij_groovy_space_before_do_left_brace = true 1033 | ij_groovy_space_before_else_keyword = true 1034 | ij_groovy_space_before_else_left_brace = true 1035 | ij_groovy_space_before_finally_keyword = true 1036 | ij_groovy_space_before_finally_left_brace = true 1037 | ij_groovy_space_before_for_left_brace = true 1038 | ij_groovy_space_before_for_parentheses = true 1039 | ij_groovy_space_before_for_semicolon = false 1040 | ij_groovy_space_before_if_left_brace = true 1041 | ij_groovy_space_before_if_parentheses = true 1042 | ij_groovy_space_before_method_call_parentheses = false 1043 | ij_groovy_space_before_method_left_brace = true 1044 | ij_groovy_space_before_method_parentheses = false 1045 | ij_groovy_space_before_quest = true 1046 | ij_groovy_space_before_switch_left_brace = true 1047 | ij_groovy_space_before_switch_parentheses = true 1048 | ij_groovy_space_before_synchronized_left_brace = true 1049 | ij_groovy_space_before_synchronized_parentheses = true 1050 | ij_groovy_space_before_try_left_brace = true 1051 | ij_groovy_space_before_try_parentheses = true 1052 | ij_groovy_space_before_while_keyword = true 1053 | ij_groovy_space_before_while_left_brace = true 1054 | ij_groovy_space_before_while_parentheses = true 1055 | ij_groovy_space_in_named_argument = true 1056 | ij_groovy_space_in_named_argument_before_colon = false 1057 | ij_groovy_space_within_empty_array_initializer_braces = true 1058 | ij_groovy_space_within_empty_method_call_parentheses = false 1059 | ij_groovy_spaces_around_additive_operators = true 1060 | ij_groovy_spaces_around_assignment_operators = true 1061 | ij_groovy_spaces_around_bitwise_operators = true 1062 | ij_groovy_spaces_around_equality_operators = true 1063 | ij_groovy_spaces_around_lambda_arrow = true 1064 | ij_groovy_spaces_around_logical_operators = true 1065 | ij_groovy_spaces_around_multiplicative_operators = true 1066 | ij_groovy_spaces_around_regex_operators = true 1067 | ij_groovy_spaces_around_relational_operators = true 1068 | ij_groovy_spaces_around_shift_operators = true 1069 | ij_groovy_spaces_within_annotation_parentheses = false 1070 | ij_groovy_spaces_within_array_initializer_braces = true 1071 | ij_groovy_spaces_within_braces = true 1072 | ij_groovy_spaces_within_brackets = false 1073 | ij_groovy_spaces_within_cast_parentheses = false 1074 | ij_groovy_spaces_within_catch_parentheses = false 1075 | ij_groovy_spaces_within_gstring_injection_braces = false 1076 | ij_groovy_spaces_within_list_or_map = false 1077 | ij_groovy_spaces_within_method_call_parentheses = false 1078 | ij_groovy_spaces_within_method_parentheses = false 1079 | ij_groovy_spaces_within_parentheses = false 1080 | ij_groovy_spaces_within_synchronized_parentheses = false 1081 | ij_groovy_spaces_within_try_parentheses = false 1082 | ij_groovy_spaces_within_tuple_expression = false 1083 | ij_groovy_special_else_if_treatment = true 1084 | ij_groovy_ternary_operation_wrap = off 1085 | ij_groovy_throws_keyword_wrap = off 1086 | ij_groovy_throws_list_wrap = off 1087 | ij_groovy_use_flying_geese_braces = false 1088 | ij_groovy_use_fq_class_names = false 1089 | ij_groovy_use_fq_class_names_in_javadoc = true 1090 | ij_groovy_use_relative_indents = false 1091 | ij_groovy_use_single_class_imports = true 1092 | ij_groovy_variable_annotation_wrap = off 1093 | ij_groovy_while_brace_force = never 1094 | ij_groovy_while_on_new_line = false 1095 | ij_groovy_wrap_long_lines = false 1096 | 1097 | # JSON files 1098 | [{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.stylelintrc,bowerrc,jest.config}] 1099 | # The json standard appears to be 2 space indents. 1100 | indent_size = 2 1101 | tab_width = 2 1102 | ij_continuation_indent_size = 4 1103 | ij_json_keep_blank_lines_in_code = 1 1104 | ij_json_keep_indents_on_empty_lines = false 1105 | ij_json_keep_line_breaks = true 1106 | ij_json_space_after_colon = true 1107 | ij_json_space_after_comma = true 1108 | ij_json_space_before_colon = false 1109 | ij_json_space_before_comma = false 1110 | ij_json_spaces_within_braces = false 1111 | ij_json_spaces_within_brackets = false 1112 | ij_json_wrap_long_lines = false 1113 | 1114 | # HTML files 1115 | [{*.htm,*.html,*.ng,*.sht,*.shtm,*.shtml}] 1116 | # HTML has a LOT of nesting, so we go with a smaller indent size. 1117 | indent_size = 2 1118 | tab_width = 2 1119 | 1120 | # The rest are defaults. 1121 | ij_continuation_indent_size = 4 1122 | ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 1123 | ij_html_align_attributes = true 1124 | ij_html_align_text = false 1125 | ij_html_attribute_wrap = normal 1126 | ij_html_block_comment_at_first_column = true 1127 | ij_html_do_not_align_children_of_min_lines = 0 1128 | ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p 1129 | ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot 1130 | ij_html_enforce_quotes = false 1131 | ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var 1132 | ij_html_keep_blank_lines = 2 1133 | ij_html_keep_indents_on_empty_lines = false 1134 | ij_html_keep_line_breaks = true 1135 | ij_html_keep_line_breaks_in_text = true 1136 | ij_html_keep_whitespaces = false 1137 | ij_html_keep_whitespaces_inside = span,pre,textarea 1138 | ij_html_line_comment_at_first_column = true 1139 | ij_html_new_line_after_last_attribute = never 1140 | ij_html_new_line_before_first_attribute = never 1141 | ij_html_quote_style = double 1142 | ij_html_remove_new_line_before_tags = br 1143 | ij_html_space_after_tag_name = false 1144 | ij_html_space_around_equality_in_attribute = false 1145 | ij_html_space_inside_empty_tag = false 1146 | ij_html_text_wrap = normal 1147 | ij_html_uniform_ident = false 1148 | 1149 | # JSP pages 1150 | [{*.jsf,*.jsp,*.jspf,*.tag,*.tagf,*.xjsp}] 1151 | # JSP pages are basically HTML, so use smaller indents for them too. 1152 | indent_size = 2 1153 | tab_width = 2 1154 | ij_continuation_indent_size = 4 1155 | ij_jsp_jsp_prefer_comma_separated_import_list = false 1156 | ij_jsp_keep_indents_on_empty_lines = false 1157 | 1158 | [{*.jspx,*.tagx}] 1159 | ij_jspx_keep_indents_on_empty_lines = false 1160 | 1161 | [{*.properties,spring.handlers,spring.schemas}] 1162 | ij_properties_align_group_field_declarations = false 1163 | ij_properties_keep_blank_lines = false 1164 | ij_properties_key_value_delimiter = equals 1165 | ij_properties_spaces_around_key_value_delimiter = false 1166 | 1167 | # Markdown files have some quirks. Tab intents cause trouble, as does 4 space 1168 | # indents, which make Markdown think we're dealing with a code block. 1169 | [*.md] 1170 | indent_style = space 1171 | indent_size = 2 1172 | insert_final_newline = false 1173 | trim_trailing_whitespace = false 1174 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # This file is inspired by https://github.com/alexkaratarakis/gitattributes 2 | # 3 | # Auto detect text files and perform LF normalization 4 | # http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/ 5 | * text=auto eol=lf 6 | 7 | # The above will handle all files NOT found below 8 | # These files are text and should be normalized (Convert crlf => lf) 9 | 10 | *.bat text eol=crlf 11 | *.cmd text eol=crlf 12 | *.ps1 text eol=crlf 13 | *.coffee text 14 | *.css text 15 | *.cql text 16 | *.df text 17 | *.ejs text 18 | *.html text 19 | *.java text 20 | *.js text 21 | *.json text 22 | *.less text 23 | *.properties text 24 | *.sass text 25 | *.scss text 26 | *.sh text eol=lf 27 | *.sql text 28 | *.txt text 29 | *.ts text 30 | *.xml text 31 | *.yaml text 32 | *.yml text 33 | 34 | # Documents 35 | *.doc diff=astextplain 36 | *.DOC diff=astextplain 37 | *.docx diff=astextplain 38 | *.DOCX diff=astextplain 39 | *.dot diff=astextplain 40 | *.DOT diff=astextplain 41 | *.pdf diff=astextplain 42 | *.PDF diff=astextplain 43 | *.rtf diff=astextplain 44 | *.RTF diff=astextplain 45 | *.markdown text 46 | *.md text 47 | *.adoc text 48 | *.textile text 49 | *.mustache text 50 | *.csv text 51 | *.tab text 52 | *.tsv text 53 | *.txt text 54 | AUTHORS text 55 | CHANGELOG text 56 | CHANGES text 57 | CONTRIBUTING text 58 | COPYING text 59 | copyright text 60 | *COPYRIGHT* text 61 | INSTALL text 62 | license text 63 | LICENSE text 64 | NEWS text 65 | readme text 66 | *README* text 67 | TODO text 68 | 69 | # Graphics 70 | *.png binary 71 | *.jpg binary 72 | *.jpeg binary 73 | *.gif binary 74 | *.tif binary 75 | *.tiff binary 76 | *.ico binary 77 | # SVG treated as an asset (binary) by default. If you want to treat it as text, 78 | # comment-out the following line and uncomment the line after. 79 | *.svg binary 80 | #*.svg text 81 | *.eps binary 82 | 83 | # These files are binary and should be left untouched 84 | # (binary is a macro for -text -diff) 85 | *.class binary 86 | *.jar binary 87 | *.war binary 88 | 89 | ## LINTERS 90 | .csslintrc text 91 | .eslintrc text 92 | .jscsrc text 93 | .jshintrc text 94 | .jshintignore text 95 | .stylelintrc text 96 | 97 | ## CONFIGS 98 | *.conf text 99 | *.config text 100 | .editorconfig text 101 | .gitattributes text 102 | .gitconfig text 103 | .gitignore text 104 | .htaccess text 105 | *.npmignore text 106 | 107 | ## HEROKU 108 | Procfile text 109 | .slugignore text 110 | 111 | ## AUDIO 112 | *.kar binary 113 | *.m4a binary 114 | *.mid binary 115 | *.midi binary 116 | *.mp3 binary 117 | *.ogg binary 118 | *.ra binary 119 | 120 | ## VIDEO 121 | *.3gpp binary 122 | *.3gp binary 123 | *.as binary 124 | *.asf binary 125 | *.asx binary 126 | *.fla binary 127 | *.flv binary 128 | *.m4v binary 129 | *.mng binary 130 | *.mov binary 131 | *.mp4 binary 132 | *.mpeg binary 133 | *.mpg binary 134 | *.swc binary 135 | *.swf binary 136 | *.webm binary 137 | 138 | ## ARCHIVES 139 | *.7z binary 140 | *.gz binary 141 | *.rar binary 142 | *.tar binary 143 | *.zip binary 144 | 145 | ## FONTS 146 | *.ttf binary 147 | *.eot binary 148 | *.otf binary 149 | *.woff binary 150 | *.woff2 binary 151 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ###################### 2 | # Local configuration 3 | ###################### 4 | gradle-local.properties 5 | application-local.yml 6 | 7 | ###################### 8 | # Node 9 | ###################### 10 | /node/ 11 | node_tmp/ 12 | node_modules/ 13 | npm-debug.log.* 14 | /.awcache/* 15 | /.cache-loader/* 16 | 17 | ###################### 18 | # SASS 19 | ###################### 20 | .sass-cache/ 21 | 22 | ###################### 23 | # Eclipse 24 | ###################### 25 | *.pydevproject 26 | .project 27 | .metadata 28 | tmp/ 29 | tmp/**/* 30 | *.tmp 31 | *~.nib 32 | local.properties 33 | .classpath 34 | .settings/ 35 | .loadpath 36 | .factorypath 37 | 38 | # External tool builders 39 | .externalToolBuilders/** 40 | 41 | # Locally stored "Eclipse launch configurations" 42 | *.launch 43 | 44 | # CDT-specific 45 | .cproject 46 | 47 | # PDT-specific 48 | .buildpath 49 | 50 | # STS-specific 51 | /.sts4-cache/* 52 | 53 | ###################### 54 | # IntelliJ 55 | ###################### 56 | .idea/ 57 | *.iml 58 | *.iws 59 | *.ipr 60 | *.ids 61 | *.orig 62 | classes/ 63 | out/ 64 | 65 | ###################### 66 | # Visual Studio Code 67 | ###################### 68 | .vscode/* 69 | 70 | ###################### 71 | # Maven 72 | ###################### 73 | /log/ 74 | /target/ 75 | 76 | ###################### 77 | # Gradle 78 | ###################### 79 | .gradle/ 80 | /build/ 81 | 82 | ###################### 83 | # Package Files 84 | ###################### 85 | *.jar 86 | *.war 87 | *.ear 88 | *.db 89 | 90 | ###################### 91 | # Windows 92 | ###################### 93 | # Windows image file caches 94 | Thumbs.db 95 | 96 | # Folder config file 97 | Desktop.ini 98 | 99 | ###################### 100 | # Mac OSX 101 | ###################### 102 | .DS_Store 103 | .svn 104 | 105 | # Thumbnails 106 | ._* 107 | 108 | # Files that might appear on external disk 109 | .Spotlight-V100 110 | .Trashes 111 | 112 | ###################### 113 | # Directories 114 | ###################### 115 | /bin/ 116 | /deploy/ 117 | 118 | ###################### 119 | # Logs 120 | ###################### 121 | *.log* 122 | 123 | ###################### 124 | # Others 125 | ###################### 126 | *.bak 127 | *.class 128 | *.*~ 129 | *~ 130 | .merge_file* 131 | *.lock 132 | *.swp 133 | 134 | ###################### 135 | # ESLint 136 | ###################### 137 | .eslintcache 138 | 139 | ###################### 140 | # Code coverage 141 | ###################### 142 | /coverage/ 143 | /.nyc_output/ 144 | 145 | ########################### 146 | # Gradle and Maven wrappers 147 | ########################### 148 | # These are exception patterns that must come after the normal patterns. 149 | !.mvn/wrapper/maven-wrapper.jar 150 | !gradle/wrapper/gradle-wrapper.jar 151 | !gradle/wrapper/gradle-wrapper.properties 152 | -------------------------------------------------------------------------------- /.java-version: -------------------------------------------------------------------------------- 1 | 1.8 -------------------------------------------------------------------------------- /.sdkmanrc: -------------------------------------------------------------------------------- 1 | # Enable auto-env through the sdkman_auto_env config 2 | # Add key=value pairs of SDKs to use below 3 | java=17.0.6-tem 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk7 4 | install: 5 | - ./gradlew assemble -PossrhUsername="${ossrhUsername}" -PossrhPassword="${ossrhPassword}" 6 | script: 7 | - ./gradlew check -PossrhUsername="${ossrhUsername}" -PossrhPassword="${ossrhPassword}" 8 | after_success: 9 | - ./gradlew jacocoTestReport coveralls -PossrhUsername="${ossrhUsername}" -PossrhPassword="${ossrhPassword}" -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changes for 1.1.2 2 | ================= 3 | - Updated the Gradle Wrapper to version 5.3 and changed the gradle publishing 4 | mechanism to use the newer maven-publish plugin. 5 | 6 | Changes for 1.1.1 7 | ================= 8 | - Fixed a bug that ignored custom header names in the application properties, 9 | with thanks to @mohamed-el-habib (#1) 10 | 11 | Changes for 1.1.0 12 | ================= 13 | - Updated the dependencies to work with Spring Boot 1.4 and 1.5. 14 | 15 | Changes for 1.0.0 16 | ================= 17 | - Initial release of my fork, added a Session Id to the headers in addition to 18 | the Request Id. 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Cloud Request Correlation 2 | 3 | A Spring Cloud starter for easy request correlation ids 4 | 5 | ## News 6 | **January 19, 2024** Version 3.0.0 now supports Spring Boot version 3, and adds support for 7 | WebClients. 8 | 9 | **June 17, 2019** Version 2.0.0 now supports Spring Boot version 2, and adds 10 | two new properties for controlling the position of the Request Correlation 11 | Filter so that this project can play nicely with the spring-session project. 12 | 13 | **April 11, 2019** Version 1.1.2 is a minor release that updated the Gradle 14 | wrapper from the long obsolete 2.9 version. It also uses Gradle's new 15 | publishing mechanism to publish its artifacts. 16 | 17 | **December 9, 2017** Version 1.1.1 now uses the custom header names correctly 18 | when they are changed in the application properties, with thanks to Mohamed 19 | El-Habib (@mohamed-el-habib) 20 | 21 | **June 27, 2017** Version 1.1.0 of this starter now works with Spring Boot 1.4 22 | and 1.5. to use this starter with spring boot 1.3, you will need version 1.0.0. 23 | 24 | ## Features 25 | 26 | This project is derived from Jakub Narloch's jmnarloch/request-correlation-spring-cloud-starter 27 | project. It adds the notion of a correlating session id in addition to the correlating request id. 28 | 29 | This starter allows you to uniquely identify and track your request by passing `X-Request-Id` and 30 | `X-Session-Id` headers across remote calls. 31 | 32 | The Request id is meant to track a single request across multiple collaborating service calls. 33 | 34 | The Session id is meant to track multiple requests made by a user across an application. 35 | 36 | For example, if a user logs into an application, requests an object from the server, then saves 37 | the modified version of the object, the "find" request and the "save" request will have different 38 | request ids, but the same session id. 39 | 40 | ## Setup 41 | 42 | Add the Spring Boot starter to your project: 43 | 44 | ```groovy 45 | dependencies { 46 | implementation "net.saliman:spring-boot-starter-request-correlation:3.0.0" 47 | } 48 | ``` 49 | 50 | This will make sure incoming requests have a correlating request id and session id. To propagate 51 | the ids to outgoing requests to collaborating services, add dependencies for the kind of calls you 52 | make: 53 | 54 | If you use Feign: 55 | ```groovy 56 | dependencies { 57 | implementation "org.springframework.cloud:spring-cloud-starter-openfeign:${feignVersion}" 58 | implementation "io.github.openfeign:feign-hc5:${feignHttpVersion}" 59 | } 60 | 61 | ``` 62 | 63 | If you use Spring WebClients: 64 | ```groovy 65 | dependencies { 66 | implementation "org.springframework.boot:spring-boot-starter-webflux:${springBootVersion}" 67 | } 68 | ``` 69 | 70 | If you use Spring RestTemplates: 71 | ```groovy 72 | dependencies { 73 | implementation "org.springframework.boot:spring-boot-starter-web:${springBootVersion}" 74 | } 75 | ``` 76 | 77 | ## Usage 78 | 79 | To use this starter, annotate every Spring Boot / Cloud Application with the 80 | `@EnableRequestCorrelation` annotation. If the application makes outgoing requests to collaborating 81 | services, the application will need to use a Spring managed Feign Client, WebClient.Builder, or 82 | RestTemplate. An example of an application that uses all of the above is: 83 | 84 | ```java 85 | @EnableRequestCorrelation 86 | @SpringBootApplication 87 | public class Application { 88 | @Autowired 89 | private WebClient.builder webClientBuilder; 90 | 91 | @Autowired 92 | private RestTemplate restTemplate; 93 | 94 | @Autowired 95 | private MyFeignClient feignClient; 96 | } 97 | ``` 98 | 99 | In practice, the beans from above would go in serivces that are making outgoing requsts. You don't 100 | need all three beans in your services, only the ones you use in your application. 101 | 102 | ## Properties 103 | 104 | You can configure fallowing options: 105 | 106 | ```yaml 107 | request: 108 | correlation: 109 | # sets the position in the filter chain for the Request Correlation Filter (Ordered.HIGHEST_PRECEDENCE by default) 110 | filter-order: 102 111 | # sets the starting position for the filter order. Defaults to "zero" 112 | filter-order-from: highest_precedence 113 | # sets the header name to be used for session identification (X-Session-Id by default) 114 | session-header-name: X-Session-Id 115 | # sets the header name to be used for request identification (X-Request-Id by default) 116 | request-header-name: X-Request-Id 117 | client: 118 | http: 119 | # enables the RestTemplate header propagation (true by default) 120 | enabled: true 121 | feign: 122 | # enables the Fegin header propagation (true by default) 123 | enabled: true 124 | 125 | ``` 126 | 127 | Note that the above example shows a configuration that will put the Request Correlation filter after 128 | the Spring Session filter (at highest precedence + 102). Failing to set these values will result in 129 | the Request Correlation filter getting the wrong request object, since the Spring Session filter 130 | hasn't run yet, and the ids will be wrong. 131 | 132 | ## How does it work? 133 | 134 | The annotation will auto register servlet filter that will process any inbound request and 135 | correlate it with unique identifier. If it finds Feign in the classpath, it registers a Feign 136 | interceptor. If it finds Spring's RestTemplate in the classpath, it creates an interceptor for 137 | Rest Templates. If it finds Spring's WebClient in the classpath, it creates WebClientCustomizer 138 | that will be used in any Spring WebClient.Builder. 139 | 140 | Bean creation for each client type can be disabled with the appropriate setting in the 141 | application.yml file. 142 | 143 | ## Retrieving the request identifier 144 | 145 | You can retrieve the current request id within any request bound thread through 146 | `RequestCorrelationUtils.getCurrentRequestId`. You can retrieve the current session id through 147 | `RequestCorrelationUtils.getCurrentSessionId` 148 | 149 | ## Propagation 150 | 151 | Besides that you will also have transparent integration with following: 152 | 153 | * RestTemplate - any Spring configured `RestTemplate` will be automatically 154 | populated with the request id. 155 | * WebClient - any Spring configured `WebClient.Builder` will automatically contain a filter that 156 | populates WebClient requests with the request and session id. 157 | * Feign clients - similarly a request interceptor is being registered for Feign 158 | clients 159 | 160 | ## Applications 161 | 162 | The extension itself simply gives you means to propagate the information. How you use it is up to 163 | you. 164 | 165 | For instance, you can apply this information to your logging MDC map. You can achieve that by 166 | registering `RequestCorrelationInterceptor` bean. The `RequestCorrelationInterceptor` gives you only 167 | an entry point so that any fallowing operation would be able to access the correlation identifier. 168 | You may also use Spring's 169 | [HandlerInterceptor](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/HandlerInterceptor.html) 170 | and set the value there. 171 | 172 | For example: 173 | 174 | ```java 175 | @Bean 176 | public RequestCorrelationInterceptor correlationLoggingInterceptor() { 177 | return new RequestCorrelationInterceptor() { 178 | @Override 179 | public void afterCorrelationIdSet(String sessionId, String requestId) { 180 | MDC.put("httpSessionId", sessionId); 181 | MDC.put("httpRequestId", requestId); 182 | } 183 | }; 184 | } 185 | ``` 186 | 187 | If you are using Vnd.errors you can use that as your logref value 188 | 189 | ```java 190 | @ExceptionHandler 191 | public ResponseEntity error(Exception ex) { 192 | 193 | final VndError vndError = new VndError(RequestCorrelationUtils.getCurrentCorrelationId(), ex.getMessage()); 194 | 195 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) 196 | .header(HttpHeaders.CONTENT_TYPE, "application/vnd.error+json") 197 | .body(vndError); 198 | } 199 | ``` 200 | 201 | Another use case is to save that with your Spring Boot Actuator's audits when you implement custom 202 | `AuditEventRepository`. 203 | 204 | ## License 205 | 206 | Apache 2.0 207 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | dependencies { 6 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' 7 | } 8 | } 9 | 10 | plugins { 11 | id "com.github.hierynomus.license" version "0.11.0" 12 | id 'com.github.kt3k.coveralls' version '2.4.0' 13 | } 14 | 15 | apply plugin: 'java-library' 16 | apply plugin: "jacoco" 17 | apply plugin: 'idea' 18 | 19 | apply from: 'license.gradle' 20 | apply from: 'release.gradle' 21 | 22 | apply plugin: 'pmd' 23 | 24 | apply plugin: 'com.github.kt3k.coveralls' 25 | 26 | sourceCompatibility = 17 27 | 28 | // Release version that won't conflict with the bintray plugin 29 | group = "net.saliman" 30 | archivesBaseName = "spring-boot-starter-request-correlation" 31 | 32 | // Java 17 started getting a lot pickier about opening classes to other classes. Tests will need 33 | // some "opens" to work properly. 34 | tasks.withType(Test).configureEach{ 35 | jvmArgs = jvmArgs + ['--add-opens=java.base/java.lang=ALL-UNNAMED'] 36 | jvmArgs = jvmArgs + ['--add-opens=java.base/java.lang.invoke=ALL-UNNAMED'] 37 | jvmArgs = jvmArgs + ['--add-opens=java.base/java.util=ALL-UNNAMED'] 38 | jvmArgs = jvmArgs + ['--add-opens=java.base/java.net=ALL-UNNAMED'] 39 | jvmArgs = jvmArgs + ['--add-opens=java.base/java.nio.charset=ALL-UNNAMED'] 40 | jvmArgs = jvmArgs + ['--add-opens=java.base/java.time=ALL-UNNAMED'] 41 | jvmArgs = jvmArgs + ['--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED'] 42 | jvmArgs = jvmArgs + ['--add-opens=java.base/java.util.stream=ALL-UNNAMED'] 43 | jvmArgs = jvmArgs + ['--add-opens=java.prefs/java.util.prefs=ALL-UNNAMED'] 44 | } 45 | 46 | jar { 47 | manifest { 48 | attributes 'Implementation-Title': 'spring-boot-starter-request-correlation', 49 | 'Implementation-Version': version 50 | } 51 | } 52 | 53 | repositories { 54 | jcenter() 55 | } 56 | 57 | configurations { 58 | archives 59 | } 60 | 61 | compileJava { 62 | options.fork = true 63 | } 64 | 65 | dependencies { 66 | api "org.apache.commons:commons-lang3:${commonsLangVersion}" 67 | 68 | // For Spring RestTemplate 69 | implementation "org.springframework.boot:spring-boot-starter-web:${springBootVersion}" 70 | 71 | // For Spring WebClient 72 | implementation "org.springframework.boot:spring-boot-starter-webflux:${springBootVersion}" 73 | 74 | // For Feign 75 | implementation "org.springframework.cloud:spring-cloud-starter-openfeign:${feignVersion}" 76 | implementation "io.github.openfeign:feign-hc5:${feignHttpVersion}" 77 | 78 | annotationProcessor "org.springframework.boot:spring-boot-configuration-processor:${springBootVersion}" 79 | 80 | testImplementation "org.springframework.boot:spring-boot-starter-test:${springBootVersion}" 81 | testImplementation "junit:junit:${junitVersion}" 82 | testImplementation "org.mockito:mockito-all:${mockitoVersion}" 83 | 84 | } 85 | 86 | 87 | -------------------------------------------------------------------------------- /codequality/HEADER: -------------------------------------------------------------------------------- 1 | Copyright (c) ${year} the original author or authors 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=3.0.0 2 | 3 | commonsLangVersion=3.4 4 | feignVersion=4.0.4 5 | feignHttpVersion=12.4 6 | springBootVersion=3.1.5 7 | 8 | junitVersion=4.13.2 9 | mockitoVersion=1.10.19 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevesaliman/spring-boot-starter-request-correlation/f8e81b4a622d9a8a4eeebb588154f373c4ebd833/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Use the maximum available, or set MAX_FD != -1 to use that value. 89 | MAX_FD=maximum 90 | 91 | warn () { 92 | echo "$*" 93 | } >&2 94 | 95 | die () { 96 | echo 97 | echo "$*" 98 | echo 99 | exit 1 100 | } >&2 101 | 102 | # OS specific support (must be 'true' or 'false'). 103 | cygwin=false 104 | msys=false 105 | darwin=false 106 | nonstop=false 107 | case "$( uname )" in #( 108 | CYGWIN* ) cygwin=true ;; #( 109 | Darwin* ) darwin=true ;; #( 110 | MSYS* | MINGW* ) msys=true ;; #( 111 | NONSTOP* ) nonstop=true ;; 112 | esac 113 | 114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 115 | 116 | 117 | # Determine the Java command to use to start the JVM. 118 | if [ -n "$JAVA_HOME" ] ; then 119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 120 | # IBM's JDK on AIX uses strange locations for the executables 121 | JAVACMD=$JAVA_HOME/jre/sh/java 122 | else 123 | JAVACMD=$JAVA_HOME/bin/java 124 | fi 125 | if [ ! -x "$JAVACMD" ] ; then 126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 127 | 128 | Please set the JAVA_HOME variable in your environment to match the 129 | location of your Java installation." 130 | fi 131 | else 132 | JAVACMD=java 133 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 134 | 135 | Please set the JAVA_HOME variable in your environment to match the 136 | location of your Java installation." 137 | fi 138 | 139 | # Increase the maximum file descriptors if we can. 140 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 141 | case $MAX_FD in #( 142 | max*) 143 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 144 | # shellcheck disable=SC3045 145 | MAX_FD=$( ulimit -H -n ) || 146 | warn "Could not query maximum file descriptor limit" 147 | esac 148 | case $MAX_FD in #( 149 | '' | soft) :;; #( 150 | *) 151 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 152 | # shellcheck disable=SC3045 153 | ulimit -n "$MAX_FD" || 154 | warn "Could not set maximum file descriptor limit to $MAX_FD" 155 | esac 156 | fi 157 | 158 | # Collect all arguments for the java command, stacking in reverse order: 159 | # * args from the command line 160 | # * the main class name 161 | # * -classpath 162 | # * -D...appname settings 163 | # * --module-path (only if needed) 164 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 165 | 166 | # For Cygwin or MSYS, switch paths to Windows format before running java 167 | if "$cygwin" || "$msys" ; then 168 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 169 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 170 | 171 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 172 | 173 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 174 | for arg do 175 | if 176 | case $arg in #( 177 | -*) false ;; # don't mess with options #( 178 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 179 | [ -e "$t" ] ;; #( 180 | *) false ;; 181 | esac 182 | then 183 | arg=$( cygpath --path --ignore --mixed "$arg" ) 184 | fi 185 | # Roll the args list around exactly as many times as the number of 186 | # args, so each arg winds up back in the position where it started, but 187 | # possibly modified. 188 | # 189 | # NB: a `for` loop captures its iteration list before it begins, so 190 | # changing the positional parameters here affects neither the number of 191 | # iterations, nor the values presented in `arg`. 192 | shift # remove old arg 193 | set -- "$@" "$arg" # push replacement arg 194 | done 195 | fi 196 | 197 | 198 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 199 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 200 | 201 | # Collect all arguments for the java command; 202 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 203 | # shell script including quotes and variable substitutions, so put them in 204 | # double quotes to make sure that they get re-expanded; and 205 | # * put everything else in single quotes, so that it's not re-expanded. 206 | 207 | set -- \ 208 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 209 | -classpath "$CLASSPATH" \ 210 | org.gradle.wrapper.GradleWrapperMain \ 211 | "$@" 212 | 213 | # Stop when "xargs" is not available. 214 | if ! command -v xargs >/dev/null 2>&1 215 | then 216 | die "xargs is not available" 217 | fi 218 | 219 | # Use "xargs" to parse quoted args. 220 | # 221 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 222 | # 223 | # In Bash we could simply go: 224 | # 225 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 226 | # set -- "${ARGS[@]}" "$@" 227 | # 228 | # but POSIX shell has neither arrays nor command substitution, so instead we 229 | # post-process each arg (as a line of input to sed) to backslash-escape any 230 | # character that might be a shell metacharacter, then use eval to reverse 231 | # that process (while maintaining the separation between arguments), and wrap 232 | # the whole thing up as a single "set" statement. 233 | # 234 | # This will of course break if any of these variables contains a newline or 235 | # an unmatched quote. 236 | # 237 | 238 | eval "set -- $( 239 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 240 | xargs -n1 | 241 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 242 | tr '\n' ' ' 243 | )" '"$@"' 244 | 245 | exec "$JAVACMD" "$@" 246 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /license.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'license' 2 | 3 | license { 4 | 5 | header rootProject.file('codequality/HEADER') 6 | strictCheck true 7 | skipExistingHeaders true 8 | include "**/*.java" 9 | 10 | ext.year = Calendar.getInstance().get(Calendar.YEAR) 11 | } -------------------------------------------------------------------------------- /release.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | apply plugin: 'signing' 3 | 4 | ext { 5 | isReleaseVersion = !version.endsWith("SNAPSHOT") 6 | } 7 | if ( isReleaseVersion ) { 8 | println 'using staging' 9 | ext.mavenCentralUploadUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' 10 | } else { 11 | println 'using snapshot' 12 | ext.mavenCentralUploadUrl = 'https://oss.sonatype.org/content/repositories/snapshots/' 13 | } 14 | 15 | tasks.register('javadocJar', Jar) { 16 | archiveClassifier = 'javadoc' 17 | from javadoc 18 | } 19 | 20 | tasks.register('sourcesJar', Jar) { 21 | archiveClassifier = 'sources' 22 | from sourceSets.main.allSource 23 | } 24 | 25 | publishing { 26 | publications { 27 | requestCorrelationStarter(MavenPublication) { 28 | from components.java 29 | artifact javadocJar 30 | artifact sourcesJar 31 | pom { 32 | name = 'spring-boot-starter-request-correlation' 33 | description = 'A Spring Boot starter that can set correlation ids on outgoing requests.' 34 | url = 'https://github.com/stevesaliman/spring-boot-starter-request-correlation' 35 | packaging = 'jar' 36 | licenses { 37 | license { 38 | name = 'The Apache Software License, Version 2.0' 39 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 40 | } 41 | } 42 | developers { 43 | developer { 44 | id = 'stevesaliman' 45 | name = 'Steven C. Saliman' 46 | email = 'support@saliman.net' 47 | } 48 | developer { 49 | id = 'jmnarloch' 50 | name = 'Jakub Narloch' 51 | email = 'jmnarloch@gmail.com' 52 | } 53 | } 54 | scm { 55 | connection = 'scm:https://stevesaliman@github.com/stevesaliman/spring-boot-starter-request-correlation' 56 | developerConnection = 'scm:git@github.com:stevesaliman/spring-boot-starter-request-correlation.git' 57 | url = 'https://github.com/stevesaliman/spring-boot-starter-request-correlation' 58 | } 59 | } 60 | 61 | } 62 | } 63 | 64 | repositories { 65 | maven { 66 | url = mavenCentralUploadUrl 67 | // We only need to mess with credentials if we're publishing... 68 | if ( gradle.startParameter.taskNames.contains("publish") ) { 69 | println "\n\nWe have to upload some things in this build...\n" 70 | 71 | if ( !project.hasProperty('mavenCentralUsername') ) { 72 | throw new MissingPropertyException("No mavenCentralUsername was provided") 73 | } 74 | 75 | if ( !project.hasProperty('mavenCentralPassword') ) { 76 | throw new MissingPropertyException("No mavenCentralPassword was provided") 77 | } 78 | credentials { 79 | username mavenCentralUsername 80 | password mavenCentralPassword 81 | } 82 | } 83 | } 84 | } 85 | } 86 | 87 | signing { 88 | sign publishing.publications.requestCorrelationStarter 89 | } 90 | 91 | // When we're ready to go, there are a couple of things we'll need to do before we execute anything. 92 | gradle.taskGraph.whenReady { taskGraph -> 93 | // Only *require* signing if we are uploading a non snapshot version. If we 94 | // do need to sign, make sure we've got the properties we need to do the 95 | // signing. 96 | tasks.withType(org.gradle.plugins.signing.Sign).all { 97 | def required = (taskGraph.hasTask(":publish") && isReleaseVersion) 98 | if ( required ) { 99 | println "\n\nWe have to sign some things in this build...\n" 100 | 101 | if ( !project.hasProperty('signing.keyId') ) { 102 | throw new MissingPropertyException("No signing.keyId was provided") 103 | } 104 | 105 | if ( !project.hasProperty('signing.secretKeyRingFile') ) { 106 | throw new MissingPropertyException("No signing.secretKeyRingFile was provided") 107 | } 108 | 109 | if ( !project.hasProperty('signing.password') ) { 110 | throw new MissingPropertyException("No signing.password was provided") 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/api/CorrelationIdGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.api; 15 | 16 | import jakarta.servlet.http.HttpServletRequest; 17 | 18 | /** 19 | * Request id generation abstraction, allows users to implement different strategies for id 20 | * generation. 21 | * 22 | * @author Jakub Narloch 23 | * @author Steven C. Saliman 24 | */ 25 | public interface CorrelationIdGenerator { 26 | 27 | /** 28 | * Generates a session id. 29 | * 30 | * @param request the request object 31 | * @return the generated session id 32 | */ 33 | String generateSessionId(HttpServletRequest request); 34 | 35 | /** 36 | * Generates a request id. 37 | * 38 | * @param request the request object 39 | * @return the generated request id 40 | */ 41 | String generateRequestId(HttpServletRequest request); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/api/EnableRequestCorrelation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.api; 15 | 16 | import net.saliman.spring.request.correlation.feign.FeignCorrelationConfiguration; 17 | import net.saliman.spring.request.correlation.http.ClientHttpCorrelationConfiguration; 18 | import net.saliman.spring.request.correlation.filter.RequestCorrelationConfiguration; 19 | import net.saliman.spring.request.correlation.webclient.WebClientCorrelationConfiguration; 20 | import org.springframework.context.annotation.Import; 21 | import org.springframework.web.client.RestTemplate; 22 | import org.springframework.web.reactive.function.client.WebClient; 23 | 24 | import java.lang.annotation.Documented; 25 | import java.lang.annotation.ElementType; 26 | import java.lang.annotation.Inherited; 27 | import java.lang.annotation.Retention; 28 | import java.lang.annotation.RetentionPolicy; 29 | import java.lang.annotation.Target; 30 | 31 | /** 32 | * Enables automatic request correlation by assigning each incoming request to an application a 33 | * session identifier and unique request identifier. These identifiers will then be propagated to 34 | * outgoing requests from the application through 'X-Session-Id' and 'X-Request-Id' headers. 35 | *

36 | * By default, the session identifier will be HTTP session id, and the request id will be generated 37 | * using a random {@code UUID}. 38 | *

39 | * The header will be automatically propagated through any Spring configured {@link RestTemplate} 40 | * bean, {@link WebClient} (through a {@link WebClient.Builder} bean), or Feign client. 41 | * 42 | * @author Jakub Narloch 43 | * @author Steven C. Saliman 44 | * 45 | * @see RequestCorrelation 46 | * @see RequestCorrelationConfiguration 47 | * @see CorrelationIdGenerator 48 | */ 49 | @Documented 50 | @Inherited 51 | @Retention(RetentionPolicy.RUNTIME) 52 | @Target(ElementType.TYPE) 53 | @Import({ 54 | RequestCorrelationConfiguration.class, 55 | ClientHttpCorrelationConfiguration.class, 56 | WebClientCorrelationConfiguration.class, 57 | FeignCorrelationConfiguration.class 58 | }) 59 | public @interface EnableRequestCorrelation { 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/api/RequestCorrelation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.api; 15 | 16 | /** 17 | * Holder for the correlation ids. 18 | * 19 | * @author Jakub Narloch 20 | */ 21 | public interface RequestCorrelation { 22 | 23 | /** 24 | * Returns the correlation session id. 25 | * 26 | * @return the correlation session id 27 | */ 28 | String getSessionId(); 29 | 30 | /** 31 | * Returns the correlation request id. 32 | * 33 | * @return the correlation request id 34 | */ 35 | String getRequestId(); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/api/RequestCorrelationInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.api; 15 | 16 | /** 17 | * An interceptor that can be used by applications to receive callbacks when correlating ids are 18 | * generated. This is most useful when an application wants to use the ids to, for example, include 19 | * the ids in application logs. 20 | *

21 | * To use {@code RequestCorrelationInterceptor}, applications simply need to declare a bean that 22 | * implements this interface. 23 | * 24 | * @author Jakub Narloch 25 | */ 26 | public interface RequestCorrelationInterceptor { 27 | 28 | /** 29 | * Callback method called whenever the correlation ids have been assigned for the current 30 | * request, whether they have been set from the request header new ids have been generated for 31 | * the incoming request. 32 | * 33 | * @param sessionId the session id 34 | * @param requestId the request id 35 | */ 36 | void afterCorrelationIdSet(String sessionId, String requestId); 37 | 38 | /** 39 | * Callback method called after filter chain has completed. 40 | * 41 | * @param sessionId the session id 42 | * @param requestId the request id 43 | */ 44 | void cleanUp(String sessionId, String requestId); 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/feign/FeignCorrelationConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.feign; 15 | 16 | import feign.Feign; 17 | import feign.RequestInterceptor; 18 | import net.saliman.spring.request.correlation.support.RequestCorrelationProperties; 19 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 20 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 21 | import org.springframework.context.annotation.Bean; 22 | import org.springframework.context.annotation.Configuration; 23 | 24 | /** 25 | * Adds a Feign {@link RequestInterceptor} for propagating the correlation id if Feign is detected 26 | * in the classpath, and we haven't disabled feign in the properties. 27 | * 28 | * @author Jakub Narloch 29 | */ 30 | @Configuration 31 | @ConditionalOnClass(Feign.class) 32 | @ConditionalOnProperty(value = "request.correlation.client.feign.enabled", matchIfMissing = true) 33 | public class FeignCorrelationConfiguration { 34 | 35 | /** 36 | * Create a Feign {@link RequestInterceptor} bean that will propagate correlation ids to Feign 37 | * clients. 38 | * 39 | * @param properties the Request Correlation properties to use when configuring the bean. 40 | * @return a RequestInterceptor for Feign. 41 | */ 42 | @Bean 43 | public RequestInterceptor feignCorrelationInterceptor(RequestCorrelationProperties properties) { 44 | return new FeignCorrelationInterceptor(properties); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/feign/FeignCorrelationInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.feign; 15 | 16 | import feign.RequestInterceptor; 17 | import feign.RequestTemplate; 18 | import net.saliman.spring.request.correlation.support.RequestCorrelationProperties; 19 | import net.saliman.spring.request.correlation.support.RequestCorrelationUtils; 20 | import org.springframework.util.Assert; 21 | 22 | /** 23 | * Feign request correlation interceptor. 24 | * 25 | * @author Jakub Narloch 26 | */ 27 | public class FeignCorrelationInterceptor implements RequestInterceptor { 28 | 29 | /** 30 | * The correlation properties. 31 | */ 32 | private final RequestCorrelationProperties properties; 33 | 34 | /** 35 | * Creates new instance of {@link FeignCorrelationInterceptor}. 36 | * 37 | * @param properties the correlation properties 38 | * @throws IllegalArgumentException if {@code properties} is {@code null} 39 | */ 40 | public FeignCorrelationInterceptor(RequestCorrelationProperties properties) { 41 | Assert.notNull(properties, "Parameter 'properties' can not be null"); 42 | 43 | this.properties = properties; 44 | } 45 | 46 | /** 47 | * {@inheritDoc} 48 | */ 49 | @Override 50 | public void apply(RequestTemplate template) { 51 | 52 | final String sessionId = RequestCorrelationUtils.getCurrentSessionId(); 53 | if ( sessionId != null ) { 54 | template.header(properties.getSessionHeaderName(), sessionId); 55 | } 56 | 57 | final String requestId = RequestCorrelationUtils.getCurrentRequestId(); 58 | if ( requestId != null ) { 59 | template.header(properties.getRequestHeaderName(), requestId); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/filter/DefaultRequestCorrelation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.filter; 15 | 16 | import net.saliman.spring.request.correlation.api.RequestCorrelation; 17 | 18 | /** 19 | * Base implementation of {@link RequestCorrelation}. 20 | * 21 | * @author Jakub Narloch 22 | * @author Steven C. Saliman 23 | */ 24 | public final class DefaultRequestCorrelation implements RequestCorrelation { 25 | 26 | /** The actual correlation session id. */ 27 | private final String sessionId; 28 | 29 | /** The actual correlation request id. */ 30 | private final String requestId; 31 | 32 | /** 33 | * Creates new instance of {@link DefaultRequestCorrelation} class. 34 | * 35 | * @param sessionId the session id 36 | * @param requestId the request id 37 | */ 38 | public DefaultRequestCorrelation(String sessionId, String requestId) { 39 | this.sessionId = sessionId; 40 | this.requestId = requestId; 41 | } 42 | 43 | /** 44 | * Retrieves the session identifier. 45 | * 46 | * @return the session identifier 47 | */ 48 | @Override 49 | public String getSessionId() { 50 | return sessionId; 51 | } 52 | 53 | /** 54 | * Retrieves the request identifier. 55 | * 56 | * @return the request identifier 57 | */ 58 | @Override 59 | public String getRequestId() { 60 | return requestId; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/filter/RequestCorrelationConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.filter; 15 | 16 | import jakarta.servlet.DispatcherType; 17 | import net.saliman.spring.request.correlation.api.CorrelationIdGenerator; 18 | import net.saliman.spring.request.correlation.api.EnableRequestCorrelation; 19 | import net.saliman.spring.request.correlation.api.RequestCorrelationInterceptor; 20 | import net.saliman.spring.request.correlation.generator.DefaultIdGenerator; 21 | import net.saliman.spring.request.correlation.support.RequestCorrelationProperties; 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 24 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 25 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 26 | import org.springframework.context.annotation.Bean; 27 | import org.springframework.context.annotation.Configuration; 28 | 29 | import java.util.ArrayList; 30 | import java.util.EnumSet; 31 | import java.util.List; 32 | 33 | /** 34 | * Configures the {@link RequestCorrelationFilter} to assign correlating ids to all incoming 35 | * requests. 36 | * 37 | * @author Jakub Narloch 38 | * @see EnableRequestCorrelation 39 | */ 40 | @Configuration 41 | @EnableConfigurationProperties(RequestCorrelationProperties.class) 42 | public class RequestCorrelationConfiguration { 43 | /** The properties to use when configuring the filter. */ 44 | @Autowired 45 | private RequestCorrelationProperties properties; 46 | 47 | /** a list of {@link RequestCorrelationInterceptor}s to call when ids get set. */ 48 | @Autowired(required = false) 49 | private List interceptors = new ArrayList<>(); 50 | 51 | 52 | /** 53 | * Define a default {@link CorrelationIdGenerator} if the application hasn't defined one of its 54 | * own. 55 | * 56 | * @return an instance of the {@link DefaultIdGenerator} to use for id generation. 57 | */ 58 | @Bean 59 | @ConditionalOnMissingBean(CorrelationIdGenerator.class) 60 | public CorrelationIdGenerator requestIdGenerator() { 61 | return new DefaultIdGenerator(); 62 | } 63 | 64 | /** 65 | * Define a {@link RequestCorrelationFilter} bean that will be added to the application's filter 66 | * chain. 67 | * 68 | * @param generator the generator to use for creating correlating ids. 69 | * @param properties the properties to use when configuring the filter. 70 | * @return a {@link RequestCorrelationFilter} bean. 71 | */ 72 | @Bean 73 | public RequestCorrelationFilter requestCorrelationFilter(CorrelationIdGenerator generator, RequestCorrelationProperties properties) { 74 | return new RequestCorrelationFilter(generator, interceptors, properties); 75 | } 76 | 77 | /** 78 | * Create a {@link FilterRegistrationBean} that registers the {@link RequestCorrelationFilter} 79 | * with Spring's filter chain. 80 | * @param correlationFilter the filter to use. 81 | * @return the Filter Registration bean. 82 | */ 83 | @Bean 84 | public FilterRegistrationBean requestCorrelationFilterBean(RequestCorrelationFilter correlationFilter) { 85 | final FilterRegistrationBean filterRegistration = new FilterRegistrationBean<>(); 86 | filterRegistration.setFilter(correlationFilter); 87 | filterRegistration.setMatchAfter(false); 88 | filterRegistration.setDispatcherTypes( 89 | EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC)); 90 | filterRegistration.setAsyncSupported(true); 91 | int filterOrder = properties.getFilterOrderFrom().offset + properties.getFilterOrder(); 92 | filterRegistration.setOrder(filterOrder); 93 | return filterRegistration; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/filter/RequestCorrelationFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.filter; 15 | 16 | import jakarta.servlet.Filter; 17 | import jakarta.servlet.FilterChain; 18 | import jakarta.servlet.FilterConfig; 19 | import jakarta.servlet.ServletException; 20 | import jakarta.servlet.ServletRequest; 21 | import jakarta.servlet.ServletResponse; 22 | import jakarta.servlet.http.HttpServletRequest; 23 | import jakarta.servlet.http.HttpServletRequestWrapper; 24 | import jakarta.servlet.http.HttpServletResponse; 25 | import net.saliman.spring.request.correlation.api.CorrelationIdGenerator; 26 | import net.saliman.spring.request.correlation.api.RequestCorrelation; 27 | import net.saliman.spring.request.correlation.api.RequestCorrelationInterceptor; 28 | import net.saliman.spring.request.correlation.support.RequestCorrelationConsts; 29 | import net.saliman.spring.request.correlation.support.RequestCorrelationProperties; 30 | import org.apache.commons.lang3.StringUtils; 31 | import org.slf4j.Logger; 32 | import org.slf4j.LoggerFactory; 33 | import org.springframework.util.Assert; 34 | 35 | import java.io.IOException; 36 | import java.util.ArrayList; 37 | import java.util.Collections; 38 | import java.util.Enumeration; 39 | import java.util.HashSet; 40 | import java.util.List; 41 | import java.util.Map; 42 | import java.util.Set; 43 | import java.util.concurrent.ConcurrentHashMap; 44 | 45 | /** 46 | * The entry point for the request correlation. This filter intercepts all incoming requests. If 47 | * they don't contain the correlation headers, it creates new identifiers and stores them both as 48 | * request headers and also as a request attribute for use by any future outgoing request from the 49 | * application. 50 | * 51 | * @author Jakub Narloch 52 | * @author Souris Stathis 53 | * @author Steven C. Saliman 54 | */ 55 | public class RequestCorrelationFilter implements Filter { 56 | 57 | /** Logger instance used by this class. */ 58 | private static final Logger logger = LoggerFactory.getLogger(RequestCorrelationFilter.class); 59 | 60 | /** The request generator used for generating new identifiers. */ 61 | private final CorrelationIdGenerator correlationIdGenerator; 62 | 63 | /** List of optional interceptors. */ 64 | private final List interceptors; 65 | 66 | /** The request correlation properties. */ 67 | private final RequestCorrelationProperties properties; 68 | 69 | /** 70 | * Creates new instance of {@link RequestCorrelationFilter} class. 71 | * 72 | * @param correlationIdGenerator the request id generator 73 | * @param interceptors the correlation interceptors 74 | * @param properties the request properties 75 | * @throws IllegalArgumentException if {@code requestIdGenerator} is {@code 76 | * null} 77 | * or {@code interceptors} is {@code null} 78 | * or {@code properties} is {@code null} 79 | */ 80 | public RequestCorrelationFilter(CorrelationIdGenerator correlationIdGenerator, 81 | List interceptors, RequestCorrelationProperties properties) { 82 | Assert.notNull(correlationIdGenerator, "Parameter 'correlationIdGenerator' can not be null."); 83 | Assert.notNull(interceptors, "Parameter 'interceptors' can not be null."); 84 | Assert.notNull(properties, "Parameter 'properties' can not be null."); 85 | 86 | this.correlationIdGenerator = correlationIdGenerator; 87 | this.interceptors = interceptors; 88 | this.properties = properties; 89 | } 90 | 91 | /** 92 | * {@inheritDoc} 93 | */ 94 | @Override 95 | public void init(FilterConfig filterConfig) throws ServletException { 96 | // empty method 97 | } 98 | 99 | /** 100 | * {@inheritDoc} 101 | */ 102 | @Override 103 | public void destroy() { 104 | // empty method 105 | } 106 | 107 | /** 108 | * {@inheritDoc} 109 | */ 110 | @Override 111 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 112 | 113 | if ( request instanceof HttpServletRequest && response instanceof HttpServletResponse ) { 114 | doHttpFilter((HttpServletRequest)request, (HttpServletResponse)response, chain); 115 | } else { 116 | // otherwise just pass through 117 | chain.doFilter(request, response); 118 | } 119 | } 120 | 121 | /** 122 | * Performs 'enrichment' of incoming HTTP request. 123 | * 124 | * @param request the http servlet request 125 | * @param response the http servlet response 126 | * @param chain the filter processing chain 127 | * @throws IOException if any error occurs 128 | * @throws ServletException if any error occurs 129 | */ 130 | private void doHttpFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { 131 | 132 | // retrieve the correlation session id 133 | String sessionId = getSessionId(request); 134 | 135 | // verify the correlation session id was set 136 | if ( StringUtils.isBlank(sessionId) ) { 137 | logger.debug("Session correlation id was not present, generating new one: {}", sessionId); 138 | sessionId = generateSessionId(request); 139 | } 140 | 141 | // retrieve the correlation request id 142 | String requestId = getRequestId(request); 143 | 144 | // verify the correlation request id was set 145 | if ( StringUtils.isBlank(requestId) ) { 146 | logger.debug("Request correlation id was not present, generating new one: {}", requestId); 147 | requestId = generateRequestId(request); 148 | } 149 | 150 | // trigger the interceptors 151 | triggerInterceptors(sessionId, requestId); 152 | 153 | // instantiate a new request correlation 154 | final RequestCorrelation requestCorrelation = new DefaultRequestCorrelation(sessionId, requestId); 155 | 156 | // populate the request attribute 157 | final ServletRequest req = enrichRequest(request, requestCorrelation); 158 | 159 | try { 160 | // proceed with execution 161 | chain.doFilter(req, response); 162 | } finally { 163 | triggerInterceptorsCleanup(sessionId, requestId); 164 | } 165 | } 166 | 167 | /** 168 | * Retrieves the correlation session id from the request, if present. 169 | * 170 | * @param request the http servlet request 171 | * @return the correlation id 172 | */ 173 | private String getSessionId(HttpServletRequest request) { 174 | return request.getHeader(properties.getSessionHeaderName()); 175 | } 176 | 177 | /** 178 | * Retrieves the request correlation id from the request, if present. 179 | * 180 | * @param request the http servlet request 181 | * @return the correlation id 182 | */ 183 | private String getRequestId(HttpServletRequest request) { 184 | return request.getHeader(properties.getRequestHeaderName()); 185 | } 186 | 187 | /** 188 | * Generates a new correlation session id. 189 | * 190 | * @return the correlation session id 191 | */ 192 | private String generateSessionId(HttpServletRequest request) { 193 | return correlationIdGenerator.generateSessionId(request); 194 | } 195 | 196 | /** 197 | * Generates new correlation request id. 198 | * 199 | * @return the correlation request id 200 | */ 201 | private String generateRequestId(HttpServletRequest request) { 202 | return correlationIdGenerator.generateRequestId(request); 203 | } 204 | 205 | /** 206 | * Triggers the configured interceptors. 207 | * 208 | * @param sessionId the correlation session id 209 | * @param requestId the correlation request id 210 | */ 211 | private void triggerInterceptors(String sessionId, String requestId) { 212 | for ( RequestCorrelationInterceptor interceptor : interceptors ) { 213 | interceptor.afterCorrelationIdSet(sessionId, requestId); 214 | } 215 | } 216 | 217 | /** 218 | * Triggers the configured interceptors cleanUp methods. 219 | * 220 | * @param sessionId the correlation session id 221 | * @param requestId the correlation request id 222 | */ 223 | private void triggerInterceptorsCleanup(String sessionId, String requestId) { 224 | 225 | for ( RequestCorrelationInterceptor interceptor : interceptors ) { 226 | interceptor.cleanUp(sessionId, requestId); 227 | } 228 | } 229 | 230 | /** 231 | * "Enriches" the request. 232 | * 233 | * @param request the http servlet request 234 | * @param correlationId the correlation id 235 | * @return the servlet request 236 | */ 237 | private ServletRequest enrichRequest(HttpServletRequest request, RequestCorrelation correlationId) { 238 | 239 | final CorrelatedServletRequest req = new CorrelatedServletRequest(request); 240 | req.setAttribute(RequestCorrelationConsts.ATTRIBUTE_NAME, correlationId); 241 | req.setHeader(properties.getSessionHeaderName(), correlationId.getSessionId()); 242 | req.setHeader(properties.getRequestHeaderName(), correlationId.getRequestId()); 243 | return req; 244 | } 245 | 246 | /** 247 | * An http servlet wrapper that allows to register additional HTTP headers. 248 | * 249 | * @author Jakub Narloch 250 | */ 251 | private static class CorrelatedServletRequest extends HttpServletRequestWrapper { 252 | 253 | /** 254 | * Map with additional customizable headers. 255 | */ 256 | private final Map additionalHeaders = new ConcurrentHashMap<>(); 257 | 258 | /** 259 | * Creates a ServletRequest adaptor wrapping the given request object. 260 | * 261 | * @param request The request to wrap 262 | * @throws IllegalArgumentException if the request is null 263 | */ 264 | public CorrelatedServletRequest(HttpServletRequest request) { 265 | super(request); 266 | } 267 | 268 | /** 269 | * Sets the header value. 270 | * 271 | * @param key the header name 272 | * @param value the header value 273 | */ 274 | public void setHeader(String key, String value) { 275 | 276 | this.additionalHeaders.put(key, value); 277 | } 278 | 279 | @Override 280 | public String getHeader(String name) { 281 | if ( additionalHeaders.containsKey(name) ) { 282 | return additionalHeaders.get(name); 283 | } 284 | return super.getHeader(name); 285 | } 286 | 287 | @Override 288 | public Enumeration getHeaders(String name) { 289 | 290 | final List values = new ArrayList<>(); 291 | if ( additionalHeaders.containsKey(name) ) { 292 | values.add(additionalHeaders.get(name)); 293 | } else { 294 | values.addAll(Collections.list(super.getHeaders(name))); 295 | } 296 | return Collections.enumeration(values); 297 | } 298 | 299 | @Override 300 | public Enumeration getHeaderNames() { 301 | 302 | final Set names = new HashSet<>(); 303 | names.addAll(additionalHeaders.keySet()); 304 | names.addAll(Collections.list(super.getHeaderNames())); 305 | return Collections.enumeration(names); 306 | } 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/generator/DefaultIdGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.generator; 15 | 16 | import jakarta.servlet.http.HttpServletRequest; 17 | import jakarta.servlet.http.HttpSession; 18 | import net.saliman.spring.request.correlation.api.CorrelationIdGenerator; 19 | 20 | import java.util.UUID; 21 | 22 | /** 23 | * Default implementation of the {@link CorrelationIdGenerator} that uses the HTTP session id for 24 | * session ids and a {@link UUID#randomUUID()} for generating new requests ids. 25 | * 26 | * @author Jakub Narloch 27 | * @author Steven C. Saliman 28 | */ 29 | public class DefaultIdGenerator implements CorrelationIdGenerator { 30 | 31 | /** 32 | * Generates a new session id from the session's id. 33 | * 34 | * @return The session id. 35 | */ 36 | @Override 37 | public String generateSessionId(HttpServletRequest request) { 38 | HttpSession session = request.getSession(); 39 | return session.getId(); 40 | } 41 | 42 | /** 43 | * Generates a new request id as a random UUID. 44 | * 45 | * @return random uuid 46 | */ 47 | @Override 48 | public String generateRequestId(HttpServletRequest request) { 49 | return UUID.randomUUID().toString(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/http/ClientHttpCorrelationConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.http; 15 | 16 | import net.saliman.spring.request.correlation.support.RequestCorrelationProperties; 17 | import org.springframework.beans.factory.InitializingBean; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 20 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 21 | import org.springframework.context.annotation.Bean; 22 | import org.springframework.context.annotation.Configuration; 23 | import org.springframework.http.client.ClientHttpRequestInterceptor; 24 | import org.springframework.http.client.support.InterceptingHttpAccessor; 25 | import org.springframework.web.client.RestTemplate; 26 | 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | /** 31 | * Configures any {@link RestTemplate} bean by adding a request interceptor to any 32 | * {@link RestTemplate} beans if it finds the RestTemplate in the class path, and we haven't 33 | * disabled it in the properties. 34 | * 35 | * @author Jakub Narloch 36 | */ 37 | @Configuration 38 | @ConditionalOnClass(InterceptingHttpAccessor.class) 39 | @ConditionalOnProperty(value = "request.correlation.client.http.enabled", matchIfMissing = true) 40 | public class ClientHttpCorrelationConfiguration { 41 | 42 | @Autowired(required = false) 43 | private List clients = new ArrayList<>(); 44 | 45 | @Bean 46 | public InitializingBean clientsCorrelationInitializer(final RequestCorrelationProperties properties) { 47 | // InitializingBean is a functional interface, so we can just return a lambda implementation 48 | // of its afterPropertiesSet() function. 49 | return () -> { 50 | if ( clients != null ) { 51 | for ( InterceptingHttpAccessor client : clients ) { 52 | final List interceptors = new ArrayList<>(client.getInterceptors()); 53 | interceptors.add(new ClientHttpRequestCorrelationInterceptor(properties)); 54 | client.setInterceptors(interceptors); 55 | } 56 | } 57 | }; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/http/ClientHttpRequestCorrelationInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.http; 15 | 16 | import net.saliman.spring.request.correlation.support.RequestCorrelationProperties; 17 | import net.saliman.spring.request.correlation.support.RequestCorrelationUtils; 18 | import org.springframework.http.HttpRequest; 19 | import org.springframework.http.client.ClientHttpRequestExecution; 20 | import org.springframework.http.client.ClientHttpRequestInterceptor; 21 | import org.springframework.http.client.ClientHttpResponse; 22 | import org.springframework.util.Assert; 23 | import org.springframework.web.client.RestTemplate; 24 | 25 | import java.io.IOException; 26 | 27 | /** 28 | * {@link RestTemplate} http interceptor, that propagates the currents thread bound request identifier to 29 | * the outgoing request, through the 'X-Session-Id' and 'X-Request-Id' headers. 30 | * 31 | * @author Jakub Narloch 32 | * @author Steven C. Saliman 33 | */ 34 | public class ClientHttpRequestCorrelationInterceptor implements ClientHttpRequestInterceptor { 35 | 36 | /** The correlation properties. */ 37 | private final RequestCorrelationProperties properties; 38 | 39 | /** 40 | * Creates new instance of {@link ClientHttpRequestCorrelationInterceptor}. 41 | * 42 | * @param properties the properties 43 | * @throws IllegalArgumentException if {@code properties} is {@code null} 44 | */ 45 | public ClientHttpRequestCorrelationInterceptor(RequestCorrelationProperties properties) { 46 | Assert.notNull(properties, "Parameter 'properties' can not be null"); 47 | 48 | this.properties = properties; 49 | } 50 | 51 | /** 52 | * {@inheritDoc} 53 | */ 54 | @Override 55 | public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { 56 | 57 | // set the correlation session id 58 | final String sessionId = RequestCorrelationUtils.getCurrentSessionId(); 59 | if ( sessionId != null ) { 60 | request.getHeaders().add(properties.getSessionHeaderName(), sessionId); 61 | } 62 | 63 | // set the correlation request id 64 | final String requestId = RequestCorrelationUtils.getCurrentRequestId(); 65 | if ( requestId != null ) { 66 | request.getHeaders().add(properties.getRequestHeaderName(), requestId); 67 | } 68 | 69 | // proceed with execution 70 | return execution.execute(request, body); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/support/FilterOrderOffset.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.support; 15 | 16 | import org.springframework.core.Ordered; 17 | 18 | /** 19 | * This enum defines valid offsets for the filter order. The actual filter order will be the stated 20 | * order plus the chosen offset. For example, a filter order of 102 and an offset of 21 | * HIGHEST_PRECEDENCE will result in a filter that is 102 after the highest precedence filter. 22 | * 23 | * @author Steven C. Saliman 24 | */ 25 | public enum FilterOrderOffset { 26 | HIGHEST_PRECEDENCE(Ordered.HIGHEST_PRECEDENCE), 27 | LOWEST_PRECEDENCE(Ordered.LOWEST_PRECEDENCE), 28 | ZERO(0); 29 | 30 | public final int offset; 31 | 32 | FilterOrderOffset(int offset) { 33 | this.offset = offset; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/support/RequestCorrelationConsts.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.support; 15 | 16 | /** 17 | * Some helpful constants used by this starter. 18 | * 19 | * @author Jakub Narloch 20 | */ 21 | public interface RequestCorrelationConsts { 22 | 23 | /** 24 | * The correlation session id header name. 25 | */ 26 | String SESSION_HEADER_NAME = "X-Session-Id"; 27 | 28 | /** 29 | * The correlation request id header name. 30 | */ 31 | String REQUEST_HEADER_NAME = "X-Request-Id"; 32 | 33 | /** 34 | * The request attribute name for storing the ids, separate from the headers. 35 | */ 36 | String ATTRIBUTE_NAME = "RequestCorrelation.ATTRIBUTE"; 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/support/RequestCorrelationProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.support; 15 | 16 | import org.springframework.boot.context.properties.ConfigurationProperties; 17 | import org.springframework.core.Ordered; 18 | 19 | /** 20 | * The request correlation properties. These properties can be in the application.properties file 21 | * to override the default behavior of this starter. 22 | * 23 | * @author Jakub Narloch 24 | * @author Stven C. Saliman 25 | */ 26 | @ConfigurationProperties(prefix = "request.correlation") 27 | public class RequestCorrelationProperties { 28 | /** 29 | * The priority order of the filter. Defaults to Ordered.HIGHEST_PRECEDENCE, so the correlating 30 | * id is set very early in the process, but you may need to override this. For example, if you 31 | * use shared sessions via the spring-session project, you need to make sure the request 32 | * correlation filter comes AFTER the Spring Session filter, or the request correlation filter 33 | * won't get the right request object and your ids will be wrong. 34 | */ 35 | private int filterOrder = Ordered.HIGHEST_PRECEDENCE; 36 | 37 | /** 38 | * The starting point of the filter order property. This value will be added to the 39 | * filter-order property to get the actual order. Defaults to "zero" so that using filter-order 40 | * by itself gives predictable results. If you want the filter order to be an offset from the 41 | * Highest Precedence, set the filter-order to the offset, and filter-order-from to 42 | * "highest_precedence" 43 | */ 44 | private FilterOrderOffset filterOrderFrom = FilterOrderOffset.ZERO; 45 | 46 | 47 | /** Header name for the session id. Defaults to "X-Session-Id" */ 48 | private String sessionHeaderName = RequestCorrelationConsts.SESSION_HEADER_NAME; 49 | /** Header name for the request id. Defaults to "X-Request-Id" */ 50 | private String requestHeaderName = RequestCorrelationConsts.REQUEST_HEADER_NAME; 51 | 52 | /** 53 | * Creates new instance of {@link RequestCorrelationProperties} class. 54 | */ 55 | public RequestCorrelationProperties() { 56 | } 57 | 58 | /** 59 | * @return the current ordering of the request correlation filter. 60 | */ 61 | public int getFilterOrder() { 62 | return filterOrder; 63 | } 64 | 65 | /** 66 | * @param filterOrder the filter order to use. 67 | */ 68 | public void setFilterOrder(int filterOrder) { 69 | this.filterOrder = filterOrder; 70 | } 71 | 72 | /** 73 | * Retrieves the name of the correlating session id header. 74 | * 75 | * @return the header name 76 | */ 77 | public String getSessionHeaderName() { 78 | return sessionHeaderName; 79 | } 80 | 81 | /** 82 | * Sets the name of the correlating session id header. 83 | * 84 | * @param headerName the header name 85 | */ 86 | public void setSessionHeaderName(String headerName) { 87 | this.sessionHeaderName = headerName; 88 | } 89 | 90 | /** 91 | * Retrieves the name of the correlating request id header. 92 | * 93 | * @return the header name 94 | */ 95 | public String getRequestHeaderName() { 96 | return requestHeaderName; 97 | } 98 | 99 | /** 100 | * Sets the name of the correlating request id header. 101 | * 102 | * @param headerName the header name 103 | */ 104 | public void setRequestHeaderName(String headerName) { 105 | this.requestHeaderName = headerName; 106 | } 107 | 108 | public FilterOrderOffset getFilterOrderFrom() { 109 | return filterOrderFrom; 110 | } 111 | 112 | public void setFilterOrderFrom(FilterOrderOffset filterOrderFrom) { 113 | this.filterOrderFrom = filterOrderFrom; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/support/RequestCorrelationUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.support; 15 | 16 | import net.saliman.spring.request.correlation.api.RequestCorrelation; 17 | import org.springframework.web.context.request.RequestAttributes; 18 | import org.springframework.web.context.request.RequestContextHolder; 19 | 20 | /** 21 | * A utility class for retrieving the currently bound request correlation ids. 22 | * 23 | * @author Jakub Narloch 24 | * @author Steven C. Saliman 25 | */ 26 | public class RequestCorrelationUtils { 27 | 28 | /** 29 | * Retrieves the current correlation session id from the request attributes if present. 30 | * 31 | * @return the correlation id or {@code null} 32 | */ 33 | public static String getCurrentSessionId() { 34 | final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); 35 | if ( requestAttributes != null ) { 36 | Object correlationId = requestAttributes 37 | .getAttribute(RequestCorrelationConsts.ATTRIBUTE_NAME, RequestAttributes.SCOPE_REQUEST); 38 | 39 | if ( correlationId instanceof RequestCorrelation ) { 40 | return ((RequestCorrelation) correlationId).getSessionId(); 41 | } 42 | } 43 | return null; 44 | } 45 | 46 | /** 47 | * Retrieves the current correlation request id from the request attributes if present. 48 | * 49 | * @return the correlation id or {@code null} 50 | */ 51 | public static String getCurrentRequestId() { 52 | 53 | final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); 54 | if ( requestAttributes != null ) { 55 | Object correlationId = requestAttributes 56 | .getAttribute(RequestCorrelationConsts.ATTRIBUTE_NAME, RequestAttributes.SCOPE_REQUEST); 57 | 58 | if ( correlationId instanceof RequestCorrelation ) { 59 | return ((RequestCorrelation) correlationId).getRequestId(); 60 | } 61 | } 62 | return null; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/webclient/WebClientCorrelationConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.webclient; 15 | 16 | import net.saliman.spring.request.correlation.support.RequestCorrelationProperties; 17 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 18 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 19 | import org.springframework.boot.web.reactive.function.client.WebClientCustomizer; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.context.annotation.Configuration; 22 | import org.springframework.web.reactive.function.client.WebClient; 23 | 24 | /** 25 | * Add a {@link WebClientCustomizer} for propagating the correlation ids to WebClient requests if 26 | * the {@link WebClient} class is in the classpath, and we haven't disabled web client correlation 27 | * in the properties. 28 | * 29 | * @author Steven C. Saliman 30 | */ 31 | @Configuration 32 | @ConditionalOnClass(WebClient.class) 33 | @ConditionalOnProperty(value = "request.correlation.web.client.enabled", matchIfMissing = true) 34 | public class WebClientCorrelationConfiguration { 35 | 36 | /** 37 | * Define a {@link WebClientCustomizer} that will be used in any Spring 38 | * {@link WebClient.Builder} beans declared in an application. 39 | * 40 | * @param properties the properties to use when configuring the customizer. 41 | * @return a WebClientCustomizer bean. 42 | */ 43 | @Bean 44 | public WebClientCustomizer webClientCorrelationInitializer(final RequestCorrelationProperties properties) { 45 | return new WebClientCorrelationInterceptor(properties); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/net/saliman/spring/request/correlation/webclient/WebClientCorrelationInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.webclient; 15 | 16 | import net.saliman.spring.request.correlation.support.RequestCorrelationProperties; 17 | import net.saliman.spring.request.correlation.support.RequestCorrelationUtils; 18 | import org.springframework.boot.web.reactive.function.client.WebClientCustomizer; 19 | import org.springframework.util.Assert; 20 | import org.springframework.web.reactive.function.client.ClientRequest; 21 | import org.springframework.web.reactive.function.client.ExchangeFilterFunction; 22 | import org.springframework.web.reactive.function.client.WebClient; 23 | 24 | /** 25 | * Rest template http interceptor, that propagates the currents thread bound request identifier to 26 | * the outgoing request, through 'X-Request-Id' header. 27 | * 28 | * @author Jakub Narloch 29 | */ 30 | public class WebClientCorrelationInterceptor implements WebClientCustomizer { 31 | 32 | /** 33 | * The correlation properties. 34 | */ 35 | private final RequestCorrelationProperties properties; 36 | 37 | /** 38 | * Creates new instance of {@link WebClientCorrelationInterceptor}. 39 | * 40 | * @param properties the properties 41 | * @throws IllegalArgumentException if {@code properties} is {@code null} 42 | */ 43 | public WebClientCorrelationInterceptor(RequestCorrelationProperties properties) { 44 | Assert.notNull(properties, "Parameter 'properties' can not be null"); 45 | 46 | this.properties = properties; 47 | } 48 | 49 | /** 50 | * Callback to customize a {@link WebClient.Builder WebClient.Builder} instance. 51 | * 52 | * @param webClientBuilder the client builder to customize 53 | */ 54 | @Override 55 | public void customize(WebClient.Builder webClientBuilder) { 56 | webClientBuilder.filter(addCorrelationHeaders()); 57 | } 58 | 59 | /** 60 | * Private helper method that is the meat of the customization. This defines a filter function 61 | * to be applied to all WebClients created from a Spring managed WebClient.Builder. The filter 62 | * will add our request correlation ids to the requests. 63 | * @return an {@link ExchangeFilterFunction} that can add correlation ids to request headers. 64 | */ 65 | private ExchangeFilterFunction addCorrelationHeaders() { 66 | return (clientRequest, next) -> { 67 | ClientRequest.Builder newRequest = ClientRequest.from(clientRequest); 68 | // sets the correlation session id 69 | final String sessionId = RequestCorrelationUtils.getCurrentSessionId(); 70 | if ( sessionId != null ) { 71 | newRequest.header(properties.getSessionHeaderName(), sessionId); 72 | } 73 | 74 | // sets the correlation request id 75 | final String requestId = RequestCorrelationUtils.getCurrentRequestId(); 76 | if ( requestId != null ) { 77 | newRequest.header(properties.getRequestHeaderName(), requestId); 78 | } 79 | 80 | return next.exchange(newRequest.build()); 81 | }; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/net/saliman/spring/request/correlation/CorrelationTestUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation; 15 | 16 | import net.saliman.spring.request.correlation.filter.DefaultRequestCorrelation; 17 | import net.saliman.spring.request.correlation.support.RequestCorrelationConsts; 18 | import org.springframework.web.context.request.RequestAttributes; 19 | import org.springframework.web.context.request.RequestContextHolder; 20 | 21 | /** 22 | * An convenient utility class. 23 | * 24 | * @author Jakub Narloch 25 | */ 26 | public class CorrelationTestUtils { 27 | 28 | /** 29 | * Sets the request correlation id. 30 | * 31 | * @param requestId the request id 32 | */ 33 | public static void setCorrelatingIds(String sessionId, String requestId) { 34 | RequestContextHolder.getRequestAttributes().setAttribute(RequestCorrelationConsts.ATTRIBUTE_NAME, 35 | new DefaultRequestCorrelation(sessionId, requestId), RequestAttributes.SCOPE_REQUEST); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/net/saliman/spring/request/correlation/RequestCorrelationIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation; 15 | 16 | import net.saliman.spring.request.correlation.demo.DemoApplication; 17 | import net.saliman.spring.request.correlation.demo.DemoConfiguration; 18 | import net.saliman.spring.request.correlation.support.RequestCorrelationConsts; 19 | import org.junit.Before; 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.springframework.beans.factory.annotation.Value; 23 | import org.springframework.boot.test.context.SpringBootTest; 24 | import org.springframework.http.HttpEntity; 25 | import org.springframework.http.HttpHeaders; 26 | import org.springframework.http.HttpMethod; 27 | import org.springframework.test.context.junit4.SpringRunner; 28 | import org.springframework.web.client.RestTemplate; 29 | 30 | import static org.junit.Assert.assertEquals; 31 | import static org.junit.Assert.assertNotNull; 32 | 33 | /** 34 | * Integration test to make sure we get correlating ids where we need them. 35 | * 36 | * @author Jakub Narloch 37 | */ 38 | @SpringBootTest(classes = { DemoApplication.class, DemoConfiguration.class }, 39 | webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) 40 | @RunWith(SpringRunner.class) 41 | public class RequestCorrelationIT { 42 | 43 | @Value("${local.server.port}") 44 | private int port; 45 | 46 | // Rest Template that can make our requests from outside the Spring application. It is not a 47 | // bean because we don't need (and don't want) headers being added to requests coming from the 48 | // tests. 49 | private RestTemplate restTemplate; 50 | 51 | @Before 52 | public void setUp() { 53 | restTemplate = new RestTemplate(); 54 | } 55 | 56 | /** 57 | * Calls the "ids" URL with no headers to prove that we'll set headers to a new values. We 58 | * can't really verify much except that the ids got set. 59 | */ 60 | @Test 61 | public void getIdsNoHeaders() { 62 | final String response = restTemplate.getForObject(url("/ids"), String.class); 63 | assertNotNull(response); 64 | final String[] responseArr = response.split(":"); 65 | assertEquals(2, responseArr.length); 66 | assertNotNull(responseArr[0]); 67 | assertNotNull(responseArr[1]); 68 | } 69 | 70 | /** 71 | * Calls the "ids" URL with existing correlating ids to prove that we won't replace them with 72 | * new ones. 73 | */ 74 | @Test 75 | public void getIdsWithHeaders() { 76 | HttpHeaders headers = new HttpHeaders(); 77 | headers.add(RequestCorrelationConsts.SESSION_HEADER_NAME, "customSessionId"); 78 | headers.add(RequestCorrelationConsts.REQUEST_HEADER_NAME, "customRequestId"); 79 | HttpEntity request = new HttpEntity<>(headers); 80 | String response = restTemplate.exchange( 81 | url("/ids"), 82 | HttpMethod.GET, 83 | request, 84 | String.class, 85 | 1 86 | ).getBody(); 87 | assertNotNull(response); 88 | assertEquals("customSessionId:customRequestId", response); 89 | } 90 | 91 | /** 92 | * Calls the rest template endpoint with no headers. That endpoint then calls the "ids" 93 | * endpoint using a RestTemplate to prove that the headers propagate on outgoing RestTemplate 94 | * requests. The heart of the test is in the rest template endpoint which verifies the contents 95 | * of the ids. We know we're good as long as we get an "OK" status and not an error. 96 | */ 97 | @Test 98 | public void testRestTemplate() { 99 | final String response = restTemplate.getForObject(url("/rest"), String.class); 100 | assertNotNull(response); 101 | final String[] responseArr = response.split(":"); 102 | assertEquals(2, responseArr.length); 103 | assertNotNull(responseArr[0]); 104 | assertNotNull(responseArr[1]); 105 | } 106 | 107 | /** 108 | * Calls the WebClient endpoint with no headers. That endpoint then calls the "ids" endpoint 109 | * using a WebClient to prove that the headers propagate on outgoing WebClient requests. The 110 | * heart of the test is in the WebClient endpoint which verifies the contents of the ids. We 111 | * know we're good as long as we get an "OK" status and not an error. 112 | */ 113 | @Test 114 | public void testWebClient() { 115 | final String response = restTemplate.getForObject(url("/webclient"), String.class); 116 | assertNotNull(response); 117 | final String[] responseArr = response.split(":"); 118 | assertEquals(2, responseArr.length); 119 | assertNotNull(responseArr[0]); 120 | assertNotNull(responseArr[1]); 121 | } 122 | 123 | /** 124 | * Calls the Feign endpoint with no headers. That endpoint then calls the "ids" endpoint using 125 | * a Feign client to prove that the headers propagate on outgoing Feign client requests. The 126 | * heart of the test is in the Feign endpoint which verifies the contents of the ids. We know 127 | * we're good as long as we get an "OK" status and not an error. 128 | */ 129 | @Test 130 | public void testFeign() { 131 | final String response = restTemplate.getForObject(url("/feign"), String.class); 132 | assertNotNull(response); 133 | final String[] responseArr = response.split(":"); 134 | assertEquals(2, responseArr.length); 135 | assertNotNull(responseArr[0]); 136 | assertNotNull(responseArr[1]); 137 | } 138 | 139 | private String url(String path) { 140 | 141 | return String.format("http://127.0.0.1:%d/%s", port, path); 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/test/java/net/saliman/spring/request/correlation/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.demo; 15 | 16 | import net.saliman.spring.request.correlation.api.EnableRequestCorrelation; 17 | import net.saliman.spring.request.correlation.support.RequestCorrelationConsts; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 20 | import org.springframework.cloud.openfeign.EnableFeignClients; 21 | import org.springframework.http.HttpStatus; 22 | import org.springframework.http.ResponseEntity; 23 | import org.springframework.web.bind.annotation.RequestHeader; 24 | import org.springframework.web.bind.annotation.RequestMapping; 25 | import org.springframework.web.bind.annotation.RequestMethod; 26 | import org.springframework.web.bind.annotation.RestController; 27 | import org.springframework.web.client.RestTemplate; 28 | import org.springframework.web.reactive.function.client.WebClient; 29 | import org.springframework.web.servlet.support.ServletUriComponentsBuilder; 30 | 31 | /** 32 | * A demo application that integration tests can use to make sure correlating ids work as they 33 | * should. 34 | * 35 | * @author Steven C. Saliman 36 | */ 37 | @RestController 38 | @EnableAutoConfiguration 39 | @EnableRequestCorrelation 40 | @EnableFeignClients 41 | public class DemoApplication { 42 | 43 | @Autowired 44 | private RestTemplate template; 45 | 46 | @Autowired 47 | private DemoFeignClient feignClient; 48 | 49 | @Autowired 50 | private WebClient.Builder webClientBuilder; 51 | 52 | 53 | /** 54 | * A basic endpoint that gets the correlating request headers and returns them in a concatenated 55 | * string. This can be called directly to make sure the incoming filter sets default ids, or 56 | * by other rest endpoints to make sure ids propagate to outgoing calls. 57 | * 58 | * @param sessionId The session id from the request headers. 59 | * @param requestId The request id from the request headers. 60 | * @return the session id and request id as a single string. 61 | */ 62 | @RequestMapping(value = "/ids", method = RequestMethod.GET) 63 | public ResponseEntity headerEcho( 64 | @RequestHeader(value = RequestCorrelationConsts.SESSION_HEADER_NAME) String sessionId, 65 | @RequestHeader(value = RequestCorrelationConsts.REQUEST_HEADER_NAME) String requestId) { 66 | 67 | return ResponseEntity.ok(sessionId + ":" + requestId); 68 | } 69 | 70 | /** 71 | * An endpoint that uses a RestTemplate to call our "ids" URL to make sure the correlating id 72 | * propagates to the outgoing RestTemplate based requests. It compares the correlating ids from 73 | * the incoming request header with the ones returned from the "ids" endpoint to prove that the 74 | * incoming ids were propagated when we made the outgoing request. 75 | * 76 | * @param sessionId The session id from the request headers. 77 | * @param requestId The request id from the request headers. 78 | * @return OK if all is well, INTERNAL SERVER ERROR, if the correlated ids of the outgoing 79 | * request don't match the incoming request. 80 | */ 81 | @RequestMapping(value = "/rest", method = RequestMethod.GET) 82 | public ResponseEntity propagateRestTemplate( 83 | @RequestHeader(value = RequestCorrelationConsts.SESSION_HEADER_NAME) String sessionId, 84 | @RequestHeader(value = RequestCorrelationConsts.REQUEST_HEADER_NAME) String requestId) { 85 | 86 | final String expectedResponse = sessionId + ":" + requestId; 87 | // Call the "ids" endpoint to see if the incoming headers are automatically added to the 88 | // RestTemplate based request. 89 | final String response = template.getForObject(url("/ids"), String.class); 90 | // If the response differs, it means the header didn't propagate and the incoming filter 91 | // assigned new ids. 92 | if ( !expectedResponse.equals(response) ) { 93 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Correlation id mismatch!"); 94 | } 95 | return ResponseEntity.ok(response); 96 | } 97 | 98 | /** 99 | * An endpoint that uses a WebClient to call our "ids" URL to make sure the correlating id 100 | * propagates to the outgoing WebClient based requests. It compares the correlating ids from 101 | * the incoming request header with the ones returned from the "ids" endpoint to prove that the 102 | * incoming ids were propagated when we made the outgoing request. 103 | * 104 | * @param sessionId The session id from the request headers. 105 | * @param requestId The request id from the request headers. 106 | * @return OK if all is well, INTERNAL SERVER ERROR, if the correlated ids of the outgoing 107 | * request don't match the incoming request. 108 | */ 109 | @RequestMapping(value = "/webclient", method = RequestMethod.GET) 110 | public ResponseEntity propagateWebClient( 111 | @RequestHeader(value = RequestCorrelationConsts.SESSION_HEADER_NAME) String sessionId, 112 | @RequestHeader(value = RequestCorrelationConsts.REQUEST_HEADER_NAME) String requestId) { 113 | 114 | final String expectedResponse = sessionId + ":" + requestId; 115 | // Call the "ids" endpoint to see if the incoming headers are automatically added to the 116 | // WebClient based request. 117 | WebClient client = webClientBuilder 118 | .baseUrl(url("/")) 119 | .build(); 120 | String response = client.get().uri("/ids") 121 | .retrieve().bodyToMono(String.class) 122 | .block(); 123 | // If the response differs, it means the header didn't propagate and the incoming filter 124 | // assigned new ids. 125 | if ( !expectedResponse.equals(response) ) { 126 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Correlation id mismatch!"); 127 | } 128 | return ResponseEntity.ok(response); 129 | } 130 | 131 | 132 | /** 133 | * An endpoint that uses a Feign client to call our "ids" URL to make sure the correlating id 134 | * propagates to the outgoing Feign based requests. It compares the correlating ids from the 135 | * incoming request header with the ones returned from the "ids" endpoint to prove that the 136 | * incoming ids were propagated when we made the outgoing request. 137 | * 138 | * @param sessionId The session id from the request headers. 139 | * @param requestId The request id from the request headers. 140 | * @return OK if all is well, INTERNAL SERVER ERROR, if the correlated ids of the outgoing 141 | * request don't match the incoming request. 142 | */ 143 | @RequestMapping(value = "/feign", method = RequestMethod.GET) 144 | public ResponseEntity propagateFeignClient( 145 | @RequestHeader(value = RequestCorrelationConsts.SESSION_HEADER_NAME) String sessionId, 146 | @RequestHeader(value = RequestCorrelationConsts.REQUEST_HEADER_NAME) String requestId) { 147 | 148 | final String expectedResponse = sessionId + ":" + requestId; 149 | // Call the "ids" endpoint to see if the incoming headers are automatically added to the 150 | // Feign based request. 151 | final String response = feignClient.getCorrelatingIds(); 152 | // If the response differs, it means the header didn't propagate and the incoming filter 153 | // assigned new ids. 154 | if ( !expectedResponse.equals(response) ) { 155 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Correlation id mismatch!"); 156 | } 157 | return ResponseEntity.ok(response); 158 | } 159 | 160 | /** 161 | * Helper method to set the URL for outgoing requests. 162 | * @param path the path to add to the base URL of the application. 163 | * @return a URL to use. 164 | */ 165 | private String url(String path) { 166 | 167 | return ServletUriComponentsBuilder.fromCurrentRequest().replacePath(path).toUriString(); 168 | } 169 | } 170 | 171 | -------------------------------------------------------------------------------- /src/test/java/net/saliman/spring/request/correlation/demo/DemoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.demo; 15 | 16 | import org.springframework.context.annotation.Bean; 17 | import org.springframework.context.annotation.Configuration; 18 | import org.springframework.web.client.RestTemplate; 19 | 20 | /** 21 | * Configuration class for the Demo application. It declares the Rest Template bean because we 22 | * can't declare it and use it in the same class. 23 | * 24 | * @author Steven C. Saliman 25 | */ 26 | @Configuration 27 | public class DemoConfiguration { 28 | @Bean 29 | public RestTemplate restTemplate() { 30 | return new RestTemplate(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/net/saliman/spring/request/correlation/demo/DemoFeignClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.demo; 15 | 16 | import org.springframework.cloud.openfeign.FeignClient; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RequestMethod; 19 | 20 | /** 21 | * A Feign client we can use to prove that correlating ids propagate to it. 22 | * 23 | * @author Steven C. Saliman 24 | */ 25 | @FeignClient(name = "local", url = "localhost:10344") 26 | interface DemoFeignClient { 27 | 28 | @RequestMapping(value = "/ids", method = RequestMethod.GET) 29 | String getCorrelatingIds(); 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/net/saliman/spring/request/correlation/feign/FeignCorrelationInterceptorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.feign; 15 | 16 | import feign.RequestTemplate; 17 | import net.saliman.spring.request.correlation.CorrelationTestUtils; 18 | import net.saliman.spring.request.correlation.support.RequestCorrelationConsts; 19 | import net.saliman.spring.request.correlation.support.RequestCorrelationProperties; 20 | import org.junit.After; 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | import org.springframework.mock.web.MockHttpServletRequest; 24 | import org.springframework.web.context.request.RequestContextHolder; 25 | import org.springframework.web.context.request.ServletRequestAttributes; 26 | 27 | import static org.junit.Assert.assertEquals; 28 | import static org.junit.Assert.assertFalse; 29 | import static org.junit.Assert.assertTrue; 30 | 31 | /** 32 | * Tests the {@link FeignCorrelationInterceptor} class. 33 | * 34 | * @author Jakub Narloch 35 | * @author Steven C. Saliman 36 | */ 37 | public class FeignCorrelationInterceptorTest { 38 | private static final String SESSION_ID = "TEST_SESSION_ID"; 39 | private static final String REQUEST_ID = "TEST_REQUEST_ID"; 40 | private RequestCorrelationProperties properties = new RequestCorrelationProperties(); 41 | private FeignCorrelationInterceptor instance; 42 | 43 | @Before 44 | public void setUp() throws Exception { 45 | instance = new FeignCorrelationInterceptor(properties); 46 | RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest())); 47 | } 48 | 49 | @After 50 | public void tearDown() throws Exception { 51 | 52 | RequestContextHolder.resetRequestAttributes(); 53 | } 54 | 55 | @Test 56 | public void shouldSetHeader() { 57 | 58 | // given 59 | CorrelationTestUtils.setCorrelatingIds(SESSION_ID, REQUEST_ID); 60 | final RequestTemplate request = new RequestTemplate(); 61 | 62 | // when 63 | instance.apply(request); 64 | 65 | // then 66 | assertTrue(request.headers().containsKey(RequestCorrelationConsts.SESSION_HEADER_NAME)); 67 | assertEquals(1, request.headers().get(RequestCorrelationConsts.SESSION_HEADER_NAME).size()); 68 | assertEquals(SESSION_ID, request.headers().get(RequestCorrelationConsts.SESSION_HEADER_NAME).iterator().next()); 69 | assertTrue(request.headers().containsKey(RequestCorrelationConsts.REQUEST_HEADER_NAME)); 70 | assertEquals(1, request.headers().get(RequestCorrelationConsts.REQUEST_HEADER_NAME).size()); 71 | assertEquals(REQUEST_ID, request.headers().get(RequestCorrelationConsts.REQUEST_HEADER_NAME).iterator().next()); 72 | } 73 | 74 | @Test 75 | public void shouldSetCustomHeader() { 76 | 77 | // given 78 | final String customSessionHeader = "My-Session"; 79 | final String customRequestHeader = "My-Request"; 80 | properties.setSessionHeaderName(customSessionHeader); 81 | properties.setRequestHeaderName(customRequestHeader); 82 | CorrelationTestUtils.setCorrelatingIds(SESSION_ID, REQUEST_ID); 83 | final RequestTemplate request = new RequestTemplate(); 84 | 85 | // when 86 | instance.apply(request); 87 | 88 | // then 89 | assertFalse(request.headers().containsKey(RequestCorrelationConsts.SESSION_HEADER_NAME)); 90 | assertTrue(request.headers().containsKey(customSessionHeader)); 91 | assertEquals(1, request.headers().get(customSessionHeader).size()); 92 | assertEquals(SESSION_ID, request.headers().get(customSessionHeader).iterator().next()); 93 | assertFalse(request.headers().containsKey(RequestCorrelationConsts.REQUEST_HEADER_NAME)); 94 | assertTrue(request.headers().containsKey(customRequestHeader)); 95 | assertEquals(1, request.headers().get(customRequestHeader).size()); 96 | assertEquals(REQUEST_ID, request.headers().get(customRequestHeader).iterator().next()); 97 | } 98 | 99 | @Test 100 | public void shouldNotSetHeader() { 101 | 102 | // given 103 | final RequestTemplate request = new RequestTemplate(); 104 | 105 | // when 106 | instance.apply(request); 107 | 108 | // then 109 | assertFalse(request.headers().containsKey(RequestCorrelationConsts.SESSION_HEADER_NAME)); 110 | assertFalse(request.headers().containsKey(RequestCorrelationConsts.REQUEST_HEADER_NAME)); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/net/saliman/spring/request/correlation/filter/RequestCorrelationFilterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.filter; 15 | 16 | import jakarta.servlet.ServletException; 17 | import jakarta.servlet.http.HttpServletRequest; 18 | import net.saliman.spring.request.correlation.api.CorrelationIdGenerator; 19 | import net.saliman.spring.request.correlation.api.RequestCorrelation; 20 | import net.saliman.spring.request.correlation.api.RequestCorrelationInterceptor; 21 | import net.saliman.spring.request.correlation.generator.DefaultIdGenerator; 22 | import net.saliman.spring.request.correlation.support.RequestCorrelationConsts; 23 | import net.saliman.spring.request.correlation.support.RequestCorrelationProperties; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.springframework.mock.web.MockFilterChain; 27 | import org.springframework.mock.web.MockHttpServletRequest; 28 | import org.springframework.mock.web.MockHttpServletResponse; 29 | 30 | import java.io.IOException; 31 | import java.util.ArrayList; 32 | import java.util.List; 33 | import java.util.UUID; 34 | 35 | import static org.junit.Assert.assertEquals; 36 | import static org.junit.Assert.assertNotNull; 37 | import static org.mockito.Mockito.mock; 38 | import static org.mockito.Mockito.verify; 39 | 40 | /** 41 | * Tests the {@link RequestCorrelationFilter} class. 42 | * 43 | * @author Jakub Narloch 44 | */ 45 | public class RequestCorrelationFilterTest { 46 | 47 | private RequestCorrelationFilter instance; 48 | 49 | private CorrelationIdGenerator generator = new DefaultIdGenerator(); 50 | 51 | private List interceptors = new ArrayList<>(); 52 | 53 | private RequestCorrelationProperties properties = new RequestCorrelationProperties(); 54 | 55 | @Before 56 | public void setUp() throws Exception { 57 | 58 | instance = new RequestCorrelationFilter(generator, interceptors, properties); 59 | } 60 | 61 | @Test 62 | public void shouldInitiateCorrelationId() throws IOException, ServletException { 63 | 64 | // given 65 | final MockHttpServletRequest request = new MockHttpServletRequest(); 66 | final MockHttpServletResponse response = new MockHttpServletResponse(); 67 | final MockFilterChain chain = new MockFilterChain(); 68 | 69 | // when 70 | instance.doFilter(request, response, chain); 71 | 72 | // then 73 | assertNotNull(request.getAttribute(RequestCorrelationConsts.ATTRIBUTE_NAME)); 74 | assertNotNull(((HttpServletRequest) chain.getRequest()).getHeader(RequestCorrelationConsts.SESSION_HEADER_NAME)); 75 | assertNotNull(((HttpServletRequest) chain.getRequest()).getHeader(RequestCorrelationConsts.REQUEST_HEADER_NAME)); 76 | } 77 | 78 | @Test 79 | public void shouldUseExistingCorrelationId() throws IOException, ServletException { 80 | 81 | // given 82 | final String sessionId = UUID.randomUUID().toString(); 83 | final String requestId = UUID.randomUUID().toString(); 84 | final MockHttpServletRequest request = new MockHttpServletRequest(); 85 | final MockHttpServletResponse response = new MockHttpServletResponse(); 86 | final MockFilterChain chain = new MockFilterChain(); 87 | 88 | request.addHeader(RequestCorrelationConsts.SESSION_HEADER_NAME, sessionId); 89 | request.addHeader(RequestCorrelationConsts.REQUEST_HEADER_NAME, requestId); 90 | 91 | // when 92 | instance.doFilter(request, response, chain); 93 | 94 | // then 95 | final Object requestCorrelation = request.getAttribute(RequestCorrelationConsts.ATTRIBUTE_NAME); 96 | assertNotNull(requestCorrelation); 97 | assertEquals(requestId, ((RequestCorrelation) requestCorrelation).getRequestId()); 98 | 99 | String header = ((HttpServletRequest) chain.getRequest()).getHeader(RequestCorrelationConsts.SESSION_HEADER_NAME); 100 | assertNotNull(header); 101 | assertEquals(sessionId, header); 102 | header = ((HttpServletRequest) chain.getRequest()).getHeader(RequestCorrelationConsts.REQUEST_HEADER_NAME); 103 | assertNotNull(header); 104 | assertEquals(requestId, header); 105 | } 106 | 107 | @Test 108 | public void shouldUseCustomHeader() throws IOException, ServletException { 109 | 110 | // given 111 | final String sessionHeaderName = "X-SessionTraceId"; 112 | final String requestHeaderName = "X-RequestTraceId"; 113 | final String sessionId = UUID.randomUUID().toString(); 114 | final String requestId = UUID.randomUUID().toString(); 115 | final MockHttpServletRequest request = new MockHttpServletRequest(); 116 | final MockHttpServletResponse response = new MockHttpServletResponse(); 117 | final MockFilterChain chain = new MockFilterChain(); 118 | 119 | request.addHeader(sessionHeaderName, sessionId); 120 | request.addHeader(requestHeaderName, requestId); 121 | properties.setSessionHeaderName(sessionHeaderName); 122 | properties.setRequestHeaderName(requestHeaderName); 123 | 124 | // when 125 | instance.doFilter(request, response, chain); 126 | 127 | // then 128 | final Object requestCorrelation = request.getAttribute(RequestCorrelationConsts.ATTRIBUTE_NAME); 129 | assertNotNull(requestCorrelation); 130 | assertEquals(requestId, ((RequestCorrelation) requestCorrelation).getRequestId()); 131 | 132 | String header = ((HttpServletRequest) chain.getRequest()).getHeader(sessionHeaderName); 133 | assertNotNull(header); 134 | assertEquals(sessionId, header); 135 | header = ((HttpServletRequest) chain.getRequest()).getHeader(requestHeaderName); 136 | assertNotNull(header); 137 | assertEquals(requestId, header); 138 | } 139 | 140 | @Test 141 | public void shouldInvokeInterceptor() throws IOException, ServletException { 142 | 143 | // given 144 | final MockHttpServletRequest request = new MockHttpServletRequest(); 145 | final MockHttpServletResponse response = new MockHttpServletResponse(); 146 | final MockFilterChain chain = new MockFilterChain(); 147 | 148 | final RequestCorrelationInterceptor interceptor = mock(RequestCorrelationInterceptor.class); 149 | interceptors.add(interceptor); 150 | 151 | // when 152 | instance.doFilter(request, response, chain); 153 | 154 | // then 155 | final String sessionId = ((HttpServletRequest) chain.getRequest()).getHeader(RequestCorrelationConsts.SESSION_HEADER_NAME); 156 | final String requestId = ((HttpServletRequest) chain.getRequest()).getHeader(RequestCorrelationConsts.REQUEST_HEADER_NAME); 157 | final RequestCorrelation correlationIds = (RequestCorrelation) request.getAttribute(RequestCorrelationConsts.ATTRIBUTE_NAME); 158 | assertNotNull(sessionId); 159 | assertNotNull(requestId); 160 | assertNotNull(correlationIds); 161 | assertEquals(sessionId, correlationIds.getSessionId()); 162 | assertEquals(requestId, correlationIds.getRequestId()); 163 | 164 | verify(interceptor).afterCorrelationIdSet(sessionId, requestId); 165 | verify(interceptor).cleanUp(sessionId, requestId); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/test/java/net/saliman/spring/request/correlation/generator/DefaultIdGeneratorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.generator; 15 | 16 | import org.junit.Test; 17 | import org.springframework.mock.web.MockHttpServletRequest; 18 | import org.springframework.mock.web.MockHttpSession; 19 | 20 | import static org.junit.Assert.assertEquals; 21 | import static org.junit.Assert.assertNotNull; 22 | 23 | /** 24 | * Tests the {@link DefaultIdGenerator} class. 25 | * 26 | * @author Jakub Narloch 27 | * @author Steven C. Saliman 28 | */ 29 | public class DefaultIdGeneratorTest { 30 | 31 | /** 32 | * "generate" a session id and make sure it comes from the HttpSession. 33 | */ 34 | @Test 35 | public void generateSessionId() { 36 | final MockHttpServletRequest request = new MockHttpServletRequest(); 37 | request.setSession(new MockHttpSession(null, "customSessionId")); 38 | 39 | final String sessionId = new DefaultIdGenerator().generateSessionId(request); 40 | 41 | assertEquals("customSessionId", sessionId); 42 | } 43 | 44 | /** 45 | * Generate a new request id and make sure it returns a result. 46 | */ 47 | @Test 48 | public void generateRequestId() { 49 | final MockHttpServletRequest request = new MockHttpServletRequest(); 50 | request.setSession(new MockHttpSession(null, "customSessionId")); 51 | 52 | final String requestId = new DefaultIdGenerator().generateRequestId(request); 53 | 54 | assertNotNull(requestId); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/net/saliman/spring/request/correlation/http/ClientHttpRequestCorrelationInterceptorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.http; 15 | 16 | import net.saliman.spring.request.correlation.CorrelationTestUtils; 17 | import net.saliman.spring.request.correlation.support.RequestCorrelationConsts; 18 | import net.saliman.spring.request.correlation.support.RequestCorrelationProperties; 19 | import org.junit.After; 20 | import org.junit.Before; 21 | import org.junit.Test; 22 | import org.springframework.http.HttpHeaders; 23 | import org.springframework.http.HttpRequest; 24 | import org.springframework.http.client.ClientHttpRequestExecution; 25 | import org.springframework.mock.web.MockHttpServletRequest; 26 | import org.springframework.web.context.request.RequestContextHolder; 27 | import org.springframework.web.context.request.ServletRequestAttributes; 28 | 29 | import java.io.IOException; 30 | 31 | import static org.junit.Assert.assertEquals; 32 | import static org.junit.Assert.assertFalse; 33 | import static org.junit.Assert.assertTrue; 34 | import static org.mockito.Mockito.mock; 35 | import static org.mockito.Mockito.verify; 36 | import static org.mockito.Mockito.when; 37 | 38 | /** 39 | * Tests the {@link ClientHttpRequestCorrelationInterceptor} class. 40 | * 41 | * @author Jakub Narloch 42 | * @author Steven C. Saliman 43 | */ 44 | public class ClientHttpRequestCorrelationInterceptorTest { 45 | private static final String SESSION_ID = "TEST_SESSION_ID"; 46 | private static final String REQUEST_ID = "TEST_REQUEST_ID"; 47 | private RequestCorrelationProperties properties = new RequestCorrelationProperties(); 48 | private ClientHttpRequestCorrelationInterceptor instance; 49 | 50 | @Before 51 | public void setUp() throws Exception { 52 | instance = new ClientHttpRequestCorrelationInterceptor(properties); 53 | RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest())); 54 | } 55 | 56 | @After 57 | public void tearDown() throws Exception { 58 | 59 | RequestContextHolder.resetRequestAttributes(); 60 | } 61 | 62 | @Test 63 | public void shouldSetHeader() throws IOException { 64 | 65 | // given 66 | CorrelationTestUtils.setCorrelatingIds(SESSION_ID, REQUEST_ID); 67 | 68 | final HttpRequest request = mock(HttpRequest.class); 69 | final ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); 70 | final byte[] body = new byte[0]; 71 | 72 | when(request.getHeaders()).thenReturn(new HttpHeaders()); 73 | 74 | // when 75 | instance.intercept(request, body, execution); 76 | 77 | // then 78 | assertTrue(request.getHeaders().containsKey(RequestCorrelationConsts.SESSION_HEADER_NAME)); 79 | assertEquals(SESSION_ID, request.getHeaders().getFirst(RequestCorrelationConsts.SESSION_HEADER_NAME)); 80 | assertTrue(request.getHeaders().containsKey(RequestCorrelationConsts.REQUEST_HEADER_NAME)); 81 | assertEquals(REQUEST_ID, request.getHeaders().getFirst(RequestCorrelationConsts.REQUEST_HEADER_NAME)); 82 | verify(execution).execute(request, body); 83 | } 84 | 85 | @Test 86 | public void shouldSetCustomHeader() throws IOException { 87 | 88 | // given 89 | final String customSessionHeader = "My-Session"; 90 | final String customRequestHeader = "My-Request"; 91 | properties.setSessionHeaderName(customSessionHeader); 92 | properties.setRequestHeaderName(customRequestHeader); 93 | CorrelationTestUtils.setCorrelatingIds(SESSION_ID, REQUEST_ID); 94 | 95 | final HttpRequest request = mock(HttpRequest.class); 96 | final ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); 97 | final byte[] body = new byte[0]; 98 | 99 | when(request.getHeaders()).thenReturn(new HttpHeaders()); 100 | 101 | // when 102 | instance.intercept(request, body, execution); 103 | 104 | // then 105 | assertFalse(request.getHeaders().containsKey(RequestCorrelationConsts.SESSION_HEADER_NAME)); 106 | assertTrue(request.getHeaders().containsKey(customSessionHeader)); 107 | assertEquals(SESSION_ID, request.getHeaders().getFirst(customSessionHeader)); 108 | assertFalse(request.getHeaders().containsKey(RequestCorrelationConsts.REQUEST_HEADER_NAME)); 109 | assertTrue(request.getHeaders().containsKey(customRequestHeader)); 110 | assertEquals(REQUEST_ID, request.getHeaders().getFirst(customRequestHeader)); 111 | verify(execution).execute(request, body); 112 | } 113 | 114 | 115 | @Test 116 | public void shouldNotSetHeader() throws IOException { 117 | 118 | // given 119 | final HttpRequest request = mock(HttpRequest.class); 120 | final ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); 121 | final byte[] body = new byte[0]; 122 | 123 | when(request.getHeaders()).thenReturn(new HttpHeaders()); 124 | 125 | // when 126 | instance.intercept(request, body, execution); 127 | 128 | // then 129 | assertFalse(request.getHeaders().containsKey(RequestCorrelationConsts.SESSION_HEADER_NAME)); 130 | assertFalse(request.getHeaders().containsKey(RequestCorrelationConsts.REQUEST_HEADER_NAME)); 131 | verify(execution).execute(request, body); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/test/java/net/saliman/spring/request/correlation/support/RequestCorrelationUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2024 the original author or authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | package net.saliman.spring.request.correlation.support; 15 | 16 | import net.saliman.spring.request.correlation.filter.DefaultRequestCorrelation; 17 | import org.junit.After; 18 | import org.junit.Before; 19 | import org.junit.Test; 20 | import org.springframework.mock.web.MockHttpServletRequest; 21 | import org.springframework.web.context.request.RequestAttributes; 22 | import org.springframework.web.context.request.RequestContextHolder; 23 | import org.springframework.web.context.request.ServletRequestAttributes; 24 | 25 | import java.util.UUID; 26 | 27 | import static org.junit.Assert.assertEquals; 28 | import static org.junit.Assert.assertNull; 29 | 30 | /** 31 | * Tests the {@link RequestCorrelationUtils} class. 32 | * 33 | * @author Jakub Narloch 34 | */ 35 | public class RequestCorrelationUtilsTest { 36 | 37 | @Before 38 | public void setUp() throws Exception { 39 | 40 | RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest())); 41 | } 42 | 43 | @After 44 | public void tearDown() throws Exception { 45 | 46 | RequestContextHolder.resetRequestAttributes(); 47 | } 48 | 49 | @Test 50 | public void shouldNotRetrieveRequestId() { 51 | 52 | // given 53 | RequestContextHolder.resetRequestAttributes(); 54 | 55 | // when 56 | final String currentSessionId = RequestCorrelationUtils.getCurrentSessionId(); 57 | final String currentRequestId = RequestCorrelationUtils.getCurrentRequestId(); 58 | 59 | // then 60 | assertNull(currentSessionId); 61 | assertNull(currentRequestId); 62 | } 63 | 64 | @Test 65 | public void shouldRetrieveCorrelatingIds() { 66 | 67 | // given 68 | final String sessionId = UUID.randomUUID().toString(); 69 | final String requestId = UUID.randomUUID().toString(); 70 | RequestContextHolder.getRequestAttributes().setAttribute(RequestCorrelationConsts.ATTRIBUTE_NAME, 71 | new DefaultRequestCorrelation(sessionId, requestId), RequestAttributes.SCOPE_REQUEST); 72 | 73 | // when 74 | final String currentSessionId = RequestCorrelationUtils.getCurrentSessionId(); 75 | final String currentRequestId = RequestCorrelationUtils.getCurrentRequestId(); 76 | 77 | // then 78 | assertEquals(sessionId, currentSessionId); 79 | assertEquals(requestId, currentRequestId); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 10344 3 | 4 | request: 5 | correlation: 6 | client: 7 | http: 8 | enable: true 9 | feign: 10 | enable: true 11 | 12 | #request.correlation.client.http.enable=true 13 | #request.correlation.client.feign.enable=true 14 | #test.url=http://localhosts:${server.port} --------------------------------------------------------------------------------