mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-11-21 22:02:06 +01:00
SmackReactor/NIO, Java8/Android19, Pretty print XML, FSM connections
This commit adds - SmackReactor / NIO - a framework for finite state machine connections - support for Java 8 - pretty printed XML debug output It also - reworks the integration test framework - raises the minimum Android API level to 19 - introduces XmppNioTcpConnection Furthermore fixes SMACK-801 (at least partly). Java 8 language features are available, but not all runtime library methods. For that we would need to raise the Android API level to 24 or higher.
This commit is contained in:
parent
dba12919d0
commit
e98d42790a
144 changed files with 8692 additions and 1455 deletions
40
build.gradle
40
build.gradle
|
@ -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]
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 + "'/>";
|
||||
}
|
||||
}
|
|
@ -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 + "'/>";
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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> {
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -26,7 +26,7 @@ import org.jxmpp.jid.EntityBareJid;
|
|||
|
||||
public class IncomingMessageListenerIntegrationTest extends AbstractChatIntegrationTest {
|
||||
|
||||
public IncomingMessageListenerIntegrationTest(SmackIntegrationTestEnvironment environment) {
|
||||
public IncomingMessageListenerIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
|
||||
super(environment);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue