1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-11-22 03:52:06 +01:00

SmackReactor/NIO, Java8/Android19, Pretty print XML, FSM connections

This commit adds
- SmackReactor / NIO
- a framework for finite state machine connections
- support for Java 8
- pretty printed XML debug output

It also
- reworks the integration test framework
- raises the minimum Android API level to 19
- introduces XmppNioTcpConnection

Furthermore fixes SMACK-801 (at least partly). Java 8 language
features are available, but not all runtime library methods. For that
we would need to raise the Android API level to 24 or higher.
This commit is contained in:
Florian Schmaus 2019-02-04 08:59:39 +01:00
parent dba12919d0
commit e98d42790a
144 changed files with 8692 additions and 1455 deletions

View file

@ -102,7 +102,7 @@ allprojects {
junitVersion = '5.2.0' junitVersion = '5.2.0'
} }
group = 'org.igniterealtime.smack' group = 'org.igniterealtime.smack'
sourceCompatibility = 1.7 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = sourceCompatibility targetCompatibility = sourceCompatibility
version = shortVersion version = shortVersion
if (isSnapshot) { if (isSnapshot) {
@ -245,7 +245,14 @@ gradle.taskGraph.whenReady { taskGraph ->
} }
} }
task javadocAll(type: Javadoc) { task copyAllJavadocDocFiles(type: Copy) {
from javadocAllProjects.collect { project ->
"${project.projectDir}/src/javadoc" }
into javadocAllDir
include '**/doc-files/*.*'
}
task javadocAll(type: Javadoc, dependsOn: copyAllJavadocDocFiles) {
source javadocAllProjects.collect {project -> source javadocAllProjects.collect {project ->
project.sourceSets.main.allJava } project.sourceSets.main.allJava }
destinationDir = javadocAllDir destinationDir = javadocAllDir
@ -449,12 +456,39 @@ subprojects {
enabled false enabled false
semver false semver false
} }
// Work around https://github.com/gradle/gradle/issues/4046
javadoc.dependsOn('copyJavadocDocFiles')
task copyJavadocDocFiles(type: Copy) {
from('src/javadoc')
into 'build/docs/javadoc'
include '**/doc-files/*.*'
}
// If this subproject has a Makefile then make copyJavadocDocFiles
// and the root project's javadocAll task dependend on
// generateFiles.
if (file("$projectDir/Makefile").exists()) {
copyJavadocDocFiles.dependsOn('generateFiles')
rootProject.copyAllJavadocDocFiles.dependsOn("${project.name}:generateFiles")
task generateFiles(type: Exec) {
workingDir projectDir
commandLine 'make'
}
clean.dependsOn('cleanGeneratedFiles')
rootProject.clean.dependsOn("${project.name}:cleanGeneratedFiles")
task cleanGeneratedFiles(type: Exec) {
workingDir projectDir
commandLine 'make', 'clean'
}
}
} }
configure (androidProjects + androidBootClasspathProjects) { configure (androidProjects + androidBootClasspathProjects) {
apply plugin: 'ru.vyarus.animalsniffer' apply plugin: 'ru.vyarus.animalsniffer'
dependencies { dependencies {
signature "net.sf.androidscents.signature:android-api-level-${smackMinAndroidSdk}:2.3.1_r2@signature" signature "net.sf.androidscents.signature:android-api-level-${smackMinAndroidSdk}:4.4.2_r4@signature"
} }
animalsniffer { animalsniffer {
sourceSets = [sourceSets.main] sourceSets = [sourceSets.main]

View file

@ -1,295 +1,315 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="12"> <profiles version="13">
<profile kind="CodeFormatterProfile" name="Smack" version="12"> <profile kind="CodeFormatterProfile" name="Smack" version="13">
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.jdt.core.compiler.source" value="1.8"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/> <setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="120"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.8"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/> <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/> <setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="120"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/> <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/> <setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/> <setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/> <setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/> <setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.8"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="120"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/> <setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/> <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/> <setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/> <setting id="org.eclipse.jdt.core.compiler.source" value="9"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="9"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.compiler.compliance" value="9"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="120"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
</profile> </profile>
</profiles> </profiles>

View file

@ -387,18 +387,6 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
readerConsumer.start(); readerConsumer.start();
} }
/**
* Sends out a notification that there was an error with the connection
* and closes the connection.
*
* @param e the exception that causes the connection close event.
*/
protected void notifyConnectionError(Exception e) {
// Closes the connection temporary. A reconnection is possible
shutdown();
callConnectionClosedOnErrorListener(e);
}
/** /**
* A listener class which listen for a successfully established connection * A listener class which listen for a successfully established connection
* and connection errors and notifies the BOSHConnection. * and connection errors and notifies the BOSHConnection.

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2009 Jive Software. * Copyright 2009 Jive Software, 2018 Florian Schmaus.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,9 +16,23 @@
*/ */
package org.jivesoftware.smack; package org.jivesoftware.smack;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.Reader; import java.io.Reader;
import java.io.Writer; import java.io.Writer;
import java.lang.reflect.Constructor;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@ -33,8 +47,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -43,6 +55,16 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.PasswordCallback;
import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.SmackConfiguration.UnknownIqRequestReplyMode; import org.jivesoftware.smack.SmackConfiguration.UnknownIqRequestReplyMode;
import org.jivesoftware.smack.SmackException.AlreadyConnectedException; import org.jivesoftware.smack.SmackException.AlreadyConnectedException;
@ -54,6 +76,7 @@ import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException;
import org.jivesoftware.smack.SmackException.SecurityRequiredByClientException; import org.jivesoftware.smack.SmackException.SecurityRequiredByClientException;
import org.jivesoftware.smack.SmackException.SecurityRequiredException; import org.jivesoftware.smack.SmackException.SecurityRequiredException;
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture; import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
import org.jivesoftware.smack.XMPPException.StreamErrorException; import org.jivesoftware.smack.XMPPException.StreamErrorException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.compress.packet.Compress; import org.jivesoftware.smack.compress.packet.Compress;
@ -67,6 +90,7 @@ import org.jivesoftware.smack.iqrequest.IQRequestHandler;
import org.jivesoftware.smack.packet.Bind; import org.jivesoftware.smack.packet.Bind;
import org.jivesoftware.smack.packet.ErrorIQ; import org.jivesoftware.smack.packet.ErrorIQ;
import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.FullyQualifiedElement;
import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Mechanisms; import org.jivesoftware.smack.packet.Mechanisms;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message;
@ -77,8 +101,11 @@ import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StanzaError; import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.packet.StartTls; import org.jivesoftware.smack.packet.StartTls;
import org.jivesoftware.smack.packet.StreamError; import org.jivesoftware.smack.packet.StreamError;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smack.packet.TopLevelStreamElement;
import org.jivesoftware.smack.parsing.ParsingExceptionCallback; import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
import org.jivesoftware.smack.provider.ExtensionElementProvider; import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smack.provider.NonzaProvider;
import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.sasl.core.SASLAnonymous; import org.jivesoftware.smack.sasl.core.SASLAnonymous;
import org.jivesoftware.smack.util.DNSUtil; import org.jivesoftware.smack.util.DNSUtil;
@ -87,6 +114,8 @@ import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.ParserUtils; import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.dns.HostAddress; import org.jivesoftware.smack.util.dns.HostAddress;
import org.jivesoftware.smack.util.dns.SmackDaneProvider;
import org.jivesoftware.smack.util.dns.SmackDaneVerifier;
import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityFullJid; import org.jxmpp.jid.EntityFullJid;
@ -132,6 +161,12 @@ import org.xmlpull.v1.XmlPullParser;
public abstract class AbstractXMPPConnection implements XMPPConnection { public abstract class AbstractXMPPConnection implements XMPPConnection {
private static final Logger LOGGER = Logger.getLogger(AbstractXMPPConnection.class.getName()); private static final Logger LOGGER = Logger.getLogger(AbstractXMPPConnection.class.getName());
protected static final SmackReactor SMACK_REACTOR;
static {
SMACK_REACTOR = SmackReactor.getInstance();
}
/** /**
* Counter to uniquely identify connections that are created. * Counter to uniquely identify connections that are created.
*/ */
@ -186,9 +221,11 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
private final Map<StanzaListener, InterceptorWrapper> interceptors = private final Map<StanzaListener, InterceptorWrapper> interceptors =
new HashMap<>(); new HashMap<>();
final Map<String, NonzaCallback> nonzaCallbacks = new HashMap<>();
protected final Lock connectionLock = new ReentrantLock(); protected final Lock connectionLock = new ReentrantLock();
protected final Map<String, ExtensionElement> streamFeatures = new HashMap<>(); protected final Map<String, FullyQualifiedElement> streamFeatures = new HashMap<>();
/** /**
* The full JID of the authenticated user, as returned by the resource binding response of the server. * The full JID of the authenticated user, as returned by the resource binding response of the server.
@ -244,6 +281,14 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
protected final SynchronizationPoint<XMPPException> saslFeatureReceived = new SynchronizationPoint<>( protected final SynchronizationPoint<XMPPException> saslFeatureReceived = new SynchronizationPoint<>(
AbstractXMPPConnection.this, "SASL mechanisms stream feature from server"); AbstractXMPPConnection.this, "SASL mechanisms stream feature from server");
/**
* A synchronization point which is successful if this connection has received the closing
* stream element from the remote end-point, i.e. the server.
*/
protected final SynchronizationPoint<Exception> closingStreamReceived = new SynchronizationPoint<>(
this, "stream closing element received");
/** /**
* The SASLAuthentication manager that is responsible for authenticating with the server. * The SASLAuthentication manager that is responsible for authenticating with the server.
*/ */
@ -269,20 +314,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
private ParsingExceptionCallback parsingExceptionCallback = SmackConfiguration.getDefaultParsingExceptionCallback(); private ParsingExceptionCallback parsingExceptionCallback = SmackConfiguration.getDefaultParsingExceptionCallback();
/**
* This scheduled thread pool executor is used to remove pending callbacks.
*/
protected static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor(
new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable);
thread.setName("Smack Scheduled Executor Service");
thread.setDaemon(true);
return thread;
}
});
/** /**
* A cached thread pool executor service with custom thread factory to set meaningful names on the threads and set * A cached thread pool executor service with custom thread factory to set meaningful names on the threads and set
* them 'daemon'. * them 'daemon'.
@ -297,7 +328,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
} }
}); });
private static final AsyncButOrdered<AbstractXMPPConnection> ASYNC_BUT_ORDERED = new AsyncButOrdered<>(); protected static final AsyncButOrdered<AbstractXMPPConnection> ASYNC_BUT_ORDERED = new AsyncButOrdered<>();
/** /**
* The used host to establish the connection to * The used host to establish the connection to
@ -320,6 +351,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
*/ */
protected boolean wasAuthenticated = false; protected boolean wasAuthenticated = false;
protected Exception currentConnectionException;
private final Map<String, IQRequestHandler> setIqRequestHandler = new HashMap<>(); private final Map<String, IQRequestHandler> setIqRequestHandler = new HashMap<>();
private final Map<String, IQRequestHandler> getIqRequestHandler = new HashMap<>(); private final Map<String, IQRequestHandler> getIqRequestHandler = new HashMap<>();
@ -566,7 +599,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
return streamId; return streamId;
} }
protected void bindResourceAndEstablishSession(Resourcepart resource) throws XMPPErrorException, protected Resourcepart bindResourceAndEstablishSession(Resourcepart resource) throws XMPPErrorException,
SmackException, InterruptedException { SmackException, InterruptedException {
// Wait until either: // Wait until either:
@ -602,6 +635,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
packetCollector = createStanzaCollectorAndSend(new StanzaIdFilter(session), session); packetCollector = createStanzaCollectorAndSend(new StanzaIdFilter(session), session);
packetCollector.nextResultOrThrow(); packetCollector.nextResultOrThrow();
} }
return response.getJid().getResourcepart();
} }
protected void afterSuccessfulLogin(final boolean resumed) throws NotConnectedException, InterruptedException { protected void afterSuccessfulLogin(final boolean resumed) throws NotConnectedException, InterruptedException {
@ -703,6 +738,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
} }
} }
// TODO: This method should be final.
@Override @Override
public void sendStanza(Stanza stanza) throws NotConnectedException, InterruptedException { public void sendStanza(Stanza stanza) throws NotConnectedException, InterruptedException {
Objects.requireNonNull(stanza, "Stanza must not be null"); Objects.requireNonNull(stanza, "Stanza must not be null");
@ -781,11 +817,61 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
callConnectionClosedListener(); callConnectionClosedListener();
} }
/**
* Sends out a notification that there was an error with the connection
* and closes the connection.
*
* @param exception the exception that causes the connection close event.
*/
protected final synchronized void notifyConnectionError(Exception exception) {
if (!isConnected()) {
LOGGER.log(Level.INFO, "Connection was already disconnected when attempting to handle " + exception,
exception);
return;
}
currentConnectionException = exception;
notifyAll();
for (StanzaCollector collector : collectors) {
collector.notifyConnectionError(exception);
}
// TODO: We should also notify things like the SASL authentication machinery about the exception.
// Closes the connection temporary. A if the connection supports stream management, then a reconnection is
// possible. Note that a connection listener of e.g. XMPPTCPConnection will drop the SM state in
// case the Exception is a StreamErrorException.
instantShutdown();
callConnectionClosedOnErrorListener(exception);
}
protected void instantShutdown() {
// Default implementation simply calls shutdown(), subclasses may override this.
shutdown();
}
/** /**
* Shuts the current connection down. * Shuts the current connection down.
*/ */
protected abstract void shutdown(); protected abstract void shutdown();
protected final boolean waitForClosingStreamTagFromServer() {
Exception exception;
try {
// After we send the closing stream element, check if there was already a
// closing stream element sent by the server or wait with a timeout for a
// closing stream element to be received from the server.
exception = closingStreamReceived.checkIfSuccessOrWait();
} catch (InterruptedException | NoResponseException e) {
exception = e;
}
if (exception != null) {
LOGGER.log(Level.INFO, "Exception while waiting for closing stream element from the server " + this, exception);
}
return exception == null;
}
@Override @Override
public void addConnectionListener(ConnectionListener connectionListener) { public void addConnectionListener(ConnectionListener connectionListener) {
if (connectionListener == null) { if (connectionListener == null) {
@ -910,20 +996,25 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
} }
/** /**
* Process all stanza listeners for sending packets. * Process all stanza listeners for sending stanzas.
* <p> * <p>
* Compared to {@link #firePacketInterceptors(Stanza)}, the listeners will be invoked in a new thread. * Compared to {@link #firePacketInterceptors(Stanza)}, the listeners will be invoked in a new thread.
* </p> * </p>
* *
* @param packet the stanza to process. * @param sendTopLevelStreamElement the top level stream element which just got send.
*/ */
// TODO: Rename to fireElementSendingListeners().
@SuppressWarnings("javadoc") @SuppressWarnings("javadoc")
protected void firePacketSendingListeners(final Stanza packet) { protected void firePacketSendingListeners(final TopLevelStreamElement sendTopLevelStreamElement) {
final SmackDebugger debugger = this.debugger;
if (debugger != null) { if (debugger != null) {
debugger.onOutgoingStreamElement(packet); debugger.onOutgoingStreamElement(sendTopLevelStreamElement);
} }
if (!(sendTopLevelStreamElement instanceof Stanza)) {
return;
}
Stanza packet = (Stanza) sendTopLevelStreamElement;
final List<StanzaListener> listenersToNotify = new LinkedList<>(); final List<StanzaListener> listenersToNotify = new LinkedList<>();
synchronized (sendListeners) { synchronized (sendListeners) {
for (ListenerWrapper listenerWrapper : sendListeners.values()) { for (ListenerWrapper listenerWrapper : sendListeners.values()) {
@ -1037,6 +1128,49 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
this.unknownIqRequestReplyMode = Objects.requireNonNull(unknownIqRequestReplyMode, "Mode must not be null"); this.unknownIqRequestReplyMode = Objects.requireNonNull(unknownIqRequestReplyMode, "Mode must not be null");
} }
protected final NonzaCallback.Builder buildNonzaCallback() {
return new NonzaCallback.Builder(this);
}
protected <SN extends Nonza, FN extends Nonza> SN sendAndWaitForResponse(Nonza nonza, Class<SN> successNonzaClass,
Class<FN> failedNonzaClass)
throws NoResponseException, NotConnectedException, InterruptedException, FailedNonzaException {
NonzaCallback.Builder builder = buildNonzaCallback();
SN successNonza = NonzaCallback.sendAndWaitForResponse(builder, nonza, successNonzaClass, failedNonzaClass);
return successNonza;
}
protected final void parseAndProcessNonza(XmlPullParser parser) throws SmackException {
final String element = parser.getName();
final String namespace = parser.getNamespace();
final String key = XmppStringUtils.generateKey(element, namespace);
NonzaProvider<? extends Nonza> nonzaProvider = ProviderManager.getNonzaProvider(key);
if (nonzaProvider == null) {
LOGGER.severe("Unknown nonza: " + key);
return;
}
NonzaCallback nonzaCallback;
synchronized (nonzaCallbacks) {
nonzaCallback = nonzaCallbacks.get(key);
}
if (nonzaCallback == null) {
LOGGER.info("No nonza callback for " + key);
return;
}
Nonza nonza;
try {
nonza = nonzaProvider.parse(parser);
}
catch (Exception e) {
throw new SmackException(e);
}
nonzaCallback.onNonzaReceived(nonza);
}
protected void parseAndProcessStanza(XmlPullParser parser) throws Exception { protected void parseAndProcessStanza(XmlPullParser parser) throws Exception {
ParserUtils.assertAtStartTag(parser); ParserUtils.assertAtStartTag(parser);
int parserDepth = parser.getDepth(); int parserDepth = parser.getDepth();
@ -1130,14 +1264,18 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
// If the IQ stanza is of type "get" or "set" with no registered IQ request handler, then answer an // If the IQ stanza is of type "get" or "set" with no registered IQ request handler, then answer an
// IQ of type 'error' with condition 'service-unavailable'. // IQ of type 'error' with condition 'service-unavailable'.
ErrorIQ errorIQ = IQ.createErrorResponse(iq, StanzaError.getBuilder( final ErrorIQ errorIQ = IQ.createErrorResponse(iq, StanzaError.getBuilder(
replyCondition)); replyCondition));
// Use async sendStanza() here, since if sendStanza() would block, then some connections, e.g.
// XmppNioTcpConnection, would deadlock, as this operation is performed in the same thread that is
asyncGo(() -> {
try { try {
sendStanza(errorIQ); sendStanza(errorIQ);
} }
catch (InterruptedException | NotConnectedException e) { catch (InterruptedException | NotConnectedException e) {
LOGGER.log(Level.WARNING, "Exception while sending error IQ to unkown IQ request", e); LOGGER.log(Level.WARNING, "Exception while sending error IQ to unkown IQ request", e);
} }
});
} else { } else {
Executor executorService = null; Executor executorService = null;
switch (iqRequestHandler.getMode()) { switch (iqRequestHandler.getMode()) {
@ -1297,7 +1435,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
} }
} }
protected void callConnectionClosedOnErrorListener(Exception e) { private void callConnectionClosedOnErrorListener(Exception e) {
boolean logWarning = true; boolean logWarning = true;
if (e instanceof StreamErrorException) { if (e instanceof StreamErrorException) {
StreamErrorException see = (StreamErrorException) e; StreamErrorException see = (StreamErrorException) e;
@ -1401,7 +1539,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
int eventType = parser.next(); int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG && parser.getDepth() == initialDepth + 1) { if (eventType == XmlPullParser.START_TAG && parser.getDepth() == initialDepth + 1) {
ExtensionElement streamFeature = null; FullyQualifiedElement streamFeature = null;
String name = parser.getName(); String name = parser.getName();
String namespace = parser.getNamespace(); String namespace = parser.getNamespace();
switch (name) { switch (name) {
@ -1435,6 +1573,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
break; break;
} }
} }
}
protected final void parseFeaturesAndNotify(XmlPullParser parser) throws Exception {
parseFeatures(parser);
if (hasFeature(Mechanisms.ELEMENT, Mechanisms.NAMESPACE)) { if (hasFeature(Mechanisms.ELEMENT, Mechanisms.NAMESPACE)) {
// Only proceed with SASL auth if TLS is disabled or if the server doesn't announce it // Only proceed with SASL auth if TLS is disabled or if the server doesn't announce it
@ -1465,7 +1607,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public <F extends ExtensionElement> F getFeature(String element, String namespace) { public <F extends FullyQualifiedElement> F getFeature(String element, String namespace) {
return (F) streamFeatures.get(XmppStringUtils.generateKey(element, namespace)); return (F) streamFeatures.get(XmppStringUtils.generateKey(element, namespace));
} }
@ -1474,7 +1616,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
return getFeature(element, namespace) != null; return getFeature(element, namespace) != null;
} }
protected void addStreamFeature(ExtensionElement feature) { protected void addStreamFeature(FullyQualifiedElement feature) {
String key = XmppStringUtils.generateKey(feature.getElementName(), feature.getNamespace()); String key = XmppStringUtils.generateKey(feature.getElementName(), feature.getNamespace());
streamFeatures.put(key, feature); streamFeatures.put(key, feature);
} }
@ -1659,7 +1801,155 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
CACHED_EXECUTOR_SERVICE.execute(runnable); CACHED_EXECUTOR_SERVICE.execute(runnable);
} }
protected static ScheduledFuture<?> schedule(Runnable runnable, long delay, TimeUnit unit) { protected static ScheduledAction schedule(Runnable runnable, long delay, TimeUnit unit) {
return SCHEDULED_EXECUTOR_SERVICE.schedule(runnable, delay, unit); return SMACK_REACTOR.schedule(runnable, delay, unit);
}
protected void onStreamOpen(XmlPullParser parser) {
// We found an opening stream.
if ("jabber:client".equals(parser.getNamespace(null))) {
streamId = parser.getAttributeValue("", "id");
String reportedServerDomain = parser.getAttributeValue("", "from");
assert (config.getXMPPServiceDomain().equals(reportedServerDomain));
}
}
protected void sendStreamOpen() throws NotConnectedException, InterruptedException {
// If possible, provide the receiving entity of the stream open tag, i.e. the server, as much information as
// possible. The 'to' attribute is *always* available. The 'from' attribute if set by the user and no external
// mechanism is used to determine the local entity (user). And the 'id' attribute is available after the first
// response from the server (see e.g. RFC 6120 § 9.1.1 Step 2.)
CharSequence to = getXMPPServiceDomain();
CharSequence from = null;
CharSequence localpart = config.getUsername();
if (localpart != null) {
from = XmppStringUtils.completeJidFrom(localpart, to);
}
String id = getStreamId();
sendNonza(new StreamOpen(to, from, id));
}
public static final class SmackTlsContext {
public final SSLContext sslContext;
public final SmackDaneVerifier daneVerifier;
private SmackTlsContext(SSLContext sslContext, SmackDaneVerifier daneVerifier) {
assert sslContext != null;
this.sslContext = sslContext;
this.daneVerifier = daneVerifier;
}
}
protected final SmackTlsContext getSmackTlsContext() throws KeyManagementException, NoSuchAlgorithmException,
CertificateException, IOException, UnrecoverableKeyException, KeyStoreException, NoSuchProviderException {
SmackDaneVerifier daneVerifier = null;
if (config.getDnssecMode() == DnssecMode.needsDnssecAndDane) {
SmackDaneProvider daneProvider = DNSUtil.getDaneProvider();
if (daneProvider == null) {
throw new UnsupportedOperationException("DANE enabled but no SmackDaneProvider configured");
}
daneVerifier = daneProvider.newInstance();
if (daneVerifier == null) {
throw new IllegalStateException("DANE requested but DANE provider did not return a DANE verifier");
}
}
SSLContext context = this.config.getCustomSSLContext();
KeyStore ks = null;
PasswordCallback pcb = null;
if (context == null) {
final String keyStoreType = config.getKeystoreType();
final CallbackHandler callbackHandler = config.getCallbackHandler();
final String keystorePath = config.getKeystorePath();
if ("PKCS11".equals(keyStoreType)) {
try {
Constructor<?> c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class);
String pkcs11Config = "name = SmartCard\nlibrary = " + config.getPKCS11Library();
ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes(StringUtils.UTF8));
Provider p = (Provider) c.newInstance(config);
Security.addProvider(p);
ks = KeyStore.getInstance("PKCS11",p);
pcb = new PasswordCallback("PKCS11 Password: ",false);
callbackHandler.handle(new Callback[] {pcb});
ks.load(null,pcb.getPassword());
}
catch (Exception e) {
LOGGER.log(Level.WARNING, "Exception", e);
ks = null;
}
}
else if ("Apple".equals(keyStoreType)) {
ks = KeyStore.getInstance("KeychainStore","Apple");
ks.load(null,null);
// pcb = new PasswordCallback("Apple Keychain",false);
// pcb.setPassword(null);
}
else if (keyStoreType != null) {
ks = KeyStore.getInstance(keyStoreType);
if (callbackHandler != null && StringUtils.isNotEmpty(keystorePath)) {
try {
pcb = new PasswordCallback("Keystore Password: ", false);
callbackHandler.handle(new Callback[] { pcb });
ks.load(new FileInputStream(keystorePath), pcb.getPassword());
}
catch (Exception e) {
LOGGER.log(Level.WARNING, "Exception", e);
ks = null;
}
} else {
ks.load(null, null);
}
}
KeyManager[] kms = null;
if (ks != null) {
String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory kmf = null;
try {
kmf = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm);
}
catch (NoSuchAlgorithmException e) {
LOGGER.log(Level.FINE, "Could get the default KeyManagerFactory for the '"
+ keyManagerFactoryAlgorithm + "' algorithm", e);
}
if (kmf != null) {
try {
if (pcb == null) {
kmf.init(ks, null);
}
else {
kmf.init(ks, pcb.getPassword());
pcb.clearPassword();
}
kms = kmf.getKeyManagers();
}
catch (NullPointerException npe) {
LOGGER.log(Level.WARNING, "NullPointerException", npe);
}
}
}
// If the user didn't specify a SSLContext, use the default one
context = SSLContext.getInstance("TLS");
final SecureRandom secureRandom = new java.security.SecureRandom();
X509TrustManager customTrustManager = config.getCustomX509TrustManager();
if (daneVerifier != null) {
// User requested DANE verification.
daneVerifier.init(context, kms, customTrustManager, secureRandom);
} else {
TrustManager[] customTrustManagers = null;
if (customTrustManager != null) {
customTrustManagers = new TrustManager[] { customTrustManager };
}
context.init(kms, customTrustManagers, secureRandom);
}
}
return new SmackTlsContext(context, daneVerifier);
} }
} }

View file

@ -0,0 +1,54 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import org.jivesoftware.smack.SmackReactor.ChannelSelectedCallback;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection;
import org.jivesoftware.smack.fsm.StateDescriptor;
import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
public abstract class AbstractXmppNioConnection extends AbstractXmppStateMachineConnection {
protected AbstractXmppNioConnection(ConnectionConfiguration configuration, GraphVertex<StateDescriptor> initialStateDescriptorVertex) {
super(configuration, initialStateDescriptorVertex);
}
protected SelectionKey registerWithSelector(SelectableChannel channel, int ops, ChannelSelectedCallback callback)
throws ClosedChannelException {
return SMACK_REACTOR.registerWithSelector(channel, ops, callback);
}
/**
* Set the interest Ops of a SelectionKey. Since Java's NIO interestOps(int) can block at any time, we use a queue
* to perform the actual operation in the reactor where we can perform this operation non-blocking.
*
* @param selectionKey
* @param interestOps
*/
protected void setInterestOps(SelectionKey selectionKey, int interestOps) {
SMACK_REACTOR.setInterestOps(selectionKey, interestOps);
}
@Override
protected void finalize() {
disconnect();
}
}

View file

@ -0,0 +1,36 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack;
import org.jivesoftware.smack.packet.Element;
public abstract class GenericElementListener<E extends Element> {
private final Class<? extends E> elementClass;
public GenericElementListener(Class<? extends E> elementClass) {
this.elementClass = elementClass;
}
public abstract void process(E element);
public final void processElement(Element element) {
E concreteEleement = elementClass.cast(element);
process(concreteEleement);
}
}

View file

@ -17,7 +17,6 @@
package org.jivesoftware.smack; package org.jivesoftware.smack;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.jivesoftware.smack.SmackException.NotLoggedInException; import org.jivesoftware.smack.SmackException.NotLoggedInException;
@ -54,7 +53,7 @@ public abstract class Manager {
return connection; return connection;
} }
protected static final ScheduledFuture<?> schedule(Runnable runnable, long delay, TimeUnit unit) { protected static final ScheduledAction schedule(Runnable runnable, long delay, TimeUnit unit) {
return AbstractXMPPConnection.SCHEDULED_EXECUTOR_SERVICE.schedule(runnable, delay, unit); return AbstractXMPPConnection.SMACK_REACTOR.schedule(runnable, delay, unit);
} }
} }

View file

@ -0,0 +1,176 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack;
import java.util.HashMap;
import java.util.Map;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.util.XmppElementUtil;
import org.jxmpp.util.XmppStringUtils;
public class NonzaCallback {
protected final AbstractXMPPConnection connection;
protected final Map<String, GenericElementListener<? extends Nonza>> filterAndListeners;
private NonzaCallback(Builder builder) {
this.connection = builder.connection;
this.filterAndListeners = builder.filterAndListeners;
install();
}
void onNonzaReceived(Nonza nonza) {
String key = XmppStringUtils.generateKey(nonza.getElementName(), nonza.getNamespace());
GenericElementListener<? extends Nonza> nonzaListener = filterAndListeners.get(key);
nonzaListener.processElement(nonza);
}
public void cancel() {
synchronized (connection.nonzaCallbacks) {
for (Map.Entry<String, GenericElementListener<? extends Nonza>> entry : filterAndListeners.entrySet()) {
String filterKey = entry.getKey();
NonzaCallback installedCallback = connection.nonzaCallbacks.get(filterKey);
if (equals(installedCallback)) {
connection.nonzaCallbacks.remove(filterKey);
}
}
}
}
protected void install() {
if (filterAndListeners.isEmpty()) {
return;
}
synchronized (connection.nonzaCallbacks) {
for (String key : filterAndListeners.keySet()) {
connection.nonzaCallbacks.put(key, this);
}
}
}
private static final class NonzaResponseCallback<SN extends Nonza, FN extends Nonza> extends NonzaCallback {
private SN successNonza;
private FN failedNonza;
private NonzaResponseCallback(Class<? extends SN> successNonzaClass, Class<? extends FN> failedNonzaClass,
Builder builder) {
super(builder);
final String successNonzaKey = XmppElementUtil.getKeyFor(successNonzaClass);
final String failedNonzaKey = XmppElementUtil.getKeyFor(failedNonzaClass);
final GenericElementListener<SN> successListener = new GenericElementListener<SN>(successNonzaClass) {
@Override
public void process(SN successNonza) {
NonzaResponseCallback.this.successNonza = successNonza;
notifyResponse();
}
};
final GenericElementListener<FN> failedListener = new GenericElementListener<FN>(failedNonzaClass) {
@Override
public void process(FN failedNonza) {
NonzaResponseCallback.this.failedNonza = failedNonza;
notifyResponse();
}
};
filterAndListeners.put(successNonzaKey, successListener);
filterAndListeners.put(failedNonzaKey, failedListener);
install();
}
private void notifyResponse() {
synchronized (this) {
notifyAll();
}
}
private boolean hasReceivedSuccessOrFailedNonza() {
return successNonza != null || failedNonza != null;
}
private SN waitForResponse() throws NoResponseException, InterruptedException, FailedNonzaException {
final long deadline = System.currentTimeMillis() + connection.getReplyTimeout();
synchronized (this) {
while (!hasReceivedSuccessOrFailedNonza()) {
final long now = System.currentTimeMillis();
if (now >= deadline) break;
wait(deadline - now);
}
}
if (!hasReceivedSuccessOrFailedNonza()) {
throw NoResponseException.newWith(connection, "Nonza Listener");
}
if (failedNonza != null) {
throw new XMPPException.FailedNonzaException(failedNonza);
}
assert successNonza != null;
return successNonza;
}
}
public static final class Builder {
private final AbstractXMPPConnection connection;
private Map<String, GenericElementListener<? extends Nonza>> filterAndListeners = new HashMap<>();
Builder(AbstractXMPPConnection connection) {
this.connection = connection;
}
public <N extends Nonza> Builder listenFor(Class<? extends N> nonza, GenericElementListener<? extends N> nonzaListener) {
String key = XmppElementUtil.getKeyFor(nonza);
filterAndListeners.put(key, nonzaListener);
return this;
}
public NonzaCallback install() {
return new NonzaCallback(this);
}
}
static <SN extends Nonza, FN extends Nonza> SN sendAndWaitForResponse(NonzaCallback.Builder builder, Nonza nonza, Class<SN> successNonzaClass,
Class<FN> failedNonzaClass)
throws NoResponseException, NotConnectedException, InterruptedException, FailedNonzaException {
NonzaResponseCallback<SN, FN> nonzaCallback = new NonzaResponseCallback<>(successNonzaClass,
failedNonzaClass, builder);
SN successNonza;
try {
nonzaCallback.connection.sendNonza(nonza);
successNonza = nonzaCallback.waitForResponse();
}
finally {
nonzaCallback.cancel();
}
return successNonza;
}
}

View file

