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'
}
group = 'org.igniterealtime.smack'
sourceCompatibility = 1.7
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = sourceCompatibility
version = shortVersion
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 ->
project.sourceSets.main.allJava }
destinationDir = javadocAllDir
@ -449,12 +456,39 @@ subprojects {
enabled 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) {
apply plugin: 'ru.vyarus.animalsniffer'
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 {
sourceSets = [sourceSets.main]

View File

@ -1,295 +1,315 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="12">
<profile kind="CodeFormatterProfile" name="Smack" version="12">
<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"/>
<profiles version="13">
<profile kind="CodeFormatterProfile" name="Smack" version="13">
<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.join_wrapped_lines" value="true"/>
<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_new_line_in_empty_annotation_declaration" 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.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.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
<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_before_unary_operator" 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.parentheses_positions_in_for_statment" value="common_lines"/>
<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_comma_in_constructor_declaration_parameters" 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_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.insert_space_before_comma_in_enum_constant_arguments" 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.brace_position_for_constructor_declaration" value="end_of_line"/>
<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_before_colon_in_conditional" 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_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" 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.blank_lines_between_type_declarations" value="1"/>
<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.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
<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.parentheses_positions_in_method_invocation" value="common_lines"/>
<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.blank_lines_after_imports" value="1"/>
<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.comment.insert_new_line_before_root_tags" 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.insert_space_before_comma_in_method_declaration_throws" 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.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" 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.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" 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.insert_space_before_comma_in_for_inits" 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_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_before_closing_paren_in_for" 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.comment.format_source_code" value="true"/>
<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_comma_in_allocation_expression" value="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.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" 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_before_parenthesized_expression_in_return" value="insert"/>
<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.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.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.comment.format_header" value="false"/>
<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.parentheses_positions_in_try_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="0"/>
<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>
</profiles>

View File

@ -387,18 +387,6 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
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
* 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");
* you may not use this file except in compliance with the License.
@ -16,9 +16,23 @@
*/
package org.jivesoftware.smack;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
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.Collection;
import java.util.HashMap;
@ -33,8 +47,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
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.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.SmackConfiguration.UnknownIqRequestReplyMode;
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.SecurityRequiredException;
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
import org.jivesoftware.smack.XMPPException.StreamErrorException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
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.ErrorIQ;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.FullyQualifiedElement;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Mechanisms;
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.StartTls;
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.provider.ExtensionElementProvider;
import org.jivesoftware.smack.provider.NonzaProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.sasl.core.SASLAnonymous;
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.StringUtils;
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.EntityFullJid;
@ -132,6 +161,12 @@ import org.xmlpull.v1.XmlPullParser;
public abstract class AbstractXMPPConnection implements XMPPConnection {
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.
*/
@ -186,9 +221,11 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
private final Map<StanzaListener, InterceptorWrapper> interceptors =
new HashMap<>();
final Map<String, NonzaCallback> nonzaCallbacks = new HashMap<>();
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.
@ -244,6 +281,14 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
protected final SynchronizationPoint<XMPPException> saslFeatureReceived = new SynchronizationPoint<>(
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.
*/
@ -269,20 +314,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
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
* 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
@ -320,6 +351,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
*/
protected boolean wasAuthenticated = false;
protected Exception currentConnectionException;
private final Map<String, IQRequestHandler> setIqRequestHandler = new HashMap<>();
private final Map<String, IQRequestHandler> getIqRequestHandler = new HashMap<>();
@ -566,7 +599,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
return streamId;
}
protected void bindResourceAndEstablishSession(Resourcepart resource) throws XMPPErrorException,
protected Resourcepart bindResourceAndEstablishSession(Resourcepart resource) throws XMPPErrorException,
SmackException, InterruptedException {
// Wait until either:
@ -602,6 +635,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
packetCollector = createStanzaCollectorAndSend(new StanzaIdFilter(session), session);
packetCollector.nextResultOrThrow();
}
return response.getJid().getResourcepart();
}
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
public void sendStanza(Stanza stanza) throws NotConnectedException, InterruptedException {
Objects.requireNonNull(stanza, "Stanza must not be null");
@ -781,11 +817,61 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
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.
*/
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
public void addConnectionListener(ConnectionListener connectionListener) {
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>
* Compared to {@link #firePacketInterceptors(Stanza)}, the listeners will be invoked in a new thread.
* </p>
*
* @param packet the stanza to process.
* @param sendTopLevelStreamElement the top level stream element which just got send.
*/
// TODO: Rename to fireElementSendingListeners().
@SuppressWarnings("javadoc")
protected void firePacketSendingListeners(final Stanza packet) {
final SmackDebugger debugger = this.debugger;
protected void firePacketSendingListeners(final TopLevelStreamElement sendTopLevelStreamElement) {
if (debugger != null) {
debugger.onOutgoingStreamElement(packet);
debugger.onOutgoingStreamElement(sendTopLevelStreamElement);
}
if (!(sendTopLevelStreamElement instanceof Stanza)) {
return;
}
Stanza packet = (Stanza) sendTopLevelStreamElement;
final List<StanzaListener> listenersToNotify = new LinkedList<>();
synchronized (sendListeners) {
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");
}
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 {
ParserUtils.assertAtStartTag(parser);
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
// IQ of type 'error' with condition 'service-unavailable'.
ErrorIQ errorIQ = IQ.createErrorResponse(iq, StanzaError.getBuilder(
final ErrorIQ errorIQ = IQ.createErrorResponse(iq, StanzaError.getBuilder(
replyCondition));
try {
sendStanza(errorIQ);
}
catch (InterruptedException | NotConnectedException e) {
LOGGER.log(Level.WARNING, "Exception while sending error IQ to unkown IQ request", e);
}
// 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 {
sendStanza(errorIQ);
}
catch (InterruptedException | NotConnectedException e) {
LOGGER.log(Level.WARNING, "Exception while sending error IQ to unkown IQ request", e);
}
});
} else {
Executor executorService = null;
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;
if (e instanceof StreamErrorException) {
StreamErrorException see = (StreamErrorException) e;
@ -1401,7 +1539,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG && parser.getDepth() == initialDepth + 1) {
ExtensionElement streamFeature = null;
FullyQualifiedElement streamFeature = null;
String name = parser.getName();
String namespace = parser.getNamespace();
switch (name) {
@ -1435,6 +1573,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
break;
}
}
}
protected final void parseFeaturesAndNotify(XmlPullParser parser) throws Exception {
parseFeatures(parser);
if (hasFeature(Mechanisms.ELEMENT, Mechanisms.NAMESPACE)) {
// 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")
@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));
}
@ -1474,7 +1616,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
return getFeature(element, namespace) != null;
}
protected void addStreamFeature(ExtensionElement feature) {
protected void addStreamFeature(FullyQualifiedElement feature) {
String key = XmppStringUtils.generateKey(feature.getElementName(), feature.getNamespace());
streamFeatures.put(key, feature);
}
@ -1659,7 +1801,155 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
CACHED_EXECUTOR_SERVICE.execute(runnable);
}
protected static ScheduledFuture<?> schedule(Runnable runnable, long delay, TimeUnit unit) {
return SCHEDULED_EXECUTOR_SERVICE.schedule(runnable, delay, unit);
protected static ScheduledAction schedule(Runnable runnable, long delay, TimeUnit 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;
import java.lang.ref.WeakReference;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.jivesoftware.smack.SmackException.NotLoggedInException;
@ -54,7 +53,7 @@ public abstract class Manager {
return connection;
}
protected static final ScheduledFuture<?> schedule(Runnable runnable, long delay, TimeUnit unit) {
return AbstractXMPPConnection.SCHEDULED_EXECUTOR_SERVICE.schedule(runnable, delay, unit);
protected static final ScheduledAction schedule(Runnable runnable, long delay, TimeUnit 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 authzid the authorization identifier (typically null).
* @param sslSession the optional SSL/TLS session (if one was established)
* @return the used SASLMechanism.
* @throws XMPPErrorException
* @throws SASLErrorException
* @throws IOException
* @throws SmackException
* @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,
SmackException, InterruptedException {
currentMechanism = selectMechanism(authzid);
@ -223,6 +224,8 @@ public final class SASLAuthentication {
if (!authenticationSuccessful) {
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,
StanzaCollector collector) {
return newWith(connection, collector.getStanzaFilter());
StanzaCollector collector, boolean stanzaCollectorCancelled) {
return newWith(connection, collector.getStanzaFilter(), stanzaCollectorCancelled);
}
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);
if (stanzaCollectorCancelled) {
sb.append(" StanzaCollector has been cancelled.");
}
sb.append(" Waited for response using: ");
if (filter != null) {
sb.append(filter.toString());
@ -182,6 +189,12 @@ public class SmackException extends Exception {
super("The connection " + connection
+ " 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 {
@ -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 {
/**

View File

@ -25,13 +25,19 @@ import java.util.List;
import java.util.logging.Level;
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.XmppCompressionManager;
import org.jivesoftware.smack.compression.zlib.ZlibXmppCompressionFactory;
import org.jivesoftware.smack.initializer.SmackInitializer;
import org.jivesoftware.smack.packet.Bind;
import org.jivesoftware.smack.packet.Message.Body;
import org.jivesoftware.smack.provider.BindIQProvider;
import org.jivesoftware.smack.provider.BodyElementProvider;
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.SASLXOauth2Mechanism;
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
SmackConfiguration.compressionHandlers.add(new Java7ZlibInputOutputStream());
XmppCompressionManager.registerXmppCompressionFactory(ZlibXmppCompressionFactory.INSTANCE);
// Use try block since we may not have permission to get a system
// property (for example, when an applet).
try {
@ -118,6 +126,11 @@ public final class SmackInitialization {
ProviderManager.addIQProvider(Bind.ELEMENT, Bind.NAMESPACE, new BindIQProvider());
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;
}

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;
import java.util.ArrayDeque;
import java.util.ArrayList;
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.NotConnectedException;
@ -46,7 +45,9 @@ public class StanzaCollector {
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.
@ -57,7 +58,9 @@ public class StanzaCollector {
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
@ -69,7 +72,8 @@ public class StanzaCollector {
protected StanzaCollector(XMPPConnection connection, Configuration configuration) {
this.connection = connection;
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.request = configuration.request;
}
@ -79,12 +83,15 @@ public class StanzaCollector {
* queued up. Once a stanza collector has been cancelled, it cannot be
* 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 (!cancelled) {
cancelled = true;
connection.removeStanzaCollector(this);
if (cancelled) {
return;
}
cancelled = true;
connection.removeStanzaCollector(this);
notifyAll();
}
/**
@ -119,7 +126,7 @@ public class StanzaCollector {
* results.
*/
@SuppressWarnings("unchecked")
public <P extends Stanza> P pollResult() {
public synchronized <P extends Stanza> P pollResult() {
return (P) resultQueue.poll();
}
@ -152,13 +159,20 @@ public class StanzaCollector {
* @throws InterruptedException
*/
@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();
P res = null;
while (res == null) {
res = (P) resultQueue.take();
while (true) {
P res = (P) resultQueue.poll();
if (res != null) {
return res;
}
if (cancelled) {
return null;
}
wait();
}
return res;
}
/**
@ -176,13 +190,13 @@ public class StanzaCollector {
private volatile long waitStart;
/**
* Returns the next available packet. The method call will block (not return)
* until a stanza is available or the <tt>timeout</tt> has elapsed. If the
* timeout elapses without a result, <tt>null</tt> will be returned.
* Returns the next available stanza. The method call will block (not return) until a stanza is available or the
* <tt>timeout</tt> has elapsed or if the connection was terminated because of an error. If the timeout elapses without a
* result or if there was an connection error, <tt>null</tt> will be returned.
*
* @param <P> type of the result stanza.
* @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
*/
@SuppressWarnings("unchecked")
@ -191,14 +205,17 @@ public class StanzaCollector {
P res = null;
long remainingWait = timeout;
waitStart = System.currentTimeMillis();
do {
res = (P) resultQueue.poll(remainingWait, TimeUnit.MILLISECONDS);
if (res != null) {
return res;
while (remainingWait > 0 && connectionException == null && !cancelled) {
synchronized (this) {
res = (P) resultQueue.poll();
if (res != null) {
return res;
}
wait(remainingWait);
}
remainingWait = timeout - (System.currentTimeMillis() - waitStart);
} while (remainingWait > 0);
return null;
}
return res;
}
/**
@ -263,10 +280,13 @@ public class StanzaCollector {
cancel();
}
if (result == null) {
if (connectionException != null) {
throw new NotConnectedException(connection, packetFilter, connectionException);
}
if (!connection.isConnected()) {
throw new NotConnectedException(connection, packetFilter);
}
throw NoResponseException.newWith(connection, this);
throw NoResponseException.newWith(connection, this, cancelled);
}
XMPPErrorException.ifHasErrorThenThrow(result);
@ -289,7 +309,7 @@ public class StanzaCollector {
if (collectedCache == null) {
collectedCache = new ArrayList<>(getCollectedCount());
resultQueue.drainTo(collectedCache);
collectedCache.addAll(resultQueue);
}
return collectedCache;
@ -301,10 +321,15 @@ public class StanzaCollector {
* @return the count of collected stanzas.
* @since 4.1
*/
public int getCollectedCount() {
public synchronized int getCollectedCount() {
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.
* If so, the stanza is added to the result queue.
@ -313,12 +338,14 @@ public class StanzaCollector {
*/
protected void processStanza(Stanza packet) {
if (packetFilter == null || packetFilter.accept(packet)) {
// CHECKSTYLE:OFF
while (!resultQueue.offer(packet)) {
// Since we know the queue is full, this poll should never actually block.
resultQueue.poll();
}
// CHECKSTYLE:ON
synchronized (this) {
if (resultQueue.size() == maxQueueSize) {
Stanza rolledOverStanza = resultQueue.poll();
assert rolledOverStanza != null;
}
resultQueue.add(packet);
notifyAll();
}
if (collectorToReset != null) {
collectorToReset.waitStart = System.currentTimeMillis();
}
@ -403,4 +430,5 @@ public class StanzaCollector {
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");
* 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 E failureException;
private volatile long waitStart;
/**
* 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}.
* {@link #reportSuccess()}, {@link #reportFailure()} and {@link #reportFailure(Exception)} will either set this
@ -247,13 +253,23 @@ public class SynchronizationPoint<E extends Exception> {
* @throws InterruptedException
*/
private void waitForConditionOrTimeout() throws InterruptedException {
long remainingWait = TimeUnit.MILLISECONDS.toNanos(connection.getReplyTimeout());
waitStart = System.currentTimeMillis();
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) {
state = State.NoResponse;
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,
Success,
Failure,
Interrupted,
}
}

View File

@ -25,6 +25,7 @@ import org.jivesoftware.smack.filter.IQReplyFilter;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.iqrequest.IQRequestHandler;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.FullyQualifiedElement;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.Stanza;
@ -474,7 +475,7 @@ public interface XMPPConnection {
* @param namespace
* @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.
@ -565,5 +566,4 @@ public interface XMPPConnection {
* @return the timestamp in milliseconds
*/
long getLastStanzaReceived();
}

View File

@ -198,6 +198,10 @@ public abstract class XMPPException extends Exception {
private final Nonza nonza;
public FailedNonzaException(Nonza failedNonza) {
this(failedNonza, null);
}
public FailedNonzaException(Nonza nonza, StanzaError.Condition condition) {
this.condition = condition;
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
public XmlStringBuilder toXML(String enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this);
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
xml.rightAngleBracket();
xml.element("method", method);
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");
* you may not use this file except in compliance with the License.
@ -36,6 +36,10 @@ public abstract class XMPPInputOutputStream {
XMPPInputOutputStream.flushMethod = flushMethod;
}
public static FlushMethod getFlushMethod() {
return flushMethod;
}
protected final 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;
import java.io.Reader;
import java.io.Writer;
import java.util.logging.Logger;
import org.jivesoftware.smack.AbstractConnectionListener;
@ -133,21 +131,13 @@ public abstract class AbstractDebugger extends SmackDebugger {
protected abstract void log(String logMessage, Throwable throwable);
@Override
public Reader newConnectionReader(Reader newReader) {
reader.removeReaderListener(readerListener);
ObservableReader debugReader = new ObservableReader(newReader);
debugReader.addReaderListener(readerListener);
reader = debugReader;
return reader;
public final void outgoingStreamSink(CharSequence outgoingCharSequence) {
log("SENT (" + connection.getConnectionCounter() + "): " + outgoingCharSequence);
}
@Override
public Writer newConnectionWriter(Writer newWriter) {
writer.removeWriterListener(writerListener);
ObservableWriter debugWriter = new ObservableWriter(newWriter);
debugWriter.addWriterListener(writerListener);
writer = debugWriter;
return writer;
public final void incomingStreamSink(CharSequence incomingCharSequence) {
log("RECV (" + connection.getConnectionCounter() + "): " + incomingCharSequence);
}
@Override

View File

@ -17,13 +17,18 @@
package org.jivesoftware.smack.debugger;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import org.jivesoftware.smack.XMPPConnection;
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.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
@ -38,6 +43,9 @@ public abstract class SmackDebugger {
protected final XMPPConnection connection;
private XmppXmlSplitter outgoingStreamSplitterForPrettyPrinting;
private XmppXmlSplitter incomingStreamSplitterForPrettyPrinting;
protected SmackDebugger(XMPPConnection connection) {
this.connection = connection;
}
@ -52,6 +60,21 @@ public abstract class SmackDebugger {
// TODO: Should be replaced with a connection listener authenticed().
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
* 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.
* @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
@ -72,7 +111,23 @@ public abstract class SmackDebugger {
* @param writer 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.

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");
* 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
* limitations under the License.
*/
package org.jivesoftware.smack.packet;
/**
@ -33,13 +32,6 @@ package org.jivesoftware.smack.packet;
* @see org.jivesoftware.smack.provider.ExtensionElementProvider
* @author Matt Tucker
*/
public interface ExtensionElement extends NamedElement {
/**
* Returns the root element XML namespace.
*
* @return the namespace.
*/
String getNamespace();
public interface ExtensionElement extends FullyQualifiedElement {
}

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
* @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");
* 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 static final StartTls INSTANCE = new StartTls();
public static final String ELEMENT = "starttls";
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-tls";
@ -49,7 +51,7 @@ public class StartTls implements Nonza {
@Override
public XmlStringBuilder toXML(String enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this);
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
xml.rightAngleBracket();
xml.condEmptyElement(required, "required");
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");
* 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
* limitations under the License.
*/
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;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import org.jivesoftware.smack.packet.Element;
import org.jivesoftware.smack.util.ParserUtils;
@ -35,6 +38,26 @@ import org.xmlpull.v1.XmlPullParser;
*/
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 {
// XPP3 calling convention assert: Parser should be at start tag
ParserUtils.assertAtStartTag(parser);

View File

@ -25,7 +25,9 @@ import java.util.concurrent.ConcurrentHashMap;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmppElementUtil;
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, 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, NonzaProvider<? extends Nonza>> nonzaProviders = new ConcurrentHashMap<>();
static {
// Ensure that Smack is initialized by calling getVersion, so that user
@ -309,6 +312,31 @@ public final class ProviderManager {
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) {
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 {
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");
* 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");
* you may not use this file except in compliance with the License.
@ -16,7 +16,10 @@
*/
package org.jivesoftware.smack.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
public class CollectionUtil {
@ -30,4 +33,20 @@ public class CollectionUtil {
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");
* you may not use this file except in compliance with the License.
@ -170,6 +170,33 @@ public class MultiMap<K,V> {
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) {
for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
put(entry.getKey(), entry.getValue());

View File

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

View File

@ -302,7 +302,7 @@ public class StringUtils {
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) {
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.util.Collection;
import java.util.Date;
import java.util.Iterator;
import org.jivesoftware.smack.packet.Element;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.FullyQualifiedElement;
import org.jivesoftware.smack.packet.NamedElement;
import org.jxmpp.util.XmppDateTime;
@ -53,7 +55,7 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
halfOpenElement(e.getElementName());
}
public XmlStringBuilder(ExtensionElement ee, String enclosingNamespace) {
public XmlStringBuilder(FullyQualifiedElement ee, String enclosingNamespace) {
this(enclosingNamespace);
prelude(ee);
}
@ -439,7 +441,7 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
return escape(text.toString());
}
public XmlStringBuilder prelude(ExtensionElement pe) {
public XmlStringBuilder prelude(FullyQualifiedElement pe) {
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
public CharSequence toXML(String enclosingNamespace) {
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;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
@ -72,6 +73,14 @@ public class HostAddress {
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() {
if (fqdn != null) {
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");
* 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.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.X509TrustManager;
@ -31,5 +32,8 @@ import javax.net.ssl.X509TrustManager;
public interface SmackDaneVerifier {
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(SSLSession sslSession) throws CertificateException;
}

View File

@ -55,7 +55,7 @@ public class DummyConnection extends AbstractXMPPConnection {
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",
"dummypass");
}
@ -77,7 +77,7 @@ public class DummyConnection extends AbstractXMPPConnection {
}
}
public DummyConnection(ConnectionConfiguration configuration) {
public DummyConnection(DummyConnectionConfiguration configuration) {
super(configuration);
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.assertNull;
import java.util.concurrent.atomic.AtomicInteger;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.packet.Stanza;
@ -55,34 +57,42 @@ public class StanzaCollectorTest {
assertEquals("14", collector.pollResult().getStanzaId());
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.
*
* @throws InterruptedException if interrupted.
*/
@SuppressWarnings("ThreadPriorityCheck")
@Test
public void verifyThreadSafety() {
int insertCount = 500;
public void verifyThreadSafety() throws InterruptedException {
final int insertCount = 500;
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() {
@Override
public void run() {
int dequeueCount = 0;
try {
while (true) {
try {
Thread.sleep(3);
} catch (InterruptedException e) {
}
@SuppressWarnings("unused")
Thread.yield();
Stanza packet = collector.nextResultBlockForever();
// System.out.println(Thread.currentThread().getName() + " packet: " + packet);
if (packet != null) {
dequeueCount++;
}
}
}
catch (InterruptedException e) {
throw new RuntimeException(e);
// Ignore as it is expected.
} finally {
consumer1Dequeued.set(dequeueCount);
}
}
});
@ -92,20 +102,20 @@ public class StanzaCollectorTest {
@Override
public void run() {
Stanza p;
int dequeueCount = 0;
do {
Thread.yield();
try {
Thread.sleep(3);
} catch (InterruptedException e) {
}
try {
p = collector.nextResult(1);
p = collector.nextResult(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// System.out.println(Thread.currentThread().getName() + " packet: " + p);
if (p != null) {
dequeueCount++;
}
}
while (p != null);
consumer2Dequeued.set(dequeueCount);
}
});
consumer2.setName("consumer 2");
@ -114,37 +124,42 @@ public class StanzaCollectorTest {
@Override
public void run() {
Stanza p;
int dequeueCount = 0;
do {
try {
Thread.sleep(3);
} catch (InterruptedException e) {
}
Thread.yield();
p = collector.pollResult();
// System.out.println(Thread.currentThread().getName() + " packet: " + p);
if (p != null) {
dequeueCount++;
}
} while (p != null);
consumer3Dequeued.set(dequeueCount);
}
});
consumer3.setName("consumer 3");
consumer1.start();
consumer2.start();
consumer3.start();
for (int i = 0; i < insertCount; i++) {
collector.processStanza(new TestPacket(i));
}
try {
Thread.sleep(5000);
consumer3.join();
consumer2.join();
consumer1.interrupt();
} catch (InterruptedException e) {
}
consumer1.start();
consumer2.start();
consumer3.start();
consumer3.join();
consumer2.join();
consumer1.interrupt();
consumer1.join();
// 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.
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 {

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

View File

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

View File

@ -304,21 +304,13 @@ public class LiteDebugger extends SmackDebugger {
}
@Override
public Reader newConnectionReader(Reader newReader) {
((ObservableReader) reader).removeReaderListener(readerListener);
ObservableReader debugReader = new ObservableReader(newReader);
debugReader.addReaderListener(readerListener);
reader = debugReader;
return reader;
public void outgoingStreamSink(CharSequence outgoingCharSequence) {
writerListener.write(outgoingCharSequence.toString());
}
@Override
public Writer newConnectionWriter(Writer newWriter) {
((ObservableWriter) writer).removeWriterListener(writerListener);
ObservableWriter debugWriter = new ObservableWriter(newWriter);
debugWriter.addWriterListener(writerListener);
writer = debugWriter;
return writer;
public void incomingStreamSink(CharSequence incomingCharSequence) {
readerListener.read(incomingCharSequence.toString());
}
@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");
* you may not use this file except in compliance with the License.
@ -17,13 +17,12 @@
package org.jivesoftware.smackx.gcm.provider;
import org.jivesoftware.smackx.gcm.packet.GcmPacketExtension;
import org.jivesoftware.smackx.json.packet.AbstractJsonPacketExtension;
import org.jivesoftware.smackx.json.provider.AbstractJsonExtensionProvider;
public class GcmExtensionProvider extends AbstractJsonExtensionProvider {
public class GcmExtensionProvider extends AbstractJsonExtensionProvider<GcmPacketExtension> {
@Override
public AbstractJsonPacketExtension from(String json) {
public GcmPacketExtension from(String 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");
* 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.XmlPullParserException;
public abstract class AbstractJsonExtensionProvider extends ExtensionElementProvider<AbstractJsonPacketExtension> {
public abstract class AbstractJsonExtensionProvider<J extends AbstractJsonPacketExtension> extends ExtensionElementProvider<J> {
@Override
public AbstractJsonPacketExtension parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException,
public J parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException,
IOException, SmackException {
String json = PacketParserUtils.parseElementText(parser);
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");
* you may not use this file except in compliance with the License.
@ -16,13 +16,12 @@
*/
package org.jivesoftware.smackx.json.provider;
import org.jivesoftware.smackx.json.packet.AbstractJsonPacketExtension;
import org.jivesoftware.smackx.json.packet.JsonPacketExtension;
public class JsonExtensionProvider extends AbstractJsonExtensionProvider {
public class JsonExtensionProvider extends AbstractJsonExtensionProvider<JsonPacketExtension> {
@Override
public AbstractJsonPacketExtension from(String json) {
public JsonPacketExtension from(String json) {
return new JsonPacketExtension(json);
}
}

View File

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

View File

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

View File

@ -15,7 +15,7 @@ dependencies {
compile project(':smack-openpgp')
compile project(':smack-debug')
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'
// Note that the junit-vintage-engine runtime dependency is not
// 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");
* 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 AbstractSmackIntTest(String testRunId, Configuration configuration) {
this.testRunId = testRunId;
this.sinttestConfiguration = configuration;
this.timeout = configuration.replyTimeout;
protected AbstractSmackIntTest(SmackIntegrationTestEnvironment<?> environment) {
this.testRunId = environment.testRunId;
this.sinttestConfiguration = environment.configuration;
this.timeout = environment.configuration.replyTimeout;
}
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");
* you may not use this file except in compliance with the License.
@ -16,6 +16,10 @@
*/
package org.igniterealtime.smack.inttest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jivesoftware.smack.XMPPConnection;
public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest {
@ -40,10 +44,18 @@ public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest
*/
protected final XMPPConnection connection;
public AbstractSmackIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment.testRunId, environment.configuration);
protected final List<XMPPConnection> connections;
public AbstractSmackIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
super(environment);
this.connection = this.conOne = environment.conOne;
this.conTwo = environment.conTwo;
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");
* you may not use this file except in compliance with the License.
@ -16,17 +16,22 @@
*/
package org.igniterealtime.smack.inttest;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smack.AbstractXMPPConnection;
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;
public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmackIntTest {
private final SmackIntegrationTestEnvironment environment;
private final SmackIntegrationTestEnvironment<?> environment;
/**
* The configuration
@ -35,33 +40,36 @@ public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmack
protected final DomainBareJid service;
public AbstractSmackLowLevelIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment.testRunId, environment.configuration);
protected AbstractSmackLowLevelIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
super(environment);
this.environment = environment;
this.configuration = environment.configuration;
this.service = configuration.service;
}
public final XMPPTCPConnectionConfiguration.Builder getConnectionConfiguration() throws KeyManagementException, NoSuchAlgorithmException {
XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder();
if (configuration.tlsContext != null) {
builder.setCustomSSLContext(configuration.tlsContext);
}
builder.setSecurityMode(configuration.securityMode);
builder.setXmppDomain(service);
return builder;
protected AbstractXMPPConnection getConnectedConnection() throws InterruptedException, XMPPException, SmackException, IOException {
AbstractXMPPConnection connection = getUnconnectedConnection();
connection.connect().login();
return connection;
}
protected void performCheck(ConnectionCallback callback) throws Exception {
XMPPTCPConnection connection = SmackIntegrationTestFramework.getConnectedConnection(environment, -1);
try {
callback.connectionCallback(connection);
} finally {
IntTestUtil.disconnectAndMaybeDelete(connection, configuration);
}
protected AbstractXMPPConnection getUnconnectedConnection()
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
return environment.connectionManager.constructConnection();
}
public interface ConnectionCallback {
void connectionCallback(XMPPTCPConnection connection) throws Exception;
protected List<AbstractXMPPConnection> getUnconnectedConnections(int count)
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");
* 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 org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.debugger.ConsoleDebugger;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.debugger.EnhancedDebugger;
import eu.geekplace.javapinning.java7.Java7Pinning;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
// TODO: Rename to SinttestConfiguration.
public final class Configuration {
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 ConnectionConfigurationBuilderApplier configurationApplier;
private Configuration(DomainBareJid service, String serviceTlsPin, SecurityMode securityMode, int replyTimeout,
Debugger debugger, String accountOneUsername, String accountOnePassword, String accountTwoUsername,
String accountTwoPassword, String accountThreeUsername, String accountThreePassword, Set<String> enabledTests, Set<String> disabledTests,
@ -126,6 +132,13 @@ public final class Configuration {
this.adminAccountUsername = adminAccountUsername;
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.accountOnePassword = accountOnePassword;
this.accountTwoUsername = accountTwoUsername;
@ -135,6 +148,26 @@ public final class Configuration {
this.enabledTests = enabledTests;
this.disabledTests = disabledTests;
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() {

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");
* you may not use this file except in compliance with the License.
@ -16,15 +16,14 @@
*/
package org.igniterealtime.smack.inttest;
import java.lang.reflect.Method;
import java.util.List;
public class FailedTest extends TestResult {
public final Throwable failureReason;
public FailedTest(Method testMethod, long startTime, long endTime, List<String> logMessages, Throwable failureReason) {
super(testMethod, startTime, endTime, logMessages);
public FailedTest(SmackIntegrationTestFramework.ConcreteTest concreteTest, long startTime, long endTime, List<String> logMessages, Throwable failureReason) {
super(concreteTest, startTime, endTime, logMessages);
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");
* you may not use this file except in compliance with the License.
@ -16,13 +16,19 @@
*/
package org.igniterealtime.smack.inttest;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
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");
* you may not use this file except in compliance with the License.
@ -16,26 +16,25 @@
*/
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 XMPPTCPConnection conTwo;
public final XMPPTCPConnection conThree;
public final C conOne, conTwo, conThree;
public final String testRunId;
public final Configuration configuration;
SmackIntegrationTestEnvironment(XMPPTCPConnection conOne, XMPPTCPConnection conTwo, XMPPTCPConnection conThree, String testRunId,
Configuration configuration) {
public final XmppConnectionManager<C> connectionManager;
SmackIntegrationTestEnvironment(C conOne, C conTwo, C conThree, String testRunId,
Configuration configuration, XmppConnectionManager<C> connectionManager) {
this.conOne = conOne;
this.conTwo = conTwo;
this.conThree = conThree;
this.testRunId = testRunId;
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");
* 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.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
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.HashSet;
@ -43,21 +47,20 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.debugger.ConsoleDebugger;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.debugger.EnhancedDebugger;
import org.jivesoftware.smackx.debugger.EnhancedDebuggerWindow;
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.BeforeClass;
import org.reflections.Reflections;
@ -66,51 +69,60 @@ import org.reflections.scanners.MethodParameterScanner;
import org.reflections.scanners.SubTypesScanner;
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 char CLASS_METHOD_SEP = '#';
public static boolean SINTTEST_UNIT_TEST = false;
private final Class<DC> defaultConnectionClass;
protected final Configuration config;
protected TestRunResult testRunResult;
private SmackIntegrationTestEnvironment environment;
private SmackIntegrationTestEnvironment<DC> environment;
protected XmppConnectionManager<DC> connectionManager;
public enum TestType {
Normal,
LowLevel,
SpecificLowLevel,
}
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);
SmackIntegrationTestFramework sinttest = new SmackIntegrationTestFramework(config);
SmackIntegrationTestFramework<XMPPTCPConnection> sinttest = new SmackIntegrationTestFramework<>(config, XMPPTCPConnection.class);
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: "
+ entry.getValue());
+ entry.getValue().getLocalizedMessage());
}
for (TestNotPossible testNotPossible : testRunResult.impossibleTestMethods) {
LOGGER.info("Could not run " + testNotPossible.testMethod.getName() + " because: "
for (TestNotPossible testNotPossible : testRunResult.impossibleIntegrationTests) {
LOGGER.info("Could not run " + testNotPossible.concreteTest + " because: "
+ 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 possibleTests = testRunResult.getNumberOfPossibleTests();
LOGGER.info("SmackIntegrationTestFramework[" + testRunResult.testRunId + ']' + ": Finished ["
+ successfulTests + '/' + possibleTests + "] (of " + availableTests + " available tests)");
+ successfulTests + '/' + totalIntegrationTests + "] (" + possibleTests + " test methods of " + availableTests + " where possible)");
int exitStatus;
if (!testRunResult.failedIntegrationTests.isEmpty()) {
final int failedTests = testRunResult.failedIntegrationTests.size();
LOGGER.warning("The following " + failedTests + " tests failed!");
final int exitStatus;
if (failedTests > 0) {
LOGGER.warning("💀 The following " + failedTests + " tests failed! 💀");
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;
LOGGER.severe(className + CLASS_METHOD_SEP + methodName + " failed: " + cause);
LOGGER.log(Level.SEVERE, failedTest.concreteTest + " failed: " + cause, cause);
}
exitStatus = 2;
} else {
@ -129,13 +141,22 @@ public class SmackIntegrationTestFramework {
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.defaultConnectionClass = defaultConnectionClass;
}
public synchronized TestRunResult run() throws KeyManagementException, NoSuchAlgorithmException, SmackException,
IOException, XMPPException, InterruptedException {
public synchronized TestRunResult run()
throws KeyManagementException, NoSuchAlgorithmException, SmackException, IOException, XMPPException,
InterruptedException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
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");
if (config.debugger != Configuration.Debugger.none) {
// 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) {
SmackConfiguration.setDefaultReplyTimeout(config.replyTimeout);
}
if (config.securityMode != SecurityMode.required) {
if (config.securityMode != SecurityMode.required && config.accountRegistration == AccountRegistration.inBandRegistration) {
AccountManager.sensitiveOperationOverInsecureConnectionDefault(true);
}
// TODO print effective configuration
@ -169,6 +190,18 @@ public class SmackIntegrationTestFramework {
classes.addAll(inttestClasses);
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()) {
throw new IllegalStateException("No test classes found");
}
@ -182,9 +215,7 @@ public class SmackIntegrationTestFramework {
}
finally {
// Ensure that the accounts are deleted and disconnected before we continue
disconnectAndMaybeDelete(environment.conOne);
disconnectAndMaybeDelete(environment.conTwo);
disconnectAndMaybeDelete(environment.conThree);
connectionManager.disconnectAndCleanup();
}
return testRunResult;
@ -192,10 +223,42 @@ public class SmackIntegrationTestFramework {
@SuppressWarnings({"unchecked", "Finally"})
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) {
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)) {
LOGGER.info("Skipping test class " + testClassName + " because it is not enabled");
continue;
@ -206,46 +269,84 @@ public class SmackIntegrationTestFramework {
continue;
}
TestType testType;
if (AbstractSmackLowLevelIntegrationTest.class.isAssignableFrom(testClass)) {
final Constructor<? extends AbstractSmackIntTest> cons;
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;
} else if (AbstractSmackIntegrationTest.class.isAssignableFrom(testClass)) {
} else if (test instanceof AbstractSmackIntegrationTest) {
testType = TestType.Normal;
} else {
throw new AssertionError();
}
List<Method> smackIntegrationTestMethods = new LinkedList<>();
for (Method method : testClass.getMethods()) {
if (!method.isAnnotationPresent(SmackIntegrationTest.class)) {
continue;
}
// Verify the method signatures, throw in case a signature is incorrect.
for (Method method : smackIntegrationTestMethods) {
Class<?> retClass = method.getReturnType();
if (!retClass.equals(Void.TYPE)) {
LOGGER.warning("SmackIntegrationTest annotation on method that does not return void");
continue;
throw new IllegalStateException(
"SmackIntegrationTest annotation on" + method + " that does not return void");
}
final Class<?>[] parameterTypes = method.getParameterTypes();
switch (testType) {
case Normal:
if (method.getParameterTypes().length > 0) {
LOGGER.warning("SmackIntegrationTest annotaton on method that takes arguments ");
continue;
final Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length > 0) {
throw new IllegalStateException(
"SmackIntegrationTest annotaton on " + method + " that takes arguments ");
}
break;
case LowLevel:
for (Class<?> parameterType : parameterTypes) {
if (!parameterType.isAssignableFrom(XMPPTCPConnection.class)) {
LOGGER.warning("SmackIntegrationTest low-level test method declares parameter that is not of type XMPPTCPConnection");
}
}
verifyLowLevelTestMethod(method, AbstractXMPPConnection.class);
break;
case SpecificLowLevel:
verifyLowLevelTestMethod(method, specificLowLevelConnectionClass);
break;
}
smackIntegrationTestMethods.add(method);
}
if (smackIntegrationTestMethods.isEmpty()) {
LOGGER.warning("No integration test methods found");
continue;
}
Iterator<Method> it = smackIntegrationTestMethods.iterator();
@ -271,75 +372,7 @@ public class SmackIntegrationTestFramework {
}
final int detectedTestMethodsCount = smackIntegrationTestMethods.size();
testRunResult.numberOfAvailableTests.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();
}
testRunResult.numberOfPossibleTestMethods.addAndGet(detectedTestMethodsCount);
try {
// Run the @BeforeClass methods (if any)
@ -373,48 +406,36 @@ public class SmackIntegrationTestFramework {
}
for (Method testMethod : smackIntegrationTestMethods) {
final String testPrefix = testClass.getSimpleName() + '.'
+ testMethod.getName() + " (" + testType + "): ";
// Invoke all test methods on the test instance
LOGGER.info(testPrefix + "Start");
long testStart = System.currentTimeMillis();
try {
List<ConcreteTest> concreteTests = null;
switch (testType) {
case Normal: {
ConcreteTest.Executor concreteTestExecutor = () -> testMethod.invoke(test);
ConcreteTest concreteTest = new ConcreteTest(testType, testMethod, concreteTestExecutor);
concreteTests = Collections.singletonList(concreteTest);
}
break;
case LowLevel:
case SpecificLowLevel:
LowLevelTestMethod lowLevelTestMethod = new LowLevelTestMethod(testMethod);
switch (testType) {
case Normal:
testMethod.invoke(test);
break;
case LowLevel:
invokeLowLevel(testMethod, test);
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;
}
LOGGER.info(testPrefix + "Success");
long testEnd = System.currentTimeMillis();
testRunResult.successfulTests.add(new SuccessfulTest(testMethod, testStart, testEnd, null));
}
catch (InvocationTargetException e) {
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;
default:
throw new AssertionError();
}
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);
}
// An integration test failed
testRunResult.failedIntegrationTests.add(new FailedTest(testMethod, testStart, testEnd, null,
nonFatalFailureReason));
LOGGER.log(Level.SEVERE, testPrefix + "Failed", e);
break;
}
catch (IllegalArgumentException | IllegalAccessException e) {
throw new AssertionError(e);
for (ConcreteTest concreteTest : concreteTests) {
runConcreteTest(concreteTest);
}
}
}
@ -452,67 +473,86 @@ public class SmackIntegrationTestFramework {
}
}
private void invokeLowLevel(Method testMethod, AbstractSmackIntTest test) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InterruptedException {
// We have checked before that every parameter, if any, is of type XMPPTCPConnection
final int numberOfConnections = testMethod.getParameterTypes().length;
XMPPTCPConnection[] connections = null;
private void runConcreteTest(ConcreteTest concreteTest)
throws InterruptedException, XMPPException, IOException, SmackException {
LOGGER.info(concreteTest + " Start");
long testStart = System.currentTimeMillis();
try {
if (numberOfConnections > 0 && !config.isAccountRegistrationPossible()) {
throw new TestNotPossibleException(
"Must create accounts for this test, but it's not enabled");
}
connections = new XMPPTCPConnection[numberOfConnections];
for (int i = 0; i < numberOfConnections; ++i) {
connections[i] = getConnectedConnection(environment, i);
}
concreteTest.executor.execute();
long testEnd = System.currentTimeMillis();
LOGGER.info(concreteTest + " Success");
testRunResult.successfulIntegrationTests.add(new SuccessfulTest(concreteTest, testStart, testEnd, null));
}
catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
catch (InvocationTargetException e) {
long testEnd = System.currentTimeMillis();
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;
}
// 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);
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);
}
// An integration test failed
testRunResult.failedIntegrationTests.add(new FailedTest(concreteTest, testStart, testEnd, null,
nonFatalFailureReason));
LOGGER.log(Level.SEVERE, concreteTest + " Failed", e);
}
catch (IllegalArgumentException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
protected void disconnectAndMaybeDelete(XMPPTCPConnection connection) throws InterruptedException {
IntTestUtil.disconnectAndMaybeDelete(connection, config);
private static void verifyLowLevelTestMethod(Method method,
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,
NoSuchAlgorithmException {
XMPPTCPConnection conOne = null;
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);
NoSuchAlgorithmException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
return connectionManager.prepareEnvironment();
}
enum AccountNum {
@ -521,99 +561,14 @@ public class SmackIntegrationTestFramework {
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) {
XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder();
if (config.tlsContext != null) {
builder.setCustomSSLContext(config.tlsContext);
}
builder.setSecurityMode(config.securityMode);
builder.setXmppDomain(config.service);
switch (config.debugger) {
case enhanced:
builder.setDebuggerFactory(EnhancedDebugger.Factory.INSTANCE);
break;
case console:
builder.setDebuggerFactory(ConsoleDebugger.Factory.INSTANCE);
break;
case none:
// Nothing to do :).
break;
}
config.configurationApplier.applyConfigurationTo(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,
InterruptedException {
if (e instanceof NoResponseException) {
@ -649,14 +604,14 @@ public class SmackIntegrationTestFramework {
*/
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<TestNotPossible> impossibleTestMethods = Collections.synchronizedList(new LinkedList<TestNotPossible>());
private final Map<Class<? extends AbstractSmackIntTest>, String> impossibleTestClasses = new HashMap<>();
private final AtomicInteger numberOfAvailableTests = new AtomicInteger();
private final AtomicInteger numberOfPossibleTests = new AtomicInteger();
private final List<TestNotPossible> impossibleIntegrationTests = Collections.synchronizedList(new LinkedList<TestNotPossible>());
private final Map<Class<? extends AbstractSmackIntTest>, Throwable> impossibleTestClasses = new HashMap<>();
private final AtomicInteger numberOfAvailableTestMethods = new AtomicInteger();
private final AtomicInteger numberOfPossibleTestMethods = new AtomicInteger();
private TestRunResult() {
TestRunResult() {
}
public String getTestRunId() {
@ -664,15 +619,15 @@ public class SmackIntegrationTestFramework {
}
public int getNumberOfAvailableTests() {
return numberOfAvailableTests.get();
return numberOfAvailableTestMethods.get();
}
public int getNumberOfPossibleTests() {
return numberOfPossibleTests.get();
return numberOfPossibleTestMethods.get();
}
public List<SuccessfulTest> getSuccessfulTests() {
return Collections.unmodifiableList(successfulTests);
return Collections.unmodifiableList(successfulIntegrationTests);
}
public List<FailedTest> getFailedTests() {
@ -680,11 +635,154 @@ public class SmackIntegrationTestFramework {
}
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);
}
}
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");
* you may not use this file except in compliance with the License.
@ -16,13 +16,12 @@
*/
package org.igniterealtime.smack.inttest;
import java.lang.reflect.Method;
import java.util.List;
public class SuccessfulTest extends TestResult {
public SuccessfulTest(Method testMethod, long startTime, long endTime, List<String> logMessages) {
super(testMethod, startTime, endTime, logMessages);
public SuccessfulTest(SmackIntegrationTestFramework.ConcreteTest concreteTest, long startTime, long endTime, List<String> 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");
* you may not use this file except in compliance with the License.
@ -16,16 +16,15 @@
*/
package org.igniterealtime.smack.inttest;
import java.lang.reflect.Method;
import java.util.List;
public class TestNotPossible extends TestResult {
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) {
super(testMethod, startTime, endTime, logMessages);
super(concreteTest, startTime, endTime, logMessages);
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");
* you may not use this file except in compliance with the License.
@ -16,19 +16,18 @@
*/
package org.igniterealtime.smack.inttest;
import java.lang.reflect.Method;
import java.util.List;
public abstract class TestResult {
public final Method testMethod;
public final SmackIntegrationTestFramework.ConcreteTest concreteTest;
public final long startTime;
public final long endTime;
public final long duration;
public final List<String> logMessages;
public TestResult(Method testMethod, long startTime, long endTime, List<String> logMessages) {
this.testMethod = testMethod;
public TestResult(SmackIntegrationTestFramework.ConcreteTest concreteTest, long startTime, long endTime, List<String> logMessages) {
this.concreteTest = concreteTest;
assert (endTime >= startTime);
this.startTime = startTime;
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");
* you may not use this file except in compliance with the License.
@ -48,7 +48,7 @@ public class ChatTest extends AbstractSmackIntegrationTest {
private boolean invoked;
@SuppressWarnings("deprecation")
public ChatTest(SmackIntegrationTestEnvironment environment) {
public ChatTest(SmackIntegrationTestEnvironment<?> environment) {
super(environment);
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");
* 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.SASLErrorException;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smack.util.StringUtils;
import org.igniterealtime.smack.inttest.AbstractSmackLowLevelIntegrationTest;
@ -35,7 +33,7 @@ import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
public class LoginIntegrationTest extends AbstractSmackLowLevelIntegrationTest {
public LoginIntegrationTest(SmackIntegrationTestEnvironment environment) {
public LoginIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
super(environment);
}
@ -54,14 +52,13 @@ public class LoginIntegrationTest extends AbstractSmackLowLevelIntegrationTest {
public void testInvalidLogin() throws SmackException, IOException, XMPPException,
InterruptedException, KeyManagementException, NoSuchAlgorithmException {
final String nonExistentUserString = StringUtils.insecureRandomString(24);
XMPPTCPConnectionConfiguration conf = getConnectionConfiguration().setUsernameAndPassword(
nonExistentUserString, "invalidPassword").build();
final String invalidPassword = "invalidPassword";
XMPPTCPConnection connection = new XMPPTCPConnection(conf);
AbstractXMPPConnection connection = getUnconnectedConnection();
connection.connect();
try {
connection.login();
connection.login(nonExistentUserString, invalidPassword);
fail("Exception expected");
}
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");
* you may not use this file except in compliance with the License.
@ -28,23 +28,20 @@ import org.jivesoftware.smack.filter.MessageWithBodiesFilter;
import org.jivesoftware.smack.packet.Message;
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.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
public class StreamManagementTest extends AbstractSmackLowLevelIntegrationTest {
public class StreamManagementTest extends AbstractSmackSpecificLowLevelIntegrationTest<XMPPTCPConnection> {
public StreamManagementTest(SmackIntegrationTestEnvironment environment) throws Exception {
super(environment);
performCheck(new ConnectionCallback() {
@Override
public void connectionCallback(XMPPTCPConnection connection) throws Exception {
if (!connection.isSmAvailable()) {
throw new TestNotPossibleException("XEP-198: Stream Mangement not supported by service");
}
}
});
public StreamManagementTest(SmackIntegrationTestEnvironment<?> environment) throws Exception {
super(environment, XMPPTCPConnection.class);
XMPPTCPConnection connection = getSpecificUnconnectedConnection();
connection.connect().login();
if (!connection.isSmAvailable()) {
throw new TestNotPossibleException("XEP-198: Stream Mangement not supported by service");
}
}
@SmackIntegrationTest

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");
* 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 org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.igniterealtime.smack.inttest.AbstractSmackLowLevelIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
public class WaitForClosingStreamElementTest extends AbstractSmackLowLevelIntegrationTest {
public WaitForClosingStreamElementTest(SmackIntegrationTestEnvironment environment) {
public WaitForClosingStreamElementTest(SmackIntegrationTestEnvironment<?> environment) {
super(environment);
}
@SmackIntegrationTest
public void waitForClosingStreamElementTest(XMPPTCPConnection connection)
public void waitForClosingStreamElementTest(AbstractXMPPConnection connection)
throws NoSuchFieldException, SecurityException, IllegalArgumentException,
IllegalAccessException {
connection.disconnect();
Field closingStreamReceivedField = connection.getClass().getDeclaredField("closingStreamReceived");
Field closingStreamReceivedField = AbstractXMPPConnection.class.getDeclaredField("closingStreamReceived");
closingStreamReceivedField.setAccessible(true);
SynchronizationPoint<?> closingStreamReceived = (SynchronizationPoint<?>) closingStreamReceivedField.get(connection);
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 chatManagerThree;
protected AbstractChatIntegrationTest(SmackIntegrationTestEnvironment environment) {
protected AbstractChatIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
super(environment);
chatManagerOne = ChatManager.getInstanceFor(conOne);
chatManagerTwo = ChatManager.getInstanceFor(conTwo);

View File

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

View File

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

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