diff options
author | Shawn O. Pearce <sop@google.com> | 2008-11-14 16:22:58 -0800 |
---|---|---|
committer | Shawn O. Pearce <sop@google.com> | 2008-11-14 16:29:21 -0800 |
commit | 28baf2a6fcf07a97227310d7dd0fb9d7df598417 (patch) | |
tree | 29d0f4f78522733008b2b129f8d176a7cf7fc7ec | |
download | gwtjsonrpc-28baf2a6fcf07a97227310d7dd0fb9d7df598417.tar.gz |
Initial version of JSON-RPC 1.1 over HTTP for GWT
The implementation is as close to the JSON-RPC 1.1 working draft
as possible, while retaining a simple code base in both the GWT
client and the Java based server.
To use this module, build the JAR, place it into your classpath
and inherit the module:
<inherits name='com.google.gwtjsonrpc.GWTJSONRPC'/>
Signed-off-by: Shawn O. Pearce <sop@google.com>
34 files changed, 3065 insertions, 0 deletions
diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..37472fd --- /dev/null +++ b/.classpath @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/> + <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/GWT"/> + <classpathentry kind="lib" path="lib/gson.jar" sourcepath="lib/gson_src.zip"/> + <classpathentry kind="lib" path="lib/commons-codec.jar"/> + <classpathentry kind="output" path="classes"/> +</classpath> diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..12e933d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/.bin +/classes +/lib/*_src.zip +/lib/gwtjsonrpc.jar +/config.mak diff --git a/.project b/.project new file mode 100644 index 0000000..44da701 --- /dev/null +++ b/.project @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8" ?> +<projectDescription> + <name>GwtJsonRpc</name> + <comment>GWT-JSON-RPC</comment> + <projects/> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments/> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..82eb859 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +#Tue Sep 02 16:59:24 PDT 2008 +eclipse.preferences.version=1 +encoding/<project>=UTF-8 diff --git a/.settings/org.eclipse.core.runtime.prefs b/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 0000000..8667cfd --- /dev/null +++ b/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,3 @@ +#Tue Sep 02 16:59:24 PDT 2008 +eclipse.preferences.version=1 +line.separator=\n diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..fa64d21 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,268 @@ +#Thu Sep 04 11:18:51 PDT 2008 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.5 +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assignment=16 +org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=0 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=0 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=true +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false +org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert +org.eclipse.jdt.core.formatter.comment.line_length=80 +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.lineSplit=80 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3 +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false +org.eclipse.jdt.core.formatter.tabulation.char=space +org.eclipse.jdt.core.formatter.tabulation.size=2 +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000..f37f6f0 --- /dev/null +++ b/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,9 @@ +#Tue Sep 02 17:00:18 PDT 2008 +eclipse.preferences.version=1 +formatter_profile=_Google Format +formatter_settings_version=11 +org.eclipse.jdt.ui.ignorelowercasenames=true +org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax; +org.eclipse.jdt.ui.ondemandthreshold=99 +org.eclipse.jdt.ui.staticondemandthreshold=99 +org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/> @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..885f65b --- /dev/null +++ b/Makefile @@ -0,0 +1,66 @@ +# JSON-RPC for Google Web Toolkit +# +# Copyright 2008 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Define GWT_SDK to the location of the Google Web Toolkit SDK. +# +# Define GWT_OS to your operating system, e.g. 'linux', 'mac'. +# + +uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') + +JAVAC = javac +GWT_OS = unknown + +ifeq ($(uname_S),Darwin) + GWT_OS = mac +endif +ifeq ($(uname_S),Linux) + GWT_OS = linux +endif +ifeq ($(uname_S),Cygwin) + GWT_OS = win +endif + +-include config.mak + +GWT_CP = $(GWT_SDK)/gwt-user.jar:$(GWT_SDK)/gwt-dev-$(GWT_OS).jar +MY_JAR = lib/gwtjsonrpc.jar +MY_JAVA = $(shell find src -name \*.java) +MY_GWT_XML = com/google/gwtjsonrpc/GWTJSONRPC.gwt.xml + +all: $(MY_JAR) + +clean: + rm -f $(MY_JAR) + rm -rf classes .bin + +$(MY_JAR): $(MY_JAVA) src/$(MY_GWT_XML) + rm -rf .bin + mkdir .bin + cd src && $(JAVAC) \ + -encoding utf-8 \ + -source 1.5 \ + -target 1.5 \ + -g \ + -cp $(GWT_CP):../lib/gson.jar:../lib/commons-codec.jar \ + -d ../.bin \ + $(patsubst src/%,%,$(MY_JAVA)) + cd .bin && jar cf ../$(MY_JAR) . + cd src && jar uf ../$(MY_JAR) . + rm -rf .bin + +.PHONY: all +.PHONY: clean @@ -0,0 +1,152 @@ +JSON-RPC for Google Web Toolkit (GWT) +------------------------------------- + +The implementation is as close to the JSON-RPC 1.1 working draft[1] +as possible, while retaining a simple code base in both the GWT +client and the Java based server. + +To use this module, build lib/gwtjsonrpc.jar (type "make"), place +the JAR into your classpath and inherit the module in your gwt.xml: + + <inherits name='com.google.gwtjsonrpc.GWTJSONRPC'/> + +Java based JSON services should extend JsonServlet and directly +implement the service interface. You will also need to include +lib/gson.jar and lib/commons-codec.jar in the server's CLASSPATH. + +[1] http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html + +Services may be implemented in any language that supports JSON-RPC +1.1, including Python, PHP, Perl, etc. This package includes an +example Java based server to make development easier in a pure +Java application. + + +Differences from JSON-RPC 1.1 +----------------------------- + +Only positional parameters are supported. Within a request the +'params' member must be missing, or must be a JSON array of the +positional parameters. + +Only Java-style method names are supported. A method name must +conform to ^[a-zA-Z_$][a-zA-Z_$0-9]*$ and thus any of the standard +"system.*" methods (e.g. "system.describe") is not supported. + +Call approximation is not supported. The 'params' member must +*exactly* match the declared parameters of the method being called. + +Error codes sent by the Java server are always 999 as the JSON RPC +specification does not call out specific errors. + + +Differences from GWT-RPC +------------------------ + +This package uses the standard JSON-RPC 1.1 for wire encoding, +rather than a custom object serialization standard. + +Benefits: + +- Clients are not tied to GWT: + + Clients may be written in any language that has a JSON parser + library available. Objects are proper JSON objects with field + names as declared in the Java classes being serialized. + +- Servers are not tied to GWT: + + Servers may be written in any language, as the only requirement + is that the server can create a properly formatted JSON string + with the expected field names. If the Java field names are easily + recognized by a "subject matter expert" (or are at least documented + in the Java class definition) it is easy to implement a server. + +- Automatic XSRF (cross-site request forgery) protection: + + When using the Java based server implementation in this package + automatic XSRF protection is enabled for every RPC. + + +Drawbacks: + +- Object field names are exposed verbatim on the wire: + + GWT-RPC protects the field names by not including them in the + JSON output. If you are using GWT to obfuscate your JavaScript + and hide intellectual property, this package isn't for you. + +- Slightly larger object transfers: + + GWT-RPC recognizes fields by the position they appear in the JSON + parse tree (the entire stream is encoded as one giant JSON array). + This package includes field names in every object instance, as that + is required by the JSON format. The resulting string to be sent in + either direction is larger. This increase in size may be negated + by the automatic "gzip" encoding, if the browser supports it. + +- Exceptions are not "thrown" to the client: + + GWT-RPC supports declared exceptions using two interfaces; this + package sends only the exception message back to the client + and does not support throwing checked exceptions from service + implementation methods. + +- Method overloading is not supported: + + Only one method of each name can be declared in the interface. + Thus "void foo(int a)" and "void foo(String a)" cannot be used. + A simple (but annoying) work around is to add a unique suffix to + each method name. + + + +Example +------- + +Define the service: + + import com.google.gwt.user.client.rpc.AsyncCallback; + import com.google.gwtjsonrpc.client.RemoteJsonService; + + public interface StringService extends RemoteJsonService { + public void append(String a, String b, AsyncCallback<String> ac); + } + +Configure GWTJSONRPC in Application.gwt.xml: + + <inherits name='com.google.gwtjsonrpc.GWTJSONRPC'/> + <servlet path='/StringService' + class='example.StringServiceImpl'/> + +Implement the service in Java as a servlet: + + import com.google.gwt.user.client.rpc.AsyncCallback; + import com.google.gwtjsonrpc.server.JsonServlet; + + public StringServiceImpl extends JsonServlet + implements StringService + { + public void append(String a, String b, AsyncCallback<String> ac) + { + if (a != null && b != null) + ac.onSuccess(a + b); + else + ac.onFailure(new IllegalArgumentException("Null input")); + } + } + +Call the service from the browser: + + StringService cs = GWT.create(StringService.class); + ((ServiceDefTarget) cs).setServiceEntryPoint( + GWT.getModuleBaseURL() + "StringService"); + + cs.append("foo", "bar", new AsyncCallback<String>() { + public void onSuccess(String result) { + GWT.log("append = " + result, null); + } + public void onFailure(Throwable why) { + GWT.log("append failure", why); + } + }); diff --git a/README_ECLIPSE b/README_ECLIPSE new file mode 100644 index 0000000..ba31783 --- /dev/null +++ b/README_ECLIPSE @@ -0,0 +1,18 @@ +Eclipse Setup +============= + +User Library +------------ + + Window > Preferences + Java > Build Path > User Libraries + +Create a User Library called "GWT": + + New + + Name: GWT + Add JARs... + + * Select gwt-user.jar from the $(GWT_SDK) directory. + * Select gwt-dev-$(GWT_OS).jar from the $(GWT_SDK) directory. diff --git a/lib/commons-codec.jar b/lib/commons-codec.jar Binary files differnew file mode 100644 index 0000000..957b675 --- /dev/null +++ b/lib/commons-codec.jar diff --git a/lib/gson.jar b/lib/gson.jar Binary files differnew file mode 100644 index 0000000..0498cf2 --- /dev/null +++ b/lib/gson.jar diff --git a/src/com/google/gwtjsonrpc/GWTJSONRPC.gwt.xml b/src/com/google/gwtjsonrpc/GWTJSONRPC.gwt.xml new file mode 100644 index 0000000..adc8479 --- /dev/null +++ b/src/com/google/gwtjsonrpc/GWTJSONRPC.gwt.xml @@ -0,0 +1,24 @@ +<!-- + Copyright 2008 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<module> + <inherits name="com.google.gwt.user.User"/> + <inherits name='com.google.gwt.http.HTTP'/> + <inherits name='com.google.gwt.json.JSON'/> + + <generate-with class="com.google.gwtjsonrpc.rebind.RemoteJsonServiceProxyGenerator"> + <when-type-assignable class="com.google.gwtjsonrpc.client.RemoteJsonService" /> + </generate-with> +</module> diff --git a/src/com/google/gwtjsonrpc/client/AbstractJsonProxy.java b/src/com/google/gwtjsonrpc/client/AbstractJsonProxy.java new file mode 100644 index 0000000..a5d1b7e --- /dev/null +++ b/src/com/google/gwtjsonrpc/client/AbstractJsonProxy.java @@ -0,0 +1,50 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.client; + +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.rpc.InvocationException; +import com.google.gwt.user.client.rpc.ServiceDefTarget; + +/** + * Base class for generated {@link RemoteJsonService} implementations. + * <p> + * At runtime <code>GWT.create(Foo.class)</code> returns a subclass of this + * class, implementing the Foo and {@link ServiceDefTarget} interfaces. + */ +public abstract class AbstractJsonProxy implements ServiceDefTarget { + /** URL of the service implementation. */ + String url; + + /** Current XSRF token associated with this service. */ + String xsrfKey; + + public String getServiceEntryPoint() { + return url; + } + + public void setServiceEntryPoint(final String address) { + url = address; + } + + protected <T> void doInvoke(final String requestData, + final JsonSerializer<T> resultSerializer, final AsyncCallback<T> callback) + throws InvocationException { + if (url == null) { + throw new NoServiceEntryPointSpecifiedException(); + } + new JsonCall<T>(this, requestData, resultSerializer, callback).send(); + } +} diff --git a/src/com/google/gwtjsonrpc/client/ArraySerializer.java b/src/com/google/gwtjsonrpc/client/ArraySerializer.java new file mode 100644 index 0000000..c15d191 --- /dev/null +++ b/src/com/google/gwtjsonrpc/client/ArraySerializer.java @@ -0,0 +1,70 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.client; + +import com.google.gwt.core.client.JavaScriptObject; + +/** + * Default serialization for any Object[] sort of type. + * <p> + * Primitive array types (like <code>int[]</code>) are not supported. + */ +public class ArraySerializer<T> extends JsonSerializer<T[]> { + private final JsonSerializer<T> serializer; + + public ArraySerializer(final JsonSerializer<T> s) { + serializer = s; + } + + @Override + public void printJson(final StringBuffer sb, final T[] o) { + if (o != null) { + sb.append('['); + for (int i = 0, n = o.length; i < n; i++) { + if (i > 0) { + sb.append(','); + } + serializer.printJson(sb, o[i]); + } + sb.append(']'); + } else { + sb.append(JS_NULL); + } + } + + @Override + public T[] fromJson(final Object o) { + if (o == null) { + return null; + } + + final JavaScriptObject jso = (JavaScriptObject) o; + final int n = size(jso); + final T[] r = ArraySerializer.<T> newArray(n); + for (int i = 0; i < n; i++) { + r[i] = serializer.fromJson(get(jso, i)); + } + return r; + } + + @SuppressWarnings("unchecked") + private static final <T> T[] newArray(final int sz) { + return (T[]) new Object[sz]; + } + + private static final native int size(JavaScriptObject o)/*-{ return o.length; }-*/; + + private static final native JavaScriptObject get(JavaScriptObject o, int i)/*-{ return o[i]; }-*/; +} diff --git a/src/com/google/gwtjsonrpc/client/JavaLangString_JsonSerializer.java b/src/com/google/gwtjsonrpc/client/JavaLangString_JsonSerializer.java new file mode 100644 index 0000000..87dffe5 --- /dev/null +++ b/src/com/google/gwtjsonrpc/client/JavaLangString_JsonSerializer.java @@ -0,0 +1,36 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.client; + +/** Default serialization for a String. */ +public final class JavaLangString_JsonSerializer extends + JsonSerializer<java.lang.String> { + public static final JavaLangString_JsonSerializer INSTANCE = + new JavaLangString_JsonSerializer(); + + @Override + public java.lang.String fromJson(final Object o) { + return (String) o; + } + + @Override + public void printJson(final StringBuffer sb, final java.lang.String o) { + if (o != null) { + sb.append(escapeString(o)); + } else { + sb.append(JS_NULL); + } + } +} diff --git a/src/com/google/gwtjsonrpc/client/JavaUtilDate_JsonSerializer.java b/src/com/google/gwtjsonrpc/client/JavaUtilDate_JsonSerializer.java new file mode 100644 index 0000000..c70b1a2 --- /dev/null +++ b/src/com/google/gwtjsonrpc/client/JavaUtilDate_JsonSerializer.java @@ -0,0 +1,48 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.client; + +import java.util.Date; + +/** Default serialization for a {@link java.util.Date}. */ +public final class JavaUtilDate_JsonSerializer extends + JsonSerializer<java.util.Date> { + public static final JavaUtilDate_JsonSerializer INSTANCE = + new JavaUtilDate_JsonSerializer(); + + @Override + public java.util.Date fromJson(final Object o) { + if (o != null) { + return parse((String) o); + } + return null; + } + + @Override + public void printJson(final StringBuffer sb, final java.util.Date o) { + if (o != null) { + sb.append('"'); + sb.append(o); + sb.append('"'); + } else { + sb.append(JS_NULL); + } + } + + @SuppressWarnings("deprecation") + private static Date parse(final String o) { + return new java.util.Date(o); + } +} diff --git a/src/com/google/gwtjsonrpc/client/JsonCall.java b/src/com/google/gwtjsonrpc/client/JsonCall.java new file mode 100644 index 0000000..37f484c --- /dev/null +++ b/src/com/google/gwtjsonrpc/client/JsonCall.java @@ -0,0 +1,141 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.client; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.http.client.Request; +import com.google.gwt.http.client.RequestBuilder; +import com.google.gwt.http.client.RequestCallback; +import com.google.gwt.http.client.RequestException; +import com.google.gwt.http.client.Response; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.rpc.InvocationException; +import com.google.gwt.user.client.rpc.StatusCodeException; + +class JsonCall<T> implements RequestCallback { + private final AbstractJsonProxy proxy; + private final String requestData; + private final JsonSerializer<T> resultSerializer; + private final AsyncCallback<T> callback; + private int attempts; + + JsonCall(final AbstractJsonProxy abstractJsonProxy, final String requestData, + final JsonSerializer<T> resultSerializer, final AsyncCallback<T> callback) { + this.proxy = abstractJsonProxy; + this.requestData = requestData; + this.resultSerializer = resultSerializer; + this.callback = callback; + } + + void send() { + final RequestBuilder rb; + + rb = new RequestBuilder(RequestBuilder.POST, proxy.url); + rb.setHeader("Content-Type", Shared.JSON_TYPE); + rb.setHeader("Accept", Shared.JSON_TYPE); + rb.setRequestData(requestData); + rb.setCallback(this); + if (proxy.xsrfKey != null) { + rb.setHeader(Shared.XSRF_HEADER, proxy.xsrfKey); + } + + try { + attempts++; + rb.send(); + } catch (RequestException e) { + callback.onFailure(e); + } + } + + public void onResponseReceived(final Request req, final Response rsp) { + final int sc = rsp.getStatusCode(); + + rememberXsrfKey(rsp); + + if (sc == Shared.SC_INVALID_XSRF + && Shared.SM_INVALID_XSRF.equals(rsp.getText())) { + // The XSRF cookie was invalidated (or didn't exist) and the + // service demands we have one in place to make calls to it. + // A new token was returned to us, so start the request over. + // + if (attempts < 2) { + send(); + } else { + callback.onFailure(new InvocationException(rsp.getText())); + } + return; + } + + if (isJsonBody(rsp)) { + final RpcResult r = parse(rsp.getText()); + if (r.error() != null) { + callback.onFailure(new InvocationException(r.error().message())); + return; + } + + if (sc == Response.SC_OK) { + callback.onSuccess(resultSerializer.fromJson(r.result())); + return; + } + } + + if (sc == Response.SC_OK) { + callback.onFailure(new InvocationException("No JSON response")); + } else { + callback.onFailure(new StatusCodeException(sc, rsp.getStatusText())); + } + } + + public void onError(final Request request, final Throwable exception) { + callback.onFailure(exception); + } + + private void rememberXsrfKey(final Response rsp) { + final String v = rsp.getHeader(Shared.XSRF_HEADER); + if (v != null) { + proxy.xsrfKey = v; + } + } + + private static boolean isJsonBody(final Response rsp) { + String type = rsp.getHeader("Content-Type"); + if (type == null) { + return false; + } + int semi = type.indexOf(';'); + if (semi >= 0) { + type = type.substring(0, semi).trim(); + } + return Shared.JSON_TYPE.equals(type); + } + + private static final native RpcResult parse(String json)/*-{ return eval('('+json+')'); }-*/; + + private static class RpcResult extends JavaScriptObject { + protected RpcResult() { + } + + final native RpcError error()/*-{ return this.error; }-*/; + + final native JavaScriptObject result()/*-{ return this.result; }-*/; + } + + private static class RpcError extends JavaScriptObject { + protected RpcError() { + } + + final native String message()/*-{ return this.message; }-*/; + } +} diff --git a/src/com/google/gwtjsonrpc/client/JsonSerializer.java b/src/com/google/gwtjsonrpc/client/JsonSerializer.java new file mode 100644 index 0000000..ab5b4f9 --- /dev/null +++ b/src/com/google/gwtjsonrpc/client/JsonSerializer.java @@ -0,0 +1,69 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.client; + +/** + * Converter between JSON and Java object representations. + * <p> + * Implementors must provide bidirectional conversion, typically using the GWT + * JavaScriptObject and native JavaScript magic to read the JSON data structure. + * <p> + * Most implementations are generated automatically at compile-time by the + * <code>RemoteJsonServiceProxyGenerator</code>. + * + * @param <T> type of Java class this type works on. + */ +public abstract class JsonSerializer<T> { + /** Magic constant in JSON to mean the same as Java null. */ + public static final String JS_NULL = "null"; + + /** + * Convert a Java object to JSON text. + * <p> + * Implementations should recursively call any nested object or collection at + * the appropriate time to append the nested item's JSON text. + * + * @param sb the output string buffer the JSON text should be appended onto + * the end of. + * @param o the Java instance being converted. May be null, in which case + * {@link #JS_NULL} should be appended instead. + */ + public abstract void printJson(StringBuffer sb, T o); + + /** + * Convert from JSON (stored as a JavaScriptObject) into a new Java instance. + * + * @param o the JSON object instance; typically this should be downcast to + * JavaScriptObject. May be null, in which case null should be returned + * instead of an instance. + * @return null if <code>o</code> was null; otherwise the new object instance + * with the data copied over form the JSON representation. + */ + public abstract T fromJson(Object o); + + /** + * Utility function to convert a String to safe JSON text. + * <p> + * For example, if <code>val = "b\nb"</code> this method returns the value + * <code>"\"b\\nb\""</code>. + * <p> + * Typically called by {@link #printJson(StringBuffer, Object)}, or through + * {@link JavaLangString_JsonSerializer}. + * + * @param val string text requiring escaping support. Must not be null. + * @return a JSON literal text value, surrounded with double quotes. + */ + public static final native String escapeString(String val)/*-{ return @com.google.gwt.json.client.JSONString::escapeValue(Ljava/lang/String;)(val); }-*/; +} diff --git a/src/com/google/gwtjsonrpc/client/ListSerializer.java b/src/com/google/gwtjsonrpc/client/ListSerializer.java new file mode 100644 index 0000000..cc22aad --- /dev/null +++ b/src/com/google/gwtjsonrpc/client/ListSerializer.java @@ -0,0 +1,71 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.client; + +import com.google.gwt.core.client.JavaScriptObject; + +import java.util.ArrayList; + +/** + * Serialization for a {@link java.util.List}. + * <p> + * When deserialized from JSON the List implementation is always an + * {@link ArrayList}. When serializing to JSON any List is permitted. + */ +public class ListSerializer<T> extends JsonSerializer<java.util.List<T>> { + private final JsonSerializer<T> serializer; + + public ListSerializer(final JsonSerializer<T> s) { + serializer = s; + } + + @Override + public void printJson(final StringBuffer sb, final java.util.List<T> o) { + if (o != null) { + sb.append('['); + boolean first = true; + for (final T item : o) { + if (first) { + first = false; + } else { + sb.append(','); + } + serializer.printJson(sb, item); + } + sb.append(']'); + } else { + sb.append(JS_NULL); + } + } + + @Override + public java.util.List<T> fromJson(final Object o) { + if (o == null) { + return null; + } + + final JavaScriptObject jso = (JavaScriptObject) o; + final int n = size(jso); + final ArrayList<T> r = new ArrayList<T>(n); + for (int i = 0; i < n; i++) { + r.add(serializer.fromJson(get(jso, i))); + } + return r; + } + + private static final native int size(JavaScriptObject o)/*-{ return o.length; }-*/; + + private static final native JavaScriptObject get(JavaScriptObject o, int i)/*-{ return o[i]; }-*/; +} diff --git a/src/com/google/gwtjsonrpc/client/RemoteJsonService.java b/src/com/google/gwtjsonrpc/client/RemoteJsonService.java new file mode 100644 index 0000000..dd7795b --- /dev/null +++ b/src/com/google/gwtjsonrpc/client/RemoteJsonService.java @@ -0,0 +1,51 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.client; + +import com.google.gwt.user.client.rpc.AsyncCallback; + +/** + * Marker interface for JSON based RPC. + * <p> + * Application service interfaces should extend this interface: + * + * <pre> + * public interface FooService extends RemoteJsonService ... + * </pre> + * <p> + * and declare each method as returning void and accepting {@link AsyncCallback} + * as the final parameter, with a concrete type specified as the result type: + * + * <pre> + * public interface FooService extends RemoteJsonService { + * public void fooItUp(AsyncCallback<ResultType> callback); + * } + * </pre> + * <p> + * Instances of the interface can be obtained in the client and configured to + * reference a particular JSON server: + * + * <pre> + * FooService mysvc = GWT.create(FooService.class); + * ((ServiceDefTarget) mysvc).setServiceEntryPoint(GWT.getModuleBaseURL() + * + "FooService"); + *</pre> + * <p> + * Calling conventions match the JSON-RPC 1.1 working draft from 7 August 2006 + * (<a href="http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html">draft</a>). + * Only positional parameters are supported. + */ +public interface RemoteJsonService { +} diff --git a/src/com/google/gwtjsonrpc/client/SetSerializer.java b/src/com/google/gwtjsonrpc/client/SetSerializer.java new file mode 100644 index 0000000..9cb2177 --- /dev/null +++ b/src/com/google/gwtjsonrpc/client/SetSerializer.java @@ -0,0 +1,71 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.client; + +import com.google.gwt.core.client.JavaScriptObject; + +import java.util.HashSet; + +/** + * Serialization for a {@link java.util.Set}. + * <p> + * When deserialized from JSON the List implementation is always a + * {@link HashSet}. When serializing to JSON any Set is permitted. + */ +public class SetSerializer<T> extends JsonSerializer<java.util.Set<T>> { + private final JsonSerializer<T> serializer; + + public SetSerializer(final JsonSerializer<T> s) { + serializer = s; + } + + @Override + public void printJson(final StringBuffer sb, final java.util.Set<T> o) { + if (o != null) { + sb.append('['); + boolean first = true; + for (final T item : o) { + if (first) { + first = false; + } else { + sb.append(','); + } + serializer.printJson(sb, item); + } + sb.append(']'); + } else { + sb.append(JS_NULL); + } + } + + @Override + public java.util.Set<T> fromJson(final Object o) { + if (o == null) { + return null; + } + + final JavaScriptObject jso = (JavaScriptObject) o; + final int n = size(jso); + final HashSet<T> r = new HashSet<T>(n); + for (int i = 0; i < n; i++) { + r.add(serializer.fromJson(get(jso, i))); + } + return r; + } + + private static final native int size(JavaScriptObject o)/*-{ return o.length; }-*/; + + private static final native JavaScriptObject get(JavaScriptObject o, int i)/*-{ return o[i]; }-*/; +} diff --git a/src/com/google/gwtjsonrpc/client/Shared.java b/src/com/google/gwtjsonrpc/client/Shared.java new file mode 100644 index 0000000..56b037a --- /dev/null +++ b/src/com/google/gwtjsonrpc/client/Shared.java @@ -0,0 +1,35 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.client; + +/** Shared constants between client and server implementations. */ +public class Shared { + /** Proper Content-Type header value for JSON encoded data. */ + public static final String JSON_TYPE = "application/json"; + + /** + * Name of the HTTP header holding the XSRF token is inserted into. + */ + public static final String XSRF_HEADER = "X-RPC-XSRF-Token"; + + /** HTTP status code when the XSRF token is missing or invalid. */ + public static final int SC_INVALID_XSRF = 400; // aka SC_BAD_REQUEST + + /** Complete content when the XSRF token is missing or invalid. */ + public static final String SM_INVALID_XSRF = "INVALID_XSRF"; + + private Shared() { + } +} diff --git a/src/com/google/gwtjsonrpc/rebind/ProxyCreator.java b/src/com/google/gwtjsonrpc/rebind/ProxyCreator.java new file mode 100644 index 0000000..fdb88e0 --- /dev/null +++ b/src/com/google/gwtjsonrpc/rebind/ProxyCreator.java @@ -0,0 +1,281 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.rebind; + +import com.google.gwt.core.ext.GeneratorContext; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JArrayType; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JPackage; +import com.google.gwt.core.ext.typeinfo.JParameter; +import com.google.gwt.core.ext.typeinfo.JPrimitiveType; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.core.ext.typeinfo.NotFoundException; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.dev.generator.NameFactory; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; +import com.google.gwt.user.rebind.SourceWriter; +import com.google.gwtjsonrpc.client.AbstractJsonProxy; +import com.google.gwtjsonrpc.client.JsonSerializer; + +import java.io.PrintWriter; +import java.util.HashSet; +import java.util.Set; + +class ProxyCreator { + private static final String PROXY_SUFFIX = "_JsonProxy"; + private JClassType svcInf; + private JClassType asyncCallbackClass; + private SerializerCreator serializerCreator; + + ProxyCreator(final JClassType remoteService) { + svcInf = remoteService; + } + + String create(final TreeLogger logger, final GeneratorContext context) + throws UnableToCompleteException { + serializerCreator = new SerializerCreator(context); + final TypeOracle typeOracle = context.getTypeOracle(); + try { + asyncCallbackClass = typeOracle.getType(AsyncCallback.class.getName()); + } catch (NotFoundException e) { + logger.log(TreeLogger.ERROR, null, e); + throw new UnableToCompleteException(); + } + checkMethods(logger, context); + + final SourceWriter srcWriter = getSourceWriter(logger, context); + if (srcWriter == null) { + return getProxyQualifiedName(); + } + + generateProxyMethods(logger, srcWriter); + srcWriter.commit(logger); + + return getProxyQualifiedName(); + } + + private void checkMethods(final TreeLogger logger, + final GeneratorContext context) throws UnableToCompleteException { + final Set<String> declaredNames = new HashSet<String>(); + final JMethod[] methodList = svcInf.getOverridableMethods(); + for (final JMethod m : methodList) { + if (!declaredNames.add(m.getName())) { + invalid(logger, "Overloading method " + m.getName() + " not supported"); + } + + if (m.getReturnType() != JPrimitiveType.VOID) { + invalid(logger, "Method " + m.getName() + " must return void"); + } + + final JParameter[] params = m.getParameters(); + if (params.length == 0) { + invalid(logger, "Method " + m.getName() + " requires " + + AsyncCallback.class.getName() + " as last parameter"); + } + + final JParameter callback = params[params.length - 1]; + if (!callback.getType().getErasedType().getQualifiedSourceName().equals( + asyncCallbackClass.getQualifiedSourceName())) { + invalid(logger, "Method " + m.getName() + " requires " + + AsyncCallback.class.getName() + " as last parameter"); + } + if (callback.getType().isParameterized() == null) { + invalid(logger, "Callback " + callback.getName() + + " must have a type parameter"); + } + + for (int i = 0; i < params.length - 1; i++) { + final JParameter p = params[i]; + final TreeLogger branch = + logger.branch(TreeLogger.DEBUG, m.getName() + ", parameter " + + p.getName()); + serializerCreator.checkCanSerialize(branch, p.getType()); + if (p.getType().isPrimitive() == null) { + serializerCreator.create((JClassType) p.getType(), branch); + } + } + + final JClassType resultType = + callback.getType().isParameterized().getTypeArgs()[0]; + final TreeLogger branch = + logger.branch(TreeLogger.DEBUG, m.getName() + ", result " + + resultType.getQualifiedSourceName()); + serializerCreator.checkCanSerialize(branch, resultType); + serializerCreator.create(resultType, branch); + } + } + + private void invalid(final TreeLogger logger, final String what) + throws UnableToCompleteException { + logger.log(TreeLogger.ERROR, what, null); + throw new UnableToCompleteException(); + } + + private SourceWriter getSourceWriter(final TreeLogger logger, + final GeneratorContext ctx) { + final JPackage servicePkg = svcInf.getPackage(); + final String pkgName = servicePkg == null ? "" : servicePkg.getName(); + final PrintWriter pw; + final ClassSourceFileComposerFactory cf; + + pw = ctx.tryCreate(logger, pkgName, getProxySimpleName()); + if (pw == null) { + return null; + } + + cf = new ClassSourceFileComposerFactory(pkgName, getProxySimpleName()); + cf.addImport(AbstractJsonProxy.class.getCanonicalName()); + cf.addImport(JsonSerializer.class.getCanonicalName()); + cf.setSuperclass(AbstractJsonProxy.class.getSimpleName()); + cf.addImplementedInterface(svcInf.getErasedType().getQualifiedSourceName()); + return cf.createSourceWriter(ctx, pw); + } + + private void generateProxyMethods(final TreeLogger logger, + final SourceWriter srcWriter) throws UnableToCompleteException { + final JMethod[] methodList = svcInf.getOverridableMethods(); + for (final JMethod m : methodList) { + generateProxyMethod(logger, m, srcWriter); + } + } + + private void generateProxyMethod(final TreeLogger logger, + final JMethod method, final SourceWriter w) + throws UnableToCompleteException { + w.println(); + + w.print("public void " + method.getName() + "("); + boolean needsComma = false; + final NameFactory nameFactory = new NameFactory(); + final JParameter[] params = method.getParameters(); + final JParameter callback = params[params.length - 1]; + final JClassType resultType = + callback.getType().isParameterized().getTypeArgs()[0]; + for (int i = 0; i < params.length; i++) { + final JParameter param = params[i]; + + if (needsComma) { + w.print(", "); + } else { + needsComma = true; + } + + final JType paramType = param.getType().getErasedType(); + w.print(paramType.getQualifiedSourceName()); + w.print(" "); + + nameFactory.addName(param.getName()); + w.print(param.getName()); + } + + w.println(") {"); + w.indent(); + + final String rVersion = "\\\"version\\\":\\\"1.1\\\""; + final String rMethod = "\\\"method\\\":\\\"" + method.getName() + "\\\""; + final String reqDataStr; + if (params.length == 1) { + reqDataStr = "\"{" + rVersion + "," + rMethod + "}\""; + } else { + final String reqData = nameFactory.createName("reqData"); + w.println("final StringBuffer " + reqData + " = new StringBuffer();"); + w.println(reqData + ".append(\"{" + rVersion + "," + rMethod + + ",\\\"params\\\":[\");"); + + needsComma = false; + for (int i = 0; i < params.length - 1; i++) { + if (needsComma) { + w.println(reqData + ".append(\",\");"); + } else { + needsComma = true; + } + + final JType pType = params[i].getType(); + final String pName = params[i].getName(); + if (SerializerCreator.isJsonPrimitive(pType) + && !SerializerCreator.isJsonString(pType)) { + w.println(reqData + ".append(" + pName + ");"); + } else { + if (pType.isParameterized() != null) { + serializerCreator.generateSerializerReference(pType + .isParameterized(), w); + } else { + w.print(serializerCreator.create((JClassType) pType, logger) + + ".INSTANCE"); + } + w.println(".printJson(" + reqData + ", " + pName + ");"); + } + } + w.println(reqData + ".append(\"]}\");"); + reqDataStr = reqData + ".toString()"; + } + + w.print("doInvoke("); + w.print(reqDataStr); + w.print(", " + serializerCreator.create(resultType, logger) + ".INSTANCE"); + w.print(", " + callback.getName()); + w.println(");"); + + w.outdent(); + w.println("}"); + } + + private String getProxyQualifiedName() { + final String[] name = synthesizeTopLevelClassName(svcInf, PROXY_SUFFIX); + return name[0].length() == 0 ? name[1] : name[0] + "." + name[1]; + } + + private String getProxySimpleName() { + return synthesizeTopLevelClassName(svcInf, PROXY_SUFFIX)[1]; + } + + static String[] synthesizeTopLevelClassName(JClassType type, String suffix) { + // Gets the basic name of the type. If it's a nested type, the type name + // will contains dots. + // + String className; + String packageName; + + JType leafType = type.getLeafType(); + if (leafType.isPrimitive() != null) { + className = leafType.getSimpleSourceName(); + packageName = ""; + } else { + JClassType classOrInterface = leafType.isClassOrInterface(); + assert (classOrInterface != null); + className = classOrInterface.getName(); + packageName = classOrInterface.getPackage().getName(); + } + + JArrayType isArray = type.isArray(); + if (isArray != null) { + className += "_Array_Rank_" + isArray.getRank(); + } + + // Add the meaningful suffix. + // + className += suffix; + + // Make it a top-level name. + // + className = className.replace('.', '_'); + + return new String[] {packageName, className}; + } +} diff --git a/src/com/google/gwtjsonrpc/rebind/RemoteJsonServiceProxyGenerator.java b/src/com/google/gwtjsonrpc/rebind/RemoteJsonServiceProxyGenerator.java new file mode 100644 index 0000000..0f9892f --- /dev/null +++ b/src/com/google/gwtjsonrpc/rebind/RemoteJsonServiceProxyGenerator.java @@ -0,0 +1,58 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.rebind; + +import com.google.gwt.core.ext.Generator; +import com.google.gwt.core.ext.GeneratorContext; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwtjsonrpc.client.RemoteJsonService; + +/** + * Generates proxy implementations of {@link RemoteJsonService}. + */ +public class RemoteJsonServiceProxyGenerator extends Generator { + @Override + public String generate(final TreeLogger logger, final GeneratorContext ctx, + final String requestedClass) throws UnableToCompleteException { + + final TypeOracle typeOracle = ctx.getTypeOracle(); + assert (typeOracle != null); + + final JClassType remoteService = typeOracle.findType(requestedClass); + if (remoteService == null) { + logger.log(TreeLogger.ERROR, "Unable to find metadata for type '" + + requestedClass + "'", null); + throw new UnableToCompleteException(); + } + + if (remoteService.isInterface() == null) { + logger.log(TreeLogger.ERROR, remoteService.getQualifiedSourceName() + + " is not an interface", null); + throw new UnableToCompleteException(); + } + + ProxyCreator proxyCreator = new ProxyCreator(remoteService); + + TreeLogger proxyLogger = + logger.branch(TreeLogger.DEBUG, + "Generating client proxy for remote service interface '" + + remoteService.getQualifiedSourceName() + "'", null); + + return proxyCreator.create(proxyLogger, ctx); + } +} diff --git a/src/com/google/gwtjsonrpc/rebind/SerializerCreator.java b/src/com/google/gwtjsonrpc/rebind/SerializerCreator.java new file mode 100644 index 0000000..6c1b2c7 --- /dev/null +++ b/src/com/google/gwtjsonrpc/rebind/SerializerCreator.java @@ -0,0 +1,497 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.rebind; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.ext.GeneratorContext; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JField; +import com.google.gwt.core.ext.typeinfo.JPackage; +import com.google.gwt.core.ext.typeinfo.JParameterizedType; +import com.google.gwt.core.ext.typeinfo.JPrimitiveType; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; +import com.google.gwt.user.rebind.SourceWriter; +import com.google.gwtjsonrpc.client.ArraySerializer; +import com.google.gwtjsonrpc.client.JavaLangString_JsonSerializer; +import com.google.gwtjsonrpc.client.JavaUtilDate_JsonSerializer; +import com.google.gwtjsonrpc.client.JsonSerializer; +import com.google.gwtjsonrpc.client.ListSerializer; +import com.google.gwtjsonrpc.client.SetSerializer; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; + +class SerializerCreator { + private static final String SER_SUFFIX = "_JsonSerializer"; + private static final Comparator<JField> FIELD_COMP = + new Comparator<JField>() { + public int compare(final JField o1, final JField o2) { + return o1.getName().compareTo(o2.getName()); + } + }; + + private static final HashMap<String, String> defaultSerializers; + private static final HashMap<String, String> parameterizedSerializers; + static { + defaultSerializers = new HashMap<String, String>(); + parameterizedSerializers = new HashMap<String, String>(); + + defaultSerializers.put(java.lang.String.class.getCanonicalName(), + JavaLangString_JsonSerializer.class.getCanonicalName()); + defaultSerializers.put(java.util.Date.class.getCanonicalName(), + JavaUtilDate_JsonSerializer.class.getCanonicalName()); + + parameterizedSerializers.put(java.util.List.class.getCanonicalName(), + ListSerializer.class.getCanonicalName()); + parameterizedSerializers.put(java.util.Set.class.getCanonicalName(), + SetSerializer.class.getCanonicalName()); + } + + private final HashMap<String, String> generatedSerializers; + private final GeneratorContext context; + private JClassType targetType; + + SerializerCreator(final GeneratorContext c) { + context = c; + generatedSerializers = new HashMap<String, String>(); + } + + String create(final JClassType targetType, final TreeLogger logger) + throws UnableToCompleteException { + String sClassName = serializerFor(targetType); + if (sClassName != null) { + return sClassName; + } + + checkCanSerialize(logger, targetType); + recursivelyCreateSerializers(logger, targetType); + + this.targetType = targetType; + final TypeOracle typeOracle = context.getTypeOracle(); + final SourceWriter srcWriter = getSourceWriter(logger, context); + if (srcWriter == null) { + return getSerializerQualifiedName(); + } + + if (targetType.isParameterized() == null) { + generateSingleton(srcWriter); + } + generateInstanceMembers(srcWriter); + generatePrintJson(srcWriter); + generateFromJson(srcWriter); + generateGetSets(srcWriter); + + srcWriter.commit(logger); + final String sn = getSerializerQualifiedName(); + generatedSerializers.put(targetType.getQualifiedSourceName(), sn); + return sn; + } + + private void recursivelyCreateSerializers(final TreeLogger logger, + final JType targetType) throws UnableToCompleteException { + if (targetType.isPrimitive() != null) { + return; + } + + for (final JField f : sortFields((JClassType) targetType)) { + final JType type = f.getType(); + if (isJsonPrimitive(type)) { + continue; + } + + if (type.isArray() != null) { + create((JClassType) type.isArray().getComponentType(), logger); + continue; + } + + if (type.isParameterized() != null) { + final JClassType[] typeArgs = type.isParameterized().getTypeArgs(); + for (final JClassType t : typeArgs) { + create(t, logger); + } + } + + final String qsn = type.getQualifiedSourceName(); + if (defaultSerializers.containsKey(qsn) + || parameterizedSerializers.containsKey(qsn)) { + continue; + } + + create((JClassType) type, logger); + } + } + + void checkCanSerialize(final TreeLogger logger, final JType type) + throws UnableToCompleteException { + if (type.isPrimitive() == JPrimitiveType.LONG) { + logger.log(TreeLogger.ERROR, + "Type 'long' not supported in JSON encoding", null); + throw new UnableToCompleteException(); + } + + if (type.isPrimitive() == JPrimitiveType.VOID) { + logger.log(TreeLogger.ERROR, + "Type 'void' not supported in JSON encoding", null); + throw new UnableToCompleteException(); + } + + final String qsn = type.getQualifiedSourceName(); + if (type.isEnum() != null) { + logger.log(TreeLogger.ERROR, "Java enum " + qsn + + " not supported in JSON encoding", null); + throw new UnableToCompleteException(); + } + + if (isJsonPrimitive(type)) { + return; + } + + if (type.isArray() != null) { + if (type.isArray().getComponentType().isPrimitive() != null) { + logger.log(TreeLogger.ERROR, + "Primitive array not supported in JSON encoding", null); + throw new UnableToCompleteException(); + } + checkCanSerialize(logger, type.isArray().getComponentType()); + return; + } + + if (defaultSerializers.containsKey(qsn)) { + return; + } + + if (type.isParameterized() != null) { + final JClassType[] typeArgs = type.isParameterized().getTypeArgs(); + for (final JClassType t : typeArgs) { + checkCanSerialize(logger, t); + } + if (parameterizedSerializers.containsKey(qsn)) { + return; + } + } else if (parameterizedSerializers.containsKey(qsn)) { + logger.log(TreeLogger.ERROR, + "Type " + qsn + " requires type paramter(s)", null); + throw new UnableToCompleteException(); + } + + if (qsn.startsWith("java.") || qsn.startsWith("javax.")) { + logger.log(TreeLogger.ERROR, "Standard type " + qsn + + " not supported in JSON encoding", null); + throw new UnableToCompleteException(); + } + + if (type.isInterface() != null) { + logger.log(TreeLogger.ERROR, "Interface " + qsn + + " not supported in JSON encoding", null); + throw new UnableToCompleteException(); + } + + final JClassType ct = (JClassType) type; + final TreeLogger branch = logger.branch(TreeLogger.DEBUG, "In type " + qsn); + for (final JField f : sortFields(ct)) { + checkCanSerialize(branch, f.getType()); + } + } + + String serializerFor(final JClassType t) { + if (t.isArray() != null) { + return ArraySerializer.class.getCanonicalName(); + } + + final String qsn = t.getQualifiedSourceName(); + if (defaultSerializers.containsKey(qsn)) { + return defaultSerializers.get(qsn); + } + + if (parameterizedSerializers.containsKey(qsn)) { + return parameterizedSerializers.get(qsn); + } + + return generatedSerializers.get(qsn); + } + + private void generateSingleton(final SourceWriter w) { + w.print("public static final "); + w.print(getSerializerSimpleName()); + w.print(" INSTANCE = new "); + w.print(getSerializerSimpleName()); + w.println("();"); + w.println(); + } + + private void generateInstanceMembers(final SourceWriter w) { + for (final JField f : sortFields(targetType)) { + final JParameterizedType pt = f.getType().isParameterized(); + if (pt == null) { + continue; + } + + final String serType = serializerFor(pt); + w.print("private final "); + w.print(serType); + w.print(" "); + w.print("ser_" + f.getName()); + w.print(" = "); + generateSerializerReference(pt, w); + w.println(";"); + } + w.println(); + } + + void generateSerializerReference(final JParameterizedType type, + final SourceWriter w) { + w.print("new " + serializerFor(type) + "("); + boolean first = true; + for (final JClassType t : type.isParameterized().getTypeArgs()) { + if (first) { + first = false; + } else { + w.print(", "); + } + + if (t.isParameterized() == null) { + w.print(serializerFor(t) + ".INSTANCE"); + } else { + generateSerializerReference(t.isParameterized(), w); + } + } + w.print(")"); + } + + private void generateGetSets(final SourceWriter w) { + for (final JField f : sortFields(targetType)) { + if (f.isPrivate()) { + w.print("private static final native "); + w.print(f.getType().getQualifiedSourceName()); + w.print(" objectGet_" + f.getName()); + w.print("("); + w.print(targetType.getQualifiedSourceName() + " instance"); + w.print(")"); + w.println("/*-{ "); + w.indent(); + + w.print("return instance.@"); + w.print(targetType.getQualifiedSourceName()); + w.print("::"); + w.print(f.getName()); + w.println(";"); + + w.outdent(); + w.println("}-*/;"); + + w.print("private static final native void "); + w.print(" objectSet_" + f.getName()); + w.print("("); + w.print(targetType.getQualifiedSourceName() + " instance, "); + w.print(f.getType().getQualifiedSourceName() + " value"); + w.print(")"); + w.println("/*-{ "); + w.indent(); + + w.print("instance.@"); + w.print(targetType.getQualifiedSourceName()); + w.print("::"); + w.print(f.getName()); + w.println(" = value;"); + + w.outdent(); + w.println("}-*/;"); + } + + w.print("private static final native "); + if (isJsonPrimitive(f.getType())) { + w.print(f.getType().getQualifiedSourceName()); + } else { + w.print("Object"); + } + w.print(" jsonGet_" + f.getName()); + w.print("(JavaScriptObject instance)"); + w.println("/*-{ "); + w.indent(); + + w.print("return instance."); + w.print(f.getName()); + w.println(";"); + + w.outdent(); + w.println("}-*/;"); + + w.println(); + } + } + + private void generatePrintJson(final SourceWriter w) { + w.print("public void printJson(StringBuffer sb, "); + w.print(targetType.getQualifiedSourceName()); + w.println(" src) {"); + w.indent(); + + w.println("int fieldCount = -1;"); + final String docomma = "if (++fieldCount > 0) sb.append(\",\");"; + + w.println("sb.append(\"{\");"); + + for (final JField f : sortFields(targetType)) { + final String doget; + if (f.isPrivate()) { + doget = "objectGet_" + f.getName() + "(src)"; + } else { + doget = "src." + f.getName(); + } + + final String doname = "sb.append(\"\\\"" + f.getName() + "\\\":\");"; + if (isJsonString(f.getType())) { + w.println("if (" + doget + " != null) {"); + w.indent(); + w.println(docomma); + w.println(doname); + w.println("sb.append(" + JsonSerializer.class.getSimpleName() + + ".escapeString(" + doget + "));"); + w.outdent(); + w.println("}"); + w.println(); + } else if (isJsonPrimitive(f.getType())) { + w.println(docomma); + w.println(doname); + w.println("sb.append(" + doget + ");"); + w.println(); + } else { + w.println("if (" + doget + " != null) {"); + w.indent(); + w.println(docomma); + w.println(doname); + if (f.getType().isParameterized() != null) { + w.print("ser_" + f.getName()); + } else { + w.print(serializerFor((JClassType) f.getType()) + ".INSTANCE"); + } + w.println(".printJson(sb, " + doget + ");"); + w.outdent(); + w.println("}"); + w.println(); + } + } + + w.println("sb.append(\"}\");"); + w.outdent(); + w.println("}"); + w.println(); + } + + private void generateFromJson(final SourceWriter w) { + w.print("public "); + w.print(targetType.getQualifiedSourceName()); + w.println(" fromJson(Object in) {"); + w.indent(); + + w.println("if (in == null) return null;"); + w.print("final JavaScriptObject jso = (JavaScriptObject)in;"); + w.print("final "); + w.print(targetType.getQualifiedSourceName()); + w.print(" dst = new "); + w.println(targetType.getQualifiedSourceName() + "();"); + + for (final JField f : sortFields(targetType)) { + final String doget = "jsonGet_" + f.getName() + "(jso)"; + final String doset0, doset1; + + if (f.isPrivate()) { + doset0 = "objectSet_" + f.getName() + "(dst, "; + doset1 = ")"; + } else { + doset0 = "dst." + f.getName() + " = "; + doset1 = ""; + } + + if (isJsonPrimitive(f.getType())) { + w.print(doset0); + w.print(doget); + w.print(doset1); + w.println(";"); + } else { + w.print(doset0); + if (f.getType().isParameterized() != null) { + w.print("ser_" + f.getName()); + } else { + w.print(serializerFor((JClassType) f.getType()) + ".INSTANCE"); + } + w.print(".fromJson(" + doget + ")"); + w.print(doset1); + w.println(";"); + } + } + + w.println("return dst;"); + w.outdent(); + w.println("}"); + w.println(); + } + + static boolean isJsonPrimitive(final JType t) { + return t.isPrimitive() != null || isJsonString(t); + } + + static boolean isJsonString(final JType t) { + return t.getQualifiedSourceName().equals(String.class.getCanonicalName()); + } + + private SourceWriter getSourceWriter(final TreeLogger logger, + final GeneratorContext ctx) { + final JPackage targetPkg = targetType.getPackage(); + final String pkgName = targetPkg == null ? "" : targetPkg.getName(); + final PrintWriter pw; + final ClassSourceFileComposerFactory cf; + + pw = ctx.tryCreate(logger, pkgName, getSerializerSimpleName()); + if (pw == null) { + return null; + } + + cf = new ClassSourceFileComposerFactory(pkgName, getSerializerSimpleName()); + cf.addImport(JavaScriptObject.class.getCanonicalName()); + cf.addImport(JsonSerializer.class.getCanonicalName()); + cf.setSuperclass(JsonSerializer.class.getSimpleName() + "<" + + targetType.getQualifiedSourceName() + ">"); + return cf.createSourceWriter(ctx, pw); + } + + private String getSerializerQualifiedName() { + final String[] name; + name = ProxyCreator.synthesizeTopLevelClassName(targetType, SER_SUFFIX); + return name[0].length() == 0 ? name[1] : name[0] + "." + name[1]; + } + + private String getSerializerSimpleName() { + return ProxyCreator.synthesizeTopLevelClassName(targetType, SER_SUFFIX)[1]; + } + + private static JField[] sortFields(final JClassType targetType) { + final ArrayList<JField> r = new ArrayList<JField>(); + for (final JField f : targetType.getFields()) { + if (!f.isStatic() && !f.isTransient() && !f.isFinal()) { + r.add(f); + } + } + Collections.sort(r, FIELD_COMP); + return r.toArray(new JField[r.size()]); + } +} diff --git a/src/com/google/gwtjsonrpc/server/ActiveCall.java b/src/com/google/gwtjsonrpc/server/ActiveCall.java new file mode 100644 index 0000000..0cf1031 --- /dev/null +++ b/src/com/google/gwtjsonrpc/server/ActiveCall.java @@ -0,0 +1,68 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.server; + +import com.google.gson.JsonElement; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** An active RPC call. */ +public class ActiveCall { + HttpServletRequest httpRequest; + HttpServletResponse httpResponse; + JsonElement id; + MethodHandle method; + Object[] params; + Object result; + Throwable error; + + /** + * Get the HTTP request that is attempting this RPC call. + * + * @return the original servlet HTTP request. + */ + public HttpServletRequest getHttpServletRequest() { + return httpRequest; + } + + /** + * Get the HTTP response that will be returned from this call. + * + * @return the original servlet HTTP response. + */ + public HttpServletResponse getHttpServletResponse() { + return httpResponse; + } + + /** + * Get the method this request is asking to invoke. + * + * @return the requested method handle. + */ + public MethodHandle getMethod() { + return method; + } + + /** + * Get the actual parameter values to be supplied to the method. + * + * @return the parameter array; never null but may be 0-length if the method + * takes no parameters. + */ + public Object[] getParams() { + return params; + } +} diff --git a/src/com/google/gwtjsonrpc/server/JsonServlet.java b/src/com/google/gwtjsonrpc/server/JsonServlet.java new file mode 100644 index 0000000..03e57ae --- /dev/null +++ b/src/com/google/gwtjsonrpc/server/JsonServlet.java @@ -0,0 +1,305 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.server; + +import static com.google.gwtjsonrpc.client.Shared.XSRF_HEADER; +import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; +import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; +import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.server.rpc.RPCServletUtils; +import com.google.gwtjsonrpc.client.RemoteJsonService; +import com.google.gwtjsonrpc.client.Shared; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.util.HashMap; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +/** + * Basic HTTP servlet to forward JSON based RPC requests onto services. + * <p> + * Implementors of a JSON-RPC service should extend this servlet and implement + * any interface(s) that extend from {@link RemoteJsonService}. Clients may + * invoke methods declared in any implemented interface. + * <p> + * Calling conventions match the JSON-RPC 1.1 working draft from 7 August 2006 + * (<a href="http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html">draft</a>). + * Only positional parameters are supported. + * <p> + * When supported by the browser/client, the "gzip" encoding is used to compress + * the resulting JSON, reducing transfer time for the response data. + */ +public abstract class JsonServlet extends HttpServlet { + static final Object[] NO_PARAMS = {}; + static final String RPC_VERSION = "1.1"; + private static final String ENC = "UTF-8"; + + private final HashMap<String, MethodHandle> myMethods; + private XsrfUtil xsrf; + + protected JsonServlet() { + myMethods = methods(this); + } + + @Override + public void init(final ServletConfig config) throws ServletException { + super.init(config); + try { + xsrf = xsrfInit(); + } catch (XsrfException e) { + throw new ServletException("Cannot initialize XSRF", e); + } + } + + /** + * Initialize the XSRF state for this service. + * <p> + * By default this method creates a unique XSRF key for this service. Service + * implementors may wish to override this method to use a pooled instance that + * relies upon a stable private key. + * + * @return new XSRF implementation. Null if the caller has overridden all + * relevant XSRF methods and is implementing their own XSRF protection + * algorithm. + * @throws XsrfException the XSRF utility could not be created. + */ + protected XsrfUtil xsrfInit() throws XsrfException { + return new XsrfUtil(); + } + + /** + * Get the user specific token to protect per-user XSRF keys. + * <p> + * By default this method uses <code>getRemoteUser()</code>. Services may + * override this method to acquire a different property of the request, such + * as data from an HTTP cookie or an extended HTTP header. + * + * @param call current RPC being processed. + * @return the user identity; null if the user is anonymous. + */ + protected String xsrfUser(final ActiveCall call) { + return call.httpRequest.getRemoteUser(); + } + + /** + * Verify the XSRF token submitted is valid. + * <p> + * By default this method validates the token, and refreshes it with a new + * token for the currently authenticated user. + * + * @param call current RPC being processed. + * @return true if the token was supplied and is valid; false otherwise. + * @throws XsrfException the token could not be validated due to an error that + * the client cannot recover from. + */ + protected boolean xsrfValidate(final ActiveCall call) throws XsrfException { + final HttpServletRequest req = call.httpRequest; + final String user = xsrfUser(call); + final String path = req.getServletPath(); + call.httpResponse.addHeader(XSRF_HEADER, xsrf.newToken(user, path)); + return xsrf.checkToken(req.getHeader(XSRF_HEADER), user, path); + } + + /** + * Lookup a method implemented by this servlet. + * + * @param methodName name of the method. + * @return the method handle; null if the method is not declared. + */ + protected MethodHandle lookupMethod(final String methodName) { + return myMethods.get(methodName); + } + + @Override + protected void doPost(final HttpServletRequest req, + final HttpServletResponse resp) throws IOException { + noCache(resp); + + if (!Shared.JSON_TYPE.equals(req.getContentType())) { + error(resp, SC_BAD_REQUEST, "Invalid request Content-Type"); + return; + } + if (!Shared.JSON_TYPE.equals(req.getHeader("Accept"))) { + error(resp, SC_BAD_REQUEST, "Must accept " + Shared.JSON_TYPE); + return; + } + if (req.getContentLength() == 0) { + error(resp, SC_BAD_REQUEST, "POST body required"); + return; + } + + final ActiveCall call; + try { + call = parseRequest(req); + } catch (NoSuchRemoteMethodException err) { + error(resp, SC_NOT_FOUND, "Not Found"); + return; + } + + if (call.method != null) { + call.httpRequest = req; + call.httpResponse = resp; + + try { + if (!xsrfValidate(call)) { + error(resp, Shared.SC_INVALID_XSRF, Shared.SM_INVALID_XSRF); + return; + } + } catch (XsrfException e) { + error(resp, Shared.SC_INVALID_XSRF, Shared.SM_INVALID_XSRF); + return; + } + + call.method.invoke(call.params, new AsyncCallback<Object>() { + public void onFailure(final Throwable c) { + call.error = c; + } + + public void onSuccess(final Object r) { + call.result = r; + } + }); + } + + final String out = formatResult(call); + if (call.error != null) { + resp.setStatus(SC_INTERNAL_SERVER_ERROR); + } + RPCServletUtils.writeResponse(getServletContext(), resp, out, + RPCServletUtils.acceptsGzipEncoding(req) + && RPCServletUtils.exceedsUncompressedContentLengthLimit(out)); + } + + private static void noCache(final HttpServletResponse resp) { + resp.setHeader("Cache-Control", "no-cache"); + resp.addDateHeader("Expires", System.currentTimeMillis()); + } + + private ActiveCall parseRequest(final HttpServletRequest req) + throws UnsupportedEncodingException, IOException { + final Reader in = new InputStreamReader(req.getInputStream(), ENC); + try { + try { + return parseRequest(in); + } catch (JsonParseException err) { + final ActiveCall call = new ActiveCall(); + call.error = err; + return call; + } + } finally { + in.close(); + } + } + + private ActiveCall parseRequest(final Reader in) { + final GsonBuilder gb = new GsonBuilder(); + gb.registerTypeAdapter(ActiveCall.class, new RpcRequestDeserializer(this)); + return gb.create().fromJson(in, ActiveCall.class); + } + + private String formatResult(final ActiveCall result) + throws UnsupportedEncodingException, IOException { + final StringWriter o = new StringWriter(); + formatResult(result, o); + o.close(); + return o.toString(); + } + + private void formatResult(final ActiveCall result, final Writer o) { + final GsonBuilder gb = new GsonBuilder(); + gb.registerTypeAdapter(ActiveCall.class, new JsonSerializer<ActiveCall>() { + public JsonElement serialize(final ActiveCall src, final Type typeOfSrc, + final JsonSerializationContext context) { + final JsonObject r = new JsonObject(); + r.addProperty("version", RPC_VERSION); + if (src.id != null) { + r.add("id", src.id); + } + if (src.error != null) { + final JsonObject error = new JsonObject(); + error.addProperty("name", "JSONRPCError"); + error.addProperty("code", 999); + error.addProperty("message", src.error.getMessage()); + r.add("error", error); + } else { + r.add("result", context.serialize(src.result)); + } + return r; + } + }); + gb.create().toJson(result, o); + } + + private static void error(final HttpServletResponse resp, final int status, + final String message) throws IOException { + resp.setStatus(status); + resp.setContentType("text/plain; charset=" + ENC); + + final Writer w = new OutputStreamWriter(resp.getOutputStream(), ENC); + try { + w.write(message); + } finally { + w.close(); + } + } + + private static HashMap<String, MethodHandle> methods(final JsonServlet impl) { + final HashMap<String, MethodHandle> r = new HashMap<String, MethodHandle>(); + for (final Method m : impl.getClass().getMethods()) { + if (!Modifier.isPublic(m.getModifiers())) { + continue; + } + + if (m.getReturnType() != Void.TYPE) { + continue; + } + + final Class<?>[] params = m.getParameterTypes(); + if (params.length < 1) { + continue; + } + + if (!params[params.length - 1].isAssignableFrom(AsyncCallback.class)) { + continue; + } + + final MethodHandle h = new MethodHandle(impl, m); + r.put(h.getName(), h); + } + return r; + } +} diff --git a/src/com/google/gwtjsonrpc/server/MethodHandle.java b/src/com/google/gwtjsonrpc/server/MethodHandle.java new file mode 100644 index 0000000..6e746c1 --- /dev/null +++ b/src/com/google/gwtjsonrpc/server/MethodHandle.java @@ -0,0 +1,88 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.server; + +import com.google.gwt.user.client.rpc.AsyncCallback; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Pairing of a specific {@link JsonServlet} implementation and method. + */ +public class MethodHandle { + private final JsonServlet imp; + private final Method method; + private final Class<?>[] parameterTypes; + + /** + * Create a new handle for a specific service implementation and method. + * + * @param imp instance of the service all calls will be made on. + * @param method Java method to invoke on <code>imp</code>. The last parameter + * of the method must accept an {@link AsyncCallback} and the method + * must return void. + */ + MethodHandle(final JsonServlet imp, final Method method) { + this.imp = imp; + this.method = method; + + final Class<?>[] args = method.getParameterTypes(); + parameterTypes = new Class<?>[args.length - 1]; + System.arraycopy(args, 0, parameterTypes, 0, parameterTypes.length); + } + + /** + * @return unique name of the method within the service. + */ + public String getName() { + return method.getName(); + } + + /** + * @return true if this method requires positional arguments. + */ + public Class<?>[] getParamTypes() { + return parameterTypes; + } + + /** + * Invoke this method with the specified arguments, updating the callback. + * + * @param arguments arguments to the method. May be the empty array if no + * parameters are declared beyond the AsyncCallback, but must not be + * null. + * @param callback the callback the implementation will invoke onSuccess or + * onFailure on as it performs its work. Only the last onSuccess or + * onFailure invocation matters. + */ + public void invoke(final Object[] arguments, + final AsyncCallback<Object> callback) { + try { + final Object[] p = new Object[arguments.length + 1]; + System.arraycopy(arguments, 0, p, 0, arguments.length); + p[p.length - 1] = callback; + method.invoke(imp, p); + } catch (IllegalAccessException e) { + callback.onFailure(e); + } catch (InvocationTargetException e) { + callback.onFailure(e); + } catch (RuntimeException e) { + callback.onFailure(e); + } catch (Error e) { + callback.onFailure(e); + } + } +} diff --git a/src/com/google/gwtjsonrpc/server/NoSuchRemoteMethodException.java b/src/com/google/gwtjsonrpc/server/NoSuchRemoteMethodException.java new file mode 100644 index 0000000..aee78a1 --- /dev/null +++ b/src/com/google/gwtjsonrpc/server/NoSuchRemoteMethodException.java @@ -0,0 +1,19 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.server; + +/** Indicates the requested method is not known. */ +class NoSuchRemoteMethodException extends RuntimeException { +} diff --git a/src/com/google/gwtjsonrpc/server/RpcRequestDeserializer.java b/src/com/google/gwtjsonrpc/server/RpcRequestDeserializer.java new file mode 100644 index 0000000..187f1c7 --- /dev/null +++ b/src/com/google/gwtjsonrpc/server/RpcRequestDeserializer.java @@ -0,0 +1,92 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.server; + +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import java.lang.reflect.Type; + +final class RpcRequestDeserializer implements JsonDeserializer<ActiveCall> { + private static final String RPC_VERSION = JsonServlet.RPC_VERSION; + private final JsonServlet server; + + RpcRequestDeserializer(final JsonServlet jsonServlet) { + server = jsonServlet; + } + + public ActiveCall deserialize(final JsonElement json, final Type typeOfT, + final JsonDeserializationContext context) throws JsonParseException, + NoSuchRemoteMethodException { + if (!json.isJsonObject()) { + throw new JsonParseException("Expected object"); + } + + final JsonObject in = json.getAsJsonObject(); + final ActiveCall req = new ActiveCall(); + req.id = in.get("id"); + + final JsonElement version = in.get("version"); + if (!isString(version) || !RPC_VERSION.equals(version.getAsString())) { + throw new JsonParseException("Expected version = " + RPC_VERSION); + } + + final JsonElement method = in.get("method"); + if (!isString(method)) { + throw new JsonParseException("Exepected method name as string"); + } + + req.method = server.lookupMethod(method.getAsString()); + if (req.method == null) { + throw new NoSuchRemoteMethodException(); + } + + final Class<?>[] paramTypes = req.method.getParamTypes(); + final JsonElement params = in.get("params"); + if (params != null) { + if (!params.isJsonArray()) { + throw new JsonParseException("Expected params array"); + } + + final JsonArray paramsArray = params.getAsJsonArray(); + if (paramsArray.size() != paramTypes.length) { + throw new JsonParseException("Expected " + paramTypes.length + + " parameter values in params array"); + } + + final Object[] r = new Object[paramTypes.length]; + for (int i = 0; i < r.length; i++) { + r[i] = context.deserialize(paramsArray.get(i), paramTypes[i]); + } + req.params = r; + } else { + if (paramTypes.length != 0) { + throw new JsonParseException("Expected params array"); + } + req.params = JsonServlet.NO_PARAMS; + } + + return req; + } + + private static boolean isString(final JsonElement e) { + return e != null && e.isJsonPrimitive() + && e.getAsJsonPrimitive().isString(); + } +} diff --git a/src/com/google/gwtjsonrpc/server/XsrfException.java b/src/com/google/gwtjsonrpc/server/XsrfException.java new file mode 100644 index 0000000..7cf1622 --- /dev/null +++ b/src/com/google/gwtjsonrpc/server/XsrfException.java @@ -0,0 +1,26 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.server; + +/** Indicates the requested method is not known. */ +class XsrfException extends Exception { + public XsrfException(final String message) { + super(message); + } + + public XsrfException(final String message, final Throwable why) { + super(message, why); + } +} diff --git a/src/com/google/gwtjsonrpc/server/XsrfUtil.java b/src/com/google/gwtjsonrpc/server/XsrfUtil.java new file mode 100644 index 0000000..f73b61d --- /dev/null +++ b/src/com/google/gwtjsonrpc/server/XsrfUtil.java @@ -0,0 +1,215 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gwtjsonrpc.server; + +import com.google.gwtjsonrpc.client.Shared; + +import org.apache.commons.codec.binary.Base64; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; + +import javax.crypto.Mac; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.SecretKeySpec; + +/** + * Utility function to compute and verify XSRF tokens. + * <p> + * {@link JsonServlet} uses this class to verify tokens appearing in the custom + * {@link Shared#XSRF_HEADER} HTTP header. The tokens protect against cross-site + * request forgery by depending upon the browser's security model. The classic + * browser security model prohibits a script from site A from reading any data + * received from site B. By sending unforgeable tokens from the server and + * asking the client to return them to us, the client script must have had read + * access to the token at some point and is therefore also from our server. + */ +public class XsrfUtil { + private static final int INT_SZ = 4; + private static final int MAX_XSRF_WINDOW = 4 * 60 * 60; // seconds + private static final String MAC_ALG = "HmacSHA1"; + + /** + * Generate a random key for use with the XSRF library. + * + * @return a new private key, base 64 encoded. + */ + public static String generateRandomKey() { + final byte[] r = new byte[26]; + new SecureRandom().nextBytes(r); + return encodeBase64(r); + } + + private final SecretKeySpec key; + private final int tokenLength; + + /** + * Create a new utility, using a randomly generated key. + * + * @throws XsrfException the JVM doesn't support the necessary algorithms. + */ + public XsrfUtil() throws XsrfException { + this(generateRandomKey()); + } + + /** + * Create a new utility, using the specific key. + * + * @param keyBase64 base 64 encoded representation of the key. + * @throws XsrfException the JVM doesn't support the necessary algorithms. + */ + public XsrfUtil(final String keyBase64) throws XsrfException { + key = new SecretKeySpec(decodeBase64(keyBase64), MAC_ALG); + tokenLength = INT_SZ + newMac().getMacLength(); + } + + /** + * Create a new token for the user and the resource. + * + * @param user name or other unique identification of the user. + * @param resource resource (e.g. servlet path) the token protects. + * @return a new token string, typically base 64 encoded. + * @throws XsrfException the JVM doesn't support the necessary algorithms to + * generate a token. XSRF services are simply not available. + */ + public String newToken(final String user, final String resource) + throws XsrfException { + final byte[] buf = new byte[tokenLength]; + encodeInt(buf, now()); + computeToken(buf, user, resource); + return encodeBase64(buf); + } + + /** + * Validate a returned token. + * + * @param tokenString a token string previously created by this class. + * @param user name or other unique identification of the user. + * @param resource resource (e.g. servlet path) the token protects. + * @return true if the token is valid; false if the token is null, the empty + * string, has expired, does not match the user and resource + * combination supplied, or is a forged token. + * @throws XsrfException the JVM doesn't support the necessary algorithms to + * generate a token. XSRF services are simply not available. + */ + public boolean checkToken(final String tokenString, final String user, + final String resource) throws XsrfException { + if (tokenString == null || tokenString.length() == 0) { + return false; + } + + final byte[] in = decodeBase64(tokenString); + if (in.length != tokenLength) { + return false; + } + + if (Math.abs(decodeInt(in) - now()) > MAX_XSRF_WINDOW) { + return false; + } + + final byte[] gen = new byte[tokenLength]; + System.arraycopy(in, 0, gen, 0, INT_SZ); + computeToken(gen, user, resource); + return Arrays.equals(gen, in); + } + + private void computeToken(final byte[] buf, final String user, + final String resource) throws XsrfException { + final Mac m = newMac(); + + m.update(buf, 0, INT_SZ); + + m.update((byte) ':'); + if (user != null) { + m.update(toBytes(user)); + } + + m.update((byte) ':'); + if (resource != null) { + m.update(toBytes(resource)); + } + + try { + m.doFinal(buf, INT_SZ); + } catch (ShortBufferException e) { + throw new XsrfException("Unexpected token overflow", e); + } + } + + private Mac newMac() throws XsrfException { + try { + final Mac m = Mac.getInstance(MAC_ALG); + m.init(key); + return m; + } catch (NoSuchAlgorithmException e) { + throw new XsrfException(MAC_ALG + " not supported", e); + } catch (InvalidKeyException e) { + throw new XsrfException("Invalid private key", e); + } + } + + private static int now() { + return (int) (System.currentTimeMillis() / 1000L); + } + + private static byte[] decodeBase64(final String s) { + return Base64.decodeBase64(toBytes(s)); + } + + private static String encodeBase64(final byte[] buf) { + return toString(Base64.encodeBase64(buf)); + } + + private static void encodeInt(final byte[] buf, int v) { + buf[3] = (byte) v; + v >>>= 8; + + buf[2] = (byte) v; + v >>>= 8; + + buf[1] = (byte) v; + v >>>= 8; + + buf[0] = (byte) v; + } + + private static int decodeInt(final byte[] buf) { + int r = buf[0] << 8; + + r |= buf[1] & 0xff; + r <<= 8; + + r |= buf[2] & 0xff; + return (r << 8) | (buf[3] & 0xff); + } + + private static byte[] toBytes(final String s) { + final byte[] r = new byte[s.length()]; + for (int k = r.length - 1; k >= 0; k--) { + r[k] = (byte) s.charAt(k); + } + return r; + } + + private static String toString(final byte[] b) { + final StringBuilder r = new StringBuilder(b.length); + for (int i = 0; i < b.length; i++) { + r.append((char) b[i]); + } + return r.toString(); + } +} |