@ -180,13 +180,14 @@ public final class SASLAuthentication {
* @param password the password to send to the server. * @param password the password to send to the server.
* @param authzid the authorization identifier (typically null). * @param authzid the authorization identifier (typically null).
* @param sslSession the optional SSL/TLS session (if one was established) * @param sslSession the optional SSL/TLS session (if one was established)
* @return the used SASLMechanism.
* @throws XMPPErrorException * @throws XMPPErrorException
* @throws SASLErrorException * @throws SASLErrorException
* @throws IOException * @throws IOException
* @throws SmackException * @throws SmackException
* @throws InterruptedException * @throws InterruptedException
*/ */
public void authenticate(String username, String password, EntityBareJid authzid, SSLSession sslSession) public SASLMechanism authenticate(String username, String password, EntityBareJid authzid, SSLSession sslSession)
throws XMPPErrorException, SASLErrorException, IOException, throws XMPPErrorException, SASLErrorException, IOException,
SmackException, InterruptedException { SmackException, InterruptedException {
currentMechanism = selectMechanism(authzid); currentMechanism = selectMechanism(authzid);
@ -223,6 +224,8 @@ public final class SASLAuthentication {
if (!authenticationSuccessful) { if (!authenticationSuccessful) {
throw NoResponseException.newWith(connection, "successful SASL authentication"); throw NoResponseException.newWith(connection, "successful SASL authentication");
} }
return currentMechanism;
} }
/** /**

View file

@ -0,0 +1,66 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack;
import java.util.Date;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class ScheduledAction implements Delayed {
final Runnable action;
final Date releaseTime;
final SmackReactor smackReactor;
ScheduledAction(Runnable action, Date releaseTime, SmackReactor smackReactor) {
this.action = action;
this.releaseTime = releaseTime;
this.smackReactor = smackReactor;
}
public void cancel() {
smackReactor.cancel(this);
}
public boolean isDue() {
Date now = new Date();
return now.after(releaseTime);
}
public long getTimeToDueMillis() {
long now = System.currentTimeMillis();
return releaseTime.getTime() - now;
}
@Override
public int compareTo(Delayed otherDelayed) {
if (this == otherDelayed) {
return 0;
}
long thisDelay = getDelay(TimeUnit.MILLISECONDS);
long otherDelay = otherDelayed.getDelay(TimeUnit.MILLISECONDS);
return Long.compare(thisDelay, otherDelay);
}
@Override
public long getDelay(TimeUnit unit) {
long delayInMillis = getTimeToDueMillis();
return unit.convert(delayInMillis, TimeUnit.MILLISECONDS);
}
}

View file

@ -94,12 +94,19 @@ public class SmackException extends Exception {
} }
public static NoResponseException newWith(XMPPConnection connection, public static NoResponseException newWith(XMPPConnection connection,
StanzaCollector collector) { StanzaCollector collector, boolean stanzaCollectorCancelled) {
return newWith(connection, collector.getStanzaFilter()); return newWith(connection, collector.getStanzaFilter(), stanzaCollectorCancelled);
} }
public static NoResponseException newWith(XMPPConnection connection, StanzaFilter filter) { public static NoResponseException newWith(XMPPConnection connection, StanzaFilter filter) {
return newWith(connection, filter, false);
}
public static NoResponseException newWith(XMPPConnection connection, StanzaFilter filter, boolean stanzaCollectorCancelled) {
final StringBuilder sb = getWaitingFor(connection); final StringBuilder sb = getWaitingFor(connection);
if (stanzaCollectorCancelled) {
sb.append(" StanzaCollector has been cancelled.");
}
sb.append(" Waited for response using: "); sb.append(" Waited for response using: ");
if (filter != null) { if (filter != null) {
sb.append(filter.toString()); sb.append(filter.toString());
@ -182,6 +189,12 @@ public class SmackException extends Exception {
super("The connection " + connection super("The connection " + connection
+ " is no longer connected while waiting for response with " + stanzaFilter); + " is no longer connected while waiting for response with " + stanzaFilter);
} }
public NotConnectedException(XMPPConnection connection, StanzaFilter stanzaFilter,
Exception connectionException) {
super("The connection " + connection + " is no longer connected while waiting for response with "
+ stanzaFilter + " because of " + connectionException, connectionException);
}
} }
public static class IllegalStateChangeException extends SmackException { public static class IllegalStateChangeException extends SmackException {
@ -283,6 +296,15 @@ public class SmackException extends Exception {
} }
} }
public static class ConnectionUnexpectedTerminatedException extends SmackException {
private static final long serialVersionUID = 1L;
public ConnectionUnexpectedTerminatedException(Throwable wrappedThrowable) {
super(wrappedThrowable);
}
}
public static class FeatureNotSupportedException extends SmackException { public static class FeatureNotSupportedException extends SmackException {
/** /**

View file

@ -25,13 +25,19 @@ import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.jivesoftware.smack.compress.provider.CompressedProvider;
import org.jivesoftware.smack.compress.provider.FailureProvider;
import org.jivesoftware.smack.compression.Java7ZlibInputOutputStream; import org.jivesoftware.smack.compression.Java7ZlibInputOutputStream;
import org.jivesoftware.smack.compression.XmppCompressionManager;
import org.jivesoftware.smack.compression.zlib.ZlibXmppCompressionFactory;
import org.jivesoftware.smack.initializer.SmackInitializer; import org.jivesoftware.smack.initializer.SmackInitializer;
import org.jivesoftware.smack.packet.Bind; import org.jivesoftware.smack.packet.Bind;
import org.jivesoftware.smack.packet.Message.Body; import org.jivesoftware.smack.packet.Message.Body;
import org.jivesoftware.smack.provider.BindIQProvider; import org.jivesoftware.smack.provider.BindIQProvider;
import org.jivesoftware.smack.provider.BodyElementProvider; import org.jivesoftware.smack.provider.BodyElementProvider;
import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.provider.TlsFailureProvider;
import org.jivesoftware.smack.provider.TlsProceedProvider;
import org.jivesoftware.smack.sasl.core.SASLAnonymous; import org.jivesoftware.smack.sasl.core.SASLAnonymous;
import org.jivesoftware.smack.sasl.core.SASLXOauth2Mechanism; import org.jivesoftware.smack.sasl.core.SASLXOauth2Mechanism;
import org.jivesoftware.smack.sasl.core.SCRAMSHA1Mechanism; import org.jivesoftware.smack.sasl.core.SCRAMSHA1Mechanism;
@ -97,6 +103,8 @@ public final class SmackInitialization {
// Add the Java7 compression handler first, since it's preferred // Add the Java7 compression handler first, since it's preferred
SmackConfiguration.compressionHandlers.add(new Java7ZlibInputOutputStream()); SmackConfiguration.compressionHandlers.add(new Java7ZlibInputOutputStream());
XmppCompressionManager.registerXmppCompressionFactory(ZlibXmppCompressionFactory.INSTANCE);
// Use try block since we may not have permission to get a system // Use try block since we may not have permission to get a system
// property (for example, when an applet). // property (for example, when an applet).
try { try {
@ -118,6 +126,11 @@ public final class SmackInitialization {
ProviderManager.addIQProvider(Bind.ELEMENT, Bind.NAMESPACE, new BindIQProvider()); ProviderManager.addIQProvider(Bind.ELEMENT, Bind.NAMESPACE, new BindIQProvider());
ProviderManager.addExtensionProvider(Body.ELEMENT, Body.NAMESPACE, new BodyElementProvider()); ProviderManager.addExtensionProvider(Body.ELEMENT, Body.NAMESPACE, new BodyElementProvider());
ProviderManager.addNonzaProvider(TlsProceedProvider.INSTANCE);
ProviderManager.addNonzaProvider(TlsFailureProvider.INSTANCE);
ProviderManager.addNonzaProvider(CompressedProvider.INSTANCE);
ProviderManager.addNonzaProvider(FailureProvider.INSTANCE);
SmackConfiguration.smackInitialized = true; SmackConfiguration.smackInitialized = true;
} }

View file

@ -0,0 +1,440 @@
/**
*
* Copyright 2018-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The SmackReactor for non-blocking I/O.
* <p>
* Highlights include:
* <ul>
* <li>Multiple reactor threads</li>
* <li>Scheduled actions</li>
* </ul>
*
* <pre>
*
* ) ) )
* ( ( (
* ) ) )
* (~~~~~~~~~)
* | Smack |
* |Reactor|
* I _._
* I /' `\
* I | |
* f | |~~~~~~~~~~~~~~|
* .' | | # # # # |
* '______|___|___________###|
* </pre>
*/
public class SmackReactor {
private static final Logger LOGGER = Logger.getLogger(SmackReactor.class.getName());
private static final int DEFAULT_REACTOR_THREAD_COUNT = 2;
private static final int PENDING_SET_INTEREST_OPS_MAX_BATCH_SIZE = 1024;
private static SmackReactor INSTANCE;
static synchronized SmackReactor getInstance() {
if (INSTANCE == null) {
INSTANCE = new SmackReactor("DefaultReactor");
}
return INSTANCE;
}
private final Selector selector;
private final String reactorName;
private final List<Reactor> reactorThreads = Collections.synchronizedList(new ArrayList<>());
private final DelayQueue<ScheduledAction> scheduledActions = new DelayQueue<>();
private final Lock registrationLock = new ReentrantLock();
/**
* The semaphore protecting the handling of the actions. Note that it is
* initialized with -1, which basically means that one thread will always do I/O using
* select().
*/
private final Semaphore actionsSemaphore = new Semaphore(-1, false);
private final Queue<SelectionKey> pendingSelectionKeys = new ConcurrentLinkedQueue<>();
private final Queue<SetInterestOps> pendingSetInterestOps = new ConcurrentLinkedQueue<>();
SmackReactor(String reactorName) {
this.reactorName = reactorName;
try {
selector = Selector.open();
}
catch (IOException e) {
throw new IllegalStateException(e);
}
setReactorThreadCount(DEFAULT_REACTOR_THREAD_COUNT);
}
SelectionKey registerWithSelector(SelectableChannel channel, int ops, ChannelSelectedCallback callback)
throws ClosedChannelException {
SelectionKeyAttachment selectionKeyAttachment = new SelectionKeyAttachment(callback);
registrationLock.lock();
try {
selector.wakeup();
return channel.register(selector, ops, selectionKeyAttachment);
} finally {
registrationLock.unlock();
}
}
void setInterestOps(SelectionKey selectionKey, int interestOps) {
SetInterestOps setInterestOps = new SetInterestOps(selectionKey, interestOps);
pendingSetInterestOps.add(setInterestOps);
selector.wakeup();
}
private static final class SetInterestOps {
private final SelectionKey selectionKey;
private final int interestOps;
private SetInterestOps(SelectionKey selectionKey, int interestOps) {
this.selectionKey = selectionKey;
this.interestOps = interestOps;
}
}
ScheduledAction schedule(Runnable runnable, long delay, TimeUnit unit) {
long releaseTimeEpoch = System.currentTimeMillis() + unit.toMillis(delay);
Date releaseTimeDate = new Date(releaseTimeEpoch);
ScheduledAction scheduledAction = new ScheduledAction(runnable, releaseTimeDate, this);
synchronized (scheduledActions) {
scheduledActions.add(scheduledAction);
}
return scheduledAction;
}
boolean cancel(ScheduledAction scheduledAction) {
return scheduledActions.remove(scheduledAction);
}
private class Reactor extends Thread {
private volatile long shutdownRequestTimestamp = -1;
@Override
public void run() {
try {
reactorLoop();
} finally {
if (shutdownRequestTimestamp > 0) {
long shutDownDelay = System.currentTimeMillis() - shutdownRequestTimestamp;
LOGGER.info(this + " shut down after " + shutDownDelay + "ms");
} else {
boolean contained = reactorThreads.remove(this);
assert (contained);
}
}
}
private void reactorLoop() {
// Loop until reactor shutdown was requested.
while (shutdownRequestTimestamp < 0) {
handleScheduledActionsOrPerformSelect();
handlePendingSelectionKeys();
}
}
@SuppressWarnings("LockNotBeforeTry")
private void handleScheduledActionsOrPerformSelect() {
ScheduledAction dueScheduledAction = null;
boolean permitToHandleScheduledActions = actionsSemaphore.tryAcquire();
if (permitToHandleScheduledActions) {
try {
dueScheduledAction = scheduledActions.poll();
} finally {
actionsSemaphore.release();
}
}
if (dueScheduledAction != null) {
dueScheduledAction.action.run();
return;
}
ScheduledAction nextScheduledAction = scheduledActions.peek();
long selectWait;
if (nextScheduledAction == null) {
// There is no next scheduled action, wait indefinitely in select().
selectWait = 0;
} else {
selectWait = nextScheduledAction.getTimeToDueMillis();
}
if (selectWait < 0) {
// A scheduled action was just released and become ready to execute.
return;
}
int newSelectedKeysCount = 0;
List<SelectionKey> selectedKeys;
synchronized (selector) {
// Before we call select, we handle the pending the interest Ops. This will not block since no other
// thread is currently in select() at this time.
// Note: This was put deliberately before the registration lock. It may cause more synchronization but
// allows for more parallelism.
// Hopefully that assumption is right.
int myHandledPendingSetInterestOps = 0;
for (SetInterestOps setInterestOps; (setInterestOps = pendingSetInterestOps.poll()) != null;) {
setInterestOpsCancelledKeySafe(setInterestOps.selectionKey, setInterestOps.interestOps);
if (myHandledPendingSetInterestOps++ >= PENDING_SET_INTEREST_OPS_MAX_BATCH_SIZE) {
// This thread has handled enough "set pending interest ops" requests. Wakeup another one to
// handle the remaining (if any).
selector.wakeup();
break;
}
}
// Ensure that a wakeup() in registerWithSelector() gives the corresponding
// register() in the same method the chance to actually register the channel. In
// other words: This construct ensures that there is never another select()
// between a corresponding wakeup() and register() calls.
// See also https://stackoverflow.com/a/1112809/194894
registrationLock.lock();
registrationLock.unlock();
try {
newSelectedKeysCount = selector.select(selectWait);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "IOException while using select()", e);
return;
}
if (newSelectedKeysCount == 0) {
return;
}
// Copy the selected-key set over to selectedKeys, remove the keys from the
// selected key set and loose interest of the key OPs for the time being.
// Note that we perform this operation in two steps in order to maximize the
// timespan setRacing() is set.
Set<SelectionKey> selectedKeySet = selector.selectedKeys();
for (SelectionKey selectionKey : selectedKeySet) {
SelectionKeyAttachment selectionKeyAttachment = (SelectionKeyAttachment) selectionKey.attachment();
selectionKeyAttachment.setRacing();
}
for (SelectionKey selectionKey : selectedKeySet) {
setInterestOpsCancelledKeySafe(selectionKey, 0);
}
selectedKeys = new ArrayList<>(selectedKeySet.size());
selectedKeys.addAll(selectedKeySet);
selectedKeySet.clear();
}
int selectedKeysCount = selectedKeys.size();
int currentReactorThreadCount = reactorThreads.size();
int myKeyCount;
if (selectedKeysCount > currentReactorThreadCount) {
myKeyCount = selectedKeysCount / currentReactorThreadCount;
} else {
myKeyCount = selectedKeysCount;
}
final Level reactorSelectStatsLogLevel = Level.FINE;
if (LOGGER.isLoggable(reactorSelectStatsLogLevel)) {
LOGGER.log(reactorSelectStatsLogLevel,
"New selected key count: " + newSelectedKeysCount
+ ". Total selected key count " + selectedKeysCount
+ ". My key count: " + myKeyCount
+ ". Current reactor thread count: " + currentReactorThreadCount);
}
Collection<SelectionKey> mySelectedKeys = new ArrayList<>(myKeyCount);
Iterator<SelectionKey> it = selectedKeys.iterator();
for (int i = 0; i < myKeyCount; i++) {
SelectionKey selectionKey = it.next();
mySelectedKeys.add(selectionKey);
}
while (it.hasNext()) {
// Drain to pendingSelectionKeys.
SelectionKey selectionKey = it.next();
pendingSelectionKeys.add(selectionKey);
}
if (selectedKeysCount - myKeyCount > 0) {
// There where pending selection keys: Wakeup another reactor thread to handle them.
selector.wakeup();
}
handleSelectedKeys(mySelectedKeys);
}
private void handlePendingSelectionKeys() {
final int pendingSelectionKeysSize = pendingSelectionKeys.size();
if (pendingSelectionKeysSize == 0) {
return;
}
int currentReactorThreadCount = reactorThreads.size();
int myKeyCount = pendingSelectionKeysSize / currentReactorThreadCount;
Collection<SelectionKey> selectedKeys = new ArrayList<>(myKeyCount);
for (int i = 0; i < myKeyCount; i++) {
SelectionKey selectionKey = pendingSelectionKeys.poll();
if (selectionKey == null) {
// We lost a race and can abort here since the pendingSelectionKeys queue is empty.
break;
}
selectedKeys.add(selectionKey);
}
if (!pendingSelectionKeys.isEmpty()) {
// There are more pending selection keys, wakeup a thread blocked in select() to handle them.
selector.wakeup();
}
handleSelectedKeys(selectedKeys);
}
private void setInterestOpsCancelledKeySafe(SelectionKey selectionKey, int interestOps) {
try {
selectionKey.interestOps(interestOps);
}
catch (CancelledKeyException e) {
final Level keyCancelledLogLevel = Level.FINER;
if (LOGGER.isLoggable(keyCancelledLogLevel)) {
LOGGER.log(keyCancelledLogLevel, "Key '" + selectionKey + "' has been cancelled", e);
}
}
}
void requestShutdown() {
shutdownRequestTimestamp = System.currentTimeMillis();
}
}
private static void handleSelectedKeys(Collection<SelectionKey> selectedKeys) {
for (SelectionKey selectionKey : selectedKeys) {
SelectableChannel channel = selectionKey.channel();
SelectionKeyAttachment selectionKeyAttachment = (SelectionKeyAttachment) selectionKey.attachment();
ChannelSelectedCallback channelSelectedCallback = selectionKeyAttachment.weaeklyReferencedChannelSelectedCallback.get();
if (channelSelectedCallback != null) {
channelSelectedCallback.onChannelSelected(channel, selectionKey);
}
else {
selectionKey.cancel();
}
}
}
public interface ChannelSelectedCallback {
void onChannelSelected(SelectableChannel channel, SelectionKey selectionKey);
}
public void setReactorThreadCount(int reactorThreadCount) {
if (reactorThreadCount < 2) {
throw new IllegalArgumentException("Must have at least two reactor threads, but you requested " + reactorThreadCount);
}
synchronized (reactorThreads) {
int deltaThreads = reactorThreadCount - reactorThreads.size();
if (deltaThreads > 0) {
// Start new reactor thread. Note that we start the threads before we increase the permits of the
// actionsSemaphore.
for (int i = 0; i < deltaThreads; i++) {
Reactor reactor = new Reactor();
reactor.setDaemon(true);
reactor.setName("Smack " + reactorName + " Thread #" + i);
reactorThreads.add(reactor);
reactor.start();
}
actionsSemaphore.release(deltaThreads);
} else {
// Stop existing reactor threads. First we change the sign of deltaThreads, then we decrease the permits
// of the actionsSemaphore *before* we signal the selected reactor threads that they should shut down.
deltaThreads -= deltaThreads;
for (int i = deltaThreads - 1; i > 0; i--) {
// Note that this could potentially block forever, starving on the unfair semaphore.
actionsSemaphore.acquireUninterruptibly();
}
for (int i = deltaThreads - 1; i > 0; i--) {
Reactor reactor = reactorThreads.remove(i);
reactor.requestShutdown();
}
selector.wakeup();
}
}
}
public static final class SelectionKeyAttachment {
private final WeakReference<ChannelSelectedCallback> weaeklyReferencedChannelSelectedCallback;
private final AtomicBoolean reactorThreadRacing = new AtomicBoolean();
private SelectionKeyAttachment(ChannelSelectedCallback channelSelectedCallback) {
this.weaeklyReferencedChannelSelectedCallback = new WeakReference<>(channelSelectedCallback);
}
private void setRacing() {
// We use lazySet here since it is sufficient if the value does not become visible immediately.
reactorThreadRacing.lazySet(true);
}
public void resetReactorThreadRacing() {
reactorThreadRacing.set(false);
}
public boolean isReactorThreadRacing() {
return reactorThreadRacing.get();
}
}
}

View file

@ -17,10 +17,9 @@
package org.jivesoftware.smack; package org.jivesoftware.smack;
import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotConnectedException;
@ -46,7 +45,9 @@ public class StanzaCollector {
private final StanzaFilter packetFilter; private final StanzaFilter packetFilter;
private final ArrayBlockingQueue<Stanza> resultQueue; private final ArrayDeque<Stanza> resultQueue;
private final int maxQueueSize;
/** /**
* The stanza collector which timeout for the next result will get reset once this collector collects a stanza. * The stanza collector which timeout for the next result will get reset once this collector collects a stanza.
@ -57,7 +58,9 @@ public class StanzaCollector {
private final Stanza request; private final Stanza request;
private volatile boolean cancelled = false; private volatile boolean cancelled;
private Exception connectionException;
/** /**
* Creates a new stanza collector. If the stanza filter is <tt>null</tt>, then * Creates a new stanza collector. If the stanza filter is <tt>null</tt>, then
@ -69,7 +72,8 @@ public class StanzaCollector {
protected StanzaCollector(XMPPConnection connection, Configuration configuration) { protected StanzaCollector(XMPPConnection connection, Configuration configuration) {
this.connection = connection; this.connection = connection;
this.packetFilter = configuration.packetFilter; this.packetFilter = configuration.packetFilter;
this.resultQueue = new ArrayBlockingQueue<>(configuration.size); this.resultQueue = new ArrayDeque<>(configuration.size);
this.maxQueueSize = configuration.size;
this.collectorToReset = configuration.collectorToReset; this.collectorToReset = configuration.collectorToReset;
this.request = configuration.request; this.request = configuration.request;
} }
@ -79,12 +83,15 @@ public class StanzaCollector {
* queued up. Once a stanza collector has been cancelled, it cannot be * queued up. Once a stanza collector has been cancelled, it cannot be
* re-enabled. Instead, a new stanza collector must be created. * re-enabled. Instead, a new stanza collector must be created.
*/ */
public void cancel() { public synchronized void cancel() {
// If the packet collector has already been cancelled, do nothing. // If the packet collector has already been cancelled, do nothing.
if (!cancelled) { if (cancelled) {
return;
}
cancelled = true; cancelled = true;
connection.removeStanzaCollector(this); connection.removeStanzaCollector(this);
} notifyAll();
} }
/** /**
@ -119,7 +126,7 @@ public class StanzaCollector {
* results. * results.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <P extends Stanza> P pollResult() { public synchronized <P extends Stanza> P pollResult() {
return (P) resultQueue.poll(); return (P) resultQueue.poll();
} }
@ -152,14 +159,21 @@ public class StanzaCollector {
* @throws InterruptedException * @throws InterruptedException
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <P extends Stanza> P nextResultBlockForever() throws InterruptedException { // TODO: Consider removing this method as it is hardly ever useful.
public synchronized <P extends Stanza> P nextResultBlockForever() throws InterruptedException {
throwIfCancelled(); throwIfCancelled();
P res = null;
while (res == null) { while (true) {
res = (P) resultQueue.take(); P res = (P) resultQueue.poll();
} if (res != null) {
return res; return res;
} }
if (cancelled) {
return null;
}
wait();
}
}
/** /**
* Returns the next available packet. The method call will block until the connection's default * Returns the next available packet. The method call will block until the connection's default
@ -176,13 +190,13 @@ public class StanzaCollector {
private volatile long waitStart; private volatile long waitStart;
/** /**
* Returns the next available packet. The method call will block (not return) * Returns the next available stanza. The method call will block (not return) until a stanza is available or the
* until a stanza is available or the <tt>timeout</tt> has elapsed. If the * <tt>timeout</tt> has elapsed or if the connection was terminated because of an error. If the timeout elapses without a
* timeout elapses without a result, <tt>null</tt> will be returned. * result or if there was an connection error, <tt>null</tt> will be returned.
* *
* @param <P> type of the result stanza. * @param <P> type of the result stanza.
* @param timeout the timeout in milliseconds. * @param timeout the timeout in milliseconds.
* @return the next available packet. * @return the next available stanza or <code>null</code> on timeout or connection error.
* @throws InterruptedException * @throws InterruptedException
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -191,14 +205,17 @@ public class StanzaCollector {
P res = null; P res = null;
long remainingWait = timeout; long remainingWait = timeout;
waitStart = System.currentTimeMillis(); waitStart = System.currentTimeMillis();
do { while (remainingWait > 0 && connectionException == null && !cancelled) {
res = (P) resultQueue.poll(remainingWait, TimeUnit.MILLISECONDS); synchronized (this) {
res = (P) resultQueue.poll();
if (res != null) { if (res != null) {
return res; return res;
} }
wait(remainingWait);
}
remainingWait = timeout - (System.currentTimeMillis() - waitStart); remainingWait = timeout - (System.currentTimeMillis() - waitStart);
} while (remainingWait > 0); }
return null; return res;
} }
/** /**
@ -263,10 +280,13 @@ public class StanzaCollector {
cancel(); cancel();
} }
if (result == null) { if (result == null) {
if (connectionException != null) {
throw new NotConnectedException(connection, packetFilter, connectionException);
}
if (!connection.isConnected()) { if (!connection.isConnected()) {
throw new NotConnectedException(connection, packetFilter); throw new NotConnectedException(connection, packetFilter);
} }
throw NoResponseException.newWith(connection, this); throw NoResponseException.newWith(connection, this, cancelled);
} }
XMPPErrorException.ifHasErrorThenThrow(result); XMPPErrorException.ifHasErrorThenThrow(result);
@ -289,7 +309,7 @@ public class StanzaCollector {
if (collectedCache == null) { if (collectedCache == null) {
collectedCache = new ArrayList<>(getCollectedCount()); collectedCache = new ArrayList<>(getCollectedCount());
resultQueue.drainTo(collectedCache); collectedCache.addAll(resultQueue);
} }
return collectedCache; return collectedCache;
@ -301,10 +321,15 @@ public class StanzaCollector {
* @return the count of collected stanzas. * @return the count of collected stanzas.
* @since 4.1 * @since 4.1
*/ */
public int getCollectedCount() { public synchronized int getCollectedCount() {
return resultQueue.size(); return resultQueue.size();
} }
synchronized void notifyConnectionError(Exception exception) {
connectionException = exception;
notifyAll();
}
/** /**
* Processes a stanza to see if it meets the criteria for this stanza collector. * Processes a stanza to see if it meets the criteria for this stanza collector.
* If so, the stanza is added to the result queue. * If so, the stanza is added to the result queue.
@ -313,12 +338,14 @@ public class StanzaCollector {
*/ */
protected void processStanza(Stanza packet) { protected void processStanza(Stanza packet) {
if (packetFilter == null || packetFilter.accept(packet)) { if (packetFilter == null || packetFilter.accept(packet)) {
// CHECKSTYLE:OFF synchronized (this) {
while (!resultQueue.offer(packet)) { if (resultQueue.size() == maxQueueSize) {
// Since we know the queue is full, this poll should never actually block. Stanza rolledOverStanza = resultQueue.poll();
resultQueue.poll(); assert rolledOverStanza != null;
}
resultQueue.add(packet);
notifyAll();
} }
// CHECKSTYLE:ON
if (collectorToReset != null) { if (collectorToReset != null) {
collectorToReset.waitStart = System.currentTimeMillis(); collectorToReset.waitStart = System.currentTimeMillis();
} }
@ -403,4 +430,5 @@ public class StanzaCollector {
return this; return this;
} }
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright © 2014-2015 Florian Schmaus * Copyright © 2014-2018 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -38,6 +38,8 @@ public class SynchronizationPoint<E extends Exception> {
private State state; private State state;
private E failureException; private E failureException;
private volatile long waitStart;
/** /**
* Construct a new synchronization point for the given connection. * Construct a new synchronization point for the given connection.
* *
@ -239,6 +241,10 @@ public class SynchronizationPoint<E extends Exception> {
} }
} }
public void resetTimeout() {
waitStart = System.currentTimeMillis();
}
/** /**
* Wait for the condition to become something else as {@link State#RequestSent} or {@link State#Initial}. * Wait for the condition to become something else as {@link State#RequestSent} or {@link State#Initial}.
* {@link #reportSuccess()}, {@link #reportFailure()} and {@link #reportFailure(Exception)} will either set this * {@link #reportSuccess()}, {@link #reportFailure()} and {@link #reportFailure(Exception)} will either set this
@ -247,13 +253,23 @@ public class SynchronizationPoint<E extends Exception> {
* @throws InterruptedException * @throws InterruptedException
*/ */
private void waitForConditionOrTimeout() throws InterruptedException { private void waitForConditionOrTimeout() throws InterruptedException {
long remainingWait = TimeUnit.MILLISECONDS.toNanos(connection.getReplyTimeout()); waitStart = System.currentTimeMillis();
while (state == State.RequestSent || state == State.Initial) { while (state == State.RequestSent || state == State.Initial) {
long timeout = connection.getReplyTimeout();
long remainingWaitMillis = timeout - (System.currentTimeMillis() - waitStart);
long remainingWait = TimeUnit.MILLISECONDS.toNanos(remainingWaitMillis);
if (remainingWait <= 0) { if (remainingWait <= 0) {
state = State.NoResponse; state = State.NoResponse;
break; break;
} }
remainingWait = condition.awaitNanos(remainingWait);
try {
condition.awaitNanos(remainingWait);
} catch (InterruptedException e) {
state = State.Interrupted;
throw e;
}
} }
} }
@ -286,5 +302,6 @@ public class SynchronizationPoint<E extends Exception> {
NoResponse, NoResponse,
Success, Success,
Failure, Failure,
Interrupted,
} }
} }

View file

@ -25,6 +25,7 @@ import org.jivesoftware.smack.filter.IQReplyFilter;
import org.jivesoftware.smack.filter.StanzaFilter; import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.iqrequest.IQRequestHandler; import org.jivesoftware.smack.iqrequest.IQRequestHandler;
import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.FullyQualifiedElement;
import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Nonza; import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.Stanza;
@ -474,7 +475,7 @@ public interface XMPPConnection {
* @param namespace * @param namespace
* @return a stanza extensions of the feature or <code>null</code> * @return a stanza extensions of the feature or <code>null</code>
*/ */
<F extends ExtensionElement> F getFeature(String element, String namespace); <F extends FullyQualifiedElement> F getFeature(String element, String namespace);
/** /**
* Return true if the server supports the given stream feature. * Return true if the server supports the given stream feature.
@ -565,5 +566,4 @@ public interface XMPPConnection {
* @return the timestamp in milliseconds * @return the timestamp in milliseconds
*/ */
long getLastStanzaReceived(); long getLastStanzaReceived();
} }

View file

@ -198,6 +198,10 @@ public abstract class XMPPException extends Exception {
private final Nonza nonza; private final Nonza nonza;
public FailedNonzaException(Nonza failedNonza) {
this(failedNonza, null);
}
public FailedNonzaException(Nonza nonza, StanzaError.Condition condition) { public FailedNonzaException(Nonza nonza, StanzaError.Condition condition) {
this.condition = condition; this.condition = condition;
this.nonza = nonza; this.nonza = nonza;

View file

@ -0,0 +1,76 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.cert.CertificateException;
import org.jivesoftware.smack.SmackException.NoResponseException;
public interface XmppInputOutputFilter {
/**
* The {@code outputData} argument may be a direct {@link ByteBuffer}. The filter has consume the data of the buffer
* completely.
*
* This method must return a {@link OutputResult}. Use {@link OutputResult#NO_OUTPUT} if there is no output.
*
* @param outputData the data this method needs to process.
* @param isFinalDataOfElement if this is the final data of the element.
* @param destinationAddressChanged if the destination address has changed.
* @param moreDataAvailable if more data is available.
* @return a output result.
* @throws IOException in case an I/O exception occurs.
*/
OutputResult output(ByteBuffer outputData, boolean isFinalDataOfElement, boolean destinationAddressChanged,
boolean moreDataAvailable) throws IOException;
class OutputResult {
public static final OutputResult NO_OUTPUT = new OutputResult(false, null);
public final boolean pendingFilterData;
public final ByteBuffer filteredOutputData;
public OutputResult(ByteBuffer filteredOutputData) {
this(false, filteredOutputData);
}
public OutputResult(boolean pendingFilterData, ByteBuffer filteredOutputData) {
this.pendingFilterData = pendingFilterData;
this.filteredOutputData = filteredOutputData;
}
}
/**
* The returned {@link ByteBuffer} is going to get fliped by the caller. The callee must not flip the buffer.
* @param inputData the data this methods needs to process.
* @return a {@link ByteBuffer} or {@code null} if no data could be produced.
* @throws IOException in case an I/O exception occurs.
*/
ByteBuffer input(ByteBuffer inputData) throws IOException;
default void closeInputOutput() {
}
default void waitUntilInputOutputClosed() throws IOException, NoResponseException, CertificateException, InterruptedException, SmackException {
}
default Object getStats() {
return null;
}
}

View file

@ -46,7 +46,7 @@ public class Compress implements Nonza {
@Override @Override
public XmlStringBuilder toXML(String enclosingNamespace) { public XmlStringBuilder toXML(String enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this); XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
xml.rightAngleBracket(); xml.rightAngleBracket();
xml.element("method", method); xml.element("method", method);
xml.closeElement(this); xml.closeElement(this);

View file

@ -0,0 +1,89 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.compress.packet;
import java.util.Objects;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.util.XmlStringBuilder;
public class Failure implements Nonza {
public static final String ELEMENT = "failure";
public static final String NAMESPACE = Compress.NAMESPACE;
public enum CompressFailureError {
setup_failed,
processing_failed,
unsupported_method,
;
private final String compressFailureError;
CompressFailureError() {
compressFailureError = name().replace('_', '-');
}
@Override
public String toString() {
return compressFailureError;
}
}
private final CompressFailureError compressFailureError;
private final StanzaError stanzaError;
public Failure(CompressFailureError compressFailureError) {
this(compressFailureError, null);
}
public Failure(CompressFailureError compressFailureError, StanzaError stanzaError) {
this.compressFailureError = Objects.requireNonNull(compressFailureError);
this.stanzaError = stanzaError;
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public String getNamespace() {
return NAMESPACE;
}
public CompressFailureError getCompressFailureError() {
return compressFailureError;
}
public StanzaError getStanzaError() {
return stanzaError;
}
@Override
public XmlStringBuilder toXML(String enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
xml.rightAngleBracket();
xml.emptyElement(compressFailureError);
xml.optElement(stanzaError);
xml.closeElement(this);
return xml;
}
}

View file

@ -0,0 +1,36 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.compress.provider;
import org.jivesoftware.smack.compress.packet.Compressed;
import org.jivesoftware.smack.provider.NonzaProvider;
import org.xmlpull.v1.XmlPullParser;
public final class CompressedProvider extends NonzaProvider<Compressed> {
public static final CompressedProvider INSTANCE = new CompressedProvider();
private CompressedProvider() {
}
@Override
public Compressed parse(XmlPullParser parser, int initialDepth) {
return Compressed.INSTANCE;
}
}

View file

@ -0,0 +1,81 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.compress.provider;
import java.util.logging.Logger;
import org.jivesoftware.smack.compress.packet.Failure;
import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smack.provider.NonzaProvider;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.xmlpull.v1.XmlPullParser;
public final class FailureProvider extends NonzaProvider<Failure> {
private static final Logger LOGGER = Logger.getLogger(FailureProvider.class.getName());
public static final FailureProvider INSTANCE = new FailureProvider();
private FailureProvider() {
}
@Override
public Failure parse(XmlPullParser parser, int initialDepth) throws Exception {
Failure.CompressFailureError compressFailureError = null;
StanzaError stanzaError = null;
outerloop: while (true) {
int eventType = parser.next();
switch (eventType) {
case XmlPullParser.START_TAG:
String name = parser.getName();
String namespace = parser.getNamespace();
switch (namespace) {
case Failure.NAMESPACE:
compressFailureError = Failure.CompressFailureError.valueOf(name.replace("-", "_"));
if (compressFailureError == null) {
LOGGER.warning("Unknown element in " + Failure.NAMESPACE + ": " + name);
}
break;
case StreamOpen.CLIENT_NAMESPACE:
case StreamOpen.SERVER_NAMESPACE:
switch (name) {
case StanzaError.ERROR:
StanzaError.Builder stanzaErrorBuilder = PacketParserUtils.parseError(parser);
stanzaError = stanzaErrorBuilder.build();
break;
default:
LOGGER.warning("Unknown element in " + namespace + ": " + name);
break;
}
break;
}
break;
case XmlPullParser.END_TAG:
if (parser.getDepth() == initialDepth) {
break outerloop;
}
break;
}
}
return new Failure(compressFailureError, stanzaError);
}
}

View file

@ -0,0 +1,21 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Providers for XMPP stream compression (XEP-138).
*/
package org.jivesoftware.smack.compress.provider;

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2013 Florian Schmaus * Copyright 2013-2018 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -36,6 +36,10 @@ public abstract class XMPPInputOutputStream {
XMPPInputOutputStream.flushMethod = flushMethod; XMPPInputOutputStream.flushMethod = flushMethod;
} }
public static FlushMethod getFlushMethod() {
return flushMethod;
}
protected final String compressionMethod; protected final String compressionMethod;
protected XMPPInputOutputStream(String compressionMethod) { protected XMPPInputOutputStream(String compressionMethod) {

View file

@ -0,0 +1,47 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.compression;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.XmppInputOutputFilter;
public abstract class XmppCompressionFactory implements Comparable<XmppCompressionFactory> {
private final String method;
private final int priority;
protected XmppCompressionFactory(String method, int priority) {
this.method = method;
this.priority = priority;
}
public final String getCompressionMethod() {
return method;
}
public final int getPriority() {
return priority;
}
@Override
public final int compareTo(XmppCompressionFactory other) {
return Integer.compare(getPriority(), other.getPriority());
}
public abstract XmppInputOutputFilter fabricate(ConnectionConfiguration configuration);
}

View file

@ -0,0 +1,65 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.compression;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.jivesoftware.smack.compress.packet.Compress;
public class XmppCompressionManager {
private static final List<XmppCompressionFactory> xmppCompressionFactories = new ArrayList<>(4);
public static XmppCompressionFactory registerXmppCompressionFactory(XmppCompressionFactory xmppCompressionFactory) {
final String method = xmppCompressionFactory.getCompressionMethod();
XmppCompressionFactory previousFactory = null;
synchronized (xmppCompressionFactories) {
for (Iterator<XmppCompressionFactory> it = xmppCompressionFactories.iterator(); it.hasNext(); ) {
XmppCompressionFactory factory = it.next();
if (factory.getCompressionMethod().equals(method)) {
it.remove();
previousFactory = factory;
break;
}
}
xmppCompressionFactories.add(xmppCompressionFactory);
Collections.sort(xmppCompressionFactories);
}
return previousFactory;
}
public static XmppCompressionFactory getBestFactory(Compress.Feature compressFeature) {
List<String> announcedMethods = compressFeature.getMethods();
synchronized (xmppCompressionFactories) {
for (XmppCompressionFactory factory : xmppCompressionFactories) {
if (announcedMethods.contains(factory.getCompressionMethod())) {
return factory;
}
}
}
return null;
}
}

View file

@ -0,0 +1,287 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.compression.zlib;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.XmppInputOutputFilter;
import org.jivesoftware.smack.compression.XMPPInputOutputStream;
import org.jivesoftware.smack.compression.XMPPInputOutputStream.FlushMethod;
import org.jivesoftware.smack.compression.XmppCompressionFactory;
public final class ZlibXmppCompressionFactory extends XmppCompressionFactory {
public static final ZlibXmppCompressionFactory INSTANCE = new ZlibXmppCompressionFactory();
private ZlibXmppCompressionFactory() {
super("zlib", 100);
}
@Override
public XmppInputOutputFilter fabricate(ConnectionConfiguration configuration) {
return new ZlibXmppInputOutputFilter();
}
private static final class ZlibXmppInputOutputFilter implements XmppInputOutputFilter {
private static final int MINIMUM_OUTPUT_BUFFER_INITIAL_SIZE = 4;
private static final int MINIMUM_OUTPUT_BUFFER_INCREASE = 480;
private final Deflater compressor;
private final Inflater decompressor = new Inflater();
private long compressorInBytes;
private long compressorOutBytes;
private long decompressorInBytes;
private long decompressorOutBytes;
private int maxOutputOutput = -1;
private int maxInputOutput = -1;
private int maxBytesWrittenAfterFullFlush = -1;
private ZlibXmppInputOutputFilter() {
this(Deflater.DEFAULT_COMPRESSION);
}
private ZlibXmppInputOutputFilter(int compressionLevel) {
compressor = new Deflater(compressionLevel);
}
private ByteBuffer outputBuffer;
@Override
public OutputResult output(ByteBuffer outputData, boolean isFinalDataOfElement, boolean destinationAddressChanged,
boolean moreDataAvailable) throws IOException {
if (destinationAddressChanged && XMPPInputOutputStream.getFlushMethod() == FlushMethod.FULL_FLUSH) {
outputBuffer = ByteBuffer.allocate(256);
int bytesWritten = deflate(Deflater.FULL_FLUSH);
maxBytesWrittenAfterFullFlush = Math.max(bytesWritten, maxBytesWrittenAfterFullFlush);
compressorOutBytes += bytesWritten;
}
if (outputData == null && outputBuffer == null) {
return OutputResult.NO_OUTPUT;
}
int bytesRemaining = outputData.remaining();
if (outputBuffer == null) {
final int outputBufferSize = bytesRemaining < MINIMUM_OUTPUT_BUFFER_INITIAL_SIZE ? MINIMUM_OUTPUT_BUFFER_INITIAL_SIZE : bytesRemaining;
// We assume that the compressed data will not take more space as the uncompressed. Even if this is not
// always true, the automatic buffer resize mechanism of deflate() will take care.
outputBuffer = ByteBuffer.allocate(outputBufferSize);
}
// There is an invariant of Deflater/Inflater that input should only be set if needsInput() return true.
assert (compressor.needsInput());
final byte[] compressorInputBuffer;
final int compressorInputBufferOffset, compressorInputBufferLength;
if (outputData.hasArray()) {
compressorInputBuffer = outputData.array();
compressorInputBufferOffset = outputData.arrayOffset();
compressorInputBufferLength = outputData.remaining();
} else {
compressorInputBuffer = new byte[outputData.remaining()];
compressorInputBufferOffset = 0;
compressorInputBufferLength = compressorInputBuffer.length;
outputData.get(compressorInputBuffer);
}
compressorInBytes += compressorInputBufferLength;
compressor.setInput(compressorInputBuffer, compressorInputBufferOffset, compressorInputBufferLength);
int flushMode;
if (moreDataAvailable) {
flushMode = Deflater.NO_FLUSH;
} else {
flushMode = Deflater.SYNC_FLUSH;
}
int bytesWritten = deflate(flushMode);
maxOutputOutput = Math.max(outputBuffer.position(), maxOutputOutput);
compressorOutBytes += bytesWritten;
OutputResult outputResult = new OutputResult(outputBuffer);
outputBuffer = null;
return outputResult;
}
private int deflate(int flushMode) {
// compressor.finish();
int totalBytesWritten = 0;
while (true) {
int initialOutputBufferPosition = outputBuffer.position();
byte[] buffer = outputBuffer.array();
int length = outputBuffer.limit() - initialOutputBufferPosition;
int bytesWritten = compressor.deflate(buffer, initialOutputBufferPosition, length, flushMode);
int newOutputBufferPosition = initialOutputBufferPosition + bytesWritten;
outputBuffer.position(newOutputBufferPosition);
totalBytesWritten += bytesWritten;
if (compressor.needsInput() && outputBuffer.hasRemaining()) {
break;
}
int increasedBufferSize = outputBuffer.capacity() * 2;
if (increasedBufferSize < MINIMUM_OUTPUT_BUFFER_INCREASE) {
increasedBufferSize = MINIMUM_OUTPUT_BUFFER_INCREASE;
}
ByteBuffer newCurrentOutputBuffer = ByteBuffer.allocate(increasedBufferSize);
outputBuffer.flip();
newCurrentOutputBuffer.put(outputBuffer);
outputBuffer = newCurrentOutputBuffer;
}
return totalBytesWritten;
}
@Override
public ByteBuffer input(ByteBuffer inputData) throws IOException {
int bytesRemaining = inputData.remaining();
final byte[] inputBytes;
final int offset, length;
if (inputData.hasArray()) {
inputBytes = inputData.array();
offset = inputData.arrayOffset();
length = inputData.remaining();
} else {
// Copy since we are dealing with a buffer whose array is not accessible (possibly a direct buffer).
inputBytes = new byte[bytesRemaining];
inputData.get(inputBytes);
offset = 0;
length = inputBytes.length;
}
decompressorInBytes += length;
decompressor.setInput(inputBytes, offset, length);
int bytesInflated;
// Assume that the inflated/decompressed result will be roughly at most twice the size of the compressed
// variant. It appears to hold most of the times, if not, then the buffer resize mechanism will take care of
// it.
ByteBuffer outputBuffer = ByteBuffer.allocate(2 * length);
while (true) {
byte[] inflateOutputBuffer = outputBuffer.array();
int inflateOutputBufferOffset = outputBuffer.position();
int inflateOutputBufferLength = outputBuffer.limit() - inflateOutputBufferOffset;
try {
bytesInflated = decompressor.inflate(inflateOutputBuffer, inflateOutputBufferOffset, inflateOutputBufferLength);
}
catch (DataFormatException e) {
throw new IOException(e);
}
outputBuffer.position(inflateOutputBufferOffset + bytesInflated);
decompressorOutBytes += bytesInflated;
if (decompressor.needsInput()) {
break;
}
int increasedBufferSize = outputBuffer.capacity() * 2;
ByteBuffer increasedOutputBuffer = ByteBuffer.allocate(increasedBufferSize);
outputBuffer.flip();
increasedOutputBuffer.put(outputBuffer);
outputBuffer = increasedOutputBuffer;
}
if (bytesInflated == 0) {
return null;
}
maxInputOutput = Math.max(outputBuffer.position(), maxInputOutput);
return outputBuffer;
}
@Override
public Stats getStats() {
return new Stats(this);
}
}
public static final class Stats {
public final long compressorInBytes;
public final long compressorOutBytes;
public final double compressionRatio;
public final long decompressorInBytes;
public final long decompressorOutBytes;
public final double decompressionRatio;
public final int maxOutputOutput;
public final int maxInputOutput;
public final int maxBytesWrittenAfterFullFlush;
private Stats(ZlibXmppInputOutputFilter filter) {
// Note that we read the out bytes before the in bytes to not over approximate the compression ratio.
compressorOutBytes = filter.compressorOutBytes;
compressorInBytes = filter.compressorInBytes;
compressionRatio = (double) compressorOutBytes / compressorInBytes;
decompressorOutBytes = filter.decompressorOutBytes;
decompressorInBytes = filter.decompressorInBytes;
decompressionRatio = (double) decompressorInBytes / decompressorOutBytes;
maxOutputOutput = filter.maxOutputOutput;
maxInputOutput = filter.maxInputOutput;
maxBytesWrittenAfterFullFlush = filter.maxBytesWrittenAfterFullFlush;
}
private transient String toStringCache;
@Override
public String toString() {
if (toStringCache != null) {
return toStringCache;
}
toStringCache =
"compressor-in-bytes: " + compressorInBytes + '\n'
+ "compressor-out-bytes: " + compressorOutBytes + '\n'
+ "compression-ratio: " + compressionRatio + '\n'
+ "decompressor-in-bytes: " + decompressorInBytes + '\n'
+ "decompressor-out-bytes: " + decompressorOutBytes + '\n'
+ "decompression-ratio: " + decompressionRatio + '\n'
+ "max-output-output: " + maxOutputOutput + '\n'
+ "max-input-output: " + maxInputOutput + '\n'
+ "max-bytes-written-after-full-flush: " + maxBytesWrittenAfterFullFlush + '\n'
;
return toStringCache;
}
}
}

View file

@ -0,0 +1,21 @@
/**
*
* Copyright 2015 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Smack classes for compression (XEP-0138) using zlib (RFC 1950).
*/
package org.jivesoftware.smack.compression.zlib;

View file

@ -16,8 +16,6 @@
*/ */
package org.jivesoftware.smack.debugger; package org.jivesoftware.smack.debugger;
import java.io.Reader;
import java.io.Writer;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.jivesoftware.smack.AbstractConnectionListener; import org.jivesoftware.smack.AbstractConnectionListener;
@ -133,21 +131,13 @@ public abstract class AbstractDebugger extends SmackDebugger {
protected abstract void log(String logMessage, Throwable throwable); protected abstract void log(String logMessage, Throwable throwable);
@Override @Override
public Reader newConnectionReader(Reader newReader) { public final void outgoingStreamSink(CharSequence outgoingCharSequence) {
reader.removeReaderListener(readerListener); log("SENT (" + connection.getConnectionCounter() + "): " + outgoingCharSequence);
ObservableReader debugReader = new ObservableReader(newReader);
debugReader.addReaderListener(readerListener);
reader = debugReader;
return reader;
} }
@Override @Override
public Writer newConnectionWriter(Writer newWriter) { public final void incomingStreamSink(CharSequence incomingCharSequence) {
writer.removeWriterListener(writerListener); log("RECV (" + connection.getConnectionCounter() + "): " + incomingCharSequence);
ObservableWriter debugWriter = new ObservableWriter(newWriter);
debugWriter.addWriterListener(writerListener);
writer = debugWriter;
return writer;
} }
@Override @Override

View file

@ -17,13 +17,18 @@
package org.jivesoftware.smack.debugger; package org.jivesoftware.smack.debugger;
import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.io.Writer; import java.io.Writer;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.TopLevelStreamElement; import org.jivesoftware.smack.packet.TopLevelStreamElement;
import org.jivesoftware.smack.util.ObservableReader;
import org.jivesoftware.smack.util.ObservableWriter;
import org.jxmpp.jid.EntityFullJid; import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.xml.splitter.XmlPrettyPrinter;
import org.jxmpp.xml.splitter.XmppXmlSplitter;
/** /**
* Interface that allows for implementing classes to debug XML traffic. That is a GUI window that * Interface that allows for implementing classes to debug XML traffic. That is a GUI window that
@ -38,6 +43,9 @@ public abstract class SmackDebugger {
protected final XMPPConnection connection; protected final XMPPConnection connection;
private XmppXmlSplitter outgoingStreamSplitterForPrettyPrinting;
private XmppXmlSplitter incomingStreamSplitterForPrettyPrinting;
protected SmackDebugger(XMPPConnection connection) { protected SmackDebugger(XMPPConnection connection) {
this.connection = connection; this.connection = connection;
} }
@ -52,6 +60,21 @@ public abstract class SmackDebugger {
// TODO: Should be replaced with a connection listener authenticed(). // TODO: Should be replaced with a connection listener authenticed().
public abstract void userHasLogged(EntityFullJid user); public abstract void userHasLogged(EntityFullJid user);
/**
* Note that the sequence of characters may be pretty printed.
*
* @param outgoingCharSequence the outgoing character sequence.
*/
public abstract void outgoingStreamSink(CharSequence outgoingCharSequence);
public void onOutgoingElementCompleted() {
}
public abstract void incomingStreamSink(CharSequence incomingCharSequence);
public void onIncomingElementCompleted() {
}
/** /**
* Returns a new special Reader that wraps the new connection Reader. The connection * Returns a new special Reader that wraps the new connection Reader. The connection
* has been secured so the connection is using a new reader and writer. The debugger * has been secured so the connection is using a new reader and writer. The debugger
@ -61,7 +84,23 @@ public abstract class SmackDebugger {
* @param reader connection reader. * @param reader connection reader.
* @return a new special Reader that wraps the new connection Reader. * @return a new special Reader that wraps the new connection Reader.
*/ */
public abstract Reader newConnectionReader(Reader reader); public final Reader newConnectionReader(Reader reader) {
XmlPrettyPrinter xmlPrettyPrinter = XmlPrettyPrinter.builder()
.setPrettyWriter((sb) -> incomingStreamSink(sb))
.build();
incomingStreamSplitterForPrettyPrinting = new XmppXmlSplitter(xmlPrettyPrinter);
ObservableReader observableReader = new ObservableReader(reader);
observableReader.addReaderListener((readString) -> {
try {
incomingStreamSplitterForPrettyPrinting.append(readString);
}
catch (IOException e) {
throw new AssertionError(e);
}
});
return observableReader;
}
/** /**
* Returns a new special Writer that wraps the new connection Writer. The connection * Returns a new special Writer that wraps the new connection Writer. The connection
@ -72,7 +111,23 @@ public abstract class SmackDebugger {
* @param writer connection writer. * @param writer connection writer.
* @return a new special Writer that wraps the new connection Writer. * @return a new special Writer that wraps the new connection Writer.
*/ */
public abstract Writer newConnectionWriter(Writer writer); public final Writer newConnectionWriter(Writer writer) {
XmlPrettyPrinter xmlPrettyPrinter = XmlPrettyPrinter.builder()
.setPrettyWriter((sb) -> outgoingStreamSink(sb))
.build();
outgoingStreamSplitterForPrettyPrinting = new XmppXmlSplitter(xmlPrettyPrinter);
ObservableWriter observableWriter = new ObservableWriter(writer);
observableWriter.addWriterListener((writtenString) -> {
try {
outgoingStreamSplitterForPrettyPrinting.append(writtenString);
}
catch (IOException e) {
throw new AssertionError(e);
}
});
return observableWriter;
}
/** /**
* Used by the connection to notify about an incoming top level stream element. * Used by the connection to notify about an incoming top level stream element.

View file

@ -0,0 +1,816 @@
/**
*
* Copyright 2018-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.fsm;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.net.ssl.SSLSession;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
import org.jivesoftware.smack.XMPPException.StreamErrorException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.XmppInputOutputFilter;
import org.jivesoftware.smack.compress.packet.Compress;
import org.jivesoftware.smack.compress.packet.Compressed;
import org.jivesoftware.smack.compress.packet.Failure;
import org.jivesoftware.smack.compression.XmppCompressionFactory;
import org.jivesoftware.smack.compression.XmppCompressionManager;
import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.StreamError;
import org.jivesoftware.smack.sasl.SASLErrorException;
import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Challenge;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jxmpp.jid.parts.Resourcepart;
import org.xmlpull.v1.XmlPullParser;
public abstract class AbstractXmppStateMachineConnection extends AbstractXMPPConnection {
private final List<ConnectionStateMachineListener> connectionStateMachineListeners = new CopyOnWriteArrayList<>();
private boolean featuresReceived;
protected boolean streamResumed;
private GraphVertex<State> currentStateVertex;
private List<State> walkFromDisconnectToAuthenticated;
private final List<XmppInputOutputFilter> inputOutputFilters = new CopyOnWriteArrayList<>();
private List<XmppInputOutputFilter> previousInputOutputFilters;
protected AbstractXmppStateMachineConnection(ConnectionConfiguration configuration, GraphVertex<StateDescriptor> initialStateDescriptorVertex) {
super(configuration);
currentStateVertex = StateDescriptorGraph.convertToStateGraph(initialStateDescriptorVertex, this);
}
@Override
protected void loginInternal(String username, String password, Resourcepart resource)
throws XMPPException, SmackException, IOException, InterruptedException {
WalkStateGraphContext walkStateGraphContext = buildNewWalkTo(AuthenticatedAndResourceBoundStateDescriptor.class)
.withLoginContext(username, password, resource)
.build();
walkStateGraph(walkStateGraphContext);
}
protected static WalkStateGraphContextBuilder buildNewWalkTo(Class<? extends StateDescriptor> finalStateClass) {
return new WalkStateGraphContextBuilder(finalStateClass);
}
protected static final class WalkStateGraphContext {
private final Class<? extends StateDescriptor> finalStateClass;
private final Class<? extends StateDescriptor> mandatoryIntermediateState;
private final LoginContext loginContext;
private final List<State> walkedStateGraphPath = new ArrayList<>();
/**
* A linked Map of failed States with their reason as value.
*/
private final Map<State, TransitionReason> failedStates = new LinkedHashMap<>();
private boolean mandatoryIntermediateStateHandled;
private WalkStateGraphContext(Class<? extends StateDescriptor> finalStateClass, Class<? extends StateDescriptor> mandatoryIntermedidateState, LoginContext loginContext) {
this.finalStateClass = Objects.requireNonNull(finalStateClass);
this.mandatoryIntermediateState = mandatoryIntermedidateState;
this.loginContext = loginContext;
}
public boolean isFinalStateAuthenticatedAndResourceBound() {
return finalStateClass == AuthenticatedAndResourceBoundStateDescriptor.class;
}
}
protected static final class WalkStateGraphContextBuilder {
private final Class<? extends StateDescriptor> finalStateClass;
private Class<? extends StateDescriptor> mandatoryIntermedidateState;
private LoginContext loginContext;
private WalkStateGraphContextBuilder(Class<? extends StateDescriptor> finalStateClass) {
this.finalStateClass = finalStateClass;
}
public WalkStateGraphContextBuilder withMandatoryIntermediateState(Class<? extends StateDescriptor> mandatoryIntermedidateState) {
this.mandatoryIntermedidateState = mandatoryIntermedidateState;
return this;
}
public WalkStateGraphContextBuilder withLoginContext(String username, String password, Resourcepart resource) {
LoginContext loginContext = new LoginContext(username, password, resource);
return withLoginContext(loginContext);
}
public WalkStateGraphContextBuilder withLoginContext(LoginContext loginContext) {
this.loginContext = loginContext;
return this;
}
public WalkStateGraphContext build() {
return new WalkStateGraphContext(finalStateClass, mandatoryIntermedidateState, loginContext);
}
}
protected final void walkStateGraph(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException, SASLErrorException,
FailedNonzaException, IOException, SmackException, InterruptedException {
// Save a copy of the current state
GraphVertex<State> previousStateVertex = currentStateVertex;
try {
walkStateGraphInternal(walkStateGraphContext);
}
catch (XMPPErrorException | SASLErrorException | FailedNonzaException | IOException | SmackException
| InterruptedException e) {
currentStateVertex = previousStateVertex;
// Reset that state.
State revertedState = currentStateVertex.getElement();
invokeConnectionStateMachineListener(new ConnectionStateEvent.StateRevertBackwardsWalk(revertedState));
revertedState.resetState();
throw e;
}
}
private void walkStateGraphInternal(WalkStateGraphContext walkStateGraphContext)
throws XMPPErrorException, SASLErrorException, IOException, SmackException, InterruptedException, FailedNonzaException {
// Save a copy of the current state
final GraphVertex<State> initialStateVertex = currentStateVertex;
final State initialState = initialStateVertex.getElement();
final StateDescriptor initialStateDescriptor = initialState.getStateDescriptor();
walkStateGraphContext.walkedStateGraphPath.add(initialState);
if (initialStateDescriptor.getClass() == walkStateGraphContext.finalStateClass) {
// If this is used as final state, then it should be marked as such.
assert (initialStateDescriptor.isFinalState());
// We reached the final state.
invokeConnectionStateMachineListener(new ConnectionStateEvent.FinalStateReached(initialState));
return;
}
List<GraphVertex<State>> outgoingStateEdges = currentStateVertex.getOutgoingEdges();
// See if we need to handle mandatory intermediate states.
if (walkStateGraphContext.mandatoryIntermediateState != null && !walkStateGraphContext.mandatoryIntermediateStateHandled) {
// Check if outgoingStateEdges contains the mandatory intermediate state.
GraphVertex<State> mandatoryIntermediateStateVertex = null;
for (GraphVertex<State> outgoingStateVertex : outgoingStateEdges) {
if (outgoingStateVertex.getElement().getStateDescriptor().getClass() == walkStateGraphContext.mandatoryIntermediateState) {
mandatoryIntermediateStateVertex = outgoingStateVertex;
break;
}
}
if (mandatoryIntermediateStateVertex != null) {
walkStateGraphContext.mandatoryIntermediateStateHandled = true;
TransitionReason reason = attemptEnterState(mandatoryIntermediateStateVertex, walkStateGraphContext);
if (reason instanceof TransitionSuccessResult) {
walkStateGraph(walkStateGraphContext);
return;
}
// We could not enter a mandatory intermediate state. Throw here.
throw new StateMachineException.SmackMandatoryStateFailedException(
mandatoryIntermediateStateVertex.getElement(), reason);
}
}
for (Iterator<GraphVertex<State>> it = outgoingStateEdges.iterator(); it.hasNext();) {
GraphVertex<State> successorStateVertex = it.next();
State successorState = successorStateVertex.getElement();
TransitionReason reason = attemptEnterState(successorStateVertex, walkStateGraphContext);
if (reason instanceof TransitionSuccessResult) {
break;
}
// If attemptEnterState did not throw and did not return a value of type TransitionSuccessResult, then we
// just record this value and go on from there. Note that reason may be null, which is returned by
// attemptEnterState in case the state was already successfully handled. If this is the case, then we don't
// record it.
if (reason != null) {
walkStateGraphContext.failedStates.put(successorState, reason);
}
if (!it.hasNext()) {
throw new StateMachineException.SmackStateGraphDeadEndException(walkStateGraphContext.walkedStateGraphPath, walkStateGraphContext.failedStates);
}
}
// Walk the state graph by recursion.
walkStateGraph(walkStateGraphContext);
}
private TransitionReason attemptEnterState(GraphVertex<State> successorStateVertex,
WalkStateGraphContext walkStateGraphContext)
throws SmackException, XMPPErrorException, SASLErrorException, IOException, InterruptedException, FailedNonzaException {
final State successorState = successorStateVertex.getElement();
final StateDescriptor successorStateDescriptor = successorState.getStateDescriptor();
if (!successorStateDescriptor.isMultiVisitState() && walkStateGraphContext.walkedStateGraphPath.contains(successorState)) {
// This can happen if a state leads back to the state where it originated from. See for example the
// 'Compression' state. We return 'null' here to signal that the state can safely be ignored.
return null;
}
if (successorStateDescriptor.isNotImplemented()) {
TransitionImpossibleBecauseNotImplemented transtionImpossibleBecauseNotImplemented = new TransitionImpossibleBecauseNotImplemented(
successorStateDescriptor);
invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionNotPossible(successorState,
transtionImpossibleBecauseNotImplemented));
return transtionImpossibleBecauseNotImplemented;
}
final TransitionIntoResult transitionIntoResult;
try {
TransitionImpossibleReason transitionImpossibleReason = successorState.isTransitionToPossible(walkStateGraphContext);
if (transitionImpossibleReason != null) {
invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionNotPossible(successorState,
transitionImpossibleReason));
return transitionImpossibleReason;
}
invokeConnectionStateMachineListener(new ConnectionStateEvent.AboutToTransitionInto(successorState));
transitionIntoResult = successorState.transitionInto(walkStateGraphContext);
} catch (SmackException | XMPPErrorException | SASLErrorException | IOException | InterruptedException
| FailedNonzaException e) {
// TODO Document why this is required given that there is another call site of resetState().
invokeConnectionStateMachineListener(new ConnectionStateEvent.StateRevertBackwardsWalk(successorState));
successorState.resetState();
throw e;
}
if (transitionIntoResult instanceof TransitionFailureResult) {
TransitionFailureResult transitionFailureResult = (TransitionFailureResult) transitionIntoResult;
invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionFailed(successorState, transitionFailureResult));
return transitionIntoResult;
}
// If transitionIntoResult is not an instance of TransitionFailureResult, then it has to be of type
// TransitionSuccessResult.
TransitionSuccessResult transitionSuccessResult = (TransitionSuccessResult) transitionIntoResult;
currentStateVertex = successorStateVertex;
invokeConnectionStateMachineListener(new ConnectionStateEvent.SuccessfullyTransitionedInto(successorState,
transitionSuccessResult));
return transitionSuccessResult;
}
protected abstract SSLSession getSSLSession();
@Override
protected void afterFeaturesReceived() {
featuresReceived = true;
synchronized (this) {
notifyAll();
}
}
protected final void parseAndProcessElement(String element) throws Exception {
XmlPullParser parser = PacketParserUtils.getParserFor(element);
// Skip the enclosing stream open what is guaranteed to be there.
parser.next();
int event = parser.getEventType();
outerloop: while (true) {
switch (event) {
case XmlPullParser.START_TAG:
final String name = parser.getName();
// Note that we don't handle "stream" here as it's done in the splitter.
switch (name) {
case Message.ELEMENT:
case IQ.IQ_ELEMENT:
case Presence.ELEMENT:
try {
parseAndProcessStanza(parser);
} finally {
// TODO: Here would be the following stream management code.
// clientHandledStanzasCount = SMUtils.incrementHeight(clientHandledStanzasCount);
}
break;
case "error":
StreamError streamError = PacketParserUtils.parseStreamError(parser);
saslFeatureReceived.reportFailure(new StreamErrorException(streamError));
throw new StreamErrorException(streamError);
case "features":
parseFeatures(parser);
afterFeaturesReceived();
break;
// SASL related top level stream elements
case Challenge.ELEMENT:
// The server is challenging the SASL authentication made by the client
String challengeData = parser.nextText();
getSASLAuthentication().challengeReceived(challengeData);
break;
case Success.ELEMENT:
Success success = new Success(parser.nextText());
// The SASL authentication with the server was successful. The next step
// will be to bind the resource
getSASLAuthentication().authenticated(success);
sendStreamOpen();
break;
default:
parseAndProcessNonza(parser);
break;
}
break;
case XmlPullParser.END_DOCUMENT:
break outerloop;
}
event = parser.next();
}
}
protected synchronized void prepareToWaitForFeaturesReceived() {
featuresReceived = false;
}
protected void waitForFeaturesReceived(String waitFor)
throws InterruptedException, ConnectionUnexpectedTerminatedException, NoResponseException {
long waitStartMs = System.currentTimeMillis();
long timeoutMs = getReplyTimeout();
synchronized (this) {
while (!featuresReceived && currentConnectionException == null) {
long remainingWaitMs = timeoutMs - (System.currentTimeMillis() - waitStartMs);
if (remainingWaitMs <= 0) {
throw NoResponseException.newWith(this, waitFor);
}
wait(remainingWaitMs);
}
if (currentConnectionException != null) {
throw new SmackException.ConnectionUnexpectedTerminatedException(currentConnectionException);
}
}
}
protected void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException {
prepareToWaitForFeaturesReceived();
sendStreamOpen();
waitForFeaturesReceived(waitFor);
}
protected final void addXmppInputOutputFilter(XmppInputOutputFilter xmppInputOutputFilter) {
inputOutputFilters.add(0, xmppInputOutputFilter);
}
protected final ListIterator<XmppInputOutputFilter> getXmppInputOutputFilterBeginIterator() {
return inputOutputFilters.listIterator();
}
protected final ListIterator<XmppInputOutputFilter> getXmppInputOutputFilterEndIterator() {
return inputOutputFilters.listIterator(inputOutputFilters.size());
}
protected final synchronized List<Object> getFilterStats() {
Collection<XmppInputOutputFilter> filters;
if (inputOutputFilters.isEmpty() && previousInputOutputFilters != null) {
filters = previousInputOutputFilters;
} else {
filters = inputOutputFilters;
}
List<Object> filterStats = new ArrayList<>(filters.size());
for (XmppInputOutputFilter xmppInputOutputFilter : filters) {
Object stats = xmppInputOutputFilter.getStats();
if (stats != null) {
filterStats.add(stats);
}
}
return Collections.unmodifiableList(filterStats);
}
protected abstract class State {
private final StateDescriptor stateDescriptor;
protected State(StateDescriptor stateDescriptor) {
this.stateDescriptor = stateDescriptor;
}
/**
* Check if the state should be activated.
*
* @return <code>null</code> if the state should be activated.
* @throws SmackException in case a Smack exception occurs.
*/
protected TransitionImpossibleReason isTransitionToPossible(WalkStateGraphContext walkStateGraphContext) throws SmackException {
return null;
}
protected abstract TransitionIntoResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws XMPPErrorException, SASLErrorException, IOException, SmackException, InterruptedException, FailedNonzaException;
StateDescriptor getStateDescriptor() {
return stateDescriptor;
}
protected void resetState() {
}
@Override
public String toString() {
return "State " + stateDescriptor + ' ' + AbstractXmppStateMachineConnection.this;
}
protected final void ensureNotOnOurWayToAuthenticatedAndResourceBound(WalkStateGraphContext walkStateGraphContext) {
if (walkStateGraphContext.isFinalStateAuthenticatedAndResourceBound()) {
throw new IllegalStateException(
"Smack should never attempt to reach the authenticated and resource bound state over " + this
+ ". This is probably a programming error within Smack, please report it to the develoeprs.");
}
}
}
abstract static class TransitionReason {
public final String reason;
private TransitionReason(String reason) {
this.reason = reason;
}
@Override
public final String toString() {
return reason;
}
}
protected static class TransitionImpossibleReason extends TransitionReason {
public TransitionImpossibleReason(String reason) {
super(reason);
}
}
protected static class TransitionImpossibleBecauseNotImplemented extends TransitionImpossibleReason {
public TransitionImpossibleBecauseNotImplemented(StateDescriptor stateDescriptor) {
super(stateDescriptor.getFullStateName(false) + " is not implemented (yet)");
}
}
protected abstract static class TransitionIntoResult extends TransitionReason {
public TransitionIntoResult(String reason) {
super(reason);
}
}
public static class TransitionSuccessResult extends TransitionIntoResult {
public static final TransitionSuccessResult EMPTY_INSTANCE = new TransitionSuccessResult();
private TransitionSuccessResult() {
super("");
}
public TransitionSuccessResult(String reason) {
super(reason);
}
}
public static final class TransitionFailureResult extends TransitionIntoResult {
private TransitionFailureResult(String reason) {
super(reason);
}
}
protected final class NoOpState extends State {
private NoOpState(StateDescriptor stateDescriptor) {
super(stateDescriptor);
}
@Override
protected TransitionImpossibleReason isTransitionToPossible(WalkStateGraphContext walkStateGraphContext) {
// Transition into a NoOpState is always possible.
return null;
}
@Override
protected TransitionIntoResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
// Transition into a NoOpState always succeeds.
return TransitionSuccessResult.EMPTY_INSTANCE;
}
}
protected static class DisconnectedStateDescriptor extends StateDescriptor {
protected DisconnectedStateDescriptor() {
super(DisconnectedState.class, StateDescriptor.Property.finalState);
}
}
private final class DisconnectedState extends State {
private DisconnectedState(StateDescriptor stateDescriptor) {
super(stateDescriptor);
}
@Override
protected TransitionIntoResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
if (inputOutputFilters.isEmpty()) {
previousInputOutputFilters = null;
} else {
previousInputOutputFilters = new ArrayList<>(inputOutputFilters.size());
previousInputOutputFilters.addAll(inputOutputFilters);
inputOutputFilters.clear();
}
ListIterator<State> it = walkFromDisconnectToAuthenticated.listIterator(
walkFromDisconnectToAuthenticated.size());
while (it.hasPrevious()) {
State stateToReset = it.previous();
stateToReset.resetState();
}
walkFromDisconnectToAuthenticated = null;
return TransitionSuccessResult.EMPTY_INSTANCE;
}
}
protected static final class ConnectedButUnauthenticatedStateDescriptor extends StateDescriptor {
private ConnectedButUnauthenticatedStateDescriptor() {
super(ConnectedButUnauthenticatedState.class, StateDescriptor.Property.finalState);
addSuccessor(SaslAuthenticationStateDescriptor.class);
}
}
private final class ConnectedButUnauthenticatedState extends State {
private ConnectedButUnauthenticatedState(StateDescriptor stateDescriptor) {
super(stateDescriptor);
}
@Override
protected TransitionIntoResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
assert (walkFromDisconnectToAuthenticated == null);
if (getStateDescriptor().getClass() == walkStateGraphContext.finalStateClass) {
// If this is the final state, then record the walk so far.
walkFromDisconnectToAuthenticated = new ArrayList<>(walkStateGraphContext.walkedStateGraphPath);
}
connected = true;
return TransitionSuccessResult.EMPTY_INSTANCE;
}
@Override
protected void resetState() {
connected = false;
}
}
protected static final class SaslAuthenticationStateDescriptor extends StateDescriptor {
private SaslAuthenticationStateDescriptor() {
super(SaslAuthenticationState.class, "RFC 6120 § 6");
addSuccessor(AuthenticatedButUnboundStateDescriptor.class);
}
}
private final class SaslAuthenticationState extends State {
private SaslAuthenticationState(StateDescriptor stateDescriptor) {
super(stateDescriptor);
}
@Override
protected TransitionIntoResult transitionInto(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException,
SASLErrorException, IOException, SmackException, InterruptedException {
prepareToWaitForFeaturesReceived();
LoginContext loginContext = walkStateGraphContext.loginContext;
SASLMechanism usedSaslMechanism = saslAuthentication.authenticate(loginContext.username, loginContext.password, config.getAuthzid(), getSSLSession());
// authenticate() will only return if the SASL authentication was successful, but we also need to wait for the next round of stream features.
waitForFeaturesReceived("server stream features after SASL authentication");
return new SaslAuthenticationSuccessResult(usedSaslMechanism);
}
}
public static final class SaslAuthenticationSuccessResult extends TransitionSuccessResult {
private final String saslMechanismName;
private SaslAuthenticationSuccessResult(SASLMechanism usedSaslMechanism) {
super("SASL authentication successfull using " + usedSaslMechanism.getName());
this.saslMechanismName = usedSaslMechanism.getName();
}
public String getSaslMechanismName() {
return saslMechanismName;
}
}
protected static final class AuthenticatedButUnboundStateDescriptor extends StateDescriptor {
private AuthenticatedButUnboundStateDescriptor() {
super(StateDescriptor.Property.multiVisitState);
addSuccessor(ResourceBindingStateDescriptor.class);
addSuccessor(CompressionStateDescriptor.class);
}
}
protected static final class ResourceBindingStateDescriptor extends StateDescriptor {
private ResourceBindingStateDescriptor() {
super(ResourceBindingState.class, "RFC 6120 § 7");
addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
}
}
private final class ResourceBindingState extends State {
private ResourceBindingState(StateDescriptor stateDescriptor) {
super(stateDescriptor);
}
@Override
protected TransitionIntoResult transitionInto(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException,
SASLErrorException, IOException, SmackException, InterruptedException {
// TODO: The reportSuccess() is just a quick fix until there is a variant of the
// bindResourceAndEstablishSession() method which does not require this.
lastFeaturesReceived.reportSuccess();
LoginContext loginContext = walkStateGraphContext.loginContext;
Resourcepart resource = bindResourceAndEstablishSession(loginContext.resource);
streamResumed = false;
return new ResourceBoundResult(resource, loginContext.resource);
}
}
public static final class ResourceBoundResult extends TransitionSuccessResult {
private final Resourcepart resource;
private ResourceBoundResult(Resourcepart boundResource, Resourcepart requestedResource) {
super("Resource '" + boundResource + "' bound (requested: '" + requestedResource + "'");
this.resource = boundResource;
}
public Resourcepart getResource() {
return resource;
}
}
protected static final class CompressionStateDescriptor extends StateDescriptor {
private CompressionStateDescriptor() {
super(CompressionState.class, 138);
addSuccessor(AuthenticatedButUnboundStateDescriptor.class);
declarePrecedenceOver(ResourceBindingStateDescriptor.class);
}
}
private boolean compressionEnabled;
private class CompressionState extends State {
private XmppCompressionFactory selectedCompressionFactory;
private XmppInputOutputFilter usedXmppInputOutputCompressionFitler;
protected CompressionState(StateDescriptor stateDescriptor) {
super(stateDescriptor);
}
@Override
protected TransitionImpossibleReason isTransitionToPossible(WalkStateGraphContext walkStateGraphContext) {
if (!config.isCompressionEnabled()) {
return new TransitionImpossibleReason("Stream compression disabled");
}
Compress.Feature compressFeature = getFeature(Compress.Feature.ELEMENT, Compress.NAMESPACE);
if (compressFeature == null) {
return new TransitionImpossibleReason("Stream compression not supported");
}
selectedCompressionFactory = XmppCompressionManager.getBestFactory(compressFeature);
if (selectedCompressionFactory == null) {
return new TransitionImpossibleReason("No matching compression factory");
}
usedXmppInputOutputCompressionFitler = selectedCompressionFactory.fabricate(config);
return null;
}
@Override
protected TransitionIntoResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws NoResponseException, NotConnectedException, FailedNonzaException, InterruptedException,
ConnectionUnexpectedTerminatedException {
final String compressionMethod = selectedCompressionFactory.getCompressionMethod();
sendAndWaitForResponse(new Compress(compressionMethod), Compressed.class, Failure.class);
addXmppInputOutputFilter(usedXmppInputOutputCompressionFitler);
newStreamOpenWaitForFeaturesSequence("server stream features after compression enabled");
compressionEnabled = true;
return new CompressionTransitionSuccessResult(compressionMethod);
}
@Override
protected void resetState() {
selectedCompressionFactory = null;
usedXmppInputOutputCompressionFitler = null;
compressionEnabled = false;
}
}
public static final class CompressionTransitionSuccessResult extends TransitionSuccessResult {
private final String compressionMethod;
private CompressionTransitionSuccessResult(String compressionMethod) {
super(compressionMethod + " compression enabled");
this.compressionMethod = compressionMethod;
}
public String getCompressionMethod() {
return compressionMethod;
}
}
@Override
public final boolean isUsingCompression() {
return compressionEnabled;
}
protected static final class AuthenticatedAndResourceBoundStateDescriptor extends StateDescriptor {
private AuthenticatedAndResourceBoundStateDescriptor() {
super(AuthenticatedAndResourceBoundState.class, StateDescriptor.Property.finalState);
}
}
private final class AuthenticatedAndResourceBoundState extends State {
private AuthenticatedAndResourceBoundState(StateDescriptor stateDescriptor) {
super(stateDescriptor);
}
@Override
protected TransitionIntoResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws NotConnectedException, InterruptedException {
if (walkFromDisconnectToAuthenticated != null) {
// If there was already a previous walk to ConnectedButUnauthenticated, then the context of the current
// walk must not start from the 'Disconnected' state.
assert (walkStateGraphContext.walkedStateGraphPath.get(0).stateDescriptor.getClass() != DisconnectedStateDescriptor.class);
walkFromDisconnectToAuthenticated.addAll(walkStateGraphContext.walkedStateGraphPath);
} else {
walkFromDisconnectToAuthenticated = new ArrayList<>(walkStateGraphContext.walkedStateGraphPath.size() + 1);
walkFromDisconnectToAuthenticated.addAll(walkStateGraphContext.walkedStateGraphPath);
}
walkFromDisconnectToAuthenticated.add(this);
afterSuccessfulLogin(streamResumed);
return TransitionSuccessResult.EMPTY_INSTANCE;
}
@Override
protected void resetState() {
authenticated = false;
}
}
public void addConnectionStateMachineListener(ConnectionStateMachineListener connectionStateMachineListener) {
connectionStateMachineListeners.add(connectionStateMachineListener);
}
public boolean removeConnectionStateMachineListener(ConnectionStateMachineListener connectionStateMachineListener) {
return connectionStateMachineListeners.remove(connectionStateMachineListener);
}
protected void invokeConnectionStateMachineListener(ConnectionStateEvent connectionStateEvent) {
if (connectionStateMachineListeners.isEmpty()) {
return;
}
ASYNC_BUT_ORDERED.performAsyncButOrdered(this, () -> {
for (ConnectionStateMachineListener connectionStateMachineListener : connectionStateMachineListeners) {
connectionStateMachineListener.onConnectionStateEvent(connectionStateEvent, this);
}
});
}
}

View file

@ -0,0 +1,113 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.fsm;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.State;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.TransitionFailureResult;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.TransitionImpossibleReason;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.TransitionSuccessResult;
public class ConnectionStateEvent {
private final StateDescriptor stateDescriptor;
private final long timestamp;
protected ConnectionStateEvent(StateDescriptor stateDescriptor) {
this.stateDescriptor = stateDescriptor;
this.timestamp = System.currentTimeMillis();
}
public StateDescriptor getStateDescriptor() {
return stateDescriptor;
}
@Override
public String toString() {
return stateDescriptor.getStateName() + ' ' + getClass().getSimpleName();
}
public long getTimestamp() {
return timestamp;
}
public static class StateRevertBackwardsWalk extends ConnectionStateEvent {
StateRevertBackwardsWalk(State state) {
super(state.getStateDescriptor());
}
}
public static class FinalStateReached extends ConnectionStateEvent {
FinalStateReached(State state) {
super(state.getStateDescriptor());
}
}
public static class TransitionNotPossible extends ConnectionStateEvent {
private final TransitionImpossibleReason transitionImpossibleReason;
TransitionNotPossible(State state, TransitionImpossibleReason reason) {
super(state.getStateDescriptor());
this.transitionImpossibleReason = reason;
}
@Override
public String toString() {
return super.toString() + ": " + transitionImpossibleReason;
}
}
public static class AboutToTransitionInto extends ConnectionStateEvent {
AboutToTransitionInto(State state) {
super(state.getStateDescriptor());
}
}
public static class TransitionFailed extends ConnectionStateEvent {
private final TransitionFailureResult transitionFailedReason;
TransitionFailed(State state, TransitionFailureResult transitionFailedReason) {
super(state.getStateDescriptor());
this.transitionFailedReason = transitionFailedReason;
}
@Override
public String toString() {
return super.toString() + ": " + transitionFailedReason;
}
}
public static class SuccessfullyTransitionedInto extends ConnectionStateEvent {
private final TransitionSuccessResult transitionSuccessResult;
SuccessfullyTransitionedInto(State state, TransitionSuccessResult transitionSuccessResult) {
super(state.getStateDescriptor());
this.transitionSuccessResult = transitionSuccessResult;
}
@Override
public String toString() {
return super.toString() + ": " + transitionSuccessResult;
}
}
public abstract static class DetailedTransitionIntoInformation extends ConnectionStateEvent {
protected DetailedTransitionIntoInformation(State state) {
super(state.getStateDescriptor());
}
}
}

View file

@ -0,0 +1,24 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.fsm;
// TODO: Mark as java.lang.FunctionalInterface once Smack's minimum Android API level is 24 or higher.
public interface ConnectionStateMachineListener {
void onConnectionStateEvent(ConnectionStateEvent connectionStateEvent, AbstractXmppStateMachineConnection connection);
}

View file

@ -0,0 +1,33 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.fsm;
import org.jxmpp.jid.parts.Resourcepart;
// TODO: At one point SASL authzid should be part of this.
public class LoginContext {
final String username;
final String password;
final Resourcepart resource;
LoginContext(String username, String password, Resourcepart resource) {
this.username = username;
this.password = password;
this.resource = resource;
}
}

View file

@ -0,0 +1,221 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.fsm;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.State;
public abstract class StateDescriptor {
public enum Property {
multiVisitState,
finalState,
notImplemented,
}
private static final Logger LOGGER = Logger.getLogger(StateDescriptor.class.getName());
private final String stateName;
private final int xepNum;
private final String rfcSection;
private final Set<Property> properties;
private final Class<? extends AbstractXmppStateMachineConnection.State> stateClass;
private final Constructor<? extends AbstractXmppStateMachineConnection.State> stateClassConstructor;
private final Set<Class<? extends StateDescriptor>> successors = new HashSet<>();
private final Set<Class<? extends StateDescriptor>> predecessors = new HashSet<>();
private final Set<Class<? extends StateDescriptor>> precedenceOver = new HashSet<>();
private final Set<Class<? extends StateDescriptor>> inferiorTo = new HashSet<>();
protected StateDescriptor() {
this(AbstractXmppStateMachineConnection.NoOpState.class, (Property) null);
}
protected StateDescriptor(Property... properties) {
this(AbstractXmppStateMachineConnection.NoOpState.class, properties);
}
protected StateDescriptor(Class<? extends AbstractXmppStateMachineConnection.State> stateClass) {
this(stateClass, -1, null, Collections.emptySet());
}
protected StateDescriptor(Class<? extends AbstractXmppStateMachineConnection.State> stateClass, Property... properties) {
this(stateClass, -1, null, new HashSet<>(Arrays.asList(properties)));
}
protected StateDescriptor(Class<? extends AbstractXmppStateMachineConnection.State> stateClass, int xepNum) {
this(stateClass, xepNum, null, Collections.emptySet());
}
protected StateDescriptor(Class<? extends AbstractXmppStateMachineConnection.State> stateClass, int xepNum,
Property... properties) {
this(stateClass, xepNum, null, new HashSet<>(Arrays.asList(properties)));
}
protected StateDescriptor(Class<? extends AbstractXmppStateMachineConnection.State> stateClass, String rfcSection) {
this(stateClass, -1, rfcSection, Collections.emptySet());
}
@SuppressWarnings("unchecked")
private StateDescriptor(Class<? extends AbstractXmppStateMachineConnection.State> stateClass, int xepNum,
String rfcSection, Set<Property> properties) {
this.stateClass = stateClass;
if (rfcSection != null && xepNum > 0) {
throw new IllegalArgumentException("Must specify either RFC or XEP");
}
this.xepNum = xepNum;
this.rfcSection = rfcSection;
this.properties = properties;
Constructor<? extends AbstractXmppStateMachineConnection.State> selectedConstructor = null;
Constructor<?>[] constructors = stateClass.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
Class<?>[] parameterTypes = constructor.getParameterTypes();
if (parameterTypes.length != 2) {
LOGGER.warning("Invalid State class constructor: " + constructor);
continue;
}
if (!AbstractXmppStateMachineConnection.class.isAssignableFrom(parameterTypes[0])) {
continue;
}
selectedConstructor = (Constructor<? extends State>) constructor;
break;
}
if (selectedConstructor == null) {
throw new IllegalArgumentException();
}
stateClassConstructor = selectedConstructor;
stateClassConstructor.setAccessible(true);
String className = getClass().getSimpleName();
stateName = className.replaceFirst("StateDescriptor", "");
}
protected void addSuccessor(Class<? extends StateDescriptor> successor) {
addAndCheckNonExistent(successors, successor);
}
protected void addPredeccessor(Class<? extends StateDescriptor> predeccessor) {
addAndCheckNonExistent(predecessors, predeccessor);
}
protected void declarePrecedenceOver(Class<? extends StateDescriptor> subordinate) {
addAndCheckNonExistent(precedenceOver, subordinate);
}
protected void declareInferiortyTo(Class<? extends StateDescriptor> superior) {
addAndCheckNonExistent(inferiorTo, superior);
}
private static <E> void addAndCheckNonExistent(Set<E> set, E e) {
boolean newElement = set.add(e);
if (!newElement) {
throw new IllegalArgumentException("Element already exists in set");
}
}
public Set<Class<? extends StateDescriptor>> getSuccessors() {
return Collections.unmodifiableSet(successors);
}
public Set<Class<? extends StateDescriptor>> getPredeccessors() {
return Collections.unmodifiableSet(predecessors);
}
public Set<Class<? extends StateDescriptor>> getSubordinates() {
return Collections.unmodifiableSet(precedenceOver);
}
public Set<Class<? extends StateDescriptor>> getSuperiors() {
return Collections.unmodifiableSet(inferiorTo);
}
public String getStateName() {
return stateName;
}
public String getFullStateName(boolean breakStateName) {
String reference = getReference();
if (reference != null) {
char sep;
if (breakStateName) {
sep = '\n';
} else {
sep = ' ';
}
return getStateName() + sep + '(' + reference + ')';
}
else {
return getStateName();
}
}
private transient String referenceCache;
public String getReference() {
if (referenceCache == null) {
if (xepNum > 0) {
referenceCache = "XEP-" + String.format("%04d", xepNum);
} else if (rfcSection != null) {
referenceCache = rfcSection;
}
}
return referenceCache;
}
public Class<? extends AbstractXmppStateMachineConnection.State> getStateClass() {
return stateClass;
}
public boolean isMultiVisitState() {
return properties.contains(Property.multiVisitState);
}
public boolean isNotImplemented() {
return properties.contains(Property.notImplemented);
}
public boolean isFinalState() {
return properties.contains(Property.finalState);
}
protected final AbstractXmppStateMachineConnection.State constructState(AbstractXmppStateMachineConnection connection) {
try {
return stateClassConstructor.newInstance(connection, this);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
@Override
public String toString() {
return "StateDescriptor " + stateName;
}
}

View file

@ -0,0 +1,419 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.fsm;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.DisconnectedStateDescriptor;
import org.jivesoftware.smack.util.MultiMap;
/**
* Smack's utility API for Finite State Machines (FSM).
*
* <p>
* Thanks to Andreas Fried for the fun and successful bug hunting session.
* </p>
*
* @author Florian Schmaus
*
*/
public class StateDescriptorGraph {
private static final Logger LOGGER = Logger.getLogger(StateDescriptorGraph.class.getName());
private static GraphVertex<StateDescriptor> addNewStateDescriptorGraphVertex(
Class<? extends StateDescriptor> stateDescriptorClass,
Map<Class<? extends StateDescriptor>, GraphVertex<StateDescriptor>> graphVertexes)
throws InstantiationException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException, NoSuchMethodException, SecurityException {
Constructor<? extends StateDescriptor> stateDescriptorConstructor = stateDescriptorClass.getDeclaredConstructor();
stateDescriptorConstructor.setAccessible(true);
StateDescriptor stateDescriptor = stateDescriptorConstructor.newInstance();
GraphVertex<StateDescriptor> graphVertexStateDescriptor = new GraphVertex<>(stateDescriptor);
GraphVertex<StateDescriptor> previous = graphVertexes.put(stateDescriptorClass, graphVertexStateDescriptor);
assert previous == null;
return graphVertexStateDescriptor;
}
private static final class HandleStateDescriptorGraphVertexContext {
private final Set<Class<? extends StateDescriptor>> handledStateDescriptors = new HashSet<>();
Map<Class<? extends StateDescriptor>, GraphVertex<StateDescriptor>> graphVertexes;
MultiMap<Class<? extends StateDescriptor>, Class<? extends StateDescriptor>> inferredForwardEdges;
private HandleStateDescriptorGraphVertexContext(
Map<Class<? extends StateDescriptor>, GraphVertex<StateDescriptor>> graphVertexes,
MultiMap<Class<? extends StateDescriptor>, Class<? extends StateDescriptor>> inferredForwardEdges) {
this.graphVertexes = graphVertexes;
this.inferredForwardEdges = inferredForwardEdges;
}
private boolean recurseInto(Class<? extends StateDescriptor> stateDescriptorClass) {
boolean wasAdded = handledStateDescriptors.add(stateDescriptorClass);
boolean alreadyHandled = !wasAdded;
return alreadyHandled;
}
private GraphVertex<StateDescriptor> getOrConstruct(Class<? extends StateDescriptor> stateDescriptorClass)
throws InstantiationException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException, NoSuchMethodException, SecurityException {
GraphVertex<StateDescriptor> graphVertexStateDescriptor = graphVertexes.get(stateDescriptorClass);
if (graphVertexStateDescriptor == null) {
graphVertexStateDescriptor = addNewStateDescriptorGraphVertex(stateDescriptorClass, graphVertexes);
for (Class<? extends StateDescriptor> inferredSuccessor : inferredForwardEdges.getAll(
stateDescriptorClass)) {
graphVertexStateDescriptor.getElement().addSuccessor(inferredSuccessor);
}
}
return graphVertexStateDescriptor;
}
}
private static void handleStateDescriptorGraphVertex(GraphVertex<StateDescriptor> node,
HandleStateDescriptorGraphVertexContext context)
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
Class<? extends StateDescriptor> stateDescriptorClass = node.element.getClass();
boolean alreadyHandled = context.recurseInto(stateDescriptorClass);
if (alreadyHandled) {
return;
}
Set<Class<? extends StateDescriptor>> successorClasses = node.element.getSuccessors();
int numSuccessors = successorClasses.size();
Map<Class<? extends StateDescriptor>, GraphVertex<StateDescriptor>> successorStateDescriptors = new HashMap<>(
numSuccessors);
for (Class<? extends StateDescriptor> successorClass : successorClasses) {
GraphVertex<StateDescriptor> successorGraphNode = context.getOrConstruct(successorClass);
successorStateDescriptors.put(successorClass, successorGraphNode);
}
switch (numSuccessors) {
case 0:
throw new IllegalStateException("State " + stateDescriptorClass + " has no successor");
case 1:
GraphVertex<StateDescriptor> soleSuccessorNode = successorStateDescriptors.values().iterator().next();
node.addOutgoingEdge(soleSuccessorNode);
handleStateDescriptorGraphVertex(soleSuccessorNode, context);
return;
}
// We hit a state with multiple successors, perform a topological sort on the successors first.
// Process the information regarding subordinates and superiors states.
// The preference graph is the graph where the precedence information of all successors is stored, which we will
// topologically sort to find out which successor we should try first. It is a further new graph we use solely in
// this step for every node. The graph is representent as map. There is no special marker for the initial node
// as it is not required for the topological sort performed later.
Map<Class<? extends StateDescriptor>, GraphVertex<Class<? extends StateDescriptor>>> preferenceGraph = new HashMap<>(numSuccessors);
// Iterate over all successor states of the current state.
for (GraphVertex<StateDescriptor> successorStateDescriptorGraphNode : successorStateDescriptors.values()) {
StateDescriptor successorStateDescriptor = successorStateDescriptorGraphNode.element;
Class<? extends StateDescriptor> successorStateDescriptorClass = successorStateDescriptor.getClass();
for (Class<? extends StateDescriptor> subordinateClass : successorStateDescriptor.getSubordinates()) {
if (!successorClasses.contains(subordinateClass)) {
LOGGER.severe(successorStateDescriptor + " points to a subordinate '" + subordinateClass + "' which is not part of the successor set");
continue;
}
GraphVertex<Class<? extends StateDescriptor>> superiorClassNode = lookupAndCreateIfRequired(
preferenceGraph, successorStateDescriptorClass);
GraphVertex<Class<? extends StateDescriptor>> subordinateClassNode = lookupAndCreateIfRequired(
preferenceGraph, subordinateClass);
superiorClassNode.addOutgoingEdge(subordinateClassNode);
}
for (Class<? extends StateDescriptor> superiorClass : successorStateDescriptor.getSuperiors()) {
if (!successorClasses.contains(superiorClass)) {
LOGGER.severe(successorStateDescriptor + " points to a superior '" + superiorClass
+ "' which is not part of the successor set");
continue;
}
GraphVertex<Class<? extends StateDescriptor>> subordinateClassNode = lookupAndCreateIfRequired(
preferenceGraph, successorStateDescriptorClass);
GraphVertex<Class<? extends StateDescriptor>> superiorClassNode = lookupAndCreateIfRequired(
preferenceGraph, superiorClass);
superiorClassNode.addOutgoingEdge(subordinateClassNode);
}
}
// Perform a topological sort which returns the state descriptor classes in their priority.
List<GraphVertex<Class<? extends StateDescriptor>>> sortedSuccessors = topologicalSort(preferenceGraph.values());
// Handle the successor nodes which have not preference information available. Simply append them to the end of
// the sorted successor list.
outerloop: for (Class<? extends StateDescriptor> successorStateDescriptor : successorClasses) {
for (GraphVertex<Class<? extends StateDescriptor>> sortedSuccessor : sortedSuccessors) {
if (sortedSuccessor.getElement() == successorStateDescriptor) {
continue outerloop;
}
}
sortedSuccessors.add(new GraphVertex<>(successorStateDescriptor));
}
for (GraphVertex<Class<? extends StateDescriptor>> successor : sortedSuccessors) {
GraphVertex<StateDescriptor> successorVertex = successorStateDescriptors.get(successor.element);
node.addOutgoingEdge(successorVertex);
// Recurse further.
handleStateDescriptorGraphVertex(successorVertex, context);
}
}
public static GraphVertex<StateDescriptor> constructStateDescriptorGraph(Set<Class<? extends StateDescriptor>> backwardEdgeStateDescriptors)
throws InstantiationException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException, NoSuchMethodException, SecurityException {
Map<Class<? extends StateDescriptor>, GraphVertex<StateDescriptor>> graphVertexes = new HashMap<>();
final Class<? extends StateDescriptor> initialStatedescriptorClass = DisconnectedStateDescriptor.class;
GraphVertex<StateDescriptor> initialNode = addNewStateDescriptorGraphVertex(initialStatedescriptorClass, graphVertexes);
MultiMap<Class<? extends StateDescriptor>, Class<? extends StateDescriptor>> inferredForwardEdges = new MultiMap<>();
for (Class<? extends StateDescriptor> backwardsEdge : backwardEdgeStateDescriptors) {
GraphVertex<StateDescriptor> graphVertexStateDescriptor = addNewStateDescriptorGraphVertex(backwardsEdge, graphVertexes);
for (Class<? extends StateDescriptor> predecessor : graphVertexStateDescriptor.getElement().getPredeccessors()) {
inferredForwardEdges.put(predecessor, backwardsEdge);
}
}
// Ensure that the intial node has their successors inferred.
for (Class<? extends StateDescriptor> inferredSuccessorOfInitialStateDescriptor : inferredForwardEdges.getAll(initialStatedescriptorClass)) {
initialNode.getElement().addSuccessor(inferredSuccessorOfInitialStateDescriptor);
}
HandleStateDescriptorGraphVertexContext context = new HandleStateDescriptorGraphVertexContext(graphVertexes, inferredForwardEdges);
handleStateDescriptorGraphVertex(initialNode, context);
return initialNode;
}
private static GraphVertex<AbstractXmppStateMachineConnection.State> convertToStateGraph(GraphVertex<StateDescriptor> stateDescriptorVertex,
AbstractXmppStateMachineConnection connection, Map<StateDescriptor, GraphVertex<AbstractXmppStateMachineConnection.State>> handledStateDescriptors) {
StateDescriptor stateDescriptor = stateDescriptorVertex.getElement();
GraphVertex<AbstractXmppStateMachineConnection.State> stateVertex = handledStateDescriptors.get(stateDescriptor);
if (stateVertex != null) {
return stateVertex;
}
AbstractXmppStateMachineConnection.State state = stateDescriptor.constructState(connection);
stateVertex = new GraphVertex<>(state);
handledStateDescriptors.put(stateDescriptor, stateVertex);
for (GraphVertex<StateDescriptor> successorStateDescriptorVertex : stateDescriptorVertex.getOutgoingEdges()) {
GraphVertex<AbstractXmppStateMachineConnection.State> successorStateVertex = convertToStateGraph(successorStateDescriptorVertex, connection, handledStateDescriptors);
// It is important that we keep the order of the edges. This should do it.
stateVertex.addOutgoingEdge(successorStateVertex);
}
return stateVertex;
}
static GraphVertex<AbstractXmppStateMachineConnection.State> convertToStateGraph(GraphVertex<StateDescriptor> initialStateDescriptor,
AbstractXmppStateMachineConnection connection) {
Map<StateDescriptor, GraphVertex<AbstractXmppStateMachineConnection.State>> handledStateDescriptors = new HashMap<>();
GraphVertex<AbstractXmppStateMachineConnection.State> initialState = convertToStateGraph(initialStateDescriptor, connection,
handledStateDescriptors);
return initialState;
}
// Graph API after here.
// This API could possibly factored out into an extra package/class, but then we will probably need a builder for
// the graph vertex in order to keep it immutable.
public static final class GraphVertex<E> {
private final E element;
private final List<GraphVertex<E>> outgoingEdges = new ArrayList<>();
private VertexColor color = VertexColor.white;
private GraphVertex(E element) {
this.element = element;
}
private void addOutgoingEdge(GraphVertex<E> vertex) {
assert vertex != null;
if (outgoingEdges.contains(vertex)) {
throw new IllegalArgumentException("This " + this + " already has an outgoing edge to " + vertex);
}
outgoingEdges.add(vertex);
}
public E getElement() {
return element;
}
public List<GraphVertex<E>> getOutgoingEdges() {
return Collections.unmodifiableList(outgoingEdges);
}
private enum VertexColor {
white,
grey,
black,
}
@Override
public String toString() {
return toString(true);
}
public String toString(boolean includeOutgoingEdges) {
StringBuilder sb = new StringBuilder();
sb.append("GraphVertex " + element + " [color=" + color
+ ", identityHashCode=" + System.identityHashCode(this)
+ ", outgoingEdgeCount=" + outgoingEdges.size());
if (includeOutgoingEdges) {
sb.append(", outgoingEdges={");
for (Iterator<GraphVertex<E>> it = outgoingEdges.iterator(); it.hasNext();) {
GraphVertex<E> outgoingEdgeVertex = it.next();
sb.append(outgoingEdgeVertex.toString(false));
if (it.hasNext()) {
sb.append(", ");
}
}
sb.append('}');
}
sb.append(']');
return sb.toString();
}
}
private static GraphVertex<Class<? extends StateDescriptor>> lookupAndCreateIfRequired(
Map<Class<? extends StateDescriptor>, GraphVertex<Class<? extends StateDescriptor>>> map,
Class<? extends StateDescriptor> clazz) {
GraphVertex<Class<? extends StateDescriptor>> vertex = map.get(clazz);
if (vertex == null) {
vertex = new GraphVertex<>(clazz);
map.put(clazz, vertex);
}
return vertex;
}
private static <E> List<GraphVertex<E>> topologicalSort(Collection<GraphVertex<E>> vertexes) {
List<GraphVertex<E>> res = new ArrayList<>();
dfs(vertexes, (vertex) -> res.add(0, vertex), null);
return res;
}
private static <E> void dfsVisit(GraphVertex<E> vertex, DfsFinishedVertex<E> dfsFinishedVertex, DfsEdgeFound<E> dfsEdgeFound) {
vertex.color = GraphVertex.VertexColor.grey;
final int totalEdgeCount = vertex.getOutgoingEdges().size();
int edgeCount = 0;
for (GraphVertex<E> successorVertex : vertex.getOutgoingEdges()) {
edgeCount++;
if (dfsEdgeFound != null) {
dfsEdgeFound.onEdgeFound(vertex, successorVertex, edgeCount, totalEdgeCount);
}
if (successorVertex.color == GraphVertex.VertexColor.white) {
dfsVisit(successorVertex, dfsFinishedVertex, dfsEdgeFound);
}
}
vertex.color = GraphVertex.VertexColor.black;
if (dfsFinishedVertex != null) {
dfsFinishedVertex.onVertexFinished(vertex);
}
}
private static <E> void dfs(Collection<GraphVertex<E>> vertexes, DfsFinishedVertex<E> dfsFinishedVertex, DfsEdgeFound<E> dfsEdgeFound) {
for (GraphVertex<E> vertex : vertexes) {
if (vertex.color == GraphVertex.VertexColor.white) {
dfsVisit(vertex, dfsFinishedVertex, dfsEdgeFound);
}
}
}
public static <E> void stateDescriptorGraphToDot(Collection<GraphVertex<StateDescriptor>> vertexes,
PrintWriter dotOut, boolean breakStateName) {
dotOut.append("digraph {\n");
dfs(vertexes,
(finishedVertex) -> {
boolean isMultiVisitState = finishedVertex.element.isMultiVisitState();
boolean isFinalState = finishedVertex.element.isFinalState();
boolean isNotImplemented = finishedVertex.element.isNotImplemented();
String style = null;
if (isMultiVisitState) {
style = "bold";
} else if (isFinalState) {
style = "filled";
} else if (isNotImplemented) {
style = "dashed";
}
if (style == null) {
return;
}
dotOut.append('"')
.append(finishedVertex.element.getFullStateName(breakStateName))
.append("\" [ ")
.append("style=")
.append(style)
.append(" ]\n");
},
(from, to, edgeId, totalEdgeCount) -> {
dotOut.append(" \"")
.append(from.element.getFullStateName(breakStateName))
.append("\" -> \"")
.append(to.element.getFullStateName(breakStateName))
.append('"');
if (totalEdgeCount > 1) {
// Note that 'dot' requires *double* quotes to enclose the value.
dotOut.append(" [xlabel=\"")
.append(Integer.toString(edgeId))
.append("\"]");
}
dotOut.append(";\n");
});
dotOut.append("}\n");
}
// TODO: Replace with java.util.function.Consumer<GraphVertex<E>> once Smack's minimum Android SDK level is 24 or higher.
private interface DfsFinishedVertex<E> {
void onVertexFinished(GraphVertex<E> vertex);
}
// TODO: Replace with java.util.function.Consumer<GraphVertex<E>> once Smack's minimum Android SDK level is 24 or higher.
private interface DfsEdgeFound<E> {
void onEdgeFound(GraphVertex<E> from, GraphVertex<E> to, int edgeId, int totalEdgeCount);
}
}

View file

@ -0,0 +1,60 @@
/**
*
* Copyright 2018-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.fsm;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.State;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.TransitionReason;
public abstract class StateMachineException extends SmackException {
private static final long serialVersionUID = 1L;
public static class SmackMandatoryStateFailedException extends StateMachineException {
private static final long serialVersionUID = 1L;
SmackMandatoryStateFailedException(State state, TransitionReason failureReason) {
}
}
public static final class SmackStateGraphDeadEndException extends StateMachineException {
private final List<State> walkedStateGraphPath;
private final Map<State, TransitionReason> failedStates;
private static final long serialVersionUID = 1L;
SmackStateGraphDeadEndException(List<State> walkedStateGraphPath, Map<State, TransitionReason> failedStates) {
this.walkedStateGraphPath = Collections.unmodifiableList(walkedStateGraphPath);
this.failedStates = Collections.unmodifiableMap(failedStates);
}
public List<State> getWalkedStateGraph() {
return walkedStateGraphPath;
}
public Map<State, TransitionReason> getFailedStates() {
return failedStates;
}
}
}

View file

@ -0,0 +1,21 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Smack's Finite State Machine to handle the login logic.
*
*/
package org.jivesoftware.smack.fsm;

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2003-2007 Jive Software. * Copyright 2003-2007 Jive Software, 2018 Florian Schmaus.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.jivesoftware.smack.packet; package org.jivesoftware.smack.packet;
/** /**
@ -33,13 +32,6 @@ package org.jivesoftware.smack.packet;
* @see org.jivesoftware.smack.provider.ExtensionElementProvider * @see org.jivesoftware.smack.provider.ExtensionElementProvider
* @author Matt Tucker * @author Matt Tucker
*/ */
public interface ExtensionElement extends NamedElement { public interface ExtensionElement extends FullyQualifiedElement {
/**
* Returns the root element XML namespace.
*
* @return the namespace.
*/
String getNamespace();
} }

View file

@ -0,0 +1,28 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.packet;
public interface FullyQualifiedElement extends NamedElement {
/**
* Returns the root element XML namespace.
*
* @return the namespace.
*/
String getNamespace();
}

View file

@ -30,6 +30,6 @@ package org.jivesoftware.smack.packet;
* @author Florian Schmaus * @author Florian Schmaus
* @see <a href="http://xmpp.org/extensions/xep-0360.html">XEP-0360: Nonzas (are not Stanzas)</a> * @see <a href="http://xmpp.org/extensions/xep-0360.html">XEP-0360: Nonzas (are not Stanzas)</a>
*/ */
public interface Nonza extends TopLevelStreamElement, ExtensionElement { public interface Nonza extends TopLevelStreamElement, FullyQualifiedElement {
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright © 2014 Florian Schmaus * Copyright © 2014-2018 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,6 +20,8 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
public class StartTls implements Nonza { public class StartTls implements Nonza {
public static final StartTls INSTANCE = new StartTls();
public static final String ELEMENT = "starttls"; public static final String ELEMENT = "starttls";
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-tls"; public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-tls";
@ -49,7 +51,7 @@ public class StartTls implements Nonza {
@Override @Override
public XmlStringBuilder toXML(String enclosingNamespace) { public XmlStringBuilder toXML(String enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this); XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
xml.rightAngleBracket(); xml.rightAngleBracket();
xml.condEmptyElement(required, "required"); xml.condEmptyElement(required, "required");
xml.closeElement(this); xml.closeElement(this);

View file

@ -0,0 +1,42 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.packet;
public final class StreamClose implements Nonza {
public static final StreamClose INSTANCE = new StreamClose();
private StreamClose() {
}
@Override
public String toXML(String enclosingNamespace) {
return "</" + getElementName() + '>';
}
@Override
public String getNamespace() {
// Closing XML tags do never explicitly state their namespace.
return "(none)";
}
@Override
public String getElementName() {
return StreamOpen.ELEMENT;
}
}

View file

@ -0,0 +1,43 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.packet;
public final class TlsFailure implements Nonza {
public static final TlsFailure INSTANCE = new TlsFailure();
public static final String ELEMENT = "failure";
public static final String NAMESPACE = TlsProceed.NAMESPACE;
private TlsFailure() {
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override
public String toXML(String enclosingNamespace) {
return '<' + ELEMENT + " xmlns='" + NAMESPACE + "'/>";
}
}

View file

@ -0,0 +1,43 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.packet;
public final class TlsProceed implements Nonza {
public static final TlsProceed INSTANCE = new TlsProceed();
public static final String ELEMENT = "proceed";
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-tls";
private TlsProceed() {
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override
public String toXML(String enclosingNamespace) {
return '<' + ELEMENT + " xmlns='" + NAMESPACE + "'/>";
}
}

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright © 2014 Florian Schmaus * Copyright © 2014-2018 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.jivesoftware.smack.packet; package org.jivesoftware.smack.packet;
/** /**

View file

@ -0,0 +1,24 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.provider;
import org.jivesoftware.smack.packet.Nonza;
public abstract class NonzaProvider<N extends Nonza> extends Provider<N> {
}

View file

@ -17,6 +17,9 @@
package org.jivesoftware.smack.provider; package org.jivesoftware.smack.provider;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import org.jivesoftware.smack.packet.Element; import org.jivesoftware.smack.packet.Element;
import org.jivesoftware.smack.util.ParserUtils; import org.jivesoftware.smack.util.ParserUtils;
@ -35,6 +38,26 @@ import org.xmlpull.v1.XmlPullParser;
*/ */
public abstract class Provider<E extends Element> { public abstract class Provider<E extends Element> {
private final Class<E> elementClass;
@SuppressWarnings("unchecked")
protected Provider() {
Type currentType = getClass().getGenericSuperclass();
while (!(currentType instanceof ParameterizedType)) {
Class<?> currentClass = (Class<?>) currentType;
currentType = currentClass.getGenericSuperclass();
}
ParameterizedType parameterizedGenericSuperclass = (ParameterizedType) currentType;
Type[] actualTypeArguments = parameterizedGenericSuperclass.getActualTypeArguments();
Type elementType = actualTypeArguments[0];
elementClass = (Class<E>) elementType;
}
public final Class<E> getElementClass() {
return elementClass;
}
public final E parse(XmlPullParser parser) throws Exception { public final E parse(XmlPullParser parser) throws Exception {
// XPP3 calling convention assert: Parser should be at start tag // XPP3 calling convention assert: Parser should be at start tag
ParserUtils.assertAtStartTag(parser); ParserUtils.assertAtStartTag(parser);

View file

@ -25,7 +25,9 @@ import java.util.concurrent.ConcurrentHashMap;
import org.jivesoftware.smack.SmackConfiguration; import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmppElementUtil;
import org.jxmpp.util.XmppStringUtils; import org.jxmpp.util.XmppStringUtils;
@ -113,6 +115,7 @@ public final class ProviderManager {
private static final Map<String, ExtensionElementProvider<ExtensionElement>> extensionProviders = new ConcurrentHashMap<String, ExtensionElementProvider<ExtensionElement>>(); private static final Map<String, ExtensionElementProvider<ExtensionElement>> extensionProviders = new ConcurrentHashMap<String, ExtensionElementProvider<ExtensionElement>>();
private static final Map<String, IQProvider<IQ>> iqProviders = new ConcurrentHashMap<String, IQProvider<IQ>>(); private static final Map<String, IQProvider<IQ>> iqProviders = new ConcurrentHashMap<String, IQProvider<IQ>>();
private static final Map<String, ExtensionElementProvider<ExtensionElement>> streamFeatureProviders = new ConcurrentHashMap<String, ExtensionElementProvider<ExtensionElement>>(); private static final Map<String, ExtensionElementProvider<ExtensionElement>> streamFeatureProviders = new ConcurrentHashMap<String, ExtensionElementProvider<ExtensionElement>>();
private static final Map<String, NonzaProvider<? extends Nonza>> nonzaProviders = new ConcurrentHashMap<>();
static { static {
// Ensure that Smack is initialized by calling getVersion, so that user // Ensure that Smack is initialized by calling getVersion, so that user
@ -309,6 +312,31 @@ public final class ProviderManager {
streamFeatureProviders.remove(key); streamFeatureProviders.remove(key);
} }
public static NonzaProvider<? extends Nonza> getNonzaProvider(String elementName, String namespace) {
String key = getKey(elementName, namespace);
return getNonzaProvider(key);
}
public static NonzaProvider<? extends Nonza> getNonzaProvider(String key) {
return nonzaProviders.get(key);
}
public static void addNonzaProvider(NonzaProvider<? extends Nonza> nonzaProvider) {
Class<? extends Nonza> nonzaClass = nonzaProvider.getElementClass();
String key = XmppElementUtil.getKeyFor(nonzaClass);
nonzaProviders.put(key, nonzaProvider);
}
public static void removeNonzaProvider(Class<? extends Nonza> nonzaClass) {
String key = XmppElementUtil.getKeyFor(nonzaClass);
nonzaProviders.remove(key);
}
public static void removeNonzaProvider(String elementName, String namespace) {
String key = getKey(elementName, namespace);
nonzaProviders.remove(key);
}
private static String getKey(String elementName, String namespace) { private static String getKey(String elementName, String namespace) {
return XmppStringUtils.generateKey(elementName, namespace); return XmppStringUtils.generateKey(elementName, namespace);
} }

View file

@ -0,0 +1,35 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.provider;
import org.jivesoftware.smack.packet.TlsProceed;
import org.xmlpull.v1.XmlPullParser;
public final class TlsFailureProvider extends NonzaProvider<TlsProceed> {
public static final TlsFailureProvider INSTANCE = new TlsFailureProvider();
private TlsFailureProvider() {
}
@Override
public TlsProceed parse(XmlPullParser parser, int initialDepth) throws Exception {
return TlsProceed.INSTANCE;
}
}

View file

@ -0,0 +1,35 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.provider;
import org.jivesoftware.smack.packet.TlsFailure;
import org.xmlpull.v1.XmlPullParser;
public final class TlsProceedProvider extends NonzaProvider<TlsFailure> {
public static final TlsProceedProvider INSTANCE = new TlsProceedProvider();
private TlsProceedProvider() {
}
@Override
public TlsFailure parse(XmlPullParser parser, int initialDepth) throws Exception {
return TlsFailure.INSTANCE;
}
}

View file

@ -232,6 +232,19 @@ public class ArrayBlockingQueueWithShutdown<E> extends AbstractQueue<E> implemen
} }
} }
public boolean offerAndShutdown(E e) {
checkNotNull(e);
boolean res;
lock.lock();
try {
res = offer(e);
shutdown();
} finally {
lock.unlock();
}
return res;
}
private void putInternal(E e, boolean signalNotEmpty) throws InterruptedException { private void putInternal(E e, boolean signalNotEmpty) throws InterruptedException {
assert lock.isHeldByCurrentThread(); assert lock.isHeldByCurrentThread();

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2014 Florian Schmaus * Copyright 2014-2018 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015 Florian Schmaus * Copyright 2015-2018 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,7 +16,10 @@
*/ */
package org.jivesoftware.smack.util; package org.jivesoftware.smack.util;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator;
import java.util.List;
public class CollectionUtil { public class CollectionUtil {
@ -30,4 +33,20 @@ public class CollectionUtil {
return collection; return collection;
} }
public static <T, C extends Collection<T>> List<T> removeUntil(C collection, Predicate<T> predicate) {
List<T> removedElements = new ArrayList<>(collection.size());
for (Iterator<T> it = collection.iterator(); it.hasNext();) {
T t = it.next();
if (predicate.test(t)) {
break;
}
removedElements.add(t);
it.remove();
}
return removedElements;
}
public interface Predicate<T> {
boolean test(T t);
}
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright © 2015 Florian Schmaus * Copyright © 2015-2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -170,6 +170,33 @@ public class MultiMap<K,V> {
return res; return res;
} }
/**
* Remove the given number of values for a given key. May return less values then requested.
*
* @param key the key to remove from.
* @param num the number of values to remove.
* @return a list of the removed values.
* @since 4.4.0
*/
public List<V> remove(K key, int num) {
List<V> values = map.get(key);
if (values == null) {
return Collections.emptyList();
}
final int resultSize = values.size() > num ? num : values.size();
final List<V> result = new ArrayList<>(resultSize);
for (int i = 0; i < resultSize; i++) {
result.add(values.get(0));
}
if (values.isEmpty()) {
map.remove(key);
}
return result;
}
public void putAll(Map<? extends K, ? extends V> map) { public void putAll(Map<? extends K, ? extends V> map) {
for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
put(entry.getKey(), entry.getValue()); put(entry.getKey(), entry.getValue());

View file

@ -96,6 +96,7 @@ public class PacketParserUtils {
XML_PULL_PARSER_SUPPORTS_ROUNDTRIP = roundtrip; XML_PULL_PARSER_SUPPORTS_ROUNDTRIP = roundtrip;
} }
// TODO: Rename argument name from 'stanza' to 'element'.
public static XmlPullParser getParserFor(String stanza) throws XmlPullParserException, IOException { public static XmlPullParser getParserFor(String stanza) throws XmlPullParserException, IOException {
return getParserFor(new StringReader(stanza)); return getParserFor(new StringReader(stanza));
} }

View file

@ -302,7 +302,7 @@ public class StringUtils {
return randomString(length, SECURE_RANDOM.get()); return randomString(length, SECURE_RANDOM.get());
} }
private static String randomString(final int length, Random random) { public static String randomString(final int length, Random random) {
if (length < 1) { if (length < 1) {
return null; return null;
} }

View file

@ -0,0 +1,39 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.util;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
public class UTF8 {
public static final String UTF8_CHARSET_NAME = "UTF-8";
private static final Charset utf8Charset;
static {
utf8Charset = Charset.forName(UTF8_CHARSET_NAME);
}
public static ByteBuffer encode(CharSequence charSequence) {
return encode(charSequence.toString());
}
public static ByteBuffer encode(String string) {
return utf8Charset.encode(string);
}
}

View file

@ -20,9 +20,11 @@ import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.Iterator;
import org.jivesoftware.smack.packet.Element; import org.jivesoftware.smack.packet.Element;
import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.FullyQualifiedElement;
import org.jivesoftware.smack.packet.NamedElement; import org.jivesoftware.smack.packet.NamedElement;
import org.jxmpp.util.XmppDateTime; import org.jxmpp.util.XmppDateTime;
@ -53,7 +55,7 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
halfOpenElement(e.getElementName()); halfOpenElement(e.getElementName());
} }
public XmlStringBuilder(ExtensionElement ee, String enclosingNamespace) { public XmlStringBuilder(FullyQualifiedElement ee, String enclosingNamespace) {
this(enclosingNamespace); this(enclosingNamespace);
prelude(ee); prelude(ee);
} }
@ -439,7 +441,7 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
return escape(text.toString()); return escape(text.toString());
} }
public XmlStringBuilder prelude(ExtensionElement pe) { public XmlStringBuilder prelude(FullyQualifiedElement pe) {
return prelude(pe.getElementName(), pe.getNamespace()); return prelude(pe.getElementName(), pe.getNamespace());
} }
@ -585,6 +587,10 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
} }
} }
public Iterator<CharSequence> getCharSequenceIterator() {
return sb.getAsList().iterator();
}
@Override @Override
public CharSequence toXML(String enclosingNamespace) { public CharSequence toXML(String enclosingNamespace) {
StringBuilder res = new StringBuilder(); StringBuilder res = new StringBuilder();

View file

@ -0,0 +1,45 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.util;
import org.jivesoftware.smack.packet.FullyQualifiedElement;
import org.jxmpp.util.XmppStringUtils;
public class XmppElementUtil {
public static String getKeyFor(Class<? extends FullyQualifiedElement> fullyQualifiedElement) {
String element, namespace;
try {
element = (String) fullyQualifiedElement.getField("ELEMENT").get(null);
namespace = (String) fullyQualifiedElement.getField("NAMESPACE").get(null);
}
catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
throw new IllegalArgumentException(e);
}
String key = XmppStringUtils.generateKey(element, namespace);
return key;
}
public static String getKeyFor(FullyQualifiedElement fullyQualifiedElement) {
String element = fullyQualifiedElement.getElementName();
String namespace = fullyQualifiedElement.getNamespace();
String key = XmppStringUtils.generateKey(element, namespace);
return key;
}
}

View file

@ -17,6 +17,7 @@
package org.jivesoftware.smack.util.dns; package org.jivesoftware.smack.util.dns;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -72,6 +73,14 @@ public class HostAddress {
setException(e); setException(e);
} }
public HostAddress(InetSocketAddress inetSocketAddress, Exception exception) {
String hostString = inetSocketAddress.getHostString();
this.fqdn = DnsName.from(hostString);
this.port = inetSocketAddress.getPort();
inetAddresses = Collections.emptyList();
setException(exception);
}
public String getHost() { public String getHost() {
if (fqdn != null) { if (fqdn != null) {
return fqdn.toString(); return fqdn.toString();

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015-2016 Florian Schmaus * Copyright 2015-2018 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,6 +22,7 @@ import java.security.cert.CertificateException;
import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocket;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
@ -31,5 +32,8 @@ import javax.net.ssl.X509TrustManager;
public interface SmackDaneVerifier { public interface SmackDaneVerifier {
void init(SSLContext context, KeyManager[] km, X509TrustManager tm, SecureRandom random) throws KeyManagementException; void init(SSLContext context, KeyManager[] km, X509TrustManager tm, SecureRandom random) throws KeyManagementException;
// TODO: Remove this method in favor of finish(SSLSession).
void finish(SSLSocket socket) throws CertificateException; void finish(SSLSocket socket) throws CertificateException;
void finish(SSLSession sslSession) throws CertificateException;
} }

View file

@ -55,7 +55,7 @@ public class DummyConnection extends AbstractXMPPConnection {
private final BlockingQueue<TopLevelStreamElement> queue = new LinkedBlockingQueue<TopLevelStreamElement>(); private final BlockingQueue<TopLevelStreamElement> queue = new LinkedBlockingQueue<TopLevelStreamElement>();
public static ConnectionConfiguration.Builder<?,?> getDummyConfigurationBuilder() { public static DummyConnectionConfiguration.Builder getDummyConfigurationBuilder() {
return DummyConnectionConfiguration.builder().setXmppDomain(JidTestUtil.EXAMPLE_ORG).setUsernameAndPassword("dummy", return DummyConnectionConfiguration.builder().setXmppDomain(JidTestUtil.EXAMPLE_ORG).setUsernameAndPassword("dummy",
"dummypass"); "dummypass");
} }
@ -77,7 +77,7 @@ public class DummyConnection extends AbstractXMPPConnection {
} }
} }
public DummyConnection(ConnectionConfiguration configuration) { public DummyConnection(DummyConnectionConfiguration configuration) {
super(configuration); super(configuration);
for (ConnectionCreationListener listener : XMPPConnectionRegistry.getConnectionCreationListeners()) { for (ConnectionCreationListener listener : XMPPConnectionRegistry.getConnectionCreationListeners()) {

View file

@ -19,6 +19,8 @@ package org.jivesoftware.smack;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import java.util.concurrent.atomic.AtomicInteger;
import org.jivesoftware.smack.filter.StanzaFilter; import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.Stanza;
@ -55,34 +57,42 @@ public class StanzaCollectorTest {
assertEquals("14", collector.pollResult().getStanzaId()); assertEquals("14", collector.pollResult().getStanzaId());
assertNull(collector.pollResult()); assertNull(collector.pollResult());
assertNull(collector.nextResult(1000)); assertNull(collector.nextResult(10));
} }
/** /**
* Although this doesn't guarentee anything due to the nature of threading, it can potentially * Although this doesn't guarantee anything due to the nature of threading, it can potentially
* catch problems. * catch problems.
*
* @throws InterruptedException if interrupted.
*/ */
@SuppressWarnings("ThreadPriorityCheck")
@Test @Test
public void verifyThreadSafety() { public void verifyThreadSafety() throws InterruptedException {
int insertCount = 500; final int insertCount = 500;
final TestStanzaCollector collector = new TestStanzaCollector(null, new OKEverything(), insertCount); final TestStanzaCollector collector = new TestStanzaCollector(null, new OKEverything(), insertCount);
final AtomicInteger consumer1Dequeued = new AtomicInteger();
final AtomicInteger consumer2Dequeued = new AtomicInteger();
final AtomicInteger consumer3Dequeued = new AtomicInteger();
Thread consumer1 = new Thread(new Runnable() { Thread consumer1 = new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
int dequeueCount = 0;
try { try {
while (true) { while (true) {
try { Thread.yield();
Thread.sleep(3);
} catch (InterruptedException e) {
}
@SuppressWarnings("unused")
Stanza packet = collector.nextResultBlockForever(); Stanza packet = collector.nextResultBlockForever();
// System.out.println(Thread.currentThread().getName() + " packet: " + packet); if (packet != null) {
dequeueCount++;
}
} }
} }
catch (InterruptedException e) { catch (InterruptedException e) {
throw new RuntimeException(e); // Ignore as it is expected.
} finally {
consumer1Dequeued.set(dequeueCount);
} }
} }
}); });
@ -92,20 +102,20 @@ public class StanzaCollectorTest {
@Override @Override
public void run() { public void run() {
Stanza p; Stanza p;
int dequeueCount = 0;
do { do {
Thread.yield();
try { try {
Thread.sleep(3); p = collector.nextResult(1000);
} catch (InterruptedException e) {
}
try {
p = collector.nextResult(1);
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
// System.out.println(Thread.currentThread().getName() + " packet: " + p); if (p != null) {
dequeueCount++;
}
} }
while (p != null); while (p != null);
consumer2Dequeued.set(dequeueCount);
} }
}); });
consumer2.setName("consumer 2"); consumer2.setName("consumer 2");
@ -114,37 +124,42 @@ public class StanzaCollectorTest {
@Override @Override
public void run() { public void run() {
Stanza p; Stanza p;
int dequeueCount = 0;
do { do {
try { Thread.yield();
Thread.sleep(3);
} catch (InterruptedException e) {
}
p = collector.pollResult(); p = collector.pollResult();
// System.out.println(Thread.currentThread().getName() + " packet: " + p); if (p != null) {
dequeueCount++;
}
} while (p != null); } while (p != null);
consumer3Dequeued.set(dequeueCount);
} }
}); });
consumer3.setName("consumer 3"); consumer3.setName("consumer 3");
consumer1.start();
consumer2.start();
consumer3.start();
for (int i = 0; i < insertCount; i++) { for (int i = 0; i < insertCount; i++) {
collector.processStanza(new TestPacket(i)); collector.processStanza(new TestPacket(i));
} }
try { consumer1.start();
Thread.sleep(5000); consumer2.start();
consumer3.start();
consumer3.join(); consumer3.join();
consumer2.join(); consumer2.join();
consumer1.interrupt(); consumer1.interrupt();
} catch (InterruptedException e) { consumer1.join();
}
// We cannot guarantee that this is going to pass due to the possible issue of timing between consumer 1 // We cannot guarantee that this is going to pass due to the possible issue of timing between consumer 1
// and main, but the probability is extremely remote. // and main, but the probability is extremely remote.
assertNull(collector.pollResult()); assertNull(collector.pollResult());
int consumer1DequeuedLocal = consumer1Dequeued.get();
int consumer2DequeuedLocal = consumer2Dequeued.get();
int consumer3DequeuedLocal = consumer3Dequeued.get();
final int totalDequeued = consumer1DequeuedLocal + consumer2DequeuedLocal + consumer3DequeuedLocal;
assertEquals("Inserted " + insertCount + " but only " + totalDequeued + " c1: " + consumer1DequeuedLocal + " c2: " + consumer2DequeuedLocal + " c3: "
+ consumer3DequeuedLocal, insertCount, totalDequeued);
} }
static class OKEverything implements StanzaFilter { static class OKEverything implements StanzaFilter {

View file

@ -0,0 +1,58 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.compress.packet;
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
import java.io.IOException;
import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.packet.StanzaError.Condition;
import org.junit.Test;
import org.xml.sax.SAXException;
public class FailureTest {
@Test
public void simpleFailureTest() throws SAXException, IOException {
Failure failure = new Failure(Failure.CompressFailureError.processing_failed);
CharSequence xml = failure.toXML(null);
final String expectedXml = "<failure xmlns='http://jabber.org/protocol/compress'><processing-failed/></failure>";
assertXMLEqual(expectedXml, xml.toString());
}
@Test
public void withStanzaErrrorFailureTest() throws SAXException, IOException {
StanzaError stanzaError = StanzaError.getBuilder()
.setCondition(Condition.bad_request)
.build();
Failure failure = new Failure(Failure.CompressFailureError.setup_failed, stanzaError);
CharSequence xml = failure.toXML(null);
final String expectedXml = "<failure xmlns='http://jabber.org/protocol/compress'>"
+ "<setup-failed/>"
+ "<error xmlns='jabber:client' type='modify'>"
+ "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+ "</error>"
+ "</failure>";
assertXMLEqual(expectedXml, xml.toString());
}
}

View file

@ -0,0 +1,56 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.compress.provider;
import static org.junit.Assert.assertEquals;
import org.jivesoftware.smack.compress.packet.Failure;
import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.packet.StanzaError.Condition;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.junit.Test;
import org.xmlpull.v1.XmlPullParser;
public class FailureProviderTest {
@Test
public void simpleFailureTest() throws Exception {
final String xml = "<failure xmlns='http://jabber.org/protocol/compress'><processing-failed/></failure>";
final XmlPullParser parser = PacketParserUtils.getParserFor(xml);
final Failure failure = FailureProvider.INSTANCE.parse(parser);
assertEquals(Failure.CompressFailureError.processing_failed, failure.getCompressFailureError());
}
@Test
public void withStanzaErrrorFailureTest() throws Exception {
final String xml = "<failure xmlns='http://jabber.org/protocol/compress'>"
+ "<setup-failed/>"
+ "<error xmlns='jabber:client' type='modify'>"
+ "<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+ "</error>"
+ "</failure>";
final XmlPullParser parser = PacketParserUtils.getParserFor(xml);
final Failure failure = FailureProvider.INSTANCE.parse(parser);
assertEquals(Failure.CompressFailureError.setup_failed, failure.getCompressFailureError());
final StanzaError error = failure.getStanzaError();
assertEquals(Condition.bad_request, error.getCondition());
}
}

View file

@ -17,8 +17,6 @@
package org.jivesoftware.smackx.debugger.slf4j; package org.jivesoftware.smackx.debugger.slf4j;
import java.io.Reader;
import java.io.Writer;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import org.jivesoftware.smack.AbstractXMPPConnection; import org.jivesoftware.smack.AbstractXMPPConnection;
@ -88,19 +86,13 @@ public class SLF4JSmackDebugger extends SmackDebugger {
} }
@Override @Override
public Reader newConnectionReader(Reader newReader) { public void outgoingStreamSink(CharSequence outgoingCharSequence) {
reader.removeReaderListener(slf4JRawXmlListener); slf4JRawXmlListener.write(outgoingCharSequence.toString());
reader = new ObservableReader(newReader);
reader.addReaderListener(slf4JRawXmlListener);
return reader;
} }
@Override @Override
public Writer newConnectionWriter(Writer newWriter) { public void incomingStreamSink(CharSequence incomingCharSequence) {
writer.removeWriterListener(slf4JRawXmlListener); slf4JRawXmlListener.read(incomingCharSequence.toString());
writer = new ObservableWriter(newWriter);
writer.addWriterListener(slf4JRawXmlListener);
return writer;
} }
@Override @Override

View file

@ -709,21 +709,13 @@ public class EnhancedDebugger extends SmackDebugger {
} }
@Override @Override
public Reader newConnectionReader(Reader newReader) { public final void outgoingStreamSink(CharSequence outgoingCharSequence) {
((ObservableReader) reader).removeReaderListener(readerListener); writerListener.write(outgoingCharSequence.toString());
ObservableReader debugReader = new ObservableReader(newReader);
debugReader.addReaderListener(readerListener);
reader = debugReader;
return reader;
} }
@Override @Override
public Writer newConnectionWriter(Writer newWriter) { public final void incomingStreamSink(CharSequence incomingCharSequence) {
((ObservableWriter) writer).removeWriterListener(writerListener); readerListener.read(incomingCharSequence.toString());
ObservableWriter debugWriter = new ObservableWriter(newWriter);
debugWriter.addWriterListener(writerListener);
writer = debugWriter;
return writer;
} }
@Override @Override

View file

@ -304,21 +304,13 @@ public class LiteDebugger extends SmackDebugger {
} }
@Override @Override
public Reader newConnectionReader(Reader newReader) { public void outgoingStreamSink(CharSequence outgoingCharSequence) {
((ObservableReader) reader).removeReaderListener(readerListener); writerListener.write(outgoingCharSequence.toString());
ObservableReader debugReader = new ObservableReader(newReader);
debugReader.addReaderListener(readerListener);
reader = debugReader;
return reader;
} }
@Override @Override
public Writer newConnectionWriter(Writer newWriter) { public void incomingStreamSink(CharSequence incomingCharSequence) {
((ObservableWriter) writer).removeWriterListener(writerListener); readerListener.read(incomingCharSequence.toString());
ObservableWriter debugWriter = new ObservableWriter(newWriter);
debugWriter.addWriterListener(writerListener);
writer = debugWriter;
return writer;
} }
@Override @Override

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright © 2014 Florian Schmaus * Copyright 2014-2018 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,13 +17,12 @@
package org.jivesoftware.smackx.gcm.provider; package org.jivesoftware.smackx.gcm.provider;
import org.jivesoftware.smackx.gcm.packet.GcmPacketExtension; import org.jivesoftware.smackx.gcm.packet.GcmPacketExtension;
import org.jivesoftware.smackx.json.packet.AbstractJsonPacketExtension;
import org.jivesoftware.smackx.json.provider.AbstractJsonExtensionProvider; import org.jivesoftware.smackx.json.provider.AbstractJsonExtensionProvider;
public class GcmExtensionProvider extends AbstractJsonExtensionProvider { public class GcmExtensionProvider extends AbstractJsonExtensionProvider<GcmPacketExtension> {
@Override @Override
public AbstractJsonPacketExtension from(String json) { public GcmPacketExtension from(String json) {
return new GcmPacketExtension(json); return new GcmPacketExtension(json);
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright © 2014-2015 Florian Schmaus * Copyright 2014-2018 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -27,14 +27,14 @@ import org.jivesoftware.smackx.json.packet.AbstractJsonPacketExtension;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
public abstract class AbstractJsonExtensionProvider extends ExtensionElementProvider<AbstractJsonPacketExtension> { public abstract class AbstractJsonExtensionProvider<J extends AbstractJsonPacketExtension> extends ExtensionElementProvider<J> {
@Override @Override
public AbstractJsonPacketExtension parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException, public J parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException,
IOException, SmackException { IOException, SmackException {
String json = PacketParserUtils.parseElementText(parser); String json = PacketParserUtils.parseElementText(parser);
return from(json); return from(json);
} }
public abstract AbstractJsonPacketExtension from(String json); public abstract J from(String json);
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright © 2014 Florian Schmaus * Copyright 2014-2018 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,13 +16,12 @@
*/ */
package org.jivesoftware.smackx.json.provider; package org.jivesoftware.smackx.json.provider;
import org.jivesoftware.smackx.json.packet.AbstractJsonPacketExtension;
import org.jivesoftware.smackx.json.packet.JsonPacketExtension; import org.jivesoftware.smackx.json.packet.JsonPacketExtension;
public class JsonExtensionProvider extends AbstractJsonExtensionProvider { public class JsonExtensionProvider extends AbstractJsonExtensionProvider<JsonPacketExtension> {
@Override @Override
public AbstractJsonPacketExtension from(String json) { public JsonPacketExtension from(String json) {
return new JsonPacketExtension(json); return new JsonPacketExtension(json);
} }
} }

View file

@ -20,15 +20,13 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.jivesoftware.smack.AbstractConnectionClosedListener; import org.jivesoftware.smack.AbstractConnectionClosedListener;
import org.jivesoftware.smack.ConnectionCreationListener; import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.Manager; import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.ScheduledAction;
import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackFuture; import org.jivesoftware.smack.SmackFuture;
@ -115,7 +113,7 @@ public final class PingManager extends Manager {
*/ */
private int pingInterval = defaultPingInterval; private int pingInterval = defaultPingInterval;
private ScheduledFuture<?> nextAutomaticPing; private ScheduledAction nextAutomaticPing;
private PingManager(XMPPConnection connection) { private PingManager(XMPPConnection connection) {
super(connection); super(connection);
@ -396,9 +394,10 @@ public final class PingManager extends Manager {
} }
private void maybeStopPingServerTask() { private void maybeStopPingServerTask() {
final ScheduledAction nextAutomaticPing = this.nextAutomaticPing;
if (nextAutomaticPing != null) { if (nextAutomaticPing != null) {
nextAutomaticPing.cancel(true); nextAutomaticPing.cancel();
nextAutomaticPing = null; this.nextAutomaticPing = null;
} }
} }
@ -406,9 +405,7 @@ public final class PingManager extends Manager {
* Ping the server if deemed necessary because automatic server pings are * Ping the server if deemed necessary because automatic server pings are
* enabled ({@link #setPingInterval(int)}) and the ping interval has expired. * enabled ({@link #setPingInterval(int)}) and the ping interval has expired.
*/ */
public synchronized void pingServerIfNecessary() { public void pingServerIfNecessary() {
final int DELTA = 1000; // 1 seconds
final int TRIES = 3; // 3 tries
final XMPPConnection connection = connection(); final XMPPConnection connection = connection();
if (connection == null) { if (connection == null) {
// connection has been collected by GC // connection has been collected by GC
@ -430,46 +427,32 @@ public final class PingManager extends Manager {
return; return;
} }
} }
if (connection.isAuthenticated()) { if (!connection.isAuthenticated()) {
boolean res = false; LOGGER.warning(connection + " was not authenticated");
for (int i = 0; i < TRIES; i++) {
if (i != 0) {
try {
Thread.sleep(DELTA);
} catch (InterruptedException e) {
// We received an interrupt
// This only happens if we should stop pinging
return; return;
} }
}
try { final long minimumTimeout = TimeUnit.MINUTES.toMillis(2);
res = pingMyServer(false); final long connectionReplyTimeout = connection.getReplyTimeout();
} final long timeout = connectionReplyTimeout > minimumTimeout ? connectionReplyTimeout : minimumTimeout;
catch (InterruptedException | SmackException e) {
// Note that we log the connection here, so that it is not GC'ed between the call to isAuthenticated SmackFuture<Boolean, Exception> pingFuture = pingAsync(connection.getXMPPServiceDomain(), timeout);
// a few lines above and the usage of the connection within pingMyServer(). In order to prevent: pingFuture.onSuccess(new SuccessCallback<Boolean>() {
// https://community.igniterealtime.org/thread/59369 @Override
LOGGER.log(Level.WARNING, "Exception while pinging server of " + connection, e); public void onSuccess(Boolean result) {
res = false;
}
// stop when we receive a pong back
if (res) {
break;
}
}
if (!res) {
for (PingFailedListener l : pingFailedListeners) {
l.pingFailed();
}
} else {
// Ping was successful, wind-up the periodic task again // Ping was successful, wind-up the periodic task again
maybeSchedulePingServerTask(); maybeSchedulePingServerTask();
} }
} else { });
LOGGER.warning("XMPPConnection was not authenticated"); pingFuture.onError(new ExceptionCallback<Exception>() {
@Override
public void processException(Exception exception) {
for (PingFailedListener l : pingFailedListeners) {
l.pingFailed();
} }
} }
});
}
private final Runnable pingServerRunnable = new Runnable() { private final Runnable pingServerRunnable = new Runnable() {
@Override @Override

View file

@ -26,7 +26,6 @@ import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.DummyConnection; import org.jivesoftware.smack.DummyConnection;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException;
@ -71,8 +70,7 @@ public class RosterVersioningTest {
DirectoryRosterStore store = DirectoryRosterStore.init(tmpFolder.newFolder("store")); DirectoryRosterStore store = DirectoryRosterStore.init(tmpFolder.newFolder("store"));
populateStore(store); populateStore(store);
ConnectionConfiguration.Builder<?, ?> builder = DummyConnection.getDummyConfigurationBuilder(); connection = new DummyConnection();
connection = new DummyConnection(builder.build());
connection.connect(); connection.connect();
connection.login(); connection.login();
rosterListener = new TestRosterListener(); rosterListener = new TestRosterListener();

View file

@ -15,7 +15,7 @@ dependencies {
compile project(':smack-openpgp') compile project(':smack-openpgp')
compile project(':smack-debug') compile project(':smack-debug')
compile project(path: ":smack-omemo", configuration: "testRuntime") compile project(path: ":smack-omemo", configuration: "testRuntime")
compile 'org.reflections:reflections:0.9.9-RC1' compile 'org.reflections:reflections:0.9.11'
compile 'eu.geekplace.javapinning:java-pinning-java7:1.1.0-alpha1' compile 'eu.geekplace.javapinning:java-pinning-java7:1.1.0-alpha1'
// Note that the junit-vintage-engine runtime dependency is not // Note that the junit-vintage-engine runtime dependency is not
// directly required, but it declares a dependency to // directly required, but it declares a dependency to

View file

@ -0,0 +1,266 @@
/**
*
* Copyright 2018-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.igniterealtime.smack;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.filter.StanzaExtensionFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.jiveproperties.JivePropertiesManager;
import org.jivesoftware.smackx.jiveproperties.packet.JivePropertiesExtension;
import org.igniterealtime.smack.XmppConnectionStressTest.StressTestFailedException.ErrorsWhileSendingOrReceivingException;
import org.igniterealtime.smack.XmppConnectionStressTest.StressTestFailedException.NotAllMessagesReceivedException;
import org.jxmpp.jid.EntityFullJid;
public class XmppConnectionStressTest {
private static final String MESSAGE_NUMBER_PROPERTY = "message-number";
public static class Configuration {
public final long seed;
public final int messagesPerConnection;
public final int maxPayloadChunkSize;
public final int maxPayloadChunks;
public final boolean intermixMessages;
public Configuration(long seed, int messagesPerConnection, int maxPayloadChunkSize, int maxPayloadChunks,
boolean intermixMessages) {
this.seed = seed;
this.messagesPerConnection = messagesPerConnection;
this.maxPayloadChunkSize = maxPayloadChunkSize;
this.maxPayloadChunks = maxPayloadChunks;
this.intermixMessages = intermixMessages;
}
}
private final Configuration configuration;
public XmppConnectionStressTest(Configuration configuration) {
this.configuration = configuration;
}
private volatile long waitStart;
public void run(List<? extends XMPPConnection> connections, final long replyTimeoutMillis)
throws InterruptedException, NotAllMessagesReceivedException, ErrorsWhileSendingOrReceivingException {
final MultiMap<XMPPConnection, Message> messages = new MultiMap<>();
final Random random = new Random(configuration.seed);
final Map<XMPPConnection, Exception> sendExceptions = new ConcurrentHashMap<>();
final Map<XMPPConnection, Exception> receiveExceptions = new ConcurrentHashMap<>();
waitStart = -1;
for (XMPPConnection fromConnection : connections) {
MultiMap<XMPPConnection, Message> toConnectionMessages = new MultiMap<>();
for (XMPPConnection toConnection : connections) {
for (int i = 0; i < configuration.messagesPerConnection; i++) {
Message message = new Message();
message.setTo(toConnection.getUser());
int payloadChunkCount = random.nextInt(configuration.maxPayloadChunks) + 1;
for (int c = 0; c < payloadChunkCount; c++) {
int payloadChunkSize = random.nextInt(configuration.maxPayloadChunkSize) + 1;
String payloadCunk = StringUtils.randomString(payloadChunkSize, random);
JivePropertiesManager.addProperty(message, "payload-chunk-" + c, payloadCunk);
}
JivePropertiesManager.addProperty(message, MESSAGE_NUMBER_PROPERTY, i);
toConnectionMessages.put(toConnection, message);
}
}
if (configuration.intermixMessages) {
while (!toConnectionMessages.isEmpty()) {
int next = random.nextInt(connections.size());
Message message = null;
while (message == null) {
XMPPConnection toConnection = connections.get(next);
message = toConnectionMessages.getFirst(toConnection);
next = (next + 1) % connections.size();
}
messages.put(fromConnection, message);
}
} else {
for (XMPPConnection toConnection : connections) {
for (Message message : toConnectionMessages.getAll(toConnection)) {
messages.put(fromConnection, message);
}
}
}
}
Semaphore receivedSemaphore = new Semaphore(-connections.size() + 1);
Map<XMPPConnection, Map<EntityFullJid, boolean[]>> receiveMarkers = new ConcurrentHashMap<>(connections.size());
for (XMPPConnection connection : connections) {
connection.addSyncStanzaListener(new StanzaListener() {
@Override
public void processStanza(Stanza stanza) {
waitStart = System.currentTimeMillis();
EntityFullJid from = stanza.getFrom().asEntityFullJidOrThrow();
Message message = (Message) stanza;
JivePropertiesExtension extension = JivePropertiesExtension.from(message);
Integer messageNumber = (Integer) extension.getProperty(MESSAGE_NUMBER_PROPERTY);
Map<EntityFullJid, boolean[]> myReceiveMarkers = receiveMarkers.get(connection);
if (myReceiveMarkers == null) {
myReceiveMarkers = new HashMap<>(connections.size());
receiveMarkers.put(connection, myReceiveMarkers);
}
boolean[] fromMarkers = myReceiveMarkers.get(from);
if (fromMarkers == null) {
fromMarkers = new boolean[configuration.messagesPerConnection];
myReceiveMarkers.put(from, fromMarkers);
}
// Sanity check: All markers before must be true, all markers including the messageNumber marker must be false.
for (int i = 0; i < fromMarkers.length; i++) {
if ((i < messageNumber && !fromMarkers[i])
|| (i >= messageNumber && fromMarkers[i])) {
// TODO: Better exception.
Exception exception = new Exception("out of order");
receiveExceptions.put(connection, exception);
// TODO: Current Smack design does not guarantee that the listener won't be invoked again.
// This is because the decission to invoke a sync listeners is done at a different place
// then invoking the listener.
connection.removeSyncStanzaListener(this);
receivedSemaphore.release();
return;
}
}
fromMarkers[messageNumber] = true;
if (myReceiveMarkers.size() != connections.size()) {
return;
}
for (boolean[] markers : myReceiveMarkers.values()) {
for (boolean b : markers) {
if (!b) {
return;
}
}
}
// All markers set to true, this means we received all messages.
receivedSemaphore.release();
}
}, new AndFilter(MessageTypeFilter.NORMAL,
new StanzaExtensionFilter(JivePropertiesExtension.ELEMENT, JivePropertiesExtension.NAMESPACE)));
}
Semaphore sendSemaphore = new Semaphore(-connections.size() + 1);
for (XMPPConnection connection : connections) {
Async.go(() -> {
List<Message> messagesToSend;
synchronized (messages) {
messagesToSend = messages.getAll(connection);
}
try {
for (Message messageToSend : messagesToSend) {
connection.sendStanza(messageToSend);
}
} catch (NotConnectedException | InterruptedException e) {
sendExceptions.put(connection, e);
} finally {
sendSemaphore.release();
}
});
}
sendSemaphore.acquire();
if (waitStart < 0) {
waitStart = System.currentTimeMillis();
}
boolean acquired;
do {
long acquireWait = waitStart + replyTimeoutMillis - System.currentTimeMillis();
acquired = receivedSemaphore.tryAcquire(acquireWait, TimeUnit.MILLISECONDS);
} while (!acquired && System.currentTimeMillis() < waitStart + replyTimeoutMillis);
if (!acquired && receiveExceptions.isEmpty() && sendExceptions.isEmpty()) {
throw new StressTestFailedException.NotAllMessagesReceivedException(receiveMarkers);
}
if (!receiveExceptions.isEmpty() || !sendExceptions.isEmpty()) {
throw new StressTestFailedException.ErrorsWhileSendingOrReceivingException(sendExceptions,
receiveExceptions);
}
// Test successful.
}
public abstract static class StressTestFailedException extends Exception {
private static final long serialVersionUID = 1L;
protected StressTestFailedException(String message) {
super(message);
}
public static final class NotAllMessagesReceivedException extends StressTestFailedException {
private static final long serialVersionUID = 1L;
public final Map<XMPPConnection, Map<EntityFullJid, boolean[]>> receiveMarkers;
private NotAllMessagesReceivedException(Map<XMPPConnection, Map<EntityFullJid, boolean[]>> receiveMarkers) {
super("Did not receive all messages");
this.receiveMarkers = receiveMarkers;
}
}
public static final class ErrorsWhileSendingOrReceivingException extends StressTestFailedException {
private static final long serialVersionUID = 1L;
public final Map<XMPPConnection, Exception> sendExceptions;
public final Map<XMPPConnection, Exception> receiveExceptions;
private ErrorsWhileSendingOrReceivingException(Map<XMPPConnection, Exception> sendExceptions,
Map<XMPPConnection, Exception> receiveExceptions) {
super("Exceptions while sending and/or receiving");
this.sendExceptions = sendExceptions;
this.receiveExceptions = receiveExceptions;
}
}
}
}

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015-2016 Florian Schmaus * Copyright 2015-2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -45,10 +45,10 @@ public abstract class AbstractSmackIntTest {
protected final Configuration sinttestConfiguration; protected final Configuration sinttestConfiguration;
protected AbstractSmackIntTest(String testRunId, Configuration configuration) { protected AbstractSmackIntTest(SmackIntegrationTestEnvironment<?> environment) {
this.testRunId = testRunId; this.testRunId = environment.testRunId;
this.sinttestConfiguration = configuration; this.sinttestConfiguration = environment.configuration;
this.timeout = configuration.replyTimeout; this.timeout = environment.configuration.replyTimeout;
} }
protected void performActionAndWaitUntilStanzaReceived(Runnable action, XMPPConnection connection, StanzaFilter filter) protected void performActionAndWaitUntilStanzaReceived(Runnable action, XMPPConnection connection, StanzaFilter filter)

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015 Florian Schmaus * Copyright 2015-2018 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,6 +16,10 @@
*/ */
package org.igniterealtime.smack.inttest; package org.igniterealtime.smack.inttest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest { public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest {
@ -40,10 +44,18 @@ public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest
*/ */
protected final XMPPConnection connection; protected final XMPPConnection connection;
public AbstractSmackIntegrationTest(SmackIntegrationTestEnvironment environment) { protected final List<XMPPConnection> connections;
super(environment.testRunId, environment.configuration);
public AbstractSmackIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
super(environment);
this.connection = this.conOne = environment.conOne; this.connection = this.conOne = environment.conOne;
this.conTwo = environment.conTwo; this.conTwo = environment.conTwo;
this.conThree = environment.conThree; this.conThree = environment.conThree;
final List<XMPPConnection> connectionsLocal = new ArrayList<>(3);
connectionsLocal.add(conOne);
connectionsLocal.add(conTwo);
connectionsLocal.add(conThree);
this.connections = Collections.unmodifiableList(connectionsLocal);
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015-2017 Florian Schmaus * Copyright 2015-2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,17 +16,22 @@
*/ */
package org.igniterealtime.smack.inttest; package org.igniterealtime.smack.inttest;
import java.security.KeyManagementException; import java.io.IOException;
import java.security.NoSuchAlgorithmException; import java.util.ArrayList;
import java.util.List;
import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.DomainBareJid;
public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmackIntTest { public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmackIntTest {
private final SmackIntegrationTestEnvironment environment; private final SmackIntegrationTestEnvironment<?> environment;
/** /**
* The configuration * The configuration
@ -35,33 +40,36 @@ public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmack
protected final DomainBareJid service; protected final DomainBareJid service;
public AbstractSmackLowLevelIntegrationTest(SmackIntegrationTestEnvironment environment) { protected AbstractSmackLowLevelIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
super(environment.testRunId, environment.configuration); super(environment);
this.environment = environment; this.environment = environment;
this.configuration = environment.configuration; this.configuration = environment.configuration;
this.service = configuration.service; this.service = configuration.service;
} }
public final XMPPTCPConnectionConfiguration.Builder getConnectionConfiguration() throws KeyManagementException, NoSuchAlgorithmException { protected AbstractXMPPConnection getConnectedConnection() throws InterruptedException, XMPPException, SmackException, IOException {
XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder(); AbstractXMPPConnection connection = getUnconnectedConnection();
if (configuration.tlsContext != null) { connection.connect().login();
builder.setCustomSSLContext(configuration.tlsContext); return connection;
}
builder.setSecurityMode(configuration.securityMode);
builder.setXmppDomain(service);
return builder;
} }
protected void performCheck(ConnectionCallback callback) throws Exception { protected AbstractXMPPConnection getUnconnectedConnection()
XMPPTCPConnection connection = SmackIntegrationTestFramework.getConnectedConnection(environment, -1); throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
try { return environment.connectionManager.constructConnection();
callback.connectionCallback(connection);
} finally {
IntTestUtil.disconnectAndMaybeDelete(connection, configuration);
}
} }
public interface ConnectionCallback { protected List<AbstractXMPPConnection> getUnconnectedConnections(int count)
void connectionCallback(XMPPTCPConnection connection) throws Exception; throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
List<AbstractXMPPConnection> connections = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
AbstractXMPPConnection connection = getUnconnectedConnection();
connections.add(connection);
} }
return connections;
}
protected void recycle(AbstractXMPPConnection connection) {
environment.connectionManager.recycle(connection);
}
} }

View file

@ -0,0 +1,63 @@
/**
*
* Copyright 2018-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.igniterealtime.smack.inttest;
import java.util.ArrayList;
import java.util.List;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
public abstract class AbstractSmackSpecificLowLevelIntegrationTest<C extends AbstractXMPPConnection>
extends AbstractSmackLowLevelIntegrationTest {
private final SmackIntegrationTestEnvironment<?> environment;
protected final Class<C> connectionClass;
private final XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor;
public AbstractSmackSpecificLowLevelIntegrationTest(SmackIntegrationTestEnvironment<?> environment,
Class<C> connectionClass) {
super(environment);
this.environment = environment;
this.connectionClass = connectionClass;
connectionDescriptor = environment.connectionManager.getConnectionDescriptorFor(connectionClass);
}
public Class<C> getConnectionClass() {
return connectionClass;
}
protected C getSpecificUnconnectedConnection() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
return environment.connectionManager.constructConnection(connectionDescriptor);
}
protected List<C> getSpecificUnconnectedConnections(int count)
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
List<C> connections = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
C connection = getSpecificUnconnectedConnection();
connections.add(connection);
}
return connections;
}
}

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015-2017 Florian Schmaus * Copyright 2015-2018 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -32,14 +32,18 @@ import java.util.logging.Logger;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.debugger.ConsoleDebugger;
import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.debugger.EnhancedDebugger;
import eu.geekplace.javapinning.java7.Java7Pinning; import eu.geekplace.javapinning.java7.Java7Pinning;
import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException; import org.jxmpp.stringprep.XmppStringprepException;
// TODO: Rename to SinttestConfiguration.
public final class Configuration { public final class Configuration {
private static final Logger LOGGER = Logger.getLogger(Configuration.class.getName()); private static final Logger LOGGER = Logger.getLogger(Configuration.class.getName());
@ -92,6 +96,8 @@ public final class Configuration {
public final Set<String> testPackages; public final Set<String> testPackages;
public final ConnectionConfigurationBuilderApplier configurationApplier;
private Configuration(DomainBareJid service, String serviceTlsPin, SecurityMode securityMode, int replyTimeout, private Configuration(DomainBareJid service, String serviceTlsPin, SecurityMode securityMode, int replyTimeout,
Debugger debugger, String accountOneUsername, String accountOnePassword, String accountTwoUsername, Debugger debugger, String accountOneUsername, String accountOnePassword, String accountTwoUsername,
String accountTwoPassword, String accountThreeUsername, String accountThreePassword, Set<String> enabledTests, Set<String> disabledTests, String accountTwoPassword, String accountThreeUsername, String accountThreePassword, Set<String> enabledTests, Set<String> disabledTests,
@ -126,6 +132,13 @@ public final class Configuration {
this.adminAccountUsername = adminAccountUsername; this.adminAccountUsername = adminAccountUsername;
this.adminAccountPassword = adminAccountPassword; this.adminAccountPassword = adminAccountPassword;
boolean accountOnePasswordSet = StringUtils.isNotEmpty(accountOnePassword);
if (accountOnePasswordSet != StringUtils.isNotEmpty(accountTwoPassword) ||
accountOnePasswordSet != StringUtils.isNotEmpty(accountThreePassword)) {
// Ensure the invariant that either all main accounts have a password set, or none.
throw new IllegalArgumentException();
}
this.accountOneUsername = accountOneUsername; this.accountOneUsername = accountOneUsername;
this.accountOnePassword = accountOnePassword; this.accountOnePassword = accountOnePassword;
this.accountTwoUsername = accountTwoUsername; this.accountTwoUsername = accountTwoUsername;
@ -135,6 +148,26 @@ public final class Configuration {
this.enabledTests = enabledTests; this.enabledTests = enabledTests;
this.disabledTests = disabledTests; this.disabledTests = disabledTests;
this.testPackages = testPackages; this.testPackages = testPackages;
this.configurationApplier = (builder) -> {
if (tlsContext != null) {
builder.setCustomSSLContext(tlsContext);
}
builder.setSecurityMode(securityMode);
builder.setXmppDomain(service);
switch (debugger) {
case enhanced:
builder.setDebuggerFactory(EnhancedDebugger.Factory.INSTANCE);
break;
case console:
builder.setDebuggerFactory(ConsoleDebugger.Factory.INSTANCE);
break;
case none:
// Nothing to do :).
break;
}
};
} }
public boolean isAccountRegistrationPossible() { public boolean isAccountRegistrationPossible() {

View file

@ -0,0 +1,23 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.igniterealtime.smack.inttest;
import org.jivesoftware.smack.ConnectionConfiguration;
public interface ConnectionConfigurationBuilderApplier {
void applyConfigurationTo(ConnectionConfiguration.Builder<?, ?> connectionConfigurationBuilder);
}

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015 Florian Schmaus * Copyright 2015-2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,15 +16,14 @@
*/ */
package org.igniterealtime.smack.inttest; package org.igniterealtime.smack.inttest;
import java.lang.reflect.Method;
import java.util.List; import java.util.List;
public class FailedTest extends TestResult { public class FailedTest extends TestResult {
public final Throwable failureReason; public final Throwable failureReason;
public FailedTest(Method testMethod, long startTime, long endTime, List<String> logMessages, Throwable failureReason) { public FailedTest(SmackIntegrationTestFramework.ConcreteTest concreteTest, long startTime, long endTime, List<String> logMessages, Throwable failureReason) {
super(testMethod, startTime, endTime, logMessages); super(concreteTest, startTime, endTime, logMessages);
this.failureReason = failureReason; this.failureReason = failureReason;
} }
} }

View file

@ -1,241 +0,0 @@
/**
*
* Copyright 2015-2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.igniterealtime.smack.inttest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.admin.ServiceAdministrationManager;
import org.jivesoftware.smackx.iqregister.AccountManager;
import org.igniterealtime.smack.inttest.Configuration.AccountRegistration;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Localpart;
import org.jxmpp.stringprep.XmppStringprepException;
public class IntTestUtil {
private static final Logger LOGGER = Logger.getLogger(IntTestUtil.class.getName());
public static UsernameAndPassword registerAccount(XMPPTCPConnection connection, SmackIntegrationTestEnvironment environment, int connectionId) throws InterruptedException, XMPPException, SmackException, IOException {
String username = "sinttest-" + environment.testRunId + "-" + connectionId;
return registerAccount(connection, username, StringUtils.insecureRandomString(12), environment.configuration);
}
public static UsernameAndPassword registerAccount(XMPPTCPConnection connection, String accountUsername, String accountPassword,
Configuration config) throws InterruptedException, XMPPException, SmackException, IOException {
switch (config.accountRegistration) {
case inBandRegistration:
return registerAccountViaIbr(connection, accountUsername, accountPassword);
case serviceAdministration:
return registerAccountViaAdmin(connection, accountUsername, accountPassword, config.adminAccountUsername, config.adminAccountPassword);
default:
throw new AssertionError();
}
}
// public static UsernameAndPassword registerAccountViaAdmin(XMPPTCPConnection connection) throws XmppStringprepException, NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
// return registerAccountViaAdmin(connection, StringUtils.insecureRandomString(12),
// StringUtils.insecureRandomString(12));
// }
public static UsernameAndPassword registerAccountViaAdmin(XMPPTCPConnection connection, String username,
String password, String adminAccountUsername, String adminAccountPassword) throws InterruptedException, XMPPException, SmackException, IOException {
connection.login(adminAccountUsername, adminAccountPassword);
ServiceAdministrationManager adminManager = ServiceAdministrationManager.getInstanceFor(connection);
EntityBareJid userJid = JidCreate.entityBareFrom(Localpart.from(username), connection.getXMPPServiceDomain());
adminManager.addUser(userJid, password);
connection.disconnect();
connection.connect();
return new UsernameAndPassword(username, password);
}
public static UsernameAndPassword registerAccountViaIbr(XMPPConnection connection)
throws NoResponseException, XMPPErrorException, NotConnectedException,
InterruptedException {
return registerAccountViaIbr(connection, StringUtils.insecureRandomString(12),
StringUtils.insecureRandomString(12));
}
public static UsernameAndPassword registerAccountViaIbr(XMPPConnection connection, String username,
String password) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException {
AccountManager accountManager = AccountManager.getInstance(connection);
if (!accountManager.supportsAccountCreation()) {
throw new UnsupportedOperationException("Account creation/registation is not supported");
}
Set<String> requiredAttributes = accountManager.getAccountAttributes();
if (requiredAttributes.size() > 4) {
throw new IllegalStateException("Unkown required attributes");
}
Map<String, String> additionalAttributes = new HashMap<>();
additionalAttributes.put("name", "Smack Integration Test");
additionalAttributes.put("email", "flow@igniterealtime.org");
Localpart usernameLocalpart;
try {
usernameLocalpart = Localpart.from(username);
}
catch (XmppStringprepException e) {
throw new IllegalArgumentException("Invalid username: " + username, e);
}
accountManager.createAccount(usernameLocalpart, password, additionalAttributes);
return new UsernameAndPassword(username, password);
}
public static final class UsernameAndPassword {
public final String username;
public final String password;
private UsernameAndPassword(String username, String password) {
this.username = username;
this.password = password;
}
}
public static void disconnectAndMaybeDelete(XMPPTCPConnection connection, Configuration config) throws InterruptedException {
try {
if (!config.isAccountRegistrationPossible()) {
return;
}
Configuration.AccountRegistration accountDeletionMethod = config.accountRegistration;
AccountManager accountManager = AccountManager.getInstance(connection);
try {
if (accountManager.isSupported()) {
accountDeletionMethod = AccountRegistration.inBandRegistration;
}
}
catch (NoResponseException | XMPPErrorException | NotConnectedException e) {
LOGGER.log(Level.WARNING, "Could not test if XEP-0077 account deletion is possible", e);
}
switch (accountDeletionMethod) {
case inBandRegistration:
deleteViaIbr(connection);
break;
case serviceAdministration:
deleteViaServiceAdministration(connection, config);
break;
default:
throw new AssertionError();
}
}
finally {
connection.disconnect();
}
}
public static void deleteViaServiceAdministration(XMPPTCPConnection connection, Configuration config) {
EntityBareJid accountToDelete = connection.getUser().asEntityBareJid();
final int maxAttempts = 3;
int attempts;
for (attempts = 0; attempts < maxAttempts; attempts++) {
connection.disconnect();
try {
connection.connect().login(config.adminAccountUsername, config.adminAccountPassword);
}
catch (XMPPException | SmackException | IOException | InterruptedException e) {
LOGGER.log(Level.WARNING, "Exception deleting account for " + connection, e);
continue;
}
ServiceAdministrationManager adminManager = ServiceAdministrationManager.getInstanceFor(connection);
try {
adminManager.deleteUser(accountToDelete);
break;
}
catch (NoResponseException | XMPPErrorException | NotConnectedException | InterruptedException e) {
LOGGER.log(Level.WARNING, "Exception deleting account for " + connection, e);
continue;
}
}
if (attempts > maxAttempts) {
LOGGER.log(Level.SEVERE, "Could not delete account for connection: " + connection);
}
}
public static void deleteViaIbr(XMPPTCPConnection connection)
throws InterruptedException {
// If the connection is disconnected, then re-reconnect and login. This could happen when
// (low-level) integration tests disconnect the connection, e.g. to test disconnection
// mechanisms
if (!connection.isConnected()) {
try {
connection.connect().login();
}
catch (XMPPException | SmackException | IOException e) {
LOGGER.log(Level.WARNING, "Exception reconnection account for deletion", e);
}
}
final int maxAttempts = 3;
AccountManager am = AccountManager.getInstance(connection);
int attempts;
for (attempts = 0; attempts < maxAttempts; attempts++) {
try {
am.deleteAccount();
}
catch (XMPPErrorException | NoResponseException e) {
LOGGER.log(Level.WARNING, "Exception deleting account for " + connection, e);
continue;
}
catch (NotConnectedException e) {
LOGGER.log(Level.WARNING, "Exception deleting account for " + connection, e);
try {
connection.connect().login();
}
catch (XMPPException | SmackException | IOException e2) {
LOGGER.log(Level.WARNING, "Exception while trying to re-connect " + connection, e);
}
continue;
}
LOGGER.info("Successfully deleted account of " + connection);
break;
}
if (attempts > maxAttempts) {
LOGGER.log(Level.SEVERE, "Could not delete account for connection: " + connection);
}
}
}

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015 Florian Schmaus * Copyright 2015-2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,13 +16,19 @@
*/ */
package org.igniterealtime.smack.inttest; package org.igniterealtime.smack.inttest;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Documented
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
public @interface SmackIntegrationTest { public @interface SmackIntegrationTest {
boolean onlyDefaultConnectionType() default false;
int connectionCount() default -1;
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015 Florian Schmaus * Copyright 2015-2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,26 +16,25 @@
*/ */
package org.igniterealtime.smack.inttest; package org.igniterealtime.smack.inttest;
import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.AbstractXMPPConnection;
public class SmackIntegrationTestEnvironment { public class SmackIntegrationTestEnvironment<C extends AbstractXMPPConnection> {
public final XMPPTCPConnection conOne; public final C conOne, conTwo, conThree;
public final XMPPTCPConnection conTwo;
public final XMPPTCPConnection conThree;
public final String testRunId; public final String testRunId;
public final Configuration configuration; public final Configuration configuration;
SmackIntegrationTestEnvironment(XMPPTCPConnection conOne, XMPPTCPConnection conTwo, XMPPTCPConnection conThree, String testRunId, public final XmppConnectionManager<C> connectionManager;
Configuration configuration) {
SmackIntegrationTestEnvironment(C conOne, C conTwo, C conThree, String testRunId,
Configuration configuration, XmppConnectionManager<C> connectionManager) {
this.conOne = conOne; this.conOne = conOne;
this.conTwo = conTwo; this.conTwo = conTwo;
this.conThree = conThree; this.conThree = conThree;
this.testRunId = testRunId; this.testRunId = testRunId;
this.configuration = configuration; this.configuration = configuration;
this.connectionManager = connectionManager;
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015-2017 Florian Schmaus * Copyright 2015-2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -27,8 +27,12 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.security.KeyManagementException; import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -43,21 +47,20 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.SmackConfiguration; import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.debugger.ConsoleDebugger;
import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.debugger.EnhancedDebugger;
import org.jivesoftware.smackx.debugger.EnhancedDebuggerWindow; import org.jivesoftware.smackx.debugger.EnhancedDebuggerWindow;
import org.jivesoftware.smackx.iqregister.AccountManager; import org.jivesoftware.smackx.iqregister.AccountManager;
import org.igniterealtime.smack.inttest.IntTestUtil.UsernameAndPassword; import org.igniterealtime.smack.inttest.Configuration.AccountRegistration;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.reflections.Reflections; import org.reflections.Reflections;
@ -66,51 +69,60 @@ import org.reflections.scanners.MethodParameterScanner;
import org.reflections.scanners.SubTypesScanner; import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner; import org.reflections.scanners.TypeAnnotationsScanner;
public class SmackIntegrationTestFramework { public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
private static final Logger LOGGER = Logger.getLogger(SmackIntegrationTestFramework.class.getName()); private static final Logger LOGGER = Logger.getLogger(SmackIntegrationTestFramework.class.getName());
private static final char CLASS_METHOD_SEP = '#';
public static boolean SINTTEST_UNIT_TEST = false;
private final Class<DC> defaultConnectionClass;
protected final Configuration config; protected final Configuration config;
protected TestRunResult testRunResult; protected TestRunResult testRunResult;
private SmackIntegrationTestEnvironment environment;
private SmackIntegrationTestEnvironment<DC> environment;
protected XmppConnectionManager<DC> connectionManager;
public enum TestType { public enum TestType {
Normal, Normal,
LowLevel, LowLevel,
SpecificLowLevel,
} }
public static void main(String[] args) throws IOException, KeyManagementException, public static void main(String[] args) throws IOException, KeyManagementException,
NoSuchAlgorithmException, SmackException, XMPPException, InterruptedException { NoSuchAlgorithmException, SmackException, XMPPException, InterruptedException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Configuration config = Configuration.newConfiguration(args); Configuration config = Configuration.newConfiguration(args);
SmackIntegrationTestFramework sinttest = new SmackIntegrationTestFramework(config); SmackIntegrationTestFramework<XMPPTCPConnection> sinttest = new SmackIntegrationTestFramework<>(config, XMPPTCPConnection.class);
TestRunResult testRunResult = sinttest.run(); TestRunResult testRunResult = sinttest.run();
for (Entry<Class<? extends AbstractSmackIntTest>, String> entry : testRunResult.impossibleTestClasses.entrySet()) { for (Entry<Class<? extends AbstractSmackIntTest>, Throwable> entry : testRunResult.impossibleTestClasses.entrySet()) {
LOGGER.info("Could not run " + entry.getKey().getName() + " because: " LOGGER.info("Could not run " + entry.getKey().getName() + " because: "
+ entry.getValue()); + entry.getValue().getLocalizedMessage());
} }
for (TestNotPossible testNotPossible : testRunResult.impossibleTestMethods) { for (TestNotPossible testNotPossible : testRunResult.impossibleIntegrationTests) {
LOGGER.info("Could not run " + testNotPossible.testMethod.getName() + " because: " LOGGER.info("Could not run " + testNotPossible.concreteTest + " because: "
+ testNotPossible.testNotPossibleException.getMessage()); + testNotPossible.testNotPossibleException.getMessage());
} }
final int successfulTests = testRunResult.successfulTests.size(); for (SuccessfulTest successfulTest : testRunResult.successfulIntegrationTests) {
LOGGER.info(successfulTest.concreteTest + "");
}
final int successfulTests = testRunResult.successfulIntegrationTests.size();
final int failedTests = testRunResult.failedIntegrationTests.size();
final int totalIntegrationTests = successfulTests + failedTests;
final int availableTests = testRunResult.getNumberOfAvailableTests(); final int availableTests = testRunResult.getNumberOfAvailableTests();
final int possibleTests = testRunResult.getNumberOfPossibleTests(); final int possibleTests = testRunResult.getNumberOfPossibleTests();
LOGGER.info("SmackIntegrationTestFramework[" + testRunResult.testRunId + ']' + ": Finished [" LOGGER.info("SmackIntegrationTestFramework[" + testRunResult.testRunId + ']' + ": Finished ["
+ successfulTests + '/' + possibleTests + "] (of " + availableTests + " available tests)"); + successfulTests + '/' + totalIntegrationTests + "] (" + possibleTests + " test methods of " + availableTests + " where possible)");
int exitStatus; final int exitStatus;
if (!testRunResult.failedIntegrationTests.isEmpty()) { if (failedTests > 0) {
final int failedTests = testRunResult.failedIntegrationTests.size(); LOGGER.warning("💀 The following " + failedTests + " tests failed! 💀");
LOGGER.warning("The following " + failedTests + " tests failed!");
for (FailedTest failedTest : testRunResult.failedIntegrationTests) { for (FailedTest failedTest : testRunResult.failedIntegrationTests) {
final Method method = failedTest.testMethod;
final String className = method.getDeclaringClass().getName();
final String methodName = method.getName();
final Throwable cause = failedTest.failureReason; final Throwable cause = failedTest.failureReason;
LOGGER.severe(className + CLASS_METHOD_SEP + methodName + " failed: " + cause); LOGGER.log(Level.SEVERE, failedTest.concreteTest + " failed: " + cause, cause);
} }
exitStatus = 2; exitStatus = 2;
} else { } else {
@ -129,13 +141,22 @@ public class SmackIntegrationTestFramework {
System.exit(exitStatus); System.exit(exitStatus);
} }
public SmackIntegrationTestFramework(Configuration configuration) { public SmackIntegrationTestFramework(Configuration configuration, Class<DC> defaultConnectionClass)
throws KeyManagementException, InstantiationException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException, NoSuchAlgorithmException, SmackException, IOException, XMPPException,
InterruptedException {
this.config = configuration; this.config = configuration;
this.defaultConnectionClass = defaultConnectionClass;
} }
public synchronized TestRunResult run() throws KeyManagementException, NoSuchAlgorithmException, SmackException, public synchronized TestRunResult run()
IOException, XMPPException, InterruptedException { throws KeyManagementException, NoSuchAlgorithmException, SmackException, IOException, XMPPException,
InterruptedException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
testRunResult = new TestRunResult(); testRunResult = new TestRunResult();
// Create a connection manager *after* we created the testRunId (in testRunResult).
this.connectionManager = new XmppConnectionManager<>(this, defaultConnectionClass);
LOGGER.info("SmackIntegrationTestFramework [" + testRunResult.testRunId + ']' + ": Starting"); LOGGER.info("SmackIntegrationTestFramework [" + testRunResult.testRunId + ']' + ": Starting");
if (config.debugger != Configuration.Debugger.none) { if (config.debugger != Configuration.Debugger.none) {
// JUL Debugger will not print any information until configured to print log messages of // JUL Debugger will not print any information until configured to print log messages of
@ -147,7 +168,7 @@ public class SmackIntegrationTestFramework {
if (config.replyTimeout > 0) { if (config.replyTimeout > 0) {
SmackConfiguration.setDefaultReplyTimeout(config.replyTimeout); SmackConfiguration.setDefaultReplyTimeout(config.replyTimeout);
} }
if (config.securityMode != SecurityMode.required) { if (config.securityMode != SecurityMode.required && config.accountRegistration == AccountRegistration.inBandRegistration) {
AccountManager.sensitiveOperationOverInsecureConnectionDefault(true); AccountManager.sensitiveOperationOverInsecureConnectionDefault(true);
} }
// TODO print effective configuration // TODO print effective configuration
@ -169,6 +190,18 @@ public class SmackIntegrationTestFramework {
classes.addAll(inttestClasses); classes.addAll(inttestClasses);
classes.addAll(lowLevelInttestClasses); classes.addAll(lowLevelInttestClasses);
{
// Remove all abstract classes.
// TODO: This may be a good candidate for Java stream filtering once Smack is Android API 24 or higher.
Iterator<Class<? extends AbstractSmackIntTest>> it = classes.iterator();
while (it.hasNext()) {
Class<? extends AbstractSmackIntTest> clazz = it.next();
if (Modifier.isAbstract(clazz.getModifiers())) {
it.remove();
}
}
}
if (classes.isEmpty()) { if (classes.isEmpty()) {
throw new IllegalStateException("No test classes found"); throw new IllegalStateException("No test classes found");
} }
@ -182,9 +215,7 @@ public class SmackIntegrationTestFramework {
} }
finally { finally {
// Ensure that the accounts are deleted and disconnected before we continue // Ensure that the accounts are deleted and disconnected before we continue
disconnectAndMaybeDelete(environment.conOne); connectionManager.disconnectAndCleanup();
disconnectAndMaybeDelete(environment.conTwo);
disconnectAndMaybeDelete(environment.conThree);
} }
return testRunResult; return testRunResult;
@ -192,10 +223,42 @@ public class SmackIntegrationTestFramework {
@SuppressWarnings({"unchecked", "Finally"}) @SuppressWarnings({"unchecked", "Finally"})
private void runTests(Set<Class<? extends AbstractSmackIntTest>> classes) private void runTests(Set<Class<? extends AbstractSmackIntTest>> classes)
throws NoResponseException, InterruptedException { throws InterruptedException, InstantiationException, IllegalAccessException,
IllegalArgumentException, SmackException, IOException, XMPPException {
for (Class<? extends AbstractSmackIntTest> testClass : classes) { for (Class<? extends AbstractSmackIntTest> testClass : classes) {
final String testClassName = testClass.getName(); final String testClassName = testClass.getName();
// TODO: Move the whole "skipping section" below one layer up?
// Skip pseudo integration tests from src/test
// Although Smack's gradle build files do not state that the 'main' sources classpath also contains the
// 'test' classes. Some IDEs like Eclipse include them. As result, a real integration test run encounters
// pseudo integration tests like the DummySmackIntegrationTest which always throws from src/test.
// It is unclear why this apparently does not happen in the 4.3 branch, one likely cause is
// compile project(path: ":smack-omemo", configuration: "testRuntime")
// in
// smack-integration-test/build.gradle:17
// added after 4.3 was branched out with
// 1f731f6318785a84b9741280d586a61dc37ecb2e
// Now "gradle integrationTest" appear to be never affected by this, i.e., they are executed with the
// correct classpath. Plain Eclipse, i.e. Smack imported into Eclipse after "gradle eclipse", appear
// to include *all* classes. Which means those runs sooner or later try to execute
// DummySmackIntegrationTest. Eclipse with buildship, the gradle plugin for Eclipse, always excludes
// *all* src/test classes, which means they do not encounter DummySmackIntegrationTest, but this means
// that the "compile project(path: ":smack-omemo", configuration: "testRuntime")" is not respected,
// which leads to
// Exception in thread "main" java.lang.NoClassDefFoundError: org/jivesoftware/smack/test/util/FileTestUtil
// at org.jivesoftware.smackx.ox.OXSecretKeyBackupIntegrationTest.<clinit>(OXSecretKeyBackupIntegrationTest.java:66)
// See
// - https://github.com/eclipse/buildship/issues/354 (Remove test dependencies from runtime classpath)
// - https://bugs.eclipse.org/bugs/show_bug.cgi?id=482315 (Runtime classpath includes test dependencies)
// - https://discuss.gradle.org/t/main-vs-test-compile-vs-runtime-classpaths-in-eclipse-once-and-for-all-how/17403
// - https://bugs.eclipse.org/bugs/show_bug.cgi?id=376616 (Scope of dependencies has no effect on Eclipse compilation)
if (!SINTTEST_UNIT_TEST && testClassName.startsWith("org.igniterealtime.smack.inttest.unittest")) {
LOGGER.finer("Skipping integration test '" + testClassName + "' from src/test classpath");
continue;
}
if (config.enabledTests != null && !isInSet(testClass, config.enabledTests)) { if (config.enabledTests != null && !isInSet(testClass, config.enabledTests)) {
LOGGER.info("Skipping test class " + testClassName + " because it is not enabled"); LOGGER.info("Skipping test class " + testClassName + " because it is not enabled");
continue; continue;
@ -206,46 +269,84 @@ public class SmackIntegrationTestFramework {
continue; continue;
} }
TestType testType; final Constructor<? extends AbstractSmackIntTest> cons;
if (AbstractSmackLowLevelIntegrationTest.class.isAssignableFrom(testClass)) { try {
cons = testClass.getConstructor(SmackIntegrationTestEnvironment.class);
}
catch (NoSuchMethodException | SecurityException e) {
throw new IllegalArgumentException(
"Smack Integration Test class does not declare the correct constructor. Is a public Constructor(SmackIntegrationTestEnvironment) missing?",
e);
}
final List<Method> smackIntegrationTestMethods;
{
Method[] testClassMethods = testClass.getMethods();
smackIntegrationTestMethods = new ArrayList<>(testClassMethods.length);
for (Method method : testClassMethods) {
if (!method.isAnnotationPresent(SmackIntegrationTest.class)) {
continue;
}
smackIntegrationTestMethods.add(method);
}
}
if (smackIntegrationTestMethods.isEmpty()) {
LOGGER.warning("No Smack integration test methods found in " + testClass);
continue;
}
testRunResult.numberOfAvailableTestMethods.addAndGet(smackIntegrationTestMethods.size());
final AbstractSmackIntTest test;
try {
test = cons.newInstance(environment);
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
throwFatalException(cause);
testRunResult.impossibleTestClasses.put(testClass, cause);
continue;
}
Class<? extends AbstractXMPPConnection> specificLowLevelConnectionClass = null;
final TestType testType;
if (test instanceof AbstractSmackSpecificLowLevelIntegrationTest) {
AbstractSmackSpecificLowLevelIntegrationTest<?> specificLowLevelTest = (AbstractSmackSpecificLowLevelIntegrationTest<?>) test;
specificLowLevelConnectionClass = specificLowLevelTest.getConnectionClass();
testType = TestType.SpecificLowLevel;
} else if (test instanceof AbstractSmackLowLevelIntegrationTest) {
testType = TestType.LowLevel; testType = TestType.LowLevel;
} else if (AbstractSmackIntegrationTest.class.isAssignableFrom(testClass)) { } else if (test instanceof AbstractSmackIntegrationTest) {
testType = TestType.Normal; testType = TestType.Normal;
} else { } else {
throw new AssertionError(); throw new AssertionError();
} }
List<Method> smackIntegrationTestMethods = new LinkedList<>();
for (Method method : testClass.getMethods()) { // Verify the method signatures, throw in case a signature is incorrect.
if (!method.isAnnotationPresent(SmackIntegrationTest.class)) { for (Method method : smackIntegrationTestMethods) {
continue;
}
Class<?> retClass = method.getReturnType(); Class<?> retClass = method.getReturnType();
if (!retClass.equals(Void.TYPE)) { if (!retClass.equals(Void.TYPE)) {
LOGGER.warning("SmackIntegrationTest annotation on method that does not return void"); throw new IllegalStateException(
continue; "SmackIntegrationTest annotation on" + method + " that does not return void");
} }
final Class<?>[] parameterTypes = method.getParameterTypes();
switch (testType) { switch (testType) {
case Normal: case Normal:
if (method.getParameterTypes().length > 0) { final Class<?>[] parameterTypes = method.getParameterTypes();
LOGGER.warning("SmackIntegrationTest annotaton on method that takes arguments "); if (parameterTypes.length > 0) {
continue; throw new IllegalStateException(
"SmackIntegrationTest annotaton on " + method + " that takes arguments ");
} }
break; break;
case LowLevel: case LowLevel:
for (Class<?> parameterType : parameterTypes) { verifyLowLevelTestMethod(method, AbstractXMPPConnection.class);
if (!parameterType.isAssignableFrom(XMPPTCPConnection.class)) { break;
LOGGER.warning("SmackIntegrationTest low-level test method declares parameter that is not of type XMPPTCPConnection"); case SpecificLowLevel:
} verifyLowLevelTestMethod(method, specificLowLevelConnectionClass);
}
break; break;
} }
smackIntegrationTestMethods.add(method);
}
if (smackIntegrationTestMethods.isEmpty()) {
LOGGER.warning("No integration test methods found");
continue;
} }
Iterator<Method> it = smackIntegrationTestMethods.iterator(); Iterator<Method> it = smackIntegrationTestMethods.iterator();
@ -271,75 +372,7 @@ public class SmackIntegrationTestFramework {
} }
final int detectedTestMethodsCount = smackIntegrationTestMethods.size(); final int detectedTestMethodsCount = smackIntegrationTestMethods.size();
testRunResult.numberOfAvailableTests.addAndGet(detectedTestMethodsCount); testRunResult.numberOfPossibleTestMethods.addAndGet(detectedTestMethodsCount);
testRunResult.numberOfPossibleTests.addAndGet(detectedTestMethodsCount);
AbstractSmackIntTest test;
switch (testType) {
case Normal: {
Constructor<? extends AbstractSmackIntegrationTest> cons;
try {
cons = ((Class<? extends AbstractSmackIntegrationTest>) testClass).getConstructor(SmackIntegrationTestEnvironment.class);
}
catch (NoSuchMethodException | SecurityException e) {
LOGGER.log(Level.WARNING,
"Smack Integration Test class could not get constructed (public Con)structor(SmackIntegrationTestEnvironment) missing?)",
e);
continue;
}
try {
test = cons.newInstance(environment);
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
throwFatalException(cause);
testRunResult.impossibleTestClasses.put(testClass, cause.getMessage());
testRunResult.numberOfPossibleTests.addAndGet(-detectedTestMethodsCount);
continue;
}
catch (InstantiationException | IllegalAccessException | IllegalArgumentException e) {
LOGGER.log(Level.WARNING, "todo", e);
continue;
}
} break;
case LowLevel: {
Constructor<? extends AbstractSmackLowLevelIntegrationTest> cons;
try {
cons = ((Class<? extends AbstractSmackLowLevelIntegrationTest>) testClass).getConstructor(
SmackIntegrationTestEnvironment.class);
}
catch (NoSuchMethodException | SecurityException e) {
LOGGER.log(Level.WARNING,
"Smack Integration Test class could not get constructed (public Con)structor(SmackIntegrationTestEnvironment) missing?)",
e);
continue;
}
try {
test = cons.newInstance(environment);
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof TestNotPossibleException) {
testRunResult.impossibleTestClasses.put(testClass, cause.getMessage());
testRunResult.numberOfPossibleTests.addAndGet(-detectedTestMethodsCount);
}
else {
throwFatalException(cause);
LOGGER.log(Level.WARNING, "Could not construct test class", e);
}
continue;
}
catch (InstantiationException | IllegalAccessException | IllegalArgumentException e) {
LOGGER.log(Level.WARNING, "todo", e);
continue;
}
} break;
default:
throw new AssertionError();
}
try { try {
// Run the @BeforeClass methods (if any) // Run the @BeforeClass methods (if any)
@ -373,48 +406,36 @@ public class SmackIntegrationTestFramework {
} }
for (Method testMethod : smackIntegrationTestMethods) { for (Method testMethod : smackIntegrationTestMethods) {
final String testPrefix = testClass.getSimpleName() + '.' List<ConcreteTest> concreteTests = null;
+ testMethod.getName() + " (" + testType + "): ";
// Invoke all test methods on the test instance
LOGGER.info(testPrefix + "Start");
long testStart = System.currentTimeMillis();
try {
switch (testType) { switch (testType) {
case Normal: case Normal: {
testMethod.invoke(test); ConcreteTest.Executor concreteTestExecutor = () -> testMethod.invoke(test);
ConcreteTest concreteTest = new ConcreteTest(testType, testMethod, concreteTestExecutor);
concreteTests = Collections.singletonList(concreteTest);
}
break; break;
case LowLevel: case LowLevel:
invokeLowLevel(testMethod, test); case SpecificLowLevel:
LowLevelTestMethod lowLevelTestMethod = new LowLevelTestMethod(testMethod);
switch (testType) {
case LowLevel:
concreteTests = invokeLowLevel(lowLevelTestMethod, (AbstractSmackLowLevelIntegrationTest) test);
break;
case SpecificLowLevel: {
ConcreteTest.Executor concreteTestExecutor = () -> invokeSpecificLowLevel(
lowLevelTestMethod, (AbstractSmackSpecificLowLevelIntegrationTest<?>) test);
ConcreteTest concreteTest = new ConcreteTest(testType, testMethod, concreteTestExecutor);
concreteTests = Collections.singletonList(concreteTest);
break; break;
} }
LOGGER.info(testPrefix + "Success"); default:
long testEnd = System.currentTimeMillis(); throw new AssertionError();
testRunResult.successfulTests.add(new SuccessfulTest(testMethod, testStart, testEnd, null));
} }
catch (InvocationTargetException e) { break;
long testEnd = System.currentTimeMillis();
Throwable cause = e.getCause();
if (cause instanceof TestNotPossibleException) {
LOGGER.info(testPrefix + "Not possible");
testRunResult.impossibleTestMethods.add(new TestNotPossible(testMethod, testStart, testEnd,
null, (TestNotPossibleException) cause));
continue;
} }
Throwable nonFatalFailureReason;
// junit assert's throw an AssertionError if they fail, those should not be for (ConcreteTest concreteTest : concreteTests) {
// thrown up, as it would be done by throwFatalException() runConcreteTest(concreteTest);
if (cause instanceof AssertionError) {
nonFatalFailureReason = cause;
} else {
nonFatalFailureReason = throwFatalException(cause);
}
// An integration test failed
testRunResult.failedIntegrationTests.add(new FailedTest(testMethod, testStart, testEnd, null,
nonFatalFailureReason));
LOGGER.log(Level.SEVERE, testPrefix + "Failed", e);
}
catch (IllegalArgumentException | IllegalAccessException e) {
throw new AssertionError(e);
} }
} }
} }
@ -452,67 +473,86 @@ public class SmackIntegrationTestFramework {
} }
} }
private void invokeLowLevel(Method testMethod, AbstractSmackIntTest test) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InterruptedException { private void runConcreteTest(ConcreteTest concreteTest)
// We have checked before that every parameter, if any, is of type XMPPTCPConnection throws InterruptedException, XMPPException, IOException, SmackException {
final int numberOfConnections = testMethod.getParameterTypes().length; LOGGER.info(concreteTest + " Start");
XMPPTCPConnection[] connections = null; long testStart = System.currentTimeMillis();
try { try {
if (numberOfConnections > 0 && !config.isAccountRegistrationPossible()) { concreteTest.executor.execute();
throw new TestNotPossibleException( long testEnd = System.currentTimeMillis();
"Must create accounts for this test, but it's not enabled"); LOGGER.info(concreteTest + " Success");
testRunResult.successfulIntegrationTests.add(new SuccessfulTest(concreteTest, testStart, testEnd, null));
} }
connections = new XMPPTCPConnection[numberOfConnections]; catch (InvocationTargetException e) {
for (int i = 0; i < numberOfConnections; ++i) { long testEnd = System.currentTimeMillis();
connections[i] = getConnectedConnection(environment, i); Throwable cause = e.getCause();
if (cause instanceof TestNotPossibleException) {
LOGGER.info(concreteTest + " is not possible");
testRunResult.impossibleIntegrationTests.add(new TestNotPossible(concreteTest, testStart, testEnd,
null, (TestNotPossibleException) cause));
return;
} }
Throwable nonFatalFailureReason;
// junit assert's throw an AssertionError if they fail, those should not be
// thrown up, as it would be done by throwFatalException()
if (cause instanceof AssertionError) {
nonFatalFailureReason = cause;
} else {
nonFatalFailureReason = throwFatalException(cause);
} }
catch (Exception e) { // An integration test failed
if (e instanceof RuntimeException) { testRunResult.failedIntegrationTests.add(new FailedTest(concreteTest, testStart, testEnd, null,
throw (RuntimeException) e; nonFatalFailureReason));
} LOGGER.log(Level.SEVERE, concreteTest + " Failed", e);
// Behave like this was an InvocationTargetException
throw new InvocationTargetException(e);
}
try {
testMethod.invoke(test, (Object[]) connections);
}
finally {
for (int i = 0; i < numberOfConnections; ++i) {
IntTestUtil.disconnectAndMaybeDelete(connections[i], config);
} }
catch (IllegalArgumentException | IllegalAccessException e) {
throw new AssertionError(e);
} }
} }
protected void disconnectAndMaybeDelete(XMPPTCPConnection connection) throws InterruptedException { private static void verifyLowLevelTestMethod(Method method,
IntTestUtil.disconnectAndMaybeDelete(connection, config); Class<? extends AbstractXMPPConnection> connectionClass) {
if (!testMethodParametersIsListOfConnections(method, connectionClass)
&& !testMethodParametersVarargsConnections(method, connectionClass)) {
throw new IllegalArgumentException(method + " is not a valid low level test method");
}
} }
protected SmackIntegrationTestEnvironment prepareEnvironment() throws SmackException, private List<ConcreteTest> invokeLowLevel(LowLevelTestMethod lowLevelTestMethod, AbstractSmackLowLevelIntegrationTest test) {
Set<Class<? extends AbstractXMPPConnection>> connectionClasses;
if (lowLevelTestMethod.smackIntegrationTestAnnotation.onlyDefaultConnectionType()) {
Class<? extends AbstractXMPPConnection> defaultConnectionClass = connectionManager.getDefaultConnectionClass();
connectionClasses = Collections.singleton(defaultConnectionClass);
} else {
connectionClasses = connectionManager.getConnectionClasses();
}
List<ConcreteTest> resultingConcreteTests = new ArrayList<>(connectionClasses.size());
for (Class<? extends AbstractXMPPConnection> connectionClass : connectionClasses) {
ConcreteTest.Executor executor = () -> lowLevelTestMethod.invoke(test, connectionClass);
ConcreteTest concreteTest = new ConcreteTest(TestType.LowLevel, lowLevelTestMethod.testMethod, executor, connectionClass.getSimpleName());
resultingConcreteTests.add(concreteTest);
}
return resultingConcreteTests;
}
private <C extends AbstractXMPPConnection> void invokeSpecificLowLevel(LowLevelTestMethod testMethod,
AbstractSmackSpecificLowLevelIntegrationTest<C> test)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InterruptedException,
SmackException, IOException, XMPPException {
if (testMethod.smackIntegrationTestAnnotation.onlyDefaultConnectionType()) {
throw new IllegalArgumentException("SpecificLowLevelTests must not have set onlyDefaultConnectionType");
}
Class<C> connectionClass = test.getConnectionClass();
testMethod.invoke(test, connectionClass);
}
protected SmackIntegrationTestEnvironment<DC> prepareEnvironment() throws SmackException,
IOException, XMPPException, InterruptedException, KeyManagementException, IOException, XMPPException, InterruptedException, KeyManagementException,
NoSuchAlgorithmException { NoSuchAlgorithmException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
XMPPTCPConnection conOne = null; return connectionManager.prepareEnvironment();
XMPPTCPConnection conTwo = null;
XMPPTCPConnection conThree = null;
try {
conOne = getConnectedConnectionFor(AccountNum.One);
conTwo = getConnectedConnectionFor(AccountNum.Two);
conThree = getConnectedConnectionFor(AccountNum.Three);
}
catch (Exception e) {
// TODO Reverse the order, i.e. conThree should be disconnected first.
if (conOne != null) {
conOne.disconnect();
}
if (conTwo != null) {
conTwo.disconnect();
}
if (conThree != null) {
conThree.disconnect();
}
throw e;
}
return new SmackIntegrationTestEnvironment(conOne, conTwo, conThree, testRunResult.testRunId, config);
} }
enum AccountNum { enum AccountNum {
@ -521,99 +561,14 @@ public class SmackIntegrationTestFramework {
Three, Three,
} }
private static final String USERNAME_PREFIX = "smack-inttest";
private XMPPTCPConnection getConnectedConnectionFor(AccountNum accountNum)
throws SmackException, IOException, XMPPException, InterruptedException,
KeyManagementException, NoSuchAlgorithmException {
String middlefix;
String accountUsername;
String accountPassword;
switch (accountNum) {
case One:
accountUsername = config.accountOneUsername;
accountPassword = config.accountOnePassword;
middlefix = "one";
break;
case Two:
accountUsername = config.accountTwoUsername;
accountPassword = config.accountTwoPassword;
middlefix = "two";
break;
case Three:
accountUsername = config.accountThreeUsername;
accountPassword = config.accountThreePassword;
middlefix = "three";
break;
default:
throw new IllegalStateException();
}
if (StringUtils.isNullOrEmpty(accountUsername)) {
accountUsername = USERNAME_PREFIX + '-' + middlefix + '-' + testRunResult.testRunId;
}
if (StringUtils.isNullOrEmpty(accountPassword)) {
accountPassword = StringUtils.insecureRandomString(16);
}
XMPPTCPConnectionConfiguration.Builder builder = getConnectionConfigurationBuilder(config);
builder.setUsernameAndPassword(accountUsername, accountPassword)
.setResource(middlefix + '-' + testRunResult.testRunId);
XMPPTCPConnection connection = new XMPPTCPConnection(builder.build());
connection.connect();
if (config.isAccountRegistrationPossible()) {
UsernameAndPassword uap = IntTestUtil.registerAccount(connection, accountUsername, accountPassword, config);
// TODO is this still required?
// Some servers, e.g. Openfire, do not support a login right after the account was
// created, so disconnect and re-connection the connection first.
connection.disconnect();
connection.connect();
connection.login(uap.username, uap.password);
} else {
connection.login();
}
return connection;
}
static XMPPTCPConnectionConfiguration.Builder getConnectionConfigurationBuilder(Configuration config) { static XMPPTCPConnectionConfiguration.Builder getConnectionConfigurationBuilder(Configuration config) {
XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder(); XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder();
if (config.tlsContext != null) {
builder.setCustomSSLContext(config.tlsContext);
}
builder.setSecurityMode(config.securityMode);
builder.setXmppDomain(config.service);
switch (config.debugger) { config.configurationApplier.applyConfigurationTo(builder);
case enhanced:
builder.setDebuggerFactory(EnhancedDebugger.Factory.INSTANCE);
break;
case console:
builder.setDebuggerFactory(ConsoleDebugger.Factory.INSTANCE);
break;
case none:
// Nothing to do :).
break;
}
return builder; return builder;
} }
static XMPPTCPConnection getConnectedConnection(SmackIntegrationTestEnvironment environment, int connectionId)
throws KeyManagementException, NoSuchAlgorithmException, InterruptedException,
SmackException, IOException, XMPPException {
Configuration config = environment.configuration;
XMPPTCPConnectionConfiguration.Builder builder = getConnectionConfigurationBuilder(config);
XMPPTCPConnection connection = new XMPPTCPConnection(builder.build());
connection.connect();
UsernameAndPassword uap = IntTestUtil.registerAccount(connection, environment, connectionId);
connection.login(uap.username, uap.password);
return connection;
}
private static Exception throwFatalException(Throwable e) throws Error, NoResponseException, private static Exception throwFatalException(Throwable e) throws Error, NoResponseException,
InterruptedException { InterruptedException {
if (e instanceof NoResponseException) { if (e instanceof NoResponseException) {
@ -649,14 +604,14 @@ public class SmackIntegrationTestFramework {
*/ */
public final String testRunId = StringUtils.insecureRandomString(5).toLowerCase(Locale.US); public final String testRunId = StringUtils.insecureRandomString(5).toLowerCase(Locale.US);
private final List<SuccessfulTest> successfulTests = Collections.synchronizedList(new LinkedList<SuccessfulTest>()); private final List<SuccessfulTest> successfulIntegrationTests = Collections.synchronizedList(new LinkedList<SuccessfulTest>());
private final List<FailedTest> failedIntegrationTests = Collections.synchronizedList(new LinkedList<FailedTest>()); private final List<FailedTest> failedIntegrationTests = Collections.synchronizedList(new LinkedList<FailedTest>());
private final List<TestNotPossible> impossibleTestMethods = Collections.synchronizedList(new LinkedList<TestNotPossible>()); private final List<TestNotPossible> impossibleIntegrationTests = Collections.synchronizedList(new LinkedList<TestNotPossible>());
private final Map<Class<? extends AbstractSmackIntTest>, String> impossibleTestClasses = new HashMap<>(); private final Map<Class<? extends AbstractSmackIntTest>, Throwable> impossibleTestClasses = new HashMap<>();
private final AtomicInteger numberOfAvailableTests = new AtomicInteger(); private final AtomicInteger numberOfAvailableTestMethods = new AtomicInteger();
private final AtomicInteger numberOfPossibleTests = new AtomicInteger(); private final AtomicInteger numberOfPossibleTestMethods = new AtomicInteger();
private TestRunResult() { TestRunResult() {
} }
public String getTestRunId() { public String getTestRunId() {
@ -664,15 +619,15 @@ public class SmackIntegrationTestFramework {
} }
public int getNumberOfAvailableTests() { public int getNumberOfAvailableTests() {
return numberOfAvailableTests.get(); return numberOfAvailableTestMethods.get();
} }
public int getNumberOfPossibleTests() { public int getNumberOfPossibleTests() {
return numberOfPossibleTests.get(); return numberOfPossibleTestMethods.get();
} }
public List<SuccessfulTest> getSuccessfulTests() { public List<SuccessfulTest> getSuccessfulTests() {
return Collections.unmodifiableList(successfulTests); return Collections.unmodifiableList(successfulIntegrationTests);
} }
public List<FailedTest> getFailedTests() { public List<FailedTest> getFailedTests() {
@ -680,11 +635,154 @@ public class SmackIntegrationTestFramework {
} }
public List<TestNotPossible> getNotPossibleTests() { public List<TestNotPossible> getNotPossibleTests() {
return Collections.unmodifiableList(impossibleTestMethods); return Collections.unmodifiableList(impossibleIntegrationTests);
} }
public Map<Class<? extends AbstractSmackIntTest>, String> getImpossibleTestClasses() { public Map<Class<? extends AbstractSmackIntTest>, Throwable> getImpossibleTestClasses() {
return Collections.unmodifiableMap(impossibleTestClasses); return Collections.unmodifiableMap(impossibleTestClasses);
} }
} }
static final class ConcreteTest {
private final TestType testType;
private final Method method;
private final Executor executor;
private final String[] subdescriptons;
private ConcreteTest(TestType testType, Method method, Executor executor, String... subdescriptions) {
this.testType = testType;
this.method = method;
this.executor = executor;
this.subdescriptons = subdescriptions;
}
private transient String stringCache;
@Override
public String toString() {
if (stringCache != null) {
return stringCache;
}
StringBuilder sb = new StringBuilder();
sb.append(method.getDeclaringClass().getSimpleName())
.append('.')
.append(method.getName())
.append(" (")
.append(testType.name());
final String SUBDESCRIPTION_DELIMITER = ", ";
sb.append(SUBDESCRIPTION_DELIMITER);
for (String subdescripton : subdescriptons) {
sb.append(subdescripton).append(SUBDESCRIPTION_DELIMITER);
}
sb.setLength(sb.length() - SUBDESCRIPTION_DELIMITER.length());
sb.append(')');
stringCache = sb.toString();
return stringCache;
}
private interface Executor {
/**
* Execute the test.
*
* @throws IllegalAccessException
* @throws InterruptedException
* @throws InvocationTargetException if the reflective invoked test throws an exception.
* @throws XMPPException in case an XMPPException happens when <em>preparing</em> the test.
* @throws IOException in case an IOException happens when <em>preparing</em> the test.
* @throws SmackException in case an SmackException happens when <em>preparing</em> the test.
*/
void execute() throws IllegalAccessException, InterruptedException, InvocationTargetException,
XMPPException, IOException, SmackException;
}
}
private final class LowLevelTestMethod {
private final Method testMethod;
private final SmackIntegrationTest smackIntegrationTestAnnotation;
private final boolean parameterListOfConnections;
private LowLevelTestMethod(Method testMethod) {
this.testMethod = testMethod;
smackIntegrationTestAnnotation = testMethod.getAnnotation(SmackIntegrationTest.class);
assert (smackIntegrationTestAnnotation != null);
parameterListOfConnections = testMethodParametersIsListOfConnections(testMethod);
}
private void invoke(AbstractSmackLowLevelIntegrationTest test,
Class<? extends AbstractXMPPConnection> connectionClass)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException,
InterruptedException, SmackException, IOException, XMPPException {
final int connectionCount;
if (parameterListOfConnections) {
connectionCount = smackIntegrationTestAnnotation.connectionCount();
if (connectionCount < 1) {
throw new IllegalArgumentException(testMethod + " is annotated to use less than one connection ('"
+ connectionCount + ')');
}
} else {
connectionCount = testMethod.getParameterCount();
}
List<? extends AbstractXMPPConnection> connections = connectionManager.constructConnectedConnections(
connectionClass, connectionCount);
if (parameterListOfConnections) {
testMethod.invoke(test, connections);
} else {
Object[] connectionsArray = new Object[connectionCount];
for (int i = 0; i < connectionsArray.length; i++) {
connectionsArray[i] = connections.remove(0);
}
testMethod.invoke(test, connectionsArray);
}
}
}
private static boolean testMethodParametersIsListOfConnections(Method testMethod) {
return testMethodParametersIsListOfConnections(testMethod, AbstractXMPPConnection.class);
}
static boolean testMethodParametersIsListOfConnections(Method testMethod, Class<? extends AbstractXMPPConnection> connectionClass) {
Type[] parameterTypes = testMethod.getGenericParameterTypes();
if (parameterTypes.length != 1) {
return false;
}
Class<?> soleParameter = testMethod.getParameterTypes()[0];
if (!Collection.class.isAssignableFrom(soleParameter)) {
return false;
}
ParameterizedType soleParameterizedType = (ParameterizedType) parameterTypes[0];
Type[] actualTypeArguments = soleParameterizedType.getActualTypeArguments();
if (actualTypeArguments.length != 1) {
return false;
}
Type soleActualTypeArgument = actualTypeArguments[0];
if (!(soleActualTypeArgument instanceof Class<?>)) {
return false;
}
Class<?> soleActualTypeArgumentAsClass = (Class<?>) soleActualTypeArgument;
if (!connectionClass.isAssignableFrom(soleActualTypeArgumentAsClass)) {
return false;
}
return true;
}
static boolean testMethodParametersVarargsConnections(Method testMethod, Class<? extends AbstractXMPPConnection> connectionClass) {
Class<?>[] parameterTypes = testMethod.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
if (!parameterType.isAssignableFrom(connectionClass)) {
return false;
}
}
return true;
}
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015 Florian Schmaus * Copyright 2015-2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,13 +16,12 @@
*/ */
package org.igniterealtime.smack.inttest; package org.igniterealtime.smack.inttest;
import java.lang.reflect.Method;
import java.util.List; import java.util.List;
public class SuccessfulTest extends TestResult { public class SuccessfulTest extends TestResult {
public SuccessfulTest(Method testMethod, long startTime, long endTime, List<String> logMessages) { public SuccessfulTest(SmackIntegrationTestFramework.ConcreteTest concreteTest, long startTime, long endTime, List<String> logMessages) {
super(testMethod, startTime, endTime, logMessages); super(concreteTest, startTime, endTime, logMessages);
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015 Florian Schmaus * Copyright 2015-2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,16 +16,15 @@
*/ */
package org.igniterealtime.smack.inttest; package org.igniterealtime.smack.inttest;
import java.lang.reflect.Method;
import java.util.List; import java.util.List;
public class TestNotPossible extends TestResult { public class TestNotPossible extends TestResult {
public final TestNotPossibleException testNotPossibleException; public final TestNotPossibleException testNotPossibleException;
public TestNotPossible(Method testMethod, long startTime, long endTime, List<String> logMessages, public TestNotPossible(SmackIntegrationTestFramework.ConcreteTest concreteTest, long startTime, long endTime, List<String> logMessages,
TestNotPossibleException testNotPossibleException) { TestNotPossibleException testNotPossibleException) {
super(testMethod, startTime, endTime, logMessages); super(concreteTest, startTime, endTime, logMessages);
this.testNotPossibleException = testNotPossibleException; this.testNotPossibleException = testNotPossibleException;
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015 Florian Schmaus * Copyright 2015-2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,19 +16,18 @@
*/ */
package org.igniterealtime.smack.inttest; package org.igniterealtime.smack.inttest;
import java.lang.reflect.Method;
import java.util.List; import java.util.List;
public abstract class TestResult { public abstract class TestResult {
public final Method testMethod; public final SmackIntegrationTestFramework.ConcreteTest concreteTest;
public final long startTime; public final long startTime;
public final long endTime; public final long endTime;
public final long duration; public final long duration;
public final List<String> logMessages; public final List<String> logMessages;
public TestResult(Method testMethod, long startTime, long endTime, List<String> logMessages) { public TestResult(SmackIntegrationTestFramework.ConcreteTest concreteTest, long startTime, long endTime, List<String> logMessages) {
this.testMethod = testMethod; this.concreteTest = concreteTest;
assert (endTime >= startTime); assert (endTime >= startTime);
this.startTime = startTime; this.startTime = startTime;
this.endTime = endTime; this.endTime = endTime;

View file

@ -0,0 +1,104 @@
/**
*
* Copyright 2018-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.igniterealtime.smack.inttest;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.XMPPConnection;
public class XmppConnectionDescriptor<C extends AbstractXMPPConnection, CC extends ConnectionConfiguration, CCB extends ConnectionConfiguration.Builder<?, CC>> {
private final Class<C> connectionClass;
private final Class<CC> connectionConfigurationClass;
private final Constructor<C> connectionConstructor;
private final Method builderMethod;
public XmppConnectionDescriptor(Class<C> connectionClass, Class<CC> connectionConfigurationClass)
throws ClassNotFoundException, NoSuchMethodException, SecurityException {
this.connectionClass = connectionClass;
this.connectionConfigurationClass = connectionConfigurationClass;
this.connectionConstructor = getConstructor(connectionClass, connectionConfigurationClass);
this.builderMethod = getBuilderMethod(connectionConfigurationClass);
}
public C construct(Configuration sinttestConfiguration)
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
return construct(sinttestConfiguration, Collections.emptyList());
}
public C construct(Configuration sinttestConfiguration,
ConnectionConfigurationBuilderApplier... customConnectionConfigurationAppliers)
throws InstantiationException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
List<ConnectionConfigurationBuilderApplier> customConnectionConfigurationAppliersList = new ArrayList<ConnectionConfigurationBuilderApplier>(
Arrays.asList(customConnectionConfigurationAppliers));
return construct(sinttestConfiguration, customConnectionConfigurationAppliersList);
}
public C construct(Configuration sinttestConfiguration,
Collection<ConnectionConfigurationBuilderApplier> customConnectionConfigurationAppliers)
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
CCB connectionConfigurationBuilder = getNewBuilder();
for (ConnectionConfigurationBuilderApplier customConnectionConfigurationApplier : customConnectionConfigurationAppliers) {
customConnectionConfigurationApplier.applyConfigurationTo(connectionConfigurationBuilder);
}
sinttestConfiguration.configurationApplier.applyConfigurationTo(connectionConfigurationBuilder);
ConnectionConfiguration connectionConfiguration = connectionConfigurationBuilder.build();
CC concreteConnectionConfiguration = connectionConfigurationClass.cast(connectionConfiguration);
return connectionConstructor.newInstance(concreteConnectionConfiguration);
}
@SuppressWarnings("unchecked")
public CCB getNewBuilder() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
return (CCB) builderMethod.invoke(null);
}
public Class<C> getConnectionClass() {
return connectionClass;
}
private static <C extends XMPPConnection> Constructor<C> getConstructor(Class<C> connectionClass,
Class<? extends ConnectionConfiguration> connectionConfigurationClass)
throws NoSuchMethodException, SecurityException {
return connectionClass.getConstructor(connectionConfigurationClass);
}
private static <CC extends ConnectionConfiguration> Method getBuilderMethod(Class<CC> connectionConfigurationClass)
throws NoSuchMethodException, SecurityException {
Method builderMethod = connectionConfigurationClass.getMethod("builder");
if (!Modifier.isStatic(builderMethod.getModifiers())) {
throw new IllegalArgumentException();
}
Class<?> returnType = builderMethod.getReturnType();
if (!ConnectionConfiguration.Builder.class.isAssignableFrom(returnType)) {
throw new IllegalArgumentException();
}
return builderMethod;
}
}

View file

@ -0,0 +1,441 @@
/**
*
* Copyright 2018-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.igniterealtime.smack.inttest;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smack.tcp.XmppNioTcpConnection;
import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.admin.ServiceAdministrationManager;
import org.jivesoftware.smackx.iqregister.AccountManager;
import org.igniterealtime.smack.inttest.Configuration.AccountRegistration;
import org.igniterealtime.smack.inttest.SmackIntegrationTestFramework.AccountNum;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Localpart;
import org.jxmpp.stringprep.XmppStringprepException;
public class XmppConnectionManager<DC extends AbstractXMPPConnection> {
private static final Logger LOGGER = Logger.getLogger(XmppConnectionManager.class.getName());
private static final Map<Class<? extends AbstractXMPPConnection>, XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>> CONNECTION_DESCRIPTORS = new ConcurrentHashMap<>();
static {
try {
addConnectionDescriptor(XmppNioTcpConnection.class, XMPPTCPConnectionConfiguration.class);
addConnectionDescriptor(XMPPTCPConnection.class, XMPPTCPConnectionConfiguration.class);
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException e) {
throw new AssertionError(e);
}
}
public static void addConnectionDescriptor(Class<? extends AbstractXMPPConnection> connectionClass,
Class<? extends ConnectionConfiguration> connectionConfigurationClass) throws ClassNotFoundException, NoSuchMethodException, SecurityException {
XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor = new XmppConnectionDescriptor<>(
connectionClass, connectionConfigurationClass);
addConnectionDescriptor(connectionDescriptor);
}
public static void addConnectionDescriptor(
XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor) {
Class<? extends AbstractXMPPConnection> connectionClass = connectionDescriptor.getConnectionClass();
CONNECTION_DESCRIPTORS.put(connectionClass, connectionDescriptor);
}
public static void removeConnectionDescriptor(Class<? extends AbstractXMPPConnection> connectionClass) {
CONNECTION_DESCRIPTORS.remove(connectionClass);
}
private final XmppConnectionDescriptor<DC, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> defaultConnectionDescriptor;
private final Map<Class<? extends AbstractXMPPConnection>, XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>> connectionDescriptors = new HashMap<>(
CONNECTION_DESCRIPTORS.size());
private final SmackIntegrationTestFramework<?> sinttestFramework;
private final Configuration sinttestConfiguration;
private final String testRunId;
private final DC accountRegistrationConnection;
private final ServiceAdministrationManager adminManager;
private final AccountManager accountManager;
/**
* One of the three main connections. The type of the main connections is the default connection type.
*/
DC conOne, conTwo, conThree;
/**
* A pool of authenticated and free to use connections.
*/
private final MultiMap<Class<? extends AbstractXMPPConnection>, AbstractXMPPConnection> connectionPool = new MultiMap<>();
/**
* A list of all ever created connections.
*/
private final List<AbstractXMPPConnection> connections = new ArrayList<>();
@SuppressWarnings("unchecked")
XmppConnectionManager(SmackIntegrationTestFramework<?> sinttestFramework,
Class<? extends DC> defaultConnectionClass)
throws SmackException, IOException, XMPPException, InterruptedException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
this.sinttestFramework = sinttestFramework;
this.sinttestConfiguration = sinttestFramework.config;
this.testRunId = sinttestFramework.testRunResult.testRunId;
connectionDescriptors.putAll(CONNECTION_DESCRIPTORS);
defaultConnectionDescriptor = (XmppConnectionDescriptor<DC, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>) connectionDescriptors.get(
defaultConnectionClass);
if (defaultConnectionDescriptor == null) {
throw new IllegalArgumentException("Could not find a connection descriptor for " + defaultConnectionClass);
}
switch (sinttestConfiguration.accountRegistration) {
case serviceAdministration:
case inBandRegistration:
accountRegistrationConnection = defaultConnectionDescriptor.construct(sinttestConfiguration);
accountRegistrationConnection.connect();
accountRegistrationConnection.login(sinttestConfiguration.adminAccountUsername,
sinttestConfiguration.adminAccountPassword);
if (sinttestConfiguration.accountRegistration == AccountRegistration.inBandRegistration) {
adminManager = null;
accountManager = AccountManager.getInstance(accountRegistrationConnection);
} else {
adminManager = ServiceAdministrationManager.getInstanceFor(accountRegistrationConnection);
accountManager = null;
}
break;
case disabled:
accountRegistrationConnection = null;
adminManager = null;
accountManager = null;
break;
default:
throw new AssertionError();
}
}
SmackIntegrationTestEnvironment<DC> prepareEnvironment() throws KeyManagementException, NoSuchAlgorithmException,
InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
SmackException, IOException, XMPPException, InterruptedException {
prepareMainConnections();
return new SmackIntegrationTestEnvironment<DC>(conOne, conTwo, conThree,
sinttestFramework.testRunResult.testRunId, sinttestConfiguration, this);
}
private void prepareMainConnections() throws KeyManagementException, NoSuchAlgorithmException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException, SmackException, IOException,
XMPPException, InterruptedException {
final int mainAccountCount = AccountNum.values().length;
List<DC> connections = new ArrayList<>(mainAccountCount);
for (AccountNum mainAccountNum : AccountNum.values()) {
DC mainConnection = getConnectedMainConnectionFor(mainAccountNum);
connections.add(mainConnection);
}
conOne = connections.get(0);
conTwo = connections.get(1);
conThree = connections.get(2);
}
public Class<? extends AbstractXMPPConnection> getDefaultConnectionClass() {
return defaultConnectionDescriptor.getConnectionClass();
}
public Set<Class<? extends AbstractXMPPConnection>> getConnectionClasses() {
return Collections.unmodifiableSet(connectionDescriptors.keySet());
}
@SuppressWarnings("unchecked")
public <C extends AbstractXMPPConnection> XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> getConnectionDescriptorFor(
Class<C> connectionClass) {
return (XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>) connectionDescriptors.get(
connectionClass);
}
void disconnectAndCleanup() throws InterruptedException {
int successfullyDeletedAccountsCount = 0;
for (AbstractXMPPConnection connection : connections) {
if (sinttestConfiguration.accountRegistration == AccountRegistration.inBandRegistration) {
// Note that we use the account manager from the to-be-deleted connection.
AccountManager accountManager = AccountManager.getInstance(connection);
try {
accountManager.deleteAccount();
successfullyDeletedAccountsCount++;
} catch (NoResponseException | XMPPErrorException | NotConnectedException e) {
LOGGER.log(Level.WARNING, "Could not delete dynamically registered account", e);
}
}
connection.disconnect();
if (sinttestConfiguration.accountRegistration == AccountRegistration.serviceAdministration) {
String username = connection.getConfiguration().getUsername().toString();
Localpart usernameAsLocalpart;
try {
usernameAsLocalpart = Localpart.from(username);
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
EntityBareJid connectionAddress = JidCreate.entityBareFrom(usernameAsLocalpart, sinttestConfiguration.service);
try {
adminManager.deleteUser(connectionAddress);
successfullyDeletedAccountsCount++;
} catch (NoResponseException | XMPPErrorException | NotConnectedException e) {
LOGGER.log(Level.WARNING, "Could not delete dynamically registered account", e);
}
}
}
if (sinttestConfiguration.isAccountRegistrationPossible()) {
int unsuccessfullyDeletedAccountsCount = connections.size() - successfullyDeletedAccountsCount;
if (unsuccessfullyDeletedAccountsCount == 0) {
LOGGER.info("Successsfully deleted all created accounts ✔");
} else {
LOGGER.warning("Could not delete all created accounts, " + unsuccessfullyDeletedAccountsCount + " remainaing");
}
}
connections.clear();
}
private static final String USERNAME_PREFIX = "smack-inttest";
private DC getConnectedMainConnectionFor(AccountNum accountNum) throws SmackException, IOException, XMPPException,
InterruptedException, KeyManagementException, NoSuchAlgorithmException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
String middlefix;
String accountUsername;
String accountPassword;
switch (accountNum) {
case One:
accountUsername = sinttestConfiguration.accountOneUsername;
accountPassword = sinttestConfiguration.accountOnePassword;
middlefix = "one";
break;
case Two:
accountUsername = sinttestConfiguration.accountTwoUsername;
accountPassword = sinttestConfiguration.accountTwoPassword;
middlefix = "two";
break;
case Three:
accountUsername = sinttestConfiguration.accountThreeUsername;
accountPassword = sinttestConfiguration.accountThreePassword;
middlefix = "three";
break;
default:
throw new IllegalStateException();
}
// Note that it is perfectly fine for account(Username|Password) to be 'null' at this point.
final String finalAccountUsername = StringUtils.isNullOrEmpty(accountUsername) ? USERNAME_PREFIX + '-' + middlefix + '-' + testRunId : accountUsername;
final String finalAccountPassword = StringUtils.isNullOrEmpty(accountPassword) ? StringUtils.insecureRandomString(16) : accountPassword;
if (sinttestConfiguration.isAccountRegistrationPossible()) {
registerAccount(finalAccountUsername, finalAccountPassword);
}
DC mainConnection = defaultConnectionDescriptor.construct(sinttestConfiguration, (builder) -> {
try {
builder.setUsernameAndPassword(finalAccountUsername, finalAccountPassword)
.setResource(middlefix + '-' + testRunId);
} catch (XmppStringprepException e) {
throw new IllegalArgumentException(e);
}
});
connections.add(mainConnection);
mainConnection.connect();
mainConnection.login();
return mainConnection;
}
private void registerAccount(String username, String password) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, XmppStringprepException {
if (accountRegistrationConnection == null) {
throw new IllegalStateException("Account registration not configured");
}
switch (sinttestConfiguration.accountRegistration) {
case serviceAdministration:
EntityBareJid userJid = JidCreate.entityBareFrom(Localpart.from(username),
accountRegistrationConnection.getXMPPServiceDomain());
adminManager.addUser(userJid, password);
break;
case inBandRegistration:
if (!accountManager.supportsAccountCreation()) {
throw new UnsupportedOperationException("Account creation/registation is not supported");
}
Set<String> requiredAttributes = accountManager.getAccountAttributes();
if (requiredAttributes.size() > 4) {
throw new IllegalStateException("Unkown required attributes");
}
Map<String, String> additionalAttributes = new HashMap<>();
additionalAttributes.put("name", "Smack Integration Test");
additionalAttributes.put("email", "flow@igniterealtime.org");
Localpart usernameLocalpart = Localpart.from(username);
accountManager.createAccount(usernameLocalpart, password, additionalAttributes);
break;
case disabled:
throw new IllegalStateException("Account creation no possible");
}
}
<C extends AbstractXMPPConnection> List<C> constructConnectedConnections(Class<C> connectionClass, int count)
throws InterruptedException, SmackException, IOException, XMPPException {
List<C> connections = new ArrayList<>(count);
synchronized (connectionPool) {
@SuppressWarnings("unchecked")
List<C> pooledConnections = (List<C>) connectionPool.getAll(connectionClass);
while (count > 0 && !pooledConnections.isEmpty()) {
C connection = pooledConnections.remove(pooledConnections.size() - 1);
connections.add(connection);
count--;
}
}
@SuppressWarnings("unchecked")
XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor = (XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>) connectionDescriptors
.get(connectionClass);
for (int i = 0; i < count; i++) {
C connection = constructConnectedConnection(connectionDescriptor);
connections.add(connection);
}
return connections;
}
private <C extends AbstractXMPPConnection> C constructConnectedConnection(
XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor)
throws InterruptedException, SmackException, IOException, XMPPException {
C connection = constructConnection(connectionDescriptor, null);
connection.connect();
connection.login();
return connection;
}
DC constructConnection()
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
return constructConnection(defaultConnectionDescriptor);
}
<C extends AbstractXMPPConnection> C constructConnection(
XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor)
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
return constructConnection(connectionDescriptor, null);
}
private <C extends AbstractXMPPConnection> C constructConnection(
XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor,
Collection<ConnectionConfigurationBuilderApplier> customConnectionConfigurationAppliers)
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
String username = "sinttest-" + testRunId + '-' + (connections.size() + 1);
String password = StringUtils.randomString(24);
return constructConnection(username, password, connectionDescriptor, customConnectionConfigurationAppliers);
}
private <C extends AbstractXMPPConnection> C constructConnection(final String username, final String password,
XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor,
Collection<ConnectionConfigurationBuilderApplier> customConnectionConfigurationAppliers)
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
try {
registerAccount(username, password);
} catch (XmppStringprepException e) {
throw new IllegalArgumentException(e);
}
ConnectionConfigurationBuilderApplier usernameAndPasswordApplier = (configurationBuilder) -> {
configurationBuilder.setUsernameAndPassword(username, password);
};
if (customConnectionConfigurationAppliers == null) {
customConnectionConfigurationAppliers = Collections.singleton(usernameAndPasswordApplier);
} else {
customConnectionConfigurationAppliers.add(usernameAndPasswordApplier);
}
C connection;
try {
connection = connectionDescriptor.construct(sinttestConfiguration, customConnectionConfigurationAppliers);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
throw new IllegalStateException(e);
}
connections.add(connection);
return connection;
}
void recycle(Collection<? extends AbstractXMPPConnection> connections) {
for (AbstractXMPPConnection connection : connections) {
recycle(connection);
}
}
void recycle(AbstractXMPPConnection connection) {
Class<? extends AbstractXMPPConnection> connectionClass = connection.getClass();
if (!connectionDescriptors.containsKey(connectionClass)) {
throw new IllegalStateException("Attempt to recycle unknown connection of class '" + connectionClass + "'");
}
if (connection.isAuthenticated()) {
synchronized (connectionPool) {
connectionPool.put(connectionClass, connection);
}
}
// Note that we do not delete the account of the unauthenticated connection here, as it is done at the end of
// the test run together with all other dynamically created accounts.
}
}

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015 Florian Schmaus * Copyright 2015-2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -48,7 +48,7 @@ public class ChatTest extends AbstractSmackIntegrationTest {
private boolean invoked; private boolean invoked;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public ChatTest(SmackIntegrationTestEnvironment environment) { public ChatTest(SmackIntegrationTestEnvironment<?> environment) {
super(environment); super(environment);
chatManagerOne = org.jivesoftware.smack.chat.ChatManager.getInstanceFor(conOne); chatManagerOne = org.jivesoftware.smack.chat.ChatManager.getInstanceFor(conOne);
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015 Florian Schmaus * Copyright 2015-2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -25,8 +25,6 @@ import java.security.NoSuchAlgorithmException;
import org.jivesoftware.smack.sasl.SASLError; import org.jivesoftware.smack.sasl.SASLError;
import org.jivesoftware.smack.sasl.SASLErrorException; import org.jivesoftware.smack.sasl.SASLErrorException;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.igniterealtime.smack.inttest.AbstractSmackLowLevelIntegrationTest; import org.igniterealtime.smack.inttest.AbstractSmackLowLevelIntegrationTest;
@ -35,7 +33,7 @@ import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
public class LoginIntegrationTest extends AbstractSmackLowLevelIntegrationTest { public class LoginIntegrationTest extends AbstractSmackLowLevelIntegrationTest {
public LoginIntegrationTest(SmackIntegrationTestEnvironment environment) { public LoginIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
super(environment); super(environment);
} }
@ -54,14 +52,13 @@ public class LoginIntegrationTest extends AbstractSmackLowLevelIntegrationTest {
public void testInvalidLogin() throws SmackException, IOException, XMPPException, public void testInvalidLogin() throws SmackException, IOException, XMPPException,
InterruptedException, KeyManagementException, NoSuchAlgorithmException { InterruptedException, KeyManagementException, NoSuchAlgorithmException {
final String nonExistentUserString = StringUtils.insecureRandomString(24); final String nonExistentUserString = StringUtils.insecureRandomString(24);
XMPPTCPConnectionConfiguration conf = getConnectionConfiguration().setUsernameAndPassword( final String invalidPassword = "invalidPassword";
nonExistentUserString, "invalidPassword").build();
XMPPTCPConnection connection = new XMPPTCPConnection(conf); AbstractXMPPConnection connection = getUnconnectedConnection();
connection.connect(); connection.connect();
try { try {
connection.login(); connection.login(nonExistentUserString, invalidPassword);
fail("Exception expected"); fail("Exception expected");
} }
catch (SASLErrorException e) { catch (SASLErrorException e) {

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015 Florian Schmaus * Copyright 2015-2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -28,24 +28,21 @@ import org.jivesoftware.smack.filter.MessageWithBodiesFilter;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.igniterealtime.smack.inttest.AbstractSmackLowLevelIntegrationTest; import org.igniterealtime.smack.inttest.AbstractSmackSpecificLowLevelIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTest; import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException; import org.igniterealtime.smack.inttest.TestNotPossibleException;
public class StreamManagementTest extends AbstractSmackLowLevelIntegrationTest { public class StreamManagementTest extends AbstractSmackSpecificLowLevelIntegrationTest<XMPPTCPConnection> {
public StreamManagementTest(SmackIntegrationTestEnvironment environment) throws Exception { public StreamManagementTest(SmackIntegrationTestEnvironment<?> environment) throws Exception {
super(environment); super(environment, XMPPTCPConnection.class);
performCheck(new ConnectionCallback() { XMPPTCPConnection connection = getSpecificUnconnectedConnection();
@Override connection.connect().login();
public void connectionCallback(XMPPTCPConnection connection) throws Exception {
if (!connection.isSmAvailable()) { if (!connection.isSmAvailable()) {
throw new TestNotPossibleException("XEP-198: Stream Mangement not supported by service"); throw new TestNotPossibleException("XEP-198: Stream Mangement not supported by service");
} }
} }
});
}
@SmackIntegrationTest @SmackIntegrationTest
public void testStreamManagement(XMPPTCPConnection conOne, XMPPTCPConnection conTwo) throws InterruptedException, public void testStreamManagement(XMPPTCPConnection conOne, XMPPTCPConnection conTwo) throws InterruptedException,

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015-2017 Florian Schmaus * Copyright 2015-2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,25 +20,23 @@ import static org.junit.Assert.assertTrue;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.igniterealtime.smack.inttest.AbstractSmackLowLevelIntegrationTest; import org.igniterealtime.smack.inttest.AbstractSmackLowLevelIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTest; import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
public class WaitForClosingStreamElementTest extends AbstractSmackLowLevelIntegrationTest { public class WaitForClosingStreamElementTest extends AbstractSmackLowLevelIntegrationTest {
public WaitForClosingStreamElementTest(SmackIntegrationTestEnvironment environment) { public WaitForClosingStreamElementTest(SmackIntegrationTestEnvironment<?> environment) {
super(environment); super(environment);
} }
@SmackIntegrationTest @SmackIntegrationTest
public void waitForClosingStreamElementTest(XMPPTCPConnection connection) public void waitForClosingStreamElementTest(AbstractXMPPConnection connection)
throws NoSuchFieldException, SecurityException, IllegalArgumentException, throws NoSuchFieldException, SecurityException, IllegalArgumentException,
IllegalAccessException { IllegalAccessException {
connection.disconnect(); connection.disconnect();
Field closingStreamReceivedField = connection.getClass().getDeclaredField("closingStreamReceived"); Field closingStreamReceivedField = AbstractXMPPConnection.class.getDeclaredField("closingStreamReceived");
closingStreamReceivedField.setAccessible(true); closingStreamReceivedField.setAccessible(true);
SynchronizationPoint<?> closingStreamReceived = (SynchronizationPoint<?>) closingStreamReceivedField.get(connection); SynchronizationPoint<?> closingStreamReceived = (SynchronizationPoint<?>) closingStreamReceivedField.get(connection);
Exception failureException = closingStreamReceived.getFailureException(); Exception failureException = closingStreamReceived.getFailureException();

View file

@ -0,0 +1,67 @@
/**
*
* Copyright 2018-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack;
import java.util.List;
import java.util.logging.Level;
import org.jivesoftware.smack.tcp.XmppNioTcpConnection;
import org.igniterealtime.smack.XmppConnectionStressTest;
import org.igniterealtime.smack.XmppConnectionStressTest.StressTestFailedException.ErrorsWhileSendingOrReceivingException;
import org.igniterealtime.smack.XmppConnectionStressTest.StressTestFailedException.NotAllMessagesReceivedException;
import org.igniterealtime.smack.inttest.AbstractSmackLowLevelIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
public class XmppConnectionIntegrationTest extends AbstractSmackLowLevelIntegrationTest {
public XmppConnectionIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
super(environment);
}
@SmackIntegrationTest(connectionCount = 4)
public void allToAllMessageSendTest(List<AbstractXMPPConnection> connections)
throws InterruptedException, NotAllMessagesReceivedException, ErrorsWhileSendingOrReceivingException {
final long seed = 42;
final int messagesPerConnection = 3; // 100
final int maxPayloadChunkSize = 16; // 512
final int maxPayloadChunks = 4; // 32
final boolean intermixMessages = false; // true
XmppConnectionStressTest.Configuration stressTestConfiguration = new XmppConnectionStressTest.Configuration(
seed, messagesPerConnection, maxPayloadChunkSize, maxPayloadChunks, intermixMessages);
XmppConnectionStressTest stressTest = new XmppConnectionStressTest(stressTestConfiguration);
stressTest.run(connections, timeout);
final Level connectionStatsLogLevel = Level.FINE;
if (LOGGER.isLoggable(connectionStatsLogLevel)) {
if (connections.get(0) instanceof XmppNioTcpConnection) {
for (XMPPConnection connection : connections) {
XmppNioTcpConnection xmppNioTcpConnection = (XmppNioTcpConnection) connection;
XmppNioTcpConnection.Stats stats = xmppNioTcpConnection.getStats();
LOGGER.log(connectionStatsLogLevel,
"Connections stats for " + xmppNioTcpConnection + ":\n{}",
stats);
}
}
}
}
}

View file

@ -25,7 +25,7 @@ public abstract class AbstractChatIntegrationTest extends AbstractSmackIntegrati
protected final ChatManager chatManagerTwo; protected final ChatManager chatManagerTwo;
protected final ChatManager chatManagerThree; protected final ChatManager chatManagerThree;
protected AbstractChatIntegrationTest(SmackIntegrationTestEnvironment environment) { protected AbstractChatIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
super(environment); super(environment);
chatManagerOne = ChatManager.getInstanceFor(conOne); chatManagerOne = ChatManager.getInstanceFor(conOne);
chatManagerTwo = ChatManager.getInstanceFor(conTwo); chatManagerTwo = ChatManager.getInstanceFor(conTwo);

View file

@ -26,7 +26,7 @@ import org.jxmpp.jid.EntityBareJid;
public class IncomingMessageListenerIntegrationTest extends AbstractChatIntegrationTest { public class IncomingMessageListenerIntegrationTest extends AbstractChatIntegrationTest {
public IncomingMessageListenerIntegrationTest(SmackIntegrationTestEnvironment environment) { public IncomingMessageListenerIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
super(environment); super(environment);
} }

View file

@ -26,7 +26,7 @@ import org.jxmpp.jid.EntityBareJid;
public class OutgoingMessageListenerIntegrationTest extends AbstractChatIntegrationTest { public class OutgoingMessageListenerIntegrationTest extends AbstractChatIntegrationTest {
public OutgoingMessageListenerIntegrationTest(SmackIntegrationTestEnvironment environment) { public OutgoingMessageListenerIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
super(environment); super(environment);
} }

Some files were not shown because too many files have changed in this diff Show more