forked from Archives/Athou_commafeed
@@ -16,6 +16,12 @@ Deploy on your own server (using TomEE, a lightweight JavaEE6 container based on
|
|||||||
|
|
||||||
[Safari extension](https://github.com/Athou/commafeed-safari)
|
[Safari extension](https://github.com/Athou/commafeed-safari)
|
||||||
|
|
||||||
|
Warning - updating from version 1.0.0
|
||||||
|
-------------------------------------
|
||||||
|
If you're updating from version 1.0.0, feed history will be deleted. See why [here](https://www.commafeed.com/announcement/20130725.html).
|
||||||
|
|
||||||
|
The last commit with no data loss has been [tagged](https://github.com/Athou/commafeed/tree/1.0.0).
|
||||||
|
|
||||||
Deployment on OpenShift
|
Deployment on OpenShift
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
|||||||
283
dev/EclipseCodeFormatter.xml
Normal file
283
dev/EclipseCodeFormatter.xml
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<profiles version="12">
|
||||||
|
<profile kind="CodeFormatterProfile" name="Eclipse [built-in] 140 chars" version="12">
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="false"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.compiler.source" value="1.5"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="140"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.5"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="48"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="140"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="48"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.5"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="48"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="false"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="48"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
|
||||||
|
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
2
pom.xml
2
pom.xml
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<groupId>com.commafeed</groupId>
|
<groupId>com.commafeed</groupId>
|
||||||
<artifactId>commafeed</artifactId>
|
<artifactId>commafeed</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>1.2.0</version>
|
||||||
<packaging>war</packaging>
|
<packaging>war</packaging>
|
||||||
<name>CommaFeed</name>
|
<name>CommaFeed</name>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.commafeed.backend;
|
package com.commafeed.backend;
|
||||||
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -10,7 +11,9 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.commafeed.backend.dao.FeedDAO;
|
import com.commafeed.backend.dao.FeedDAO;
|
||||||
|
import com.commafeed.backend.dao.FeedEntryContentDAO;
|
||||||
import com.commafeed.backend.dao.FeedEntryDAO;
|
import com.commafeed.backend.dao.FeedEntryDAO;
|
||||||
|
import com.commafeed.backend.dao.FeedEntryStatusDAO;
|
||||||
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||||
import com.commafeed.backend.model.Feed;
|
import com.commafeed.backend.model.Feed;
|
||||||
import com.commafeed.backend.model.FeedSubscription;
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
@@ -29,6 +32,12 @@ public class DatabaseCleaner {
|
|||||||
@Inject
|
@Inject
|
||||||
FeedSubscriptionDAO feedSubscriptionDAO;
|
FeedSubscriptionDAO feedSubscriptionDAO;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FeedEntryContentDAO feedEntryContentDAO;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FeedEntryStatusDAO feedEntryStatusDAO;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ApplicationSettingsService applicationSettingsService;
|
ApplicationSettingsService applicationSettingsService;
|
||||||
|
|
||||||
@@ -45,16 +54,16 @@ public class DatabaseCleaner {
|
|||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long cleanEntriesWithoutFeeds() {
|
public long cleanContentsWithoutEntries() {
|
||||||
|
|
||||||
long total = 0;
|
long total = 0;
|
||||||
int deleted = -1;
|
int deleted = -1;
|
||||||
do {
|
do {
|
||||||
deleted = feedEntryDAO.deleteWithoutFeeds(100);
|
deleted = feedEntryContentDAO.deleteWithoutEntries(10);
|
||||||
total += deleted;
|
total += deleted;
|
||||||
log.info("removed {} entries without feeds", total);
|
log.info("removed {} feeds without subscriptions", total);
|
||||||
} while (deleted != 0);
|
} while (deleted != 0);
|
||||||
log.info("cleanup done: {} entries without feeds deleted", total);
|
log.info("cleanup done: {} feeds without subscriptions deleted", total);
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,4 +97,10 @@ public class DatabaseCleaner {
|
|||||||
}
|
}
|
||||||
feedDAO.saveOrUpdate(into);
|
feedDAO.saveOrUpdate(into);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void cleanStatusesOlderThan(Date olderThan) {
|
||||||
|
log.info("cleaning old read statuses");
|
||||||
|
int deleted = feedEntryStatusDAO.deleteOldStatuses(olderThan);
|
||||||
|
log.info("cleaned {} read statuses", deleted);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,18 +33,14 @@ public class DatabaseUpdater {
|
|||||||
try {
|
try {
|
||||||
Thread currentThread = Thread.currentThread();
|
Thread currentThread = Thread.currentThread();
|
||||||
ClassLoader classLoader = currentThread.getContextClassLoader();
|
ClassLoader classLoader = currentThread.getContextClassLoader();
|
||||||
ResourceAccessor accessor = new ClassLoaderResourceAccessor(
|
ResourceAccessor accessor = new ClassLoaderResourceAccessor(classLoader);
|
||||||
classLoader);
|
|
||||||
|
|
||||||
context = new InitialContext();
|
context = new InitialContext();
|
||||||
DataSource dataSource = (DataSource) context
|
DataSource dataSource = (DataSource) context.lookup(datasourceName);
|
||||||
.lookup(datasourceName);
|
|
||||||
connection = dataSource.getConnection();
|
connection = dataSource.getConnection();
|
||||||
JdbcConnection jdbcConnection = new JdbcConnection(connection);
|
JdbcConnection jdbcConnection = new JdbcConnection(connection);
|
||||||
|
|
||||||
Database database = DatabaseFactory.getInstance()
|
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(jdbcConnection);
|
||||||
.findCorrectDatabaseImplementation(
|
|
||||||
jdbcConnection);
|
|
||||||
|
|
||||||
if (database instanceof PostgresDatabase) {
|
if (database instanceof PostgresDatabase) {
|
||||||
database = new PostgresDatabase() {
|
database = new PostgresDatabase() {
|
||||||
@@ -56,9 +52,7 @@ public class DatabaseUpdater {
|
|||||||
database.setConnection(jdbcConnection);
|
database.setConnection(jdbcConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
Liquibase liq = new Liquibase(
|
Liquibase liq = new Liquibase("changelogs/db.changelog-master.xml", accessor, database);
|
||||||
"changelogs/db.changelog-master.xml", accessor,
|
|
||||||
database);
|
|
||||||
liq.update("prod");
|
liq.update("prod");
|
||||||
} finally {
|
} finally {
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
|
|||||||
@@ -1,62 +1,44 @@
|
|||||||
package com.commafeed.backend;
|
package com.commafeed.backend;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
public class FixedSizeSortedSet<E> {
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
private List<E> inner;
|
||||||
|
|
||||||
public class FixedSizeSortedSet<E> extends TreeSet<E> {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private final Comparator<? super E> comparator;
|
private final Comparator<? super E> comparator;
|
||||||
private final int maxSize;
|
private final int capacity;
|
||||||
|
|
||||||
public FixedSizeSortedSet(int maxSize, Comparator<? super E> comparator) {
|
public FixedSizeSortedSet(int capacity, Comparator<? super E> comparator) {
|
||||||
super(comparator);
|
this.inner = new ArrayList<E>(Math.max(0, capacity));
|
||||||
this.maxSize = maxSize;
|
this.capacity = capacity < 0 ? Integer.MAX_VALUE : capacity;
|
||||||
this.comparator = comparator;
|
this.comparator = comparator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void add(E e) {
|
||||||
public boolean add(E e) {
|
int position = Math.abs(Collections.binarySearch(inner, e, comparator) + 1);
|
||||||
if (isFull()) {
|
if (isFull()) {
|
||||||
E last = last();
|
if (position < inner.size()) {
|
||||||
int comparison = comparator.compare(e, last);
|
inner.remove(inner.size() - 1);
|
||||||
if (comparison < 0) {
|
inner.add(position, e);
|
||||||
remove(last);
|
|
||||||
return super.add(e);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return super.add(e);
|
inner.add(position, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public E last() {
|
||||||
public boolean addAll(Collection<? extends E> c) {
|
return inner.get(inner.size() - 1);
|
||||||
if (CollectionUtils.isEmpty(c)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean success = true;
|
|
||||||
for (E e : c) {
|
|
||||||
success &= add(e);
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFull() {
|
public boolean isFull() {
|
||||||
return size() == maxSize;
|
return inner.size() == capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public List<E> asList() {
|
public List<E> asList() {
|
||||||
return (List<E>) Lists.newArrayList(toArray());
|
return inner;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,9 +56,7 @@ public class HttpGetter {
|
|||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
SSL_CONTEXT = SSLContext.getInstance("TLS");
|
SSL_CONTEXT = SSLContext.getInstance("TLS");
|
||||||
SSL_CONTEXT.init(new KeyManager[0],
|
SSL_CONTEXT.init(new KeyManager[0], new TrustManager[] { new DefaultTrustManager() }, new SecureRandom());
|
||||||
new TrustManager[] { new DefaultTrustManager() },
|
|
||||||
new SecureRandom());
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Could not configure ssl context");
|
log.error("Could not configure ssl context");
|
||||||
}
|
}
|
||||||
@@ -66,8 +64,7 @@ public class HttpGetter {
|
|||||||
|
|
||||||
private static final X509HostnameVerifier VERIFIER = new DefaultHostnameVerifier();
|
private static final X509HostnameVerifier VERIFIER = new DefaultHostnameVerifier();
|
||||||
|
|
||||||
public HttpResult getBinary(String url, int timeout) throws ClientProtocolException,
|
public HttpResult getBinary(String url, int timeout) throws ClientProtocolException, IOException, NotModifiedException {
|
||||||
IOException, NotModifiedException {
|
|
||||||
return getBinary(url, null, null, timeout);
|
return getBinary(url, null, null, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,8 +82,8 @@ public class HttpGetter {
|
|||||||
* @throws NotModifiedException
|
* @throws NotModifiedException
|
||||||
* if the url hasn't changed since we asked for it last time
|
* if the url hasn't changed since we asked for it last time
|
||||||
*/
|
*/
|
||||||
public HttpResult getBinary(String url, String lastModified, String eTag, int timeout)
|
public HttpResult getBinary(String url, String lastModified, String eTag, int timeout) throws ClientProtocolException, IOException,
|
||||||
throws ClientProtocolException, IOException, NotModifiedException {
|
NotModifiedException {
|
||||||
HttpResult result = null;
|
HttpResult result = null;
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
@@ -112,8 +109,7 @@ public class HttpGetter {
|
|||||||
if (code == HttpStatus.SC_NOT_MODIFIED) {
|
if (code == HttpStatus.SC_NOT_MODIFIED) {
|
||||||
throw new NotModifiedException("304 http code");
|
throw new NotModifiedException("304 http code");
|
||||||
} else if (code >= 300) {
|
} else if (code >= 300) {
|
||||||
throw new HttpResponseException(code,
|
throw new HttpResponseException(code, "Server returned HTTP error code " + code);
|
||||||
"Server returned HTTP error code " + code);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (HttpResponseException e) {
|
} catch (HttpResponseException e) {
|
||||||
@@ -123,14 +119,11 @@ public class HttpGetter {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Header lastModifiedHeader = response
|
Header lastModifiedHeader = response.getFirstHeader(HttpHeaders.LAST_MODIFIED);
|
||||||
.getFirstHeader(HttpHeaders.LAST_MODIFIED);
|
|
||||||
Header eTagHeader = response.getFirstHeader(HttpHeaders.ETAG);
|
Header eTagHeader = response.getFirstHeader(HttpHeaders.ETAG);
|
||||||
|
|
||||||
String lastModifiedResponse = lastModifiedHeader == null ? null
|
String lastModifiedResponse = lastModifiedHeader == null ? null : StringUtils.trimToNull(lastModifiedHeader.getValue());
|
||||||
: StringUtils.trimToNull(lastModifiedHeader.getValue());
|
if (lastModified != null && StringUtils.equals(lastModified, lastModifiedResponse)) {
|
||||||
if (lastModified != null
|
|
||||||
&& StringUtils.equals(lastModified, lastModifiedResponse)) {
|
|
||||||
throw new NotModifiedException("lastModifiedHeader is the same");
|
throw new NotModifiedException("lastModifiedHeader is the same");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,10 +140,8 @@ public class HttpGetter {
|
|||||||
|
|
||||||
long duration = System.currentTimeMillis() - start;
|
long duration = System.currentTimeMillis() - start;
|
||||||
Header contentType = entity.getContentType();
|
Header contentType = entity.getContentType();
|
||||||
result = new HttpResult(content, contentType == null ? null
|
result = new HttpResult(content, contentType == null ? null : contentType.getValue(), lastModifiedHeader == null ? null
|
||||||
: contentType.getValue(), lastModifiedHeader == null ? null
|
: lastModifiedHeader.getValue(), eTagHeader == null ? null : eTagHeader.getValue(), duration);
|
||||||
: lastModifiedHeader.getValue(), eTagHeader == null ? null
|
|
||||||
: eTagHeader.getValue(), duration);
|
|
||||||
} finally {
|
} finally {
|
||||||
client.getConnectionManager().shutdown();
|
client.getConnectionManager().shutdown();
|
||||||
}
|
}
|
||||||
@@ -165,8 +156,7 @@ public class HttpGetter {
|
|||||||
private String eTag;
|
private String eTag;
|
||||||
private long duration;
|
private long duration;
|
||||||
|
|
||||||
public HttpResult(byte[] content, String contentType,
|
public HttpResult(byte[] content, String contentType, String lastModifiedSince, String eTag, long duration) {
|
||||||
String lastModifiedSince, String eTag, long duration) {
|
|
||||||
this.content = content;
|
this.content = content;
|
||||||
this.contentType = contentType;
|
this.contentType = contentType;
|
||||||
this.lastModifiedSince = lastModifiedSince;
|
this.lastModifiedSince = lastModifiedSince;
|
||||||
@@ -209,8 +199,7 @@ public class HttpGetter {
|
|||||||
HttpProtocolParams.setContentCharset(params, UTF8);
|
HttpProtocolParams.setContentCharset(params, UTF8);
|
||||||
HttpConnectionParams.setConnectionTimeout(params, timeout);
|
HttpConnectionParams.setConnectionTimeout(params, timeout);
|
||||||
HttpConnectionParams.setSoTimeout(params, timeout);
|
HttpConnectionParams.setSoTimeout(params, timeout);
|
||||||
client.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0,
|
client.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false));
|
||||||
false));
|
|
||||||
return new DecompressingHttpClient(client);
|
return new DecompressingHttpClient(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,13 +214,11 @@ public class HttpGetter {
|
|||||||
|
|
||||||
private static class DefaultTrustManager implements X509TrustManager {
|
private static class DefaultTrustManager implements X509TrustManager {
|
||||||
@Override
|
@Override
|
||||||
public void checkClientTrusted(X509Certificate[] arg0, String arg1)
|
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
|
||||||
throws CertificateException {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkServerTrusted(X509Certificate[] arg0, String arg1)
|
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
|
||||||
throws CertificateException {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -240,21 +227,18 @@ public class HttpGetter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DefaultHostnameVerifier implements
|
private static class DefaultHostnameVerifier implements X509HostnameVerifier {
|
||||||
X509HostnameVerifier {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void verify(String string, SSLSocket ssls) throws IOException {
|
public void verify(String string, SSLSocket ssls) throws IOException {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void verify(String string, X509Certificate xc)
|
public void verify(String string, X509Certificate xc) throws SSLException {
|
||||||
throws SSLException {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void verify(String string, String[] strings, String[] strings1)
|
public void verify(String string, String[] strings, String[] strings1) throws SSLException {
|
||||||
throws SSLException {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -55,13 +55,11 @@ public class MetricsBean {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void entryUpdated(int statusesCount) {
|
public void entryInserted() {
|
||||||
|
|
||||||
thisHour.entriesInserted++;
|
thisHour.entriesInserted++;
|
||||||
thisMinute.entriesInserted++;
|
thisMinute.entriesInserted++;
|
||||||
|
|
||||||
thisHour.statusesInserted += statusesCount;
|
|
||||||
thisMinute.statusesInserted += statusesCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void entryCacheHit() {
|
public void entryCacheHit() {
|
||||||
@@ -107,7 +105,6 @@ public class MetricsBean {
|
|||||||
private int feedsRefreshed;
|
private int feedsRefreshed;
|
||||||
private int feedsUpdated;
|
private int feedsUpdated;
|
||||||
private int entriesInserted;
|
private int entriesInserted;
|
||||||
private int statusesInserted;
|
|
||||||
private int threadWaited;
|
private int threadWaited;
|
||||||
private int pushNotificationsReceived;
|
private int pushNotificationsReceived;
|
||||||
private int pushFeedsQueued;
|
private int pushFeedsQueued;
|
||||||
@@ -138,14 +135,6 @@ public class MetricsBean {
|
|||||||
this.entriesInserted = entriesInserted;
|
this.entriesInserted = entriesInserted;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getStatusesInserted() {
|
|
||||||
return statusesInserted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatusesInserted(int statusesInserted) {
|
|
||||||
this.statusesInserted = statusesInserted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getThreadWaited() {
|
public int getThreadWaited() {
|
||||||
return threadWaited;
|
return threadWaited;
|
||||||
}
|
}
|
||||||
|
|||||||
37
src/main/java/com/commafeed/backend/ScheduledTasks.java
Normal file
37
src/main/java/com/commafeed/backend/ScheduledTasks.java
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package com.commafeed.backend;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import javax.ejb.Schedule;
|
||||||
|
import javax.ejb.Stateless;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.PersistenceContext;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.commafeed.backend.services.ApplicationSettingsService;
|
||||||
|
|
||||||
|
@Stateless
|
||||||
|
public class ScheduledTasks {
|
||||||
|
protected final static Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ApplicationSettingsService applicationSettingsService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
DatabaseCleaner cleaner;
|
||||||
|
|
||||||
|
@PersistenceContext
|
||||||
|
EntityManager em;
|
||||||
|
|
||||||
|
// every day at midnight
|
||||||
|
@Schedule(hour = "0", persistent = false)
|
||||||
|
private void cleanupOldStatuses() {
|
||||||
|
Date threshold = applicationSettingsService.get().getUnreadThreshold();
|
||||||
|
if (threshold != null) {
|
||||||
|
cleaner.cleanStatusesOlderThan(threshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ import org.apache.commons.io.IOUtils;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.commafeed.backend.dao.UserDAO;
|
import com.commafeed.backend.dao.ApplicationSettingsDAO;
|
||||||
import com.commafeed.backend.feeds.FeedRefreshTaskGiver;
|
import com.commafeed.backend.feeds.FeedRefreshTaskGiver;
|
||||||
import com.commafeed.backend.model.ApplicationSettings;
|
import com.commafeed.backend.model.ApplicationSettings;
|
||||||
import com.commafeed.backend.model.UserRole.Role;
|
import com.commafeed.backend.model.UserRole.Role;
|
||||||
@@ -38,7 +38,7 @@ public class StartupBean {
|
|||||||
DatabaseUpdater databaseUpdater;
|
DatabaseUpdater databaseUpdater;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UserDAO userDAO;
|
ApplicationSettingsDAO applicationSettingsDAO;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UserService userService;
|
UserService userService;
|
||||||
@@ -58,7 +58,7 @@ public class StartupBean {
|
|||||||
startupTime = System.currentTimeMillis();
|
startupTime = System.currentTimeMillis();
|
||||||
databaseUpdater.update();
|
databaseUpdater.update();
|
||||||
|
|
||||||
if (userDAO.getCount() == 0) {
|
if (applicationSettingsDAO.getCount() == 0) {
|
||||||
initialData();
|
initialData();
|
||||||
}
|
}
|
||||||
applicationSettingsService.applyLogLevel();
|
applicationSettingsService.applyLogLevel();
|
||||||
@@ -79,8 +79,7 @@ public class StartupBean {
|
|||||||
IOUtils.closeQuietly(is);
|
IOUtils.closeQuietly(is);
|
||||||
}
|
}
|
||||||
for (Object key : props.keySet()) {
|
for (Object key : props.keySet()) {
|
||||||
supportedLanguages.put(key.toString(),
|
supportedLanguages.put(key.toString(), props.getProperty(key.toString()));
|
||||||
props.getProperty(key.toString()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,11 +91,8 @@ public class StartupBean {
|
|||||||
applicationSettingsService.save(settings);
|
applicationSettingsService.save(settings);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
userService.register(USERNAME_ADMIN, "admin",
|
userService.register(USERNAME_ADMIN, "admin", "admin@commafeed.com", Arrays.asList(Role.ADMIN, Role.USER), true);
|
||||||
"admin@commafeed.com",
|
userService.register(USERNAME_DEMO, "demo", "demo@commafeed.com", Arrays.asList(Role.USER), true);
|
||||||
Arrays.asList(Role.ADMIN, Role.USER), true);
|
|
||||||
userService.register(USERNAME_DEMO, "demo", "demo@commafeed.com",
|
|
||||||
Arrays.asList(Role.USER), true);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,38 @@
|
|||||||
package com.commafeed.backend.cache;
|
package com.commafeed.backend.cache;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.commons.codec.digest.DigestUtils;
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
|
|
||||||
import com.commafeed.backend.model.Feed;
|
import com.commafeed.backend.model.Feed;
|
||||||
import com.commafeed.backend.model.FeedEntry;
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
import com.commafeed.backend.model.User;
|
import com.commafeed.backend.model.User;
|
||||||
import com.commafeed.frontend.model.Category;
|
import com.commafeed.frontend.model.Category;
|
||||||
|
|
||||||
public abstract class CacheService {
|
public abstract class CacheService {
|
||||||
|
|
||||||
|
// feed entries for faster refresh
|
||||||
public abstract List<String> getLastEntries(Feed feed);
|
public abstract List<String> getLastEntries(Feed feed);
|
||||||
|
|
||||||
public abstract void setLastEntries(Feed feed, List<String> entries);
|
public abstract void setLastEntries(Feed feed, List<String> entries);
|
||||||
|
|
||||||
public String buildUniqueEntryKey(Feed feed, FeedEntry entry) {
|
public String buildUniqueEntryKey(Feed feed, FeedEntry entry) {
|
||||||
return DigestUtils.sha1Hex(entry.getGuid() +
|
return DigestUtils.sha1Hex(entry.getGuid() + entry.getUrl());
|
||||||
entry.getUrl());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Category getRootCategory(User user);
|
// user categories
|
||||||
|
public abstract Category getUserRootCategory(User user);
|
||||||
|
|
||||||
public abstract void setRootCategory(User user, Category category);
|
public abstract void setUserRootCategory(User user, Category category);
|
||||||
|
|
||||||
public abstract Map<Long, Long> getUnreadCounts(User user);
|
|
||||||
|
|
||||||
public abstract void setUnreadCounts(User user, Map<Long, Long> map);
|
public abstract void invalidateUserRootCategory(User... users);
|
||||||
|
|
||||||
public abstract void invalidateUserData(User... users);
|
// unread count
|
||||||
|
public abstract Long getUnreadCount(FeedSubscription sub);
|
||||||
|
|
||||||
|
public abstract void setUnreadCount(FeedSubscription sub, Long count);
|
||||||
|
|
||||||
|
public abstract void invalidateUnreadCount(FeedSubscription... subs);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ package com.commafeed.backend.cache;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.enterprise.context.ApplicationScoped;
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
import javax.enterprise.inject.Alternative;
|
import javax.enterprise.inject.Alternative;
|
||||||
|
|
||||||
import com.commafeed.backend.model.Feed;
|
import com.commafeed.backend.model.Feed;
|
||||||
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
import com.commafeed.backend.model.User;
|
import com.commafeed.backend.model.User;
|
||||||
import com.commafeed.frontend.model.Category;
|
import com.commafeed.frontend.model.Category;
|
||||||
|
|
||||||
@@ -25,27 +25,32 @@ public class NoopCacheService extends CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Category getRootCategory(User user) {
|
public Long getUnreadCount(FeedSubscription sub) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setRootCategory(User user, Category category) {
|
public void setUnreadCount(FeedSubscription sub, Long count) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<Long, Long> getUnreadCounts(User user) {
|
public void invalidateUnreadCount(FeedSubscription... subs) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Category getUserRootCategory(User user) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUnreadCounts(User user, Map<Long, Long> map) {
|
public void setUserRootCategory(User user, Category category) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invalidateUserData(User... users) {
|
public void invalidateUserRootCategory(User... users) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.commafeed.backend.cache;
|
package com.commafeed.backend.cache;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -17,23 +16,22 @@ import redis.clients.jedis.JedisPoolConfig;
|
|||||||
import redis.clients.jedis.Pipeline;
|
import redis.clients.jedis.Pipeline;
|
||||||
|
|
||||||
import com.commafeed.backend.model.Feed;
|
import com.commafeed.backend.model.Feed;
|
||||||
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
import com.commafeed.backend.model.Models;
|
import com.commafeed.backend.model.Models;
|
||||||
import com.commafeed.backend.model.User;
|
import com.commafeed.backend.model.User;
|
||||||
import com.commafeed.frontend.model.Category;
|
import com.commafeed.frontend.model.Category;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.type.MapType;
|
|
||||||
import com.google.api.client.util.Lists;
|
import com.google.api.client.util.Lists;
|
||||||
|
|
||||||
@Alternative
|
@Alternative
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class RedisCacheService extends CacheService {
|
public class RedisCacheService extends CacheService {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory
|
private static final Logger log = LoggerFactory.getLogger(RedisCacheService.class);
|
||||||
.getLogger(RedisCacheService.class);
|
private static ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
private JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost");
|
private JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost");
|
||||||
private ObjectMapper mapper = new ObjectMapper();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getLastEntries(Feed feed) {
|
public List<String> getLastEntries(Feed feed) {
|
||||||
@@ -70,11 +68,11 @@ public class RedisCacheService extends CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Category getRootCategory(User user) {
|
public Category getUserRootCategory(User user) {
|
||||||
Category cat = null;
|
Category cat = null;
|
||||||
Jedis jedis = pool.getResource();
|
Jedis jedis = pool.getResource();
|
||||||
try {
|
try {
|
||||||
String key = buildRedisRootCategoryKey(user);
|
String key = buildRedisUserRootCategoryKey(user);
|
||||||
String json = jedis.get(key);
|
String json = jedis.get(key);
|
||||||
if (json != null) {
|
if (json != null) {
|
||||||
cat = mapper.readValue(json, Category.class);
|
cat = mapper.readValue(json, Category.class);
|
||||||
@@ -88,10 +86,10 @@ public class RedisCacheService extends CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setRootCategory(User user, Category category) {
|
public void setUserRootCategory(User user, Category category) {
|
||||||
Jedis jedis = pool.getResource();
|
Jedis jedis = pool.getResource();
|
||||||
try {
|
try {
|
||||||
String key = buildRedisRootCategoryKey(user);
|
String key = buildRedisUserRootCategoryKey(user);
|
||||||
|
|
||||||
Pipeline pipe = jedis.pipelined();
|
Pipeline pipe = jedis.pipelined();
|
||||||
pipe.del(key);
|
pipe.del(key);
|
||||||
@@ -106,37 +104,35 @@ public class RedisCacheService extends CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<Long, Long> getUnreadCounts(User user) {
|
public Long getUnreadCount(FeedSubscription sub) {
|
||||||
Map<Long, Long> map = null;
|
Long count = null;
|
||||||
Jedis jedis = pool.getResource();
|
Jedis jedis = pool.getResource();
|
||||||
try {
|
try {
|
||||||
String key = buildRedisUnreadCountKey(user);
|
String key = buildRedisUnreadCountKey(sub);
|
||||||
String json = jedis.get(key);
|
String countString = jedis.get(key);
|
||||||
if (json != null) {
|
if (countString != null) {
|
||||||
MapType type = mapper.getTypeFactory().constructMapType(
|
count = Long.valueOf(countString);
|
||||||
Map.class, Long.class, Long.class);
|
|
||||||
map = mapper.readValue(json, type);
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
} finally {
|
} finally {
|
||||||
pool.returnResource(jedis);
|
pool.returnResource(jedis);
|
||||||
}
|
}
|
||||||
return map;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUnreadCounts(User user, Map<Long, Long> map) {
|
public void setUnreadCount(FeedSubscription sub, Long count) {
|
||||||
Jedis jedis = pool.getResource();
|
Jedis jedis = pool.getResource();
|
||||||
try {
|
try {
|
||||||
String key = buildRedisUnreadCountKey(user);
|
String key = buildRedisUnreadCountKey(sub);
|
||||||
|
|
||||||
Pipeline pipe = jedis.pipelined();
|
Pipeline pipe = jedis.pipelined();
|
||||||
pipe.del(key);
|
pipe.del(key);
|
||||||
pipe.set(key, mapper.writeValueAsString(map));
|
pipe.set(key, String.valueOf(count));
|
||||||
pipe.expire(key, (int) TimeUnit.MINUTES.toSeconds(30));
|
pipe.expire(key, (int) TimeUnit.MINUTES.toSeconds(30));
|
||||||
pipe.sync();
|
pipe.sync();
|
||||||
} catch (JsonProcessingException e) {
|
} catch (Exception e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
} finally {
|
} finally {
|
||||||
pool.returnResource(jedis);
|
pool.returnResource(jedis);
|
||||||
@@ -144,15 +140,13 @@ public class RedisCacheService extends CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invalidateUserData(User... users) {
|
public void invalidateUserRootCategory(User... users) {
|
||||||
Jedis jedis = pool.getResource();
|
Jedis jedis = pool.getResource();
|
||||||
try {
|
try {
|
||||||
Pipeline pipe = jedis.pipelined();
|
Pipeline pipe = jedis.pipelined();
|
||||||
if (users != null) {
|
if (users != null) {
|
||||||
for (User user : users) {
|
for (User user : users) {
|
||||||
String key = buildRedisRootCategoryKey(user);
|
String key = buildRedisUserRootCategoryKey(user);
|
||||||
pipe.del(key);
|
|
||||||
key = buildRedisUnreadCountKey(user);
|
|
||||||
pipe.del(key);
|
pipe.del(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,16 +156,33 @@ public class RedisCacheService extends CacheService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildRedisRootCategoryKey(User user) {
|
@Override
|
||||||
return "root_cat:" + Models.getId(user);
|
public void invalidateUnreadCount(FeedSubscription... subs) {
|
||||||
}
|
Jedis jedis = pool.getResource();
|
||||||
|
try {
|
||||||
private String buildRedisUnreadCountKey(User user) {
|
Pipeline pipe = jedis.pipelined();
|
||||||
return "unread_count:" + Models.getId(user);
|
if (subs != null) {
|
||||||
|
for (FeedSubscription sub : subs) {
|
||||||
|
String key = buildRedisUnreadCountKey(sub);
|
||||||
|
pipe.del(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pipe.sync();
|
||||||
|
} finally {
|
||||||
|
pool.returnResource(jedis);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildRedisEntryKey(Feed feed) {
|
private String buildRedisEntryKey(Feed feed) {
|
||||||
return "feed:" + feed.getId();
|
return "f:" + Models.getId(feed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildRedisUserRootCategoryKey(User user) {
|
||||||
|
return "c:" + Models.getId(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildRedisUnreadCountKey(FeedSubscription sub) {
|
||||||
|
return "u:" + Models.getId(sub);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ public class FeedCategoryDAO extends GenericDAO<FeedCategory> {
|
|||||||
|
|
||||||
CriteriaQuery<FeedCategory> query = builder.createQuery(getType());
|
CriteriaQuery<FeedCategory> query = builder.createQuery(getType());
|
||||||
Root<FeedCategory> root = query.from(getType());
|
Root<FeedCategory> root = query.from(getType());
|
||||||
Join<FeedCategory, User> userJoin = (Join<FeedCategory, User>) root
|
Join<FeedCategory, User> userJoin = (Join<FeedCategory, User>) root.fetch(FeedCategory_.user);
|
||||||
.fetch(FeedCategory_.user);
|
|
||||||
|
|
||||||
query.where(builder.equal(userJoin.get(User_.id), user.getId()));
|
query.where(builder.equal(userJoin.get(User_.id), user.getId()));
|
||||||
|
|
||||||
@@ -38,14 +37,12 @@ public class FeedCategoryDAO extends GenericDAO<FeedCategory> {
|
|||||||
CriteriaQuery<FeedCategory> query = builder.createQuery(getType());
|
CriteriaQuery<FeedCategory> query = builder.createQuery(getType());
|
||||||
Root<FeedCategory> root = query.from(getType());
|
Root<FeedCategory> root = query.from(getType());
|
||||||
|
|
||||||
Predicate p1 = builder.equal(
|
Predicate p1 = builder.equal(root.get(FeedCategory_.user).get(User_.id), user.getId());
|
||||||
root.get(FeedCategory_.user).get(User_.id), user.getId());
|
|
||||||
Predicate p2 = builder.equal(root.get(FeedCategory_.id), id);
|
Predicate p2 = builder.equal(root.get(FeedCategory_.id), id);
|
||||||
|
|
||||||
query.where(p1, p2);
|
query.where(p1, p2);
|
||||||
|
|
||||||
return Iterables.getFirst(cache(em.createQuery(query)).getResultList(),
|
return Iterables.getFirst(cache(em.createQuery(query)).getResultList(), null);
|
||||||
null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public FeedCategory findByName(User user, String name, FeedCategory parent) {
|
public FeedCategory findByName(User user, String name, FeedCategory parent) {
|
||||||
@@ -60,8 +57,7 @@ public class FeedCategoryDAO extends GenericDAO<FeedCategory> {
|
|||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
predicates.add(builder.isNull(root.get(FeedCategory_.parent)));
|
predicates.add(builder.isNull(root.get(FeedCategory_.parent)));
|
||||||
} else {
|
} else {
|
||||||
predicates
|
predicates.add(builder.equal(root.get(FeedCategory_.parent), parent));
|
||||||
.add(builder.equal(root.get(FeedCategory_.parent), parent));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
query.where(predicates.toArray(new Predicate[0]));
|
query.where(predicates.toArray(new Predicate[0]));
|
||||||
@@ -85,8 +81,7 @@ public class FeedCategoryDAO extends GenericDAO<FeedCategory> {
|
|||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
predicates.add(builder.isNull(root.get(FeedCategory_.parent)));
|
predicates.add(builder.isNull(root.get(FeedCategory_.parent)));
|
||||||
} else {
|
} else {
|
||||||
predicates
|
predicates.add(builder.equal(root.get(FeedCategory_.parent), parent));
|
||||||
.add(builder.equal(root.get(FeedCategory_.parent), parent));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
query.where(predicates.toArray(new Predicate[0]));
|
query.where(predicates.toArray(new Predicate[0]));
|
||||||
@@ -94,8 +89,7 @@ public class FeedCategoryDAO extends GenericDAO<FeedCategory> {
|
|||||||
return em.createQuery(query).getResultList();
|
return em.createQuery(query).getResultList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FeedCategory> findAllChildrenCategories(User user,
|
public List<FeedCategory> findAllChildrenCategories(User user, FeedCategory parent) {
|
||||||
FeedCategory parent) {
|
|
||||||
List<FeedCategory> list = Lists.newArrayList();
|
List<FeedCategory> list = Lists.newArrayList();
|
||||||
List<FeedCategory> all = findAll(user);
|
List<FeedCategory> all = findAll(user);
|
||||||
for (FeedCategory cat : all) {
|
for (FeedCategory cat : all) {
|
||||||
|
|||||||
@@ -39,23 +39,17 @@ public class FeedDAO extends GenericDAO<Feed> {
|
|||||||
public List<Feed> feeds;
|
public List<Feed> feeds;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Predicate> getUpdatablePredicates(Root<Feed> root,
|
private List<Predicate> getUpdatablePredicates(Root<Feed> root, Date threshold) {
|
||||||
Date threshold) {
|
|
||||||
|
|
||||||
Predicate hasSubscriptions = builder.isNotEmpty(root
|
Predicate hasSubscriptions = builder.isNotEmpty(root.get(Feed_.subscriptions));
|
||||||
.get(Feed_.subscriptions));
|
|
||||||
|
|
||||||
Predicate neverUpdated = builder.isNull(root.get(Feed_.lastUpdated));
|
Predicate neverUpdated = builder.isNull(root.get(Feed_.lastUpdated));
|
||||||
Predicate updatedBeforeThreshold = builder.lessThan(
|
Predicate updatedBeforeThreshold = builder.lessThan(root.get(Feed_.lastUpdated), threshold);
|
||||||
root.get(Feed_.lastUpdated), threshold);
|
|
||||||
|
|
||||||
Predicate disabledDateIsNull = builder.isNull(root
|
Predicate disabledDateIsNull = builder.isNull(root.get(Feed_.disabledUntil));
|
||||||
.get(Feed_.disabledUntil));
|
Predicate disabledDateIsInPast = builder.lessThan(root.get(Feed_.disabledUntil), new Date());
|
||||||
Predicate disabledDateIsInPast = builder.lessThan(
|
|
||||||
root.get(Feed_.disabledUntil), new Date());
|
|
||||||
|
|
||||||
return Lists.newArrayList(hasSubscriptions,
|
return Lists.newArrayList(hasSubscriptions, builder.or(neverUpdated, updatedBeforeThreshold),
|
||||||
builder.or(neverUpdated, updatedBeforeThreshold),
|
|
||||||
builder.or(disabledDateIsNull, disabledDateIsInPast));
|
builder.or(disabledDateIsNull, disabledDateIsInPast));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,8 +58,7 @@ public class FeedDAO extends GenericDAO<Feed> {
|
|||||||
Root<Feed> root = query.from(getType());
|
Root<Feed> root = query.from(getType());
|
||||||
|
|
||||||
query.select(builder.count(root));
|
query.select(builder.count(root));
|
||||||
query.where(getUpdatablePredicates(root, threshold).toArray(
|
query.where(getUpdatablePredicates(root, threshold).toArray(new Predicate[0]));
|
||||||
new Predicate[0]));
|
|
||||||
|
|
||||||
TypedQuery<Long> q = em.createQuery(query);
|
TypedQuery<Long> q = em.createQuery(query);
|
||||||
return q.getSingleResult();
|
return q.getSingleResult();
|
||||||
@@ -75,8 +68,7 @@ public class FeedDAO extends GenericDAO<Feed> {
|
|||||||
CriteriaQuery<Feed> query = builder.createQuery(getType());
|
CriteriaQuery<Feed> query = builder.createQuery(getType());
|
||||||
Root<Feed> root = query.from(getType());
|
Root<Feed> root = query.from(getType());
|
||||||
|
|
||||||
query.where(getUpdatablePredicates(root, threshold).toArray(
|
query.where(getUpdatablePredicates(root, threshold).toArray(new Predicate[0]));
|
||||||
new Predicate[0]));
|
|
||||||
|
|
||||||
query.orderBy(builder.asc(root.get(Feed_.lastUpdated)));
|
query.orderBy(builder.asc(root.get(Feed_.lastUpdated)));
|
||||||
|
|
||||||
@@ -94,11 +86,9 @@ public class FeedDAO extends GenericDAO<Feed> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String normalized = FeedUtils.normalizeURL(url);
|
String normalized = FeedUtils.normalizeURL(url);
|
||||||
feeds = findByField(Feed_.normalizedUrlHash,
|
feeds = findByField(Feed_.normalizedUrlHash, DigestUtils.sha1Hex(normalized));
|
||||||
DigestUtils.sha1Hex(normalized));
|
|
||||||
feed = Iterables.getFirst(feeds, null);
|
feed = Iterables.getFirst(feeds, null);
|
||||||
if (feed != null
|
if (feed != null && StringUtils.equals(normalized, feed.getNormalizedUrl())) {
|
||||||
&& StringUtils.equals(normalized, feed.getNormalizedUrl())) {
|
|
||||||
return feed;
|
return feed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,8 +100,7 @@ public class FeedDAO extends GenericDAO<Feed> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void deleteRelationships(Feed feed) {
|
public void deleteRelationships(Feed feed) {
|
||||||
Query relationshipDeleteQuery = em
|
Query relationshipDeleteQuery = em.createNamedQuery("Feed.deleteEntryRelationships");
|
||||||
.createNamedQuery("Feed.deleteEntryRelationships");
|
|
||||||
relationshipDeleteQuery.setParameter("feedId", feed.getId());
|
relationshipDeleteQuery.setParameter("feedId", feed.getId());
|
||||||
relationshipDeleteQuery.executeUpdate();
|
relationshipDeleteQuery.executeUpdate();
|
||||||
}
|
}
|
||||||
@@ -120,8 +109,7 @@ public class FeedDAO extends GenericDAO<Feed> {
|
|||||||
CriteriaQuery<Feed> query = builder.createQuery(getType());
|
CriteriaQuery<Feed> query = builder.createQuery(getType());
|
||||||
Root<Feed> root = query.from(getType());
|
Root<Feed> root = query.from(getType());
|
||||||
|
|
||||||
SetJoin<Feed, FeedSubscription> join = root.join(Feed_.subscriptions,
|
SetJoin<Feed, FeedSubscription> join = root.join(Feed_.subscriptions, JoinType.LEFT);
|
||||||
JoinType.LEFT);
|
|
||||||
query.where(builder.isNull(join.get(FeedSubscription_.id)));
|
query.where(builder.isNull(join.get(FeedSubscription_.id)));
|
||||||
TypedQuery<Feed> q = em.createQuery(query);
|
TypedQuery<Feed> q = em.createQuery(query);
|
||||||
q.setMaxResults(max);
|
q.setMaxResults(max);
|
||||||
@@ -138,8 +126,7 @@ public class FeedDAO extends GenericDAO<Feed> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static enum DuplicateMode {
|
public static enum DuplicateMode {
|
||||||
NORMALIZED_URL(Feed_.normalizedUrlHash), LAST_CONTENT(
|
NORMALIZED_URL(Feed_.normalizedUrlHash), LAST_CONTENT(Feed_.lastContentHash), PUSH_TOPIC(Feed_.pushTopicHash);
|
||||||
Feed_.lastContentHash), PUSH_TOPIC(Feed_.pushTopicHash);
|
|
||||||
private SingularAttribute<Feed, String> path;
|
private SingularAttribute<Feed, String> path;
|
||||||
|
|
||||||
private DuplicateMode(SingularAttribute<Feed, String> path) {
|
private DuplicateMode(SingularAttribute<Feed, String> path) {
|
||||||
@@ -151,8 +138,7 @@ public class FeedDAO extends GenericDAO<Feed> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FeedCount> findDuplicates(DuplicateMode mode, int offset,
|
public List<FeedCount> findDuplicates(DuplicateMode mode, int offset, int limit, long minCount) {
|
||||||
int limit, long minCount) {
|
|
||||||
CriteriaQuery<String> query = builder.createQuery(String.class);
|
CriteriaQuery<String> query = builder.createQuery(String.class);
|
||||||
Root<Feed> root = query.from(getType());
|
Root<Feed> root = query.from(getType());
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.commafeed.backend.dao;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.persistence.TypedQuery;
|
||||||
|
import javax.persistence.criteria.CriteriaQuery;
|
||||||
|
import javax.persistence.criteria.Join;
|
||||||
|
import javax.persistence.criteria.JoinType;
|
||||||
|
import javax.persistence.criteria.Predicate;
|
||||||
|
import javax.persistence.criteria.Root;
|
||||||
|
|
||||||
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
|
import com.commafeed.backend.model.FeedEntryContent;
|
||||||
|
import com.commafeed.backend.model.FeedEntryContent_;
|
||||||
|
import com.commafeed.backend.model.FeedEntry_;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
public class FeedEntryContentDAO extends GenericDAO<FeedEntryContent> {
|
||||||
|
|
||||||
|
public FeedEntryContent findExisting(String contentHash, String titleHash) {
|
||||||
|
|
||||||
|
CriteriaQuery<FeedEntryContent> query = builder.createQuery(getType());
|
||||||
|
Root<FeedEntryContent> root = query.from(getType());
|
||||||
|
|
||||||
|
Predicate p1 = builder.equal(root.get(FeedEntryContent_.contentHash), contentHash);
|
||||||
|
Predicate p2 = builder.equal(root.get(FeedEntryContent_.titleHash), titleHash);
|
||||||
|
|
||||||
|
query.where(p1, p2);
|
||||||
|
TypedQuery<FeedEntryContent> q = em.createQuery(query);
|
||||||
|
return Iterables.getFirst(q.getResultList(), null);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public int deleteWithoutEntries(int max) {
|
||||||
|
CriteriaQuery<FeedEntryContent> query = builder.createQuery(getType());
|
||||||
|
Root<FeedEntryContent> root = query.from(getType());
|
||||||
|
|
||||||
|
Join<FeedEntryContent, FeedEntry> join = root.join(FeedEntryContent_.entries, JoinType.LEFT);
|
||||||
|
query.where(builder.isNull(join.get(FeedEntry_.id)));
|
||||||
|
TypedQuery<FeedEntryContent> q = em.createQuery(query);
|
||||||
|
q.setMaxResults(max);
|
||||||
|
|
||||||
|
List<FeedEntryContent> list = q.getResultList();
|
||||||
|
int deleted = list.size();
|
||||||
|
return deleted;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,77 +4,38 @@ import java.util.Date;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.ejb.Stateless;
|
import javax.ejb.Stateless;
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.persistence.TypedQuery;
|
import javax.persistence.TypedQuery;
|
||||||
import javax.persistence.criteria.CriteriaQuery;
|
import javax.persistence.criteria.CriteriaQuery;
|
||||||
import javax.persistence.criteria.JoinType;
|
import javax.persistence.criteria.Predicate;
|
||||||
import javax.persistence.criteria.Root;
|
import javax.persistence.criteria.Root;
|
||||||
import javax.persistence.criteria.SetJoin;
|
|
||||||
|
|
||||||
import org.apache.commons.codec.digest.DigestUtils;
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.commafeed.backend.model.Feed;
|
|
||||||
import com.commafeed.backend.model.FeedEntry;
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
import com.commafeed.backend.model.FeedEntry_;
|
import com.commafeed.backend.model.FeedEntry_;
|
||||||
import com.commafeed.backend.model.FeedFeedEntry;
|
import com.commafeed.backend.model.Feed_;
|
||||||
import com.commafeed.backend.model.FeedFeedEntry_;
|
|
||||||
import com.commafeed.backend.services.ApplicationSettingsService;
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
@Stateless
|
@Stateless
|
||||||
public class FeedEntryDAO extends GenericDAO<FeedEntry> {
|
public class FeedEntryDAO extends GenericDAO<FeedEntry> {
|
||||||
|
|
||||||
@Inject
|
protected static final Logger log = LoggerFactory.getLogger(FeedEntryDAO.class);
|
||||||
ApplicationSettingsService applicationSettingsService;
|
|
||||||
|
|
||||||
protected static final Logger log = LoggerFactory
|
public FeedEntry findExisting(String guid, String url, Long feedId) {
|
||||||
.getLogger(FeedEntryDAO.class);
|
|
||||||
|
|
||||||
public static class EntryWithFeed {
|
|
||||||
public FeedEntry entry;
|
|
||||||
public FeedFeedEntry ffe;
|
|
||||||
|
|
||||||
public EntryWithFeed(FeedEntry entry, FeedFeedEntry ffe) {
|
|
||||||
this.entry = entry;
|
|
||||||
this.ffe = ffe;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public EntryWithFeed findExisting(String guid, String url, Long feedId) {
|
|
||||||
|
|
||||||
TypedQuery<EntryWithFeed> q = em.createNamedQuery(
|
|
||||||
"EntryStatus.existing", EntryWithFeed.class);
|
|
||||||
q.setParameter("guidHash", DigestUtils.sha1Hex(guid));
|
|
||||||
q.setParameter("url", url);
|
|
||||||
q.setParameter("feedId", feedId);
|
|
||||||
|
|
||||||
EntryWithFeed result = null;
|
|
||||||
List<EntryWithFeed> list = q.getResultList();
|
|
||||||
for (EntryWithFeed ewf : list) {
|
|
||||||
if (ewf.entry != null && ewf.ffe != null) {
|
|
||||||
result = ewf;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (result == null) {
|
|
||||||
result = Iterables.getFirst(list, null);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<FeedEntry> findByFeed(Feed feed, int offset, int limit) {
|
|
||||||
CriteriaQuery<FeedEntry> query = builder.createQuery(getType());
|
CriteriaQuery<FeedEntry> query = builder.createQuery(getType());
|
||||||
Root<FeedEntry> root = query.from(getType());
|
Root<FeedEntry> root = query.from(getType());
|
||||||
SetJoin<FeedEntry, FeedFeedEntry> feedsJoin = root.join(FeedEntry_.feedRelationships);
|
|
||||||
|
|
||||||
query.where(builder.equal(feedsJoin.get(FeedFeedEntry_.feed), feed));
|
Predicate p1 = builder.equal(root.get(FeedEntry_.guidHash), DigestUtils.sha1Hex(guid));
|
||||||
query.orderBy(builder.desc(feedsJoin.get(FeedFeedEntry_.entryUpdated)));
|
Predicate p2 = builder.equal(root.get(FeedEntry_.url), url);
|
||||||
TypedQuery<FeedEntry> q = em.createQuery(query);
|
Predicate p3 = builder.equal(root.get(FeedEntry_.feed).get(Feed_.id), feedId);
|
||||||
limit(q, offset, limit);
|
|
||||||
setTimeout(q, applicationSettingsService.get().getQueryTimeout());
|
query.where(p1, p2, p3);
|
||||||
return q.getResultList();
|
|
||||||
|
List<FeedEntry> list = em.createQuery(query).getResultList();
|
||||||
|
return Iterables.getFirst(list, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int delete(Date olderThan, int max) {
|
public int delete(Date olderThan, int max) {
|
||||||
@@ -90,20 +51,4 @@ public class FeedEntryDAO extends GenericDAO<FeedEntry> {
|
|||||||
delete(list);
|
delete(list);
|
||||||
return deleted;
|
return deleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int deleteWithoutFeeds(int max) {
|
|
||||||
CriteriaQuery<FeedEntry> query = builder.createQuery(getType());
|
|
||||||
Root<FeedEntry> root = query.from(getType());
|
|
||||||
|
|
||||||
SetJoin<FeedEntry, FeedFeedEntry> join = root.join(FeedEntry_.feedRelationships,
|
|
||||||
JoinType.LEFT);
|
|
||||||
query.where(builder.isNull(join.get(FeedFeedEntry_.feed)));
|
|
||||||
TypedQuery<FeedEntry> q = em.createQuery(query);
|
|
||||||
q.setMaxResults(max);
|
|
||||||
|
|
||||||
List<FeedEntry> list = q.getResultList();
|
|
||||||
int deleted = list.size();
|
|
||||||
delete(list);
|
|
||||||
return deleted;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,25 +10,30 @@ import javax.inject.Inject;
|
|||||||
import javax.persistence.Query;
|
import javax.persistence.Query;
|
||||||
import javax.persistence.TypedQuery;
|
import javax.persistence.TypedQuery;
|
||||||
import javax.persistence.criteria.CriteriaQuery;
|
import javax.persistence.criteria.CriteriaQuery;
|
||||||
import javax.persistence.criteria.Join;
|
|
||||||
import javax.persistence.criteria.Path;
|
import javax.persistence.criteria.Path;
|
||||||
import javax.persistence.criteria.Predicate;
|
import javax.persistence.criteria.Predicate;
|
||||||
import javax.persistence.criteria.Root;
|
import javax.persistence.criteria.Root;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
|
import org.hibernate.Criteria;
|
||||||
|
import org.hibernate.criterion.CriteriaSpecification;
|
||||||
|
import org.hibernate.criterion.Disjunction;
|
||||||
|
import org.hibernate.criterion.MatchMode;
|
||||||
|
import org.hibernate.criterion.Order;
|
||||||
|
import org.hibernate.criterion.ProjectionList;
|
||||||
|
import org.hibernate.criterion.Projections;
|
||||||
|
import org.hibernate.criterion.Restrictions;
|
||||||
|
import org.hibernate.sql.JoinType;
|
||||||
|
import org.hibernate.transform.Transformers;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.commafeed.backend.FixedSizeSortedSet;
|
import com.commafeed.backend.FixedSizeSortedSet;
|
||||||
import com.commafeed.backend.model.FeedEntry;
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
import com.commafeed.backend.model.FeedEntryContent;
|
|
||||||
import com.commafeed.backend.model.FeedEntryContent_;
|
import com.commafeed.backend.model.FeedEntryContent_;
|
||||||
import com.commafeed.backend.model.FeedEntryStatus;
|
import com.commafeed.backend.model.FeedEntryStatus;
|
||||||
import com.commafeed.backend.model.FeedEntryStatus_;
|
import com.commafeed.backend.model.FeedEntryStatus_;
|
||||||
import com.commafeed.backend.model.FeedEntry_;
|
import com.commafeed.backend.model.FeedEntry_;
|
||||||
import com.commafeed.backend.model.FeedFeedEntry;
|
|
||||||
import com.commafeed.backend.model.FeedFeedEntry_;
|
|
||||||
import com.commafeed.backend.model.FeedSubscription;
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
import com.commafeed.backend.model.Models;
|
import com.commafeed.backend.model.Models;
|
||||||
import com.commafeed.backend.model.User;
|
import com.commafeed.backend.model.User;
|
||||||
@@ -36,27 +41,14 @@ import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
|||||||
import com.commafeed.backend.services.ApplicationSettingsService;
|
import com.commafeed.backend.services.ApplicationSettingsService;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
|
|
||||||
@Stateless
|
@Stateless
|
||||||
public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||||
|
|
||||||
protected static Logger log = LoggerFactory
|
protected static Logger log = LoggerFactory.getLogger(FeedEntryStatusDAO.class);
|
||||||
.getLogger(FeedEntryStatusDAO.class);
|
|
||||||
|
|
||||||
private static final Comparator<FeedEntry> ENTRY_COMPARATOR_DESC = new Comparator<FeedEntry>() {
|
private static final String ALIAS_STATUS = "status";
|
||||||
@Override
|
private static final String ALIAS_ENTRY = "entry";
|
||||||
public int compare(FeedEntry o1, FeedEntry o2) {
|
|
||||||
return ObjectUtils.compare(o2.getUpdated(), o1.getUpdated());
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final Comparator<FeedEntry> ENTRY_COMPARATOR_ASC = new Comparator<FeedEntry>() {
|
|
||||||
@Override
|
|
||||||
public int compare(FeedEntry o1, FeedEntry o2) {
|
|
||||||
return ObjectUtils.compare(o1.getUpdated(), o2.getUpdated());
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final Comparator<FeedEntryStatus> STATUS_COMPARATOR_DESC = new Comparator<FeedEntryStatus>() {
|
private static final Comparator<FeedEntryStatus> STATUS_COMPARATOR_DESC = new Comparator<FeedEntryStatus>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -81,22 +73,30 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
Root<FeedEntryStatus> root = query.from(getType());
|
Root<FeedEntryStatus> root = query.from(getType());
|
||||||
|
|
||||||
Predicate p1 = builder.equal(root.get(FeedEntryStatus_.entry), entry);
|
Predicate p1 = builder.equal(root.get(FeedEntryStatus_.entry), entry);
|
||||||
Predicate p2 = builder.equal(root.get(FeedEntryStatus_.subscription),
|
Predicate p2 = builder.equal(root.get(FeedEntryStatus_.subscription), sub);
|
||||||
sub);
|
|
||||||
|
|
||||||
query.where(p1, p2);
|
query.where(p1, p2);
|
||||||
|
|
||||||
List<FeedEntryStatus> statuses = em.createQuery(query).getResultList();
|
List<FeedEntryStatus> statuses = em.createQuery(query).getResultList();
|
||||||
FeedEntryStatus status = Iterables.getFirst(statuses, null);
|
FeedEntryStatus status = Iterables.getFirst(statuses, null);
|
||||||
|
|
||||||
|
return handleStatus(status, sub, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FeedEntryStatus handleStatus(FeedEntryStatus status, FeedSubscription sub, FeedEntry entry) {
|
||||||
if (status == null) {
|
if (status == null) {
|
||||||
|
Date unreadThreshold = applicationSettingsService.get().getUnreadThreshold();
|
||||||
|
boolean read = unreadThreshold == null ? false : entry.getUpdated().before(unreadThreshold);
|
||||||
status = new FeedEntryStatus(sub.getUser(), sub, entry);
|
status = new FeedEntryStatus(sub.getUser(), sub, entry);
|
||||||
status.setRead(true);
|
status.setRead(read);
|
||||||
|
status.setMarkable(!read);
|
||||||
|
} else {
|
||||||
|
status.setMarkable(true);
|
||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FeedEntryStatus> findStarred(User user, Date newerThan,
|
public List<FeedEntryStatus> findStarred(User user, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) {
|
||||||
int offset, int limit, ReadingOrder order, boolean includeContent) {
|
|
||||||
|
|
||||||
CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType());
|
CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType());
|
||||||
Root<FeedEntryStatus> root = query.from(getType());
|
Root<FeedEntryStatus> root = query.from(getType());
|
||||||
@@ -108,8 +108,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
query.where(predicates.toArray(new Predicate[0]));
|
query.where(predicates.toArray(new Predicate[0]));
|
||||||
|
|
||||||
if (newerThan != null) {
|
if (newerThan != null) {
|
||||||
predicates.add(builder.greaterThanOrEqualTo(
|
predicates.add(builder.greaterThanOrEqualTo(root.get(FeedEntryStatus_.entryInserted), newerThan));
|
||||||
root.get(FeedEntryStatus_.entryInserted), newerThan));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
orderStatusesBy(query, root, order);
|
orderStatusesBy(query, root, order);
|
||||||
@@ -117,200 +116,129 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
TypedQuery<FeedEntryStatus> q = em.createQuery(query);
|
TypedQuery<FeedEntryStatus> q = em.createQuery(query);
|
||||||
limit(q, offset, limit);
|
limit(q, offset, limit);
|
||||||
setTimeout(q);
|
setTimeout(q);
|
||||||
return lazyLoadContent(includeContent, q.getResultList());
|
List<FeedEntryStatus> statuses = q.getResultList();
|
||||||
|
for (FeedEntryStatus status : statuses) {
|
||||||
|
status = handleStatus(status, status.getSubscription(), status.getEntry());
|
||||||
|
}
|
||||||
|
return lazyLoadContent(includeContent, statuses);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FeedEntryStatus> findBySubscriptions(
|
private Criteria buildSearchCriteria(FeedSubscription sub, boolean unreadOnly, String keywords, Date newerThan, int offset, int limit,
|
||||||
List<FeedSubscription> subscriptions, String keywords,
|
ReadingOrder order, boolean includeContent, Date last) {
|
||||||
Date newerThan, int offset, int limit, ReadingOrder order,
|
Criteria criteria = getSession().createCriteria(FeedEntry.class, ALIAS_ENTRY);
|
||||||
boolean includeContent) {
|
|
||||||
|
|
||||||
int capacity = offset + limit;
|
criteria.add(Restrictions.eq(FeedEntry_.feed.getName(), sub.getFeed()));
|
||||||
Comparator<FeedEntry> comparator = order == ReadingOrder.desc ? ENTRY_COMPARATOR_DESC
|
|
||||||
: ENTRY_COMPARATOR_ASC;
|
|
||||||
FixedSizeSortedSet<FeedEntry> set = new FixedSizeSortedSet<FeedEntry>(
|
|
||||||
capacity < 0 ? Integer.MAX_VALUE : capacity, comparator);
|
|
||||||
for (FeedSubscription sub : subscriptions) {
|
|
||||||
CriteriaQuery<FeedEntry> query = builder
|
|
||||||
.createQuery(FeedEntry.class);
|
|
||||||
Root<FeedEntry> root = query.from(FeedEntry.class);
|
|
||||||
Join<FeedEntry, FeedFeedEntry> ffeJoin = root
|
|
||||||
.join(FeedEntry_.feedRelationships);
|
|
||||||
|
|
||||||
List<Predicate> predicates = Lists.newArrayList();
|
if (keywords != null) {
|
||||||
predicates.add(builder.equal(ffeJoin.get(FeedFeedEntry_.feed),
|
Criteria contentJoin = criteria.createCriteria(FeedEntry_.content.getName(), "content", JoinType.INNER_JOIN);
|
||||||
sub.getFeed()));
|
|
||||||
|
|
||||||
if (newerThan != null) {
|
for (String keyword : keywords.split(" ")) {
|
||||||
predicates.add(builder.greaterThanOrEqualTo(
|
Disjunction or = Restrictions.disjunction();
|
||||||
root.get(FeedEntry_.inserted), newerThan));
|
or.add(Restrictions.ilike(FeedEntryContent_.content.getName(), keyword, MatchMode.ANYWHERE));
|
||||||
|
or.add(Restrictions.ilike(FeedEntryContent_.title.getName(), keyword, MatchMode.ANYWHERE));
|
||||||
|
contentJoin.add(or);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keywords != null) {
|
|
||||||
Join<FeedEntry, FeedEntryContent> contentJoin = root
|
|
||||||
.join(FeedEntry_.content);
|
|
||||||
|
|
||||||
String joinedKeywords = StringUtils.join(keywords.toLowerCase()
|
|
||||||
.split(" "), "%");
|
|
||||||
joinedKeywords = "%" + joinedKeywords + "%";
|
|
||||||
|
|
||||||
Predicate content = builder.like(builder.lower(contentJoin
|
|
||||||
.get(FeedEntryContent_.content)), joinedKeywords);
|
|
||||||
Predicate title = builder
|
|
||||||
.like(builder.lower(contentJoin
|
|
||||||
.get(FeedEntryContent_.title)), joinedKeywords);
|
|
||||||
predicates.add(builder.or(content, title));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (order != null && !set.isEmpty() && set.isFull()) {
|
|
||||||
Predicate filter = null;
|
|
||||||
FeedEntry last = set.last();
|
|
||||||
if (order == ReadingOrder.desc) {
|
|
||||||
filter = builder.greaterThan(
|
|
||||||
ffeJoin.get(FeedFeedEntry_.entryUpdated),
|
|
||||||
last.getUpdated());
|
|
||||||
} else {
|
|
||||||
filter = builder.lessThan(
|
|
||||||
ffeJoin.get(FeedFeedEntry_.entryUpdated),
|
|
||||||
last.getUpdated());
|
|
||||||
}
|
|
||||||
predicates.add(filter);
|
|
||||||
}
|
|
||||||
query.where(predicates.toArray(new Predicate[0]));
|
|
||||||
orderEntriesBy(query, ffeJoin, order);
|
|
||||||
TypedQuery<FeedEntry> q = em.createQuery(query);
|
|
||||||
limit(q, 0, capacity);
|
|
||||||
setTimeout(q);
|
|
||||||
|
|
||||||
List<FeedEntry> list = q.getResultList();
|
|
||||||
for (FeedEntry entry : list) {
|
|
||||||
entry.setSubscription(sub);
|
|
||||||
}
|
|
||||||
set.addAll(list);
|
|
||||||
}
|
}
|
||||||
|
Criteria statusJoin = criteria.createCriteria(FeedEntry_.statuses.getName(), ALIAS_STATUS, JoinType.LEFT_OUTER_JOIN,
|
||||||
|
Restrictions.eq(FeedEntryStatus_.subscription.getName(), sub));
|
||||||
|
|
||||||
List<FeedEntry> entries = set.asList();
|
if (unreadOnly) {
|
||||||
int size = entries.size();
|
|
||||||
if (size < offset) {
|
|
||||||
return Lists.newArrayList();
|
|
||||||
}
|
|
||||||
|
|
||||||
entries = entries.subList(Math.max(offset, 0), size);
|
Disjunction or = Restrictions.disjunction();
|
||||||
|
or.add(Restrictions.isNull(FeedEntryStatus_.read.getName()));
|
||||||
|
or.add(Restrictions.eq(FeedEntryStatus_.read.getName(), false));
|
||||||
|
statusJoin.add(or);
|
||||||
|
|
||||||
List<FeedEntryStatus> results = Lists.newArrayList();
|
Date unreadThreshold = applicationSettingsService.get().getUnreadThreshold();
|
||||||
for (FeedEntry entry : entries) {
|
if (unreadThreshold != null) {
|
||||||
FeedSubscription subscription = entry.getSubscription();
|
criteria.add(Restrictions.ge(FeedEntry_.updated.getName(), unreadThreshold));
|
||||||
results.add(getStatus(subscription, entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
return lazyLoadContent(includeContent, results);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<FeedEntryStatus> findUnreadBySubscriptions(
|
|
||||||
List<FeedSubscription> subscriptions, Date newerThan, int offset,
|
|
||||||
int limit, ReadingOrder order, boolean includeContent) {
|
|
||||||
|
|
||||||
int capacity = offset + limit;
|
|
||||||
Comparator<FeedEntryStatus> comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC
|
|
||||||
: STATUS_COMPARATOR_ASC;
|
|
||||||
FixedSizeSortedSet<FeedEntryStatus> set = new FixedSizeSortedSet<FeedEntryStatus>(
|
|
||||||
capacity < 0 ? Integer.MAX_VALUE : capacity, comparator);
|
|
||||||
for (FeedSubscription sub : subscriptions) {
|
|
||||||
CriteriaQuery<FeedEntryStatus> query = builder
|
|
||||||
.createQuery(getType());
|
|
||||||
Root<FeedEntryStatus> root = query.from(getType());
|
|
||||||
|
|
||||||
List<Predicate> predicates = Lists.newArrayList();
|
|
||||||
|
|
||||||
predicates.add(builder.equal(
|
|
||||||
root.get(FeedEntryStatus_.subscription), sub));
|
|
||||||
predicates.add(builder.isFalse(root.get(FeedEntryStatus_.read)));
|
|
||||||
|
|
||||||
if (newerThan != null) {
|
|
||||||
predicates.add(builder.greaterThanOrEqualTo(
|
|
||||||
root.get(FeedEntryStatus_.entryInserted), newerThan));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (order != null && !set.isEmpty() && set.isFull()) {
|
|
||||||
Predicate filter = null;
|
|
||||||
FeedEntryStatus last = set.last();
|
|
||||||
if (order == ReadingOrder.desc) {
|
|
||||||
filter = builder.greaterThan(
|
|
||||||
root.get(FeedEntryStatus_.entryUpdated),
|
|
||||||
last.getEntryUpdated());
|
|
||||||
} else {
|
|
||||||
filter = builder.lessThan(
|
|
||||||
root.get(FeedEntryStatus_.entryUpdated),
|
|
||||||
last.getEntryUpdated());
|
|
||||||
}
|
|
||||||
predicates.add(filter);
|
|
||||||
}
|
|
||||||
query.where(predicates.toArray(new Predicate[0]));
|
|
||||||
orderStatusesBy(query, root, order);
|
|
||||||
|
|
||||||
TypedQuery<FeedEntryStatus> q = em.createQuery(query);
|
|
||||||
limit(q, -1, limit);
|
|
||||||
setTimeout(q);
|
|
||||||
|
|
||||||
List<FeedEntryStatus> list = q.getResultList();
|
|
||||||
set.addAll(list);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<FeedEntryStatus> entries = set.asList();
|
|
||||||
int size = entries.size();
|
|
||||||
if (size < offset) {
|
|
||||||
return Lists.newArrayList();
|
|
||||||
}
|
|
||||||
|
|
||||||
entries = entries.subList(Math.max(offset, 0), size);
|
|
||||||
return lazyLoadContent(includeContent, entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<FeedEntryStatus> findAllUnread(User user, Date newerThan,
|
|
||||||
int offset, int limit, ReadingOrder order, boolean includeContent) {
|
|
||||||
|
|
||||||
CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType());
|
|
||||||
Root<FeedEntryStatus> root = query.from(getType());
|
|
||||||
|
|
||||||
List<Predicate> predicates = Lists.newArrayList();
|
|
||||||
|
|
||||||
predicates.add(builder.equal(root.get(FeedEntryStatus_.user), user));
|
|
||||||
predicates.add(builder.isFalse(root.get(FeedEntryStatus_.read)));
|
|
||||||
|
|
||||||
if (newerThan != null) {
|
if (newerThan != null) {
|
||||||
predicates.add(builder.greaterThanOrEqualTo(
|
criteria.add(Restrictions.ge(FeedEntry_.inserted.getName(), newerThan));
|
||||||
root.get(FeedEntryStatus_.entryInserted), newerThan));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
query.where(predicates.toArray(new Predicate[0]));
|
if (last != null) {
|
||||||
orderStatusesBy(query, root, order);
|
if (order == ReadingOrder.desc) {
|
||||||
|
criteria.add(Restrictions.gt(FeedEntry_.updated.getName(), last));
|
||||||
TypedQuery<FeedEntryStatus> q = em.createQuery(query);
|
} else {
|
||||||
limit(q, offset, limit);
|
criteria.add(Restrictions.lt(FeedEntry_.updated.getName(), last));
|
||||||
setTimeout(q);
|
}
|
||||||
|
|
||||||
return lazyLoadContent(includeContent, q.getResultList());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map between subscriptionId and unread count
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public Map<Long, Long> getUnreadCount(User user) {
|
|
||||||
Map<Long, Long> map = Maps.newHashMap();
|
|
||||||
Query query = em.createNamedQuery("EntryStatus.unreadCounts");
|
|
||||||
query.setParameter("user", user);
|
|
||||||
setTimeout(query);
|
|
||||||
List resultList = query.getResultList();
|
|
||||||
for (Object o : resultList) {
|
|
||||||
Object[] array = (Object[]) o;
|
|
||||||
map.put((Long) array[0], (Long) array[1]);
|
|
||||||
}
|
}
|
||||||
return map;
|
|
||||||
|
if (order != null) {
|
||||||
|
Order o = null;
|
||||||
|
if (order == ReadingOrder.asc) {
|
||||||
|
o = Order.asc(FeedEntry_.updated.getName());
|
||||||
|
} else {
|
||||||
|
o = Order.desc(FeedEntry_.updated.getName());
|
||||||
|
}
|
||||||
|
criteria.addOrder(o);
|
||||||
|
}
|
||||||
|
if (offset > -1) {
|
||||||
|
criteria.setFirstResult(offset);
|
||||||
|
}
|
||||||
|
if (limit > -1) {
|
||||||
|
criteria.setMaxResults(limit);
|
||||||
|
}
|
||||||
|
int timeout = applicationSettingsService.get().getQueryTimeout();
|
||||||
|
if (timeout > 0) {
|
||||||
|
criteria.setTimeout(timeout);
|
||||||
|
}
|
||||||
|
return criteria;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<FeedEntryStatus> lazyLoadContent(boolean includeContent,
|
@SuppressWarnings("unchecked")
|
||||||
List<FeedEntryStatus> results) {
|
public List<FeedEntryStatus> findBySubscriptions(List<FeedSubscription> subscriptions, boolean unreadOnly, String keywords,
|
||||||
|
Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) {
|
||||||
|
|
||||||
|
int capacity = offset + limit;
|
||||||
|
Comparator<FeedEntryStatus> comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC : STATUS_COMPARATOR_ASC;
|
||||||
|
FixedSizeSortedSet<FeedEntryStatus> set = new FixedSizeSortedSet<FeedEntryStatus>(capacity, comparator);
|
||||||
|
for (FeedSubscription sub : subscriptions) {
|
||||||
|
Date last = (order != null && set.isFull()) ? set.last().getEntryUpdated() : null;
|
||||||
|
Criteria criteria = buildSearchCriteria(sub, unreadOnly, keywords, newerThan, -1, capacity, order, includeContent, last);
|
||||||
|
criteria.setResultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP);
|
||||||
|
List<Map<String, Object>> list = criteria.list();
|
||||||
|
for (Map<String, Object> map : list) {
|
||||||
|
FeedEntryStatus status = (FeedEntryStatus) map.get(ALIAS_STATUS);
|
||||||
|
FeedEntry entry = (FeedEntry) map.get(ALIAS_ENTRY);
|
||||||
|
entry.setSubscription(sub);
|
||||||
|
|
||||||
|
status = handleStatus(status, sub, entry);
|
||||||
|
|
||||||
|
status.setEntry(entry);
|
||||||
|
set.add(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<FeedEntryStatus> statuses = set.asList();
|
||||||
|
int size = statuses.size();
|
||||||
|
if (size < offset) {
|
||||||
|
return Lists.newArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
|
statuses = statuses.subList(Math.max(offset, 0), size);
|
||||||
|
return lazyLoadContent(includeContent, statuses);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Long getUnreadCount(FeedSubscription subscription) {
|
||||||
|
Long count = null;
|
||||||
|
Criteria criteria = buildSearchCriteria(subscription, true, null, null, -1, -1, null, false, null);
|
||||||
|
ProjectionList projection = Projections.projectionList();
|
||||||
|
projection.add(Projections.rowCount(), "count");
|
||||||
|
criteria.setProjection(projection);
|
||||||
|
criteria.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
|
||||||
|
List<Map<String, Long>> list = criteria.list();
|
||||||
|
for (Map<String, Long> row : list) {
|
||||||
|
count = row.get("count");
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FeedEntryStatus> lazyLoadContent(boolean includeContent, List<FeedEntryStatus> results) {
|
||||||
if (includeContent) {
|
if (includeContent) {
|
||||||
for (FeedEntryStatus status : results) {
|
for (FeedEntryStatus status : results) {
|
||||||
Models.initialize(status.getSubscription().getFeed());
|
Models.initialize(status.getSubscription().getFeed());
|
||||||
@@ -320,18 +248,11 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void orderEntriesBy(CriteriaQuery<?> query,
|
private void orderStatusesBy(CriteriaQuery<?> query, Path<FeedEntryStatus> statusJoin, ReadingOrder order) {
|
||||||
Path<FeedFeedEntry> ffeJoin, ReadingOrder order) {
|
|
||||||
orderBy(query, ffeJoin.get(FeedFeedEntry_.entryUpdated), order);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void orderStatusesBy(CriteriaQuery<?> query,
|
|
||||||
Path<FeedEntryStatus> statusJoin, ReadingOrder order) {
|
|
||||||
orderBy(query, statusJoin.get(FeedEntryStatus_.entryUpdated), order);
|
orderBy(query, statusJoin.get(FeedEntryStatus_.entryUpdated), order);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void orderBy(CriteriaQuery<?> query, Path<Date> date,
|
private void orderBy(CriteriaQuery<?> query, Path<Date> date, ReadingOrder order) {
|
||||||
ReadingOrder order) {
|
|
||||||
if (order != null) {
|
if (order != null) {
|
||||||
if (order == ReadingOrder.asc) {
|
if (order == ReadingOrder.asc) {
|
||||||
query.orderBy(builder.asc(date));
|
query.orderBy(builder.asc(date));
|
||||||
@@ -345,43 +266,10 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
setTimeout(query, applicationSettingsService.get().getQueryTimeout());
|
setTimeout(query, applicationSettingsService.get().getQueryTimeout());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markAllEntries(User user, Date olderThan) {
|
public int deleteOldStatuses(Date olderThan) {
|
||||||
List<FeedEntryStatus> statuses = findAllUnread(user, null, -1, -1,
|
Query query = em.createNamedQuery("Statuses.deleteOld");
|
||||||
null, false);
|
query.setParameter("date", olderThan);
|
||||||
markList(statuses, olderThan);
|
return query.executeUpdate();
|
||||||
}
|
|
||||||
|
|
||||||
public void markSubscriptionEntries(List<FeedSubscription> subscriptions,
|
|
||||||
Date olderThan) {
|
|
||||||
List<FeedEntryStatus> statuses = findUnreadBySubscriptions(
|
|
||||||
subscriptions, null, -1, -1, null, false);
|
|
||||||
markList(statuses, olderThan);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void markStarredEntries(User user, Date olderThan) {
|
|
||||||
List<FeedEntryStatus> statuses = findStarred(user, null, -1, -1, null,
|
|
||||||
false);
|
|
||||||
markList(statuses, olderThan);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void markList(List<FeedEntryStatus> statuses, Date olderThan) {
|
|
||||||
List<FeedEntryStatus> list = Lists.newArrayList();
|
|
||||||
for (FeedEntryStatus status : statuses) {
|
|
||||||
if (!status.isRead()) {
|
|
||||||
Date inserted = status.getEntry().getInserted();
|
|
||||||
if (olderThan == null || inserted == null
|
|
||||||
|| olderThan.after(inserted)) {
|
|
||||||
if (status.isStarred()) {
|
|
||||||
status.setRead(true);
|
|
||||||
list.add(status);
|
|
||||||
} else {
|
|
||||||
delete(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
saveOrUpdate(list);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
|
|||||||
CriteriaQuery<FeedSubscription> query = builder.createQuery(getType());
|
CriteriaQuery<FeedSubscription> query = builder.createQuery(getType());
|
||||||
Root<FeedSubscription> root = query.from(getType());
|
Root<FeedSubscription> root = query.from(getType());
|
||||||
|
|
||||||
Predicate p1 = builder.equal(
|
Predicate p1 = builder.equal(root.get(FeedSubscription_.user).get(User_.id), user.getId());
|
||||||
root.get(FeedSubscription_.user).get(User_.id), user.getId());
|
|
||||||
Predicate p2 = builder.equal(root.get(FeedSubscription_.id), id);
|
Predicate p2 = builder.equal(root.get(FeedSubscription_.id), id);
|
||||||
|
|
||||||
root.fetch(FeedSubscription_.feed, JoinType.LEFT);
|
root.fetch(FeedSubscription_.feed, JoinType.LEFT);
|
||||||
@@ -37,8 +36,7 @@ public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
|
|||||||
|
|
||||||
query.where(p1, p2);
|
query.where(p1, p2);
|
||||||
|
|
||||||
FeedSubscription sub = Iterables.getFirst(cache(em.createQuery(query))
|
FeedSubscription sub = Iterables.getFirst(cache(em.createQuery(query)).getResultList(), null);
|
||||||
.getResultList(), null);
|
|
||||||
initRelations(sub);
|
initRelations(sub);
|
||||||
return sub;
|
return sub;
|
||||||
}
|
}
|
||||||
@@ -47,10 +45,8 @@ public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
|
|||||||
CriteriaQuery<FeedSubscription> query = builder.createQuery(getType());
|
CriteriaQuery<FeedSubscription> query = builder.createQuery(getType());
|
||||||
Root<FeedSubscription> root = query.from(getType());
|
Root<FeedSubscription> root = query.from(getType());
|
||||||
|
|
||||||
query.where(builder.equal(root.get(FeedSubscription_.feed)
|
query.where(builder.equal(root.get(FeedSubscription_.feed).get(Feed_.id), feed.getId()));
|
||||||
.get(Feed_.id), feed.getId()));
|
List<FeedSubscription> list = cache(em.createQuery(query)).getResultList();
|
||||||
List<FeedSubscription> list = cache(em.createQuery(query))
|
|
||||||
.getResultList();
|
|
||||||
initRelations(list);
|
initRelations(list);
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
@@ -60,18 +56,15 @@ public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
|
|||||||
CriteriaQuery<FeedSubscription> query = builder.createQuery(getType());
|
CriteriaQuery<FeedSubscription> query = builder.createQuery(getType());
|
||||||
Root<FeedSubscription> root = query.from(getType());
|
Root<FeedSubscription> root = query.from(getType());
|
||||||
|
|
||||||
Predicate p1 = builder.equal(
|
Predicate p1 = builder.equal(root.get(FeedSubscription_.user).get(User_.id), user.getId());
|
||||||
root.get(FeedSubscription_.user).get(User_.id), user.getId());
|
Predicate p2 = builder.equal(root.get(FeedSubscription_.feed).get(Feed_.id), feed.getId());
|
||||||
Predicate p2 = builder.equal(
|
|
||||||
root.get(FeedSubscription_.feed).get(Feed_.id), feed.getId());
|
|
||||||
|
|
||||||
root.fetch(FeedSubscription_.feed, JoinType.LEFT);
|
root.fetch(FeedSubscription_.feed, JoinType.LEFT);
|
||||||
root.fetch(FeedSubscription_.category, JoinType.LEFT);
|
root.fetch(FeedSubscription_.category, JoinType.LEFT);
|
||||||
|
|
||||||
query.where(p1, p2);
|
query.where(p1, p2);
|
||||||
|
|
||||||
FeedSubscription sub = Iterables.getFirst(cache(em.createQuery(query))
|
FeedSubscription sub = Iterables.getFirst(cache(em.createQuery(query)).getResultList(), null);
|
||||||
.getResultList(), null);
|
|
||||||
initRelations(sub);
|
initRelations(sub);
|
||||||
return sub;
|
return sub;
|
||||||
}
|
}
|
||||||
@@ -84,57 +77,46 @@ public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
|
|||||||
root.fetch(FeedSubscription_.feed, JoinType.LEFT);
|
root.fetch(FeedSubscription_.feed, JoinType.LEFT);
|
||||||
root.fetch(FeedSubscription_.category, JoinType.LEFT);
|
root.fetch(FeedSubscription_.category, JoinType.LEFT);
|
||||||
|
|
||||||
query.where(builder.equal(root.get(FeedSubscription_.user)
|
query.where(builder.equal(root.get(FeedSubscription_.user).get(User_.id), user.getId()));
|
||||||
.get(User_.id), user.getId()));
|
|
||||||
|
|
||||||
List<FeedSubscription> list = cache(em.createQuery(query))
|
List<FeedSubscription> list = cache(em.createQuery(query)).getResultList();
|
||||||
.getResultList();
|
|
||||||
initRelations(list);
|
initRelations(list);
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FeedSubscription> findByCategory(User user,
|
public List<FeedSubscription> findByCategory(User user, FeedCategory category) {
|
||||||
FeedCategory category) {
|
|
||||||
|
|
||||||
CriteriaQuery<FeedSubscription> query = builder.createQuery(getType());
|
CriteriaQuery<FeedSubscription> query = builder.createQuery(getType());
|
||||||
Root<FeedSubscription> root = query.from(getType());
|
Root<FeedSubscription> root = query.from(getType());
|
||||||
|
|
||||||
Predicate p1 = builder.equal(
|
Predicate p1 = builder.equal(root.get(FeedSubscription_.user).get(User_.id), user.getId());
|
||||||
root.get(FeedSubscription_.user).get(User_.id), user.getId());
|
|
||||||
Predicate p2 = null;
|
Predicate p2 = null;
|
||||||
if (category == null) {
|
if (category == null) {
|
||||||
p2 = builder.isNull(
|
p2 = builder.isNull(root.get(FeedSubscription_.category));
|
||||||
root.get(FeedSubscription_.category));
|
|
||||||
} else {
|
} else {
|
||||||
p2 = builder.equal(
|
p2 = builder.equal(root.get(FeedSubscription_.category).get(FeedCategory_.id), category.getId());
|
||||||
root.get(FeedSubscription_.category).get(FeedCategory_.id),
|
|
||||||
category.getId());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
query.where(p1, p2);
|
query.where(p1, p2);
|
||||||
|
|
||||||
List<FeedSubscription> list = cache(em.createQuery(query))
|
List<FeedSubscription> list = cache(em.createQuery(query)).getResultList();
|
||||||
.getResultList();
|
|
||||||
initRelations(list);
|
initRelations(list);
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FeedSubscription> findByCategories(User user,
|
|
||||||
List<FeedCategory> categories) {
|
|
||||||
|
|
||||||
List<Long> categoryIds = Lists.transform(categories,
|
public List<FeedSubscription> findByCategories(User user, List<FeedCategory> categories) {
|
||||||
new Function<FeedCategory, Long>() {
|
|
||||||
@Override
|
List<Long> categoryIds = Lists.transform(categories, new Function<FeedCategory, Long>() {
|
||||||
public Long apply(FeedCategory input) {
|
@Override
|
||||||
return input.getId();
|
public Long apply(FeedCategory input) {
|
||||||
}
|
return input.getId();
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
List<FeedSubscription> subscriptions = Lists.newArrayList();
|
List<FeedSubscription> subscriptions = Lists.newArrayList();
|
||||||
for (FeedSubscription sub : findAll(user)) {
|
for (FeedSubscription sub : findAll(user)) {
|
||||||
if (sub.getCategory() != null
|
if (sub.getCategory() != null && categoryIds.contains(sub.getCategory().getId())) {
|
||||||
&& categoryIds.contains(sub.getCategory().getId())) {
|
|
||||||
subscriptions.add(sub);
|
subscriptions.add(sub);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,13 @@ public abstract class GenericDAO<T extends AbstractModel> {
|
|||||||
builder = em.getCriteriaBuilder();
|
builder = em.getCriteriaBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveOrUpdate(Collection<? extends AbstractModel> models) {
|
public Session getSession() {
|
||||||
Session session = em.unwrap(Session.class);
|
Session session = em.unwrap(Session.class);
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveOrUpdate(Collection<? extends AbstractModel> models) {
|
||||||
|
Session session = getSession();
|
||||||
int i = 1;
|
int i = 1;
|
||||||
for (AbstractModel model : models) {
|
for (AbstractModel model : models) {
|
||||||
session.saveOrUpdate(model);
|
session.saveOrUpdate(model);
|
||||||
@@ -91,8 +96,7 @@ public abstract class GenericDAO<T extends AbstractModel> {
|
|||||||
return q.getResultList();
|
return q.getResultList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<T> findAll(int startIndex, int count, String orderBy,
|
public List<T> findAll(int startIndex, int count, String orderBy, boolean asc) {
|
||||||
boolean asc) {
|
|
||||||
|
|
||||||
CriteriaQuery<T> query = builder.createQuery(getType());
|
CriteriaQuery<T> query = builder.createQuery(getType());
|
||||||
Root<T> root = query.from(getType());
|
Root<T> root = query.from(getType());
|
||||||
@@ -121,8 +125,7 @@ public abstract class GenericDAO<T extends AbstractModel> {
|
|||||||
return findByField(field, value, false);
|
return findByField(field, value, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <V> List<T> findByField(Attribute<T, V> field, V value,
|
protected <V> List<T> findByField(Attribute<T, V> field, V value, boolean cache) {
|
||||||
boolean cache) {
|
|
||||||
CriteriaQuery<T> query = builder.createQuery(getType());
|
CriteriaQuery<T> query = builder.createQuery(getType());
|
||||||
Root<T> root = query.from(getType());
|
Root<T> root = query.from(getType());
|
||||||
|
|
||||||
@@ -152,7 +155,7 @@ public abstract class GenericDAO<T extends AbstractModel> {
|
|||||||
query.unwrap(Query.class).setCacheable(true);
|
query.unwrap(Query.class).setCacheable(true);
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setTimeout(javax.persistence.Query query, int queryTimeout) {
|
protected void setTimeout(javax.persistence.Query query, int queryTimeout) {
|
||||||
if (queryTimeout > 0) {
|
if (queryTimeout > 0) {
|
||||||
query.setHint("javax.persistence.query.timeout", queryTimeout);
|
query.setHint("javax.persistence.query.timeout", queryTimeout);
|
||||||
|
|||||||
@@ -18,11 +18,10 @@ public class UserDAO extends GenericDAO<User> {
|
|||||||
|
|
||||||
CriteriaQuery<User> query = builder.createQuery(getType());
|
CriteriaQuery<User> query = builder.createQuery(getType());
|
||||||
Root<User> root = query.from(getType());
|
Root<User> root = query.from(getType());
|
||||||
query.where(builder.equal(builder.lower(root.get(User_.name)),
|
query.where(builder.equal(builder.lower(root.get(User_.name)), name.toLowerCase()));
|
||||||
name.toLowerCase()));
|
|
||||||
TypedQuery<User> q = em.createQuery(query);
|
TypedQuery<User> q = em.createQuery(query);
|
||||||
cache(q);
|
cache(q);
|
||||||
|
|
||||||
User user = null;
|
User user = null;
|
||||||
try {
|
try {
|
||||||
user = q.getSingleResult();
|
user = q.getSingleResult();
|
||||||
@@ -38,7 +37,7 @@ public class UserDAO extends GenericDAO<User> {
|
|||||||
query.where(builder.equal(root.get(User_.apiKey), key));
|
query.where(builder.equal(root.get(User_.apiKey), key));
|
||||||
TypedQuery<User> q = em.createQuery(query);
|
TypedQuery<User> q = em.createQuery(query);
|
||||||
cache(q);
|
cache(q);
|
||||||
|
|
||||||
User user = null;
|
User user = null;
|
||||||
try {
|
try {
|
||||||
user = q.getSingleResult();
|
user = q.getSingleResult();
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ public class UserRoleDAO extends GenericDAO<UserRole> {
|
|||||||
CriteriaQuery<UserRole> query = builder.createQuery(getType());
|
CriteriaQuery<UserRole> query = builder.createQuery(getType());
|
||||||
Root<UserRole> root = query.from(getType());
|
Root<UserRole> root = query.from(getType());
|
||||||
|
|
||||||
query.where(builder.equal(root.get(UserRole_.user).get(User_.id),
|
query.where(builder.equal(root.get(UserRole_.user).get(User_.id), user.getId()));
|
||||||
user.getId()));
|
|
||||||
return cache(em.createQuery(query)).getResultList();
|
return cache(em.createQuery(query)).getResultList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ public class UserSettingsDAO extends GenericDAO<UserSettings> {
|
|||||||
CriteriaQuery<UserSettings> query = builder.createQuery(getType());
|
CriteriaQuery<UserSettings> query = builder.createQuery(getType());
|
||||||
Root<UserSettings> root = query.from(getType());
|
Root<UserSettings> root = query.from(getType());
|
||||||
|
|
||||||
query.where(builder.equal(root.get(UserSettings_.user).get(User_.id),
|
query.where(builder.equal(root.get(UserSettings_.user).get(User_.id), user.getId()));
|
||||||
user.getId()));
|
|
||||||
|
|
||||||
UserSettings settings = null;
|
UserSettings settings = null;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -27,12 +27,9 @@ public class FaviconFetcher {
|
|||||||
private static long MAX_ICON_LENGTH = 20000;
|
private static long MAX_ICON_LENGTH = 20000;
|
||||||
private static int TIMEOUT = 4000;
|
private static int TIMEOUT = 4000;
|
||||||
|
|
||||||
protected static List<String> ICON_MIMETYPES = Arrays.asList(
|
protected static List<String> ICON_MIMETYPES = Arrays.asList("image/x-icon", "image/vnd.microsoft.icon", "image/ico", "image/icon",
|
||||||
"image/x-icon", "image/vnd.microsoft.icon", "image/ico",
|
"text/ico", "application/ico", "image/x-ms-bmp", "image/x-bmp", "image/gif", "image/png", "image/jpeg");
|
||||||
"image/icon", "text/ico", "application/ico", "image/x-ms-bmp",
|
private static List<String> ICON_MIMETYPE_BLACKLIST = Arrays.asList("application/xml", "text/html");
|
||||||
"image/x-bmp", "image/gif", "image/png", "image/jpeg");
|
|
||||||
private static List<String> ICON_MIMETYPE_BLACKLIST = Arrays.asList(
|
|
||||||
"application/xml", "text/html");
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
HttpGetter getter;
|
HttpGetter getter;
|
||||||
@@ -101,14 +98,12 @@ public class FaviconFetcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (length < MIN_ICON_LENGTH) {
|
if (length < MIN_ICON_LENGTH) {
|
||||||
log.debug("Length {} below MIN_ICON_LENGTH {}", length,
|
log.debug("Length {} below MIN_ICON_LENGTH {}", length, MIN_ICON_LENGTH);
|
||||||
MIN_ICON_LENGTH);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (length > MAX_ICON_LENGTH) {
|
if (length > MAX_ICON_LENGTH) {
|
||||||
log.debug("Length {} greater than MAX_ICON_LENGTH {}", length,
|
log.debug("Length {} greater than MAX_ICON_LENGTH {}", length, MAX_ICON_LENGTH);
|
||||||
MAX_ICON_LENGTH);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,8 +121,7 @@ public class FaviconFetcher {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Elements icons = doc
|
Elements icons = doc.select("link[rel~=(?i)^(shortcut|icon|shortcut icon)$]");
|
||||||
.select("link[rel~=(?i)^(shortcut|icon|shortcut icon)$]");
|
|
||||||
|
|
||||||
if (icons.isEmpty()) {
|
if (icons.isEmpty()) {
|
||||||
log.debug("No icon found in page {}", url);
|
log.debug("No icon found in page {}", url);
|
||||||
|
|||||||
@@ -30,18 +30,15 @@ public class FeedFetcher {
|
|||||||
@Inject
|
@Inject
|
||||||
HttpGetter getter;
|
HttpGetter getter;
|
||||||
|
|
||||||
public FetchedFeed fetch(String feedUrl, boolean extractFeedUrlFromHtml,
|
public FetchedFeed fetch(String feedUrl, boolean extractFeedUrlFromHtml, String lastModified, String eTag, Date lastPublishedDate,
|
||||||
String lastModified, String eTag, Date lastPublishedDate,
|
String lastContentHash) throws FeedException, ClientProtocolException, IOException, NotModifiedException {
|
||||||
String lastContentHash) throws FeedException,
|
|
||||||
ClientProtocolException, IOException, NotModifiedException {
|
|
||||||
log.debug("Fetching feed {}", feedUrl);
|
log.debug("Fetching feed {}", feedUrl);
|
||||||
FetchedFeed fetchedFeed = null;
|
FetchedFeed fetchedFeed = null;
|
||||||
|
|
||||||
int timeout = 20000;
|
int timeout = 20000;
|
||||||
HttpResult result = getter.getBinary(feedUrl, lastModified, eTag, timeout);
|
HttpResult result = getter.getBinary(feedUrl, lastModified, eTag, timeout);
|
||||||
if (extractFeedUrlFromHtml) {
|
if (extractFeedUrlFromHtml) {
|
||||||
String extractedUrl = extractFeedUrl(
|
String extractedUrl = extractFeedUrl(StringUtils.newStringUtf8(result.getContent()), feedUrl);
|
||||||
StringUtils.newStringUtf8(result.getContent()), feedUrl);
|
|
||||||
if (org.apache.commons.lang.StringUtils.isNotBlank(extractedUrl)) {
|
if (org.apache.commons.lang.StringUtils.isNotBlank(extractedUrl)) {
|
||||||
result = getter.getBinary(extractedUrl, lastModified, eTag, timeout);
|
result = getter.getBinary(extractedUrl, lastModified, eTag, timeout);
|
||||||
feedUrl = extractedUrl;
|
feedUrl = extractedUrl;
|
||||||
@@ -54,18 +51,15 @@ public class FeedFetcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String hash = DigestUtils.sha1Hex(content);
|
String hash = DigestUtils.sha1Hex(content);
|
||||||
if (lastContentHash != null && hash != null
|
if (lastContentHash != null && hash != null && lastContentHash.equals(hash)) {
|
||||||
&& lastContentHash.equals(hash)) {
|
|
||||||
log.debug("content hash not modified: {}", feedUrl);
|
log.debug("content hash not modified: {}", feedUrl);
|
||||||
throw new NotModifiedException("content hash not modified");
|
throw new NotModifiedException("content hash not modified");
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchedFeed = parser.parse(feedUrl, content);
|
fetchedFeed = parser.parse(feedUrl, content);
|
||||||
|
|
||||||
if (lastPublishedDate != null
|
if (lastPublishedDate != null && fetchedFeed.getFeed().getLastPublishedDate() != null
|
||||||
&& fetchedFeed.getFeed().getLastPublishedDate() != null
|
&& lastPublishedDate.getTime() == fetchedFeed.getFeed().getLastPublishedDate().getTime()) {
|
||||||
&& lastPublishedDate.getTime() == fetchedFeed.getFeed()
|
|
||||||
.getLastPublishedDate().getTime()) {
|
|
||||||
log.debug("publishedDate not modified: {}", feedUrl);
|
log.debug("publishedDate not modified: {}", feedUrl);
|
||||||
throw new NotModifiedException("publishedDate not modified");
|
throw new NotModifiedException("publishedDate not modified");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.text.DateFormat;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.codec.digest.DigestUtils;
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.lang.SystemUtils;
|
import org.apache.commons.lang.SystemUtils;
|
||||||
import org.jdom.Element;
|
import org.jdom.Element;
|
||||||
@@ -34,12 +33,10 @@ public class FeedParser {
|
|||||||
private static Logger log = LoggerFactory.getLogger(FeedParser.class);
|
private static Logger log = LoggerFactory.getLogger(FeedParser.class);
|
||||||
|
|
||||||
private static final String ATOM_10_URI = "http://www.w3.org/2005/Atom";
|
private static final String ATOM_10_URI = "http://www.w3.org/2005/Atom";
|
||||||
private static final Namespace ATOM_10_NS = Namespace
|
private static final Namespace ATOM_10_NS = Namespace.getNamespace(ATOM_10_URI);
|
||||||
.getNamespace(ATOM_10_URI);
|
|
||||||
|
|
||||||
private static final Date START = new Date(86400000);
|
private static final Date START = new Date(86400000);
|
||||||
private static final Date END = new Date(
|
private static final Date END = new Date(1000l * Integer.MAX_VALUE - 86400000);
|
||||||
1000l * Integer.MAX_VALUE - 86400000);
|
|
||||||
|
|
||||||
private static final Function<SyndContent, String> CONTENT_TO_STRING = new Function<SyndContent, String>() {
|
private static final Function<SyndContent, String> CONTENT_TO_STRING = new Function<SyndContent, String>() {
|
||||||
public String apply(SyndContent content) {
|
public String apply(SyndContent content) {
|
||||||
@@ -56,11 +53,9 @@ public class FeedParser {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
String encoding = FeedUtils.guessEncoding(xml);
|
String encoding = FeedUtils.guessEncoding(xml);
|
||||||
String xmlString = FeedUtils.trimInvalidXmlCharacters(new String(
|
String xmlString = FeedUtils.trimInvalidXmlCharacters(new String(xml, encoding));
|
||||||
xml, encoding));
|
|
||||||
if (xmlString == null) {
|
if (xmlString == null) {
|
||||||
throw new FeedException("Input string is null for url "
|
throw new FeedException("Input string is null for url " + feedUrl);
|
||||||
+ feedUrl);
|
|
||||||
}
|
}
|
||||||
InputSource source = new InputSource(new StringReader(xmlString));
|
InputSource source = new InputSource(new StringReader(xmlString));
|
||||||
SyndFeed rss = new SyndFeedInput().build(source);
|
SyndFeed rss = new SyndFeedInput().build(source);
|
||||||
@@ -89,21 +84,16 @@ public class FeedParser {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
entry.setGuid(FeedUtils.truncate(guid, 2048));
|
entry.setGuid(FeedUtils.truncate(guid, 2048));
|
||||||
entry.setGuidHash(DigestUtils.sha1Hex(guid));
|
entry.setUrl(FeedUtils.truncate(FeedUtils.toAbsoluteUrl(item.getLink(), feed.getLink()), 2048));
|
||||||
entry.setUrl(FeedUtils.truncate(
|
|
||||||
FeedUtils.toAbsoluteUrl(item.getLink(), feed.getLink()),
|
|
||||||
2048));
|
|
||||||
entry.setUpdated(validateDate(getEntryUpdateDate(item), true));
|
entry.setUpdated(validateDate(getEntryUpdateDate(item), true));
|
||||||
entry.setAuthor(item.getAuthor());
|
|
||||||
|
|
||||||
FeedEntryContent content = new FeedEntryContent();
|
FeedEntryContent content = new FeedEntryContent();
|
||||||
content.setContent(getContent(item));
|
content.setContent(getContent(item));
|
||||||
content.setTitle(getTitle(item));
|
content.setTitle(getTitle(item));
|
||||||
SyndEnclosure enclosure = (SyndEnclosure) Iterables.getFirst(
|
content.setAuthor(StringUtils.trimToNull(item.getAuthor()));
|
||||||
item.getEnclosures(), null);
|
SyndEnclosure enclosure = (SyndEnclosure) Iterables.getFirst(item.getEnclosures(), null);
|
||||||
if (enclosure != null) {
|
if (enclosure != null) {
|
||||||
content.setEnclosureUrl(FeedUtils.truncate(
|
content.setEnclosureUrl(FeedUtils.truncate(enclosure.getUrl(), 2048));
|
||||||
enclosure.getUrl(), 2048));
|
|
||||||
content.setEnclosureType(enclosure.getType());
|
content.setEnclosureType(enclosure.getType());
|
||||||
}
|
}
|
||||||
entry.setContent(content);
|
entry.setContent(content);
|
||||||
@@ -113,21 +103,17 @@ public class FeedParser {
|
|||||||
Date lastEntryDate = null;
|
Date lastEntryDate = null;
|
||||||
Date publishedDate = validateDate(rss.getPublishedDate(), false);
|
Date publishedDate = validateDate(rss.getPublishedDate(), false);
|
||||||
if (!entries.isEmpty()) {
|
if (!entries.isEmpty()) {
|
||||||
List<Long> sortedTimestamps = FeedUtils
|
List<Long> sortedTimestamps = FeedUtils.getSortedTimestamps(entries);
|
||||||
.getSortedTimestamps(entries);
|
|
||||||
Long timestamp = sortedTimestamps.get(0);
|
Long timestamp = sortedTimestamps.get(0);
|
||||||
lastEntryDate = new Date(timestamp);
|
lastEntryDate = new Date(timestamp);
|
||||||
publishedDate = getFeedPublishedDate(publishedDate, entries);
|
publishedDate = getFeedPublishedDate(publishedDate, entries);
|
||||||
}
|
}
|
||||||
feed.setLastPublishedDate(validateDate(publishedDate, true));
|
feed.setLastPublishedDate(validateDate(publishedDate, true));
|
||||||
feed.setAverageEntryInterval(FeedUtils
|
feed.setAverageEntryInterval(FeedUtils.averageTimeBetweenEntries(entries));
|
||||||
.averageTimeBetweenEntries(entries));
|
|
||||||
feed.setLastEntryDate(lastEntryDate);
|
feed.setLastEntryDate(lastEntryDate);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new FeedException(String.format(
|
throw new FeedException(String.format("Could not parse feed from %s : %s", feedUrl, e.getMessage()), e);
|
||||||
"Could not parse feed from %s : %s", feedUrl,
|
|
||||||
e.getMessage()), e);
|
|
||||||
}
|
}
|
||||||
return fetchedFeed;
|
return fetchedFeed;
|
||||||
}
|
}
|
||||||
@@ -146,8 +132,7 @@ public class FeedParser {
|
|||||||
for (Object object : elements) {
|
for (Object object : elements) {
|
||||||
if (object instanceof Element) {
|
if (object instanceof Element) {
|
||||||
Element element = (Element) object;
|
Element element = (Element) object;
|
||||||
if ("link".equals(element.getName())
|
if ("link".equals(element.getName()) && ATOM_10_NS.equals(element.getNamespace())) {
|
||||||
&& ATOM_10_NS.equals(element.getNamespace())) {
|
|
||||||
SyndLink link = new SyndLinkImpl();
|
SyndLink link = new SyndLinkImpl();
|
||||||
link.setRel(element.getAttributeValue("rel"));
|
link.setRel(element.getAttributeValue("rel"));
|
||||||
link.setHref(element.getAttributeValue("href"));
|
link.setHref(element.getAttributeValue("href"));
|
||||||
@@ -158,8 +143,7 @@ public class FeedParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Date getFeedPublishedDate(Date publishedDate,
|
private Date getFeedPublishedDate(Date publishedDate, List<FeedEntry> entries) {
|
||||||
List<FeedEntry> entries) {
|
|
||||||
|
|
||||||
for (FeedEntry entry : entries) {
|
for (FeedEntry entry : entries) {
|
||||||
if (publishedDate == null || entry.getUpdated().getTime() > publishedDate.getTime()) {
|
if (publishedDate == null || entry.getUpdated().getTime() > publishedDate.getTime()) {
|
||||||
@@ -199,14 +183,11 @@ public class FeedParser {
|
|||||||
private String getContent(SyndEntry item) {
|
private String getContent(SyndEntry item) {
|
||||||
String content = null;
|
String content = null;
|
||||||
if (item.getContents().isEmpty()) {
|
if (item.getContents().isEmpty()) {
|
||||||
content = item.getDescription() == null ? null : item
|
content = item.getDescription() == null ? null : item.getDescription().getValue();
|
||||||
.getDescription().getValue();
|
|
||||||
} else {
|
} else {
|
||||||
content = StringUtils.join(Collections2.transform(
|
content = StringUtils.join(Collections2.transform(item.getContents(), CONTENT_TO_STRING), SystemUtils.LINE_SEPARATOR);
|
||||||
item.getContents(), CONTENT_TO_STRING),
|
|
||||||
SystemUtils.LINE_SEPARATOR);
|
|
||||||
}
|
}
|
||||||
return content;
|
return StringUtils.trimToNull(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getTitle(SyndEntry item) {
|
private String getTitle(SyndEntry item) {
|
||||||
@@ -219,15 +200,14 @@ public class FeedParser {
|
|||||||
title = "(no title)";
|
title = "(no title)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return title;
|
return StringUtils.trimToNull(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private String findHub(SyndFeed feed) {
|
private String findHub(SyndFeed feed) {
|
||||||
for (SyndLink l : (List<SyndLink>) feed.getLinks()) {
|
for (SyndLink l : (List<SyndLink>) feed.getLinks()) {
|
||||||
if ("hub".equalsIgnoreCase(l.getRel())) {
|
if ("hub".equalsIgnoreCase(l.getRel())) {
|
||||||
log.debug("found hub {} for feed {}", l.getHref(),
|
log.debug("found hub {} for feed {}", l.getHref(), feed.getLink());
|
||||||
feed.getLink());
|
|
||||||
return l.getHref();
|
return l.getHref();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,8 +218,7 @@ public class FeedParser {
|
|||||||
private String findSelf(SyndFeed feed) {
|
private String findSelf(SyndFeed feed) {
|
||||||
for (SyndLink l : (List<SyndLink>) feed.getLinks()) {
|
for (SyndLink l : (List<SyndLink>) feed.getLinks()) {
|
||||||
if ("self".equalsIgnoreCase(l.getRel())) {
|
if ("self".equalsIgnoreCase(l.getRel())) {
|
||||||
log.debug("found self {} for feed {}", l.getHref(),
|
log.debug("found self {} for feed {}", l.getHref(), feed.getLink());
|
||||||
feed.getLink());
|
|
||||||
return l.getHref();
|
return l.getHref();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,32 +12,28 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
public class FeedRefreshExecutor {
|
public class FeedRefreshExecutor {
|
||||||
|
|
||||||
private static Logger log = LoggerFactory
|
private static Logger log = LoggerFactory.getLogger(FeedRefreshExecutor.class);
|
||||||
.getLogger(FeedRefreshExecutor.class);
|
|
||||||
|
|
||||||
private String poolName;
|
private String poolName;
|
||||||
private ThreadPoolExecutor pool;
|
private ThreadPoolExecutor pool;
|
||||||
private LinkedBlockingDeque<Runnable> queue;
|
private LinkedBlockingDeque<Runnable> queue;
|
||||||
|
|
||||||
public FeedRefreshExecutor(final String poolName, int threads,
|
public FeedRefreshExecutor(final String poolName, int threads, int queueCapacity) {
|
||||||
int queueCapacity) {
|
|
||||||
log.info("Creating pool {} with {} threads", poolName, threads);
|
log.info("Creating pool {} with {} threads", poolName, threads);
|
||||||
this.poolName = poolName;
|
this.poolName = poolName;
|
||||||
pool = new ThreadPoolExecutor(threads, threads, 0,
|
pool = new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS, queue = new LinkedBlockingDeque<Runnable>(queueCapacity) {
|
||||||
TimeUnit.MILLISECONDS,
|
private static final long serialVersionUID = 1L;
|
||||||
queue = new LinkedBlockingDeque<Runnable>(queueCapacity) {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean offer(Runnable r) {
|
public boolean offer(Runnable r) {
|
||||||
Task task = (Task) r;
|
Task task = (Task) r;
|
||||||
if (task.isUrgent()) {
|
if (task.isUrgent()) {
|
||||||
return offerFirst(r);
|
return offerFirst(r);
|
||||||
} else {
|
} else {
|
||||||
return offerLast(r);
|
return offerLast(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
pool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
|
pool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
|
||||||
@Override
|
@Override
|
||||||
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
|
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
|
||||||
@@ -50,8 +46,7 @@ public class FeedRefreshExecutor {
|
|||||||
queue.put(r);
|
queue.put(r);
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e1) {
|
} catch (InterruptedException e1) {
|
||||||
log.error(poolName
|
log.error(poolName + " interrupted while waiting for queue.", e1);
|
||||||
+ " interrupted while waiting for queue.", e1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -80,13 +75,11 @@ public class FeedRefreshExecutor {
|
|||||||
try {
|
try {
|
||||||
Thread.sleep(100);
|
Thread.sleep(100);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
log.error(
|
log.error("{} interrupted while waiting for threads to finish.", poolName);
|
||||||
"{} interrupted while waiting for threads to finish.",
|
|
||||||
poolName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class NamedThreadFactory implements ThreadFactory {
|
private static class NamedThreadFactory implements ThreadFactory {
|
||||||
private final ThreadGroup group;
|
private final ThreadGroup group;
|
||||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||||
@@ -94,15 +87,12 @@ public class FeedRefreshExecutor {
|
|||||||
|
|
||||||
private NamedThreadFactory(String poolName) {
|
private NamedThreadFactory(String poolName) {
|
||||||
SecurityManager s = System.getSecurityManager();
|
SecurityManager s = System.getSecurityManager();
|
||||||
group = (s != null) ? s.getThreadGroup() :
|
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
|
||||||
Thread.currentThread().getThreadGroup();
|
|
||||||
namePrefix = poolName + "-thread-";
|
namePrefix = poolName + "-thread-";
|
||||||
}
|
}
|
||||||
|
|
||||||
public Thread newThread(Runnable r) {
|
public Thread newThread(Runnable r) {
|
||||||
Thread t = new Thread(group, r,
|
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
|
||||||
namePrefix + threadNumber.getAndIncrement(),
|
|
||||||
0);
|
|
||||||
if (t.isDaemon())
|
if (t.isDaemon())
|
||||||
t.setDaemon(false);
|
t.setDaemon(false);
|
||||||
if (t.getPriority() != Thread.NORM_PRIORITY)
|
if (t.getPriority() != Thread.NORM_PRIORITY)
|
||||||
|
|||||||
@@ -52,8 +52,7 @@ public class FeedRefreshTaskGiver {
|
|||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
backgroundThreads = applicationSettingsService.get()
|
backgroundThreads = applicationSettingsService.get().getBackgroundThreads();
|
||||||
.getBackgroundThreads();
|
|
||||||
executor = Executors.newFixedThreadPool(1);
|
executor = Executors.newFixedThreadPool(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,8 +124,7 @@ public class FeedRefreshTaskGiver {
|
|||||||
|
|
||||||
public void add(Feed feed) {
|
public void add(Feed feed) {
|
||||||
Date threshold = getThreshold();
|
Date threshold = getThreshold();
|
||||||
if (feed.getLastUpdated() == null
|
if (feed.getLastUpdated() == null || feed.getLastUpdated().before(threshold)) {
|
||||||
|| feed.getLastUpdated().before(threshold)) {
|
|
||||||
addQueue.add(feed);
|
addQueue.add(feed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.commafeed.backend.feeds;
|
package com.commafeed.backend.feeds;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
@@ -11,6 +13,7 @@ import javax.annotation.PreDestroy;
|
|||||||
import javax.enterprise.context.ApplicationScoped;
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.lang3.time.DateUtils;
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -25,6 +28,7 @@ import com.commafeed.backend.feeds.FeedRefreshExecutor.Task;
|
|||||||
import com.commafeed.backend.model.ApplicationSettings;
|
import com.commafeed.backend.model.ApplicationSettings;
|
||||||
import com.commafeed.backend.model.Feed;
|
import com.commafeed.backend.model.Feed;
|
||||||
import com.commafeed.backend.model.FeedEntry;
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
|
import com.commafeed.backend.model.FeedEntryContent;
|
||||||
import com.commafeed.backend.model.FeedSubscription;
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
import com.commafeed.backend.pubsubhubbub.SubscriptionHandler;
|
import com.commafeed.backend.pubsubhubbub.SubscriptionHandler;
|
||||||
import com.commafeed.backend.services.ApplicationSettingsService;
|
import com.commafeed.backend.services.ApplicationSettingsService;
|
||||||
@@ -35,8 +39,7 @@ import com.google.common.util.concurrent.Striped;
|
|||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class FeedRefreshUpdater {
|
public class FeedRefreshUpdater {
|
||||||
|
|
||||||
protected static Logger log = LoggerFactory
|
protected static Logger log = LoggerFactory.getLogger(FeedRefreshUpdater.class);
|
||||||
.getLogger(FeedRefreshUpdater.class);
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FeedUpdateService feedUpdateService;
|
FeedUpdateService feedUpdateService;
|
||||||
@@ -109,10 +112,9 @@ public class FeedRefreshUpdater {
|
|||||||
if (!lastEntries.contains(cacheKey)) {
|
if (!lastEntries.contains(cacheKey)) {
|
||||||
log.debug("cache miss for {}", entry.getUrl());
|
log.debug("cache miss for {}", entry.getUrl());
|
||||||
if (subscriptions == null) {
|
if (subscriptions == null) {
|
||||||
subscriptions = feedSubscriptionDAO
|
subscriptions = feedSubscriptionDAO.findByFeed(feed);
|
||||||
.findByFeed(feed);
|
|
||||||
}
|
}
|
||||||
ok &= updateEntry(feed, entry, subscriptions);
|
ok &= addEntry(feed, entry, subscriptions);
|
||||||
metricsBean.entryCacheMiss();
|
metricsBean.entryCacheMiss();
|
||||||
} else {
|
} else {
|
||||||
log.debug("cache hit for {}", entry.getUrl());
|
log.debug("cache hit for {}", entry.getUrl());
|
||||||
@@ -139,27 +141,40 @@ public class FeedRefreshUpdater {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean updateEntry(final Feed feed, final FeedEntry entry,
|
private boolean addEntry(final Feed feed, final FeedEntry entry, final List<FeedSubscription> subscriptions) {
|
||||||
final List<FeedSubscription> subscriptions) {
|
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
|
|
||||||
String key = StringUtils.trimToEmpty(entry.getGuid() + entry.getUrl());
|
// lock on feed, make sure we are not updating the same feed twice at
|
||||||
Lock lock = locks.get(key);
|
// the same time
|
||||||
boolean locked = false;
|
String key1 = StringUtils.trimToEmpty("" + feed.getId());
|
||||||
|
|
||||||
|
// lock on content, make sure we are not updating the same entry
|
||||||
|
// twice at the same time
|
||||||
|
FeedEntryContent content = entry.getContent();
|
||||||
|
String key2 = DigestUtils.sha1Hex(StringUtils.trimToEmpty(content.getContent() + content.getTitle()));
|
||||||
|
|
||||||
|
Iterator<Lock> iterator = locks.bulkGet(Arrays.asList(key1, key2)).iterator();
|
||||||
|
Lock lock1 = iterator.next();
|
||||||
|
Lock lock2 = iterator.next();
|
||||||
|
boolean locked1 = false;
|
||||||
|
boolean locked2 = false;
|
||||||
try {
|
try {
|
||||||
locked = lock.tryLock(1, TimeUnit.MINUTES);
|
locked1 = lock1.tryLock(1, TimeUnit.MINUTES);
|
||||||
if (locked) {
|
locked2 = lock2.tryLock(1, TimeUnit.MINUTES);
|
||||||
|
if (locked1 && locked2) {
|
||||||
feedUpdateService.updateEntry(feed, entry, subscriptions);
|
feedUpdateService.updateEntry(feed, entry, subscriptions);
|
||||||
success = true;
|
success = true;
|
||||||
} else {
|
} else {
|
||||||
log.error("lock timeout for " + feed.getUrl() + " - " + key);
|
log.error("lock timeout for " + feed.getUrl() + " - " + key1);
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
log.error("interrupted while waiting for lock for " + feed.getUrl()
|
log.error("interrupted while waiting for lock for " + feed.getUrl() + " : " + e.getMessage(), e);
|
||||||
+ " : " + e.getMessage(), e);
|
|
||||||
} finally {
|
} finally {
|
||||||
if (locked) {
|
if (locked1) {
|
||||||
lock.unlock();
|
lock1.unlock();
|
||||||
|
}
|
||||||
|
if (locked2) {
|
||||||
|
lock2.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ import com.sun.syndication.io.FeedException;
|
|||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class FeedRefreshWorker {
|
public class FeedRefreshWorker {
|
||||||
|
|
||||||
private static Logger log = LoggerFactory
|
private static Logger log = LoggerFactory.getLogger(FeedRefreshWorker.class);
|
||||||
.getLogger(FeedRefreshWorker.class);
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FeedRefreshUpdater feedRefreshUpdater;
|
FeedRefreshUpdater feedRefreshUpdater;
|
||||||
@@ -52,8 +51,7 @@ public class FeedRefreshWorker {
|
|||||||
private void init() {
|
private void init() {
|
||||||
ApplicationSettings settings = applicationSettingsService.get();
|
ApplicationSettings settings = applicationSettingsService.get();
|
||||||
int threads = settings.getBackgroundThreads();
|
int threads = settings.getBackgroundThreads();
|
||||||
pool = new FeedRefreshExecutor("feed-refresh-worker", threads,
|
pool = new FeedRefreshExecutor("feed-refresh-worker", threads, 20 * threads);
|
||||||
20 * threads);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
@@ -95,8 +93,7 @@ public class FeedRefreshWorker {
|
|||||||
private void update(Feed feed) {
|
private void update(Feed feed) {
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
try {
|
try {
|
||||||
FetchedFeed fetchedFeed = fetcher.fetch(feed.getUrl(), false,
|
FetchedFeed fetchedFeed = fetcher.fetch(feed.getUrl(), false, feed.getLastModifiedHeader(), feed.getEtagHeader(),
|
||||||
feed.getLastModifiedHeader(), feed.getEtagHeader(),
|
|
||||||
feed.getLastPublishedDate(), feed.getLastContentHash());
|
feed.getLastPublishedDate(), feed.getLastContentHash());
|
||||||
// stops here if NotModifiedException or any other exception is
|
// stops here if NotModifiedException or any other exception is
|
||||||
// thrown
|
// thrown
|
||||||
@@ -104,21 +101,17 @@ public class FeedRefreshWorker {
|
|||||||
|
|
||||||
Date disabledUntil = null;
|
Date disabledUntil = null;
|
||||||
if (applicationSettingsService.get().isHeavyLoad()) {
|
if (applicationSettingsService.get().isHeavyLoad()) {
|
||||||
disabledUntil = FeedUtils.buildDisabledUntil(fetchedFeed
|
disabledUntil = FeedUtils.buildDisabledUntil(fetchedFeed.getFeed().getLastEntryDate(), fetchedFeed.getFeed()
|
||||||
.getFeed().getLastEntryDate(), fetchedFeed.getFeed()
|
|
||||||
.getAverageEntryInterval());
|
.getAverageEntryInterval());
|
||||||
}
|
}
|
||||||
|
|
||||||
feed.setLastUpdateSuccess(now);
|
feed.setLastUpdateSuccess(now);
|
||||||
feed.setLink(fetchedFeed.getFeed().getLink());
|
feed.setLink(fetchedFeed.getFeed().getLink());
|
||||||
feed.setLastModifiedHeader(fetchedFeed.getFeed()
|
feed.setLastModifiedHeader(fetchedFeed.getFeed().getLastModifiedHeader());
|
||||||
.getLastModifiedHeader());
|
|
||||||
feed.setEtagHeader(fetchedFeed.getFeed().getEtagHeader());
|
feed.setEtagHeader(fetchedFeed.getFeed().getEtagHeader());
|
||||||
feed.setLastContentHash(fetchedFeed.getFeed().getLastContentHash());
|
feed.setLastContentHash(fetchedFeed.getFeed().getLastContentHash());
|
||||||
feed.setLastPublishedDate(fetchedFeed.getFeed()
|
feed.setLastPublishedDate(fetchedFeed.getFeed().getLastPublishedDate());
|
||||||
.getLastPublishedDate());
|
feed.setAverageEntryInterval(fetchedFeed.getFeed().getAverageEntryInterval());
|
||||||
feed.setAverageEntryInterval(fetchedFeed.getFeed()
|
|
||||||
.getAverageEntryInterval());
|
|
||||||
feed.setLastEntryDate(fetchedFeed.getFeed().getLastEntryDate());
|
feed.setLastEntryDate(fetchedFeed.getFeed().getLastEntryDate());
|
||||||
|
|
||||||
feed.setErrorCount(0);
|
feed.setErrorCount(0);
|
||||||
@@ -129,14 +122,11 @@ public class FeedRefreshWorker {
|
|||||||
feedRefreshUpdater.updateFeed(feed, entries);
|
feedRefreshUpdater.updateFeed(feed, entries);
|
||||||
|
|
||||||
} catch (NotModifiedException e) {
|
} catch (NotModifiedException e) {
|
||||||
log.debug("Feed not modified : {} - {}", feed.getUrl(),
|
log.debug("Feed not modified : {} - {}", feed.getUrl(), e.getMessage());
|
||||||
e.getMessage());
|
|
||||||
|
|
||||||
Date disabledUntil = null;
|
Date disabledUntil = null;
|
||||||
if (applicationSettingsService.get().isHeavyLoad()) {
|
if (applicationSettingsService.get().isHeavyLoad()) {
|
||||||
disabledUntil = FeedUtils
|
disabledUntil = FeedUtils.buildDisabledUntil(feed.getLastEntryDate(), feed.getAverageEntryInterval());
|
||||||
.buildDisabledUntil(feed.getLastEntryDate(),
|
|
||||||
feed.getAverageEntryInterval());
|
|
||||||
}
|
}
|
||||||
feed.setErrorCount(0);
|
feed.setErrorCount(0);
|
||||||
feed.setMessage(null);
|
feed.setMessage(null);
|
||||||
@@ -144,8 +134,7 @@ public class FeedRefreshWorker {
|
|||||||
|
|
||||||
taskGiver.giveBack(feed);
|
taskGiver.giveBack(feed);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String message = "Unable to refresh feed " + feed.getUrl() + " : "
|
String message = "Unable to refresh feed " + feed.getUrl() + " : " + e.getMessage();
|
||||||
+ e.getMessage();
|
|
||||||
if (e instanceof FeedException) {
|
if (e instanceof FeedException) {
|
||||||
log.debug(e.getClass().getName() + " " + message, e);
|
log.debug(e.getClass().getName() + " " + message, e);
|
||||||
} else {
|
} else {
|
||||||
@@ -154,8 +143,7 @@ public class FeedRefreshWorker {
|
|||||||
|
|
||||||
feed.setErrorCount(feed.getErrorCount() + 1);
|
feed.setErrorCount(feed.getErrorCount() + 1);
|
||||||
feed.setMessage(message);
|
feed.setMessage(message);
|
||||||
feed.setDisabledUntil(FeedUtils.buildDisabledUntil(feed
|
feed.setDisabledUntil(FeedUtils.buildDisabledUntil(feed.getErrorCount()));
|
||||||
.getErrorCount()));
|
|
||||||
|
|
||||||
taskGiver.giveBack(feed);
|
taskGiver.giveBack(feed);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,10 +41,8 @@ public class FeedUtils {
|
|||||||
protected static Logger log = LoggerFactory.getLogger(FeedUtils.class);
|
protected static Logger log = LoggerFactory.getLogger(FeedUtils.class);
|
||||||
|
|
||||||
private static final String ESCAPED_QUESTION_MARK = Pattern.quote("?");
|
private static final String ESCAPED_QUESTION_MARK = Pattern.quote("?");
|
||||||
private static final List<String> ALLOWED_IFRAME_CSS_RULES = Arrays.asList(
|
private static final List<String> ALLOWED_IFRAME_CSS_RULES = Arrays.asList("height", "width", "border");
|
||||||
"height", "width", "border");
|
private static final char[] DISALLOWED_IFRAME_CSS_RULE_CHARACTERS = new char[] { '(', ')' };
|
||||||
private static final char[] DISALLOWED_IFRAME_CSS_RULE_CHARACTERS = new char[] {
|
|
||||||
'(', ')' };
|
|
||||||
|
|
||||||
public static String truncate(String string, int length) {
|
public static String truncate(String string, int length) {
|
||||||
if (string != null) {
|
if (string != null) {
|
||||||
@@ -54,8 +52,8 @@ public class FeedUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect feed encoding by using the declared encoding in the xml processing
|
* Detect feed encoding by using the declared encoding in the xml processing instruction and by detecting the characters used in the
|
||||||
* instruction and by detecting the characters used in the feed
|
* feed
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public static String guessEncoding(byte[] bytes) {
|
public static String guessEncoding(byte[] bytes) {
|
||||||
@@ -87,8 +85,7 @@ public class FeedUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize the url. The resulting url is not meant to be fetched but
|
* Normalize the url. The resulting url is not meant to be fetched but rather used as a mean to identify a feed and avoid duplicates
|
||||||
* rather used as a mean to identify a feed and avoid duplicates
|
|
||||||
*/
|
*/
|
||||||
public static String normalizeURL(String url) {
|
public static String normalizeURL(String url) {
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
@@ -113,13 +110,11 @@ public class FeedUtils {
|
|||||||
normalized = normalized.replace("//www.", "//");
|
normalized = normalized.replace("//www.", "//");
|
||||||
|
|
||||||
// feedproxy redirects to feedburner
|
// feedproxy redirects to feedburner
|
||||||
normalized = normalized.replace("feedproxy.google.com",
|
normalized = normalized.replace("feedproxy.google.com", "feeds.feedburner.com");
|
||||||
"feeds.feedburner.com");
|
|
||||||
|
|
||||||
// feedburner feeds have a special treatment
|
// feedburner feeds have a special treatment
|
||||||
if (normalized.split(ESCAPED_QUESTION_MARK)[0].contains("feedburner.com")) {
|
if (normalized.split(ESCAPED_QUESTION_MARK)[0].contains("feedburner.com")) {
|
||||||
normalized = normalized.replace("feeds2.feedburner.com",
|
normalized = normalized.replace("feeds2.feedburner.com", "feeds.feedburner.com");
|
||||||
"feeds.feedburner.com");
|
|
||||||
normalized = normalized.split(ESCAPED_QUESTION_MARK)[0];
|
normalized = normalized.split(ESCAPED_QUESTION_MARK)[0];
|
||||||
normalized = StringUtils.removeEnd(normalized, "/");
|
normalized = StringUtils.removeEnd(normalized, "/");
|
||||||
}
|
}
|
||||||
@@ -146,17 +141,13 @@ public class FeedUtils {
|
|||||||
return encoding;
|
return encoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String handleContent(String content, String baseUri,
|
public static String handleContent(String content, String baseUri, boolean keepTextOnly) {
|
||||||
boolean keepTextOnly) {
|
|
||||||
if (StringUtils.isNotBlank(content)) {
|
if (StringUtils.isNotBlank(content)) {
|
||||||
baseUri = StringUtils.trimToEmpty(baseUri);
|
baseUri = StringUtils.trimToEmpty(baseUri);
|
||||||
Whitelist whitelist = new Whitelist();
|
Whitelist whitelist = new Whitelist();
|
||||||
whitelist.addTags("a", "b", "blockquote", "br", "caption", "cite",
|
whitelist.addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl", "dt", "em",
|
||||||
"code", "col", "colgroup", "dd", "div", "dl", "dt", "em",
|
"h1", "h2", "h3", "h4", "h5", "h6", "i", "iframe", "img", "li", "ol", "p", "pre", "q", "small", "strike", "strong",
|
||||||
"h1", "h2", "h3", "h4", "h5", "h6", "i", "iframe", "img",
|
"sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul");
|
||||||
"li", "ol", "p", "pre", "q", "small", "strike", "strong",
|
|
||||||
"sub", "sup", "table", "tbody", "td", "tfoot", "th",
|
|
||||||
"thead", "tr", "u", "ul");
|
|
||||||
|
|
||||||
whitelist.addAttributes("div", "dir");
|
whitelist.addAttributes("div", "dir");
|
||||||
whitelist.addAttributes("pre", "dir");
|
whitelist.addAttributes("pre", "dir");
|
||||||
@@ -167,22 +158,16 @@ public class FeedUtils {
|
|||||||
whitelist.addAttributes("blockquote", "cite");
|
whitelist.addAttributes("blockquote", "cite");
|
||||||
whitelist.addAttributes("col", "span", "width");
|
whitelist.addAttributes("col", "span", "width");
|
||||||
whitelist.addAttributes("colgroup", "span", "width");
|
whitelist.addAttributes("colgroup", "span", "width");
|
||||||
whitelist.addAttributes("iframe", "src", "height", "width",
|
whitelist.addAttributes("iframe", "src", "height", "width", "allowfullscreen", "frameborder", "style");
|
||||||
"allowfullscreen", "frameborder", "style");
|
whitelist.addAttributes("img", "align", "alt", "height", "src", "title", "width");
|
||||||
whitelist.addAttributes("img", "align", "alt", "height", "src",
|
|
||||||
"title", "width");
|
|
||||||
whitelist.addAttributes("ol", "start", "type");
|
whitelist.addAttributes("ol", "start", "type");
|
||||||
whitelist.addAttributes("q", "cite");
|
whitelist.addAttributes("q", "cite");
|
||||||
whitelist.addAttributes("table", "border", "bordercolor",
|
whitelist.addAttributes("table", "border", "bordercolor", "summary", "width");
|
||||||
"summary", "width");
|
whitelist.addAttributes("td", "border", "bordercolor", "abbr", "axis", "colspan", "rowspan", "width");
|
||||||
whitelist.addAttributes("td", "border", "bordercolor", "abbr",
|
whitelist.addAttributes("th", "border", "bordercolor", "abbr", "axis", "colspan", "rowspan", "scope", "width");
|
||||||
"axis", "colspan", "rowspan", "width");
|
|
||||||
whitelist.addAttributes("th", "border", "bordercolor", "abbr",
|
|
||||||
"axis", "colspan", "rowspan", "scope", "width");
|
|
||||||
whitelist.addAttributes("ul", "type");
|
whitelist.addAttributes("ul", "type");
|
||||||
|
|
||||||
whitelist.addProtocols("a", "href", "ftp", "http", "https",
|
whitelist.addProtocols("a", "href", "ftp", "http", "https", "mailto");
|
||||||
"mailto");
|
|
||||||
whitelist.addProtocols("blockquote", "cite", "http", "https");
|
whitelist.addProtocols("blockquote", "cite", "http", "https");
|
||||||
whitelist.addProtocols("img", "src", "http", "https");
|
whitelist.addProtocols("img", "src", "http", "https");
|
||||||
whitelist.addProtocols("q", "cite", "http", "https");
|
whitelist.addProtocols("q", "cite", "http", "https");
|
||||||
@@ -199,8 +184,7 @@ public class FeedUtils {
|
|||||||
e.attr("style", escaped);
|
e.attr("style", escaped);
|
||||||
}
|
}
|
||||||
|
|
||||||
clean.outputSettings(new OutputSettings().escapeMode(
|
clean.outputSettings(new OutputSettings().escapeMode(EscapeMode.base).prettyPrint(false));
|
||||||
EscapeMode.base).prettyPrint(false));
|
|
||||||
Element body = clean.body();
|
Element body = clean.body();
|
||||||
if (keepTextOnly) {
|
if (keepTextOnly) {
|
||||||
content = body.text();
|
content = body.text();
|
||||||
@@ -215,9 +199,7 @@ public class FeedUtils {
|
|||||||
List<String> rules = Lists.newArrayList();
|
List<String> rules = Lists.newArrayList();
|
||||||
CSSOMParser parser = new CSSOMParser();
|
CSSOMParser parser = new CSSOMParser();
|
||||||
try {
|
try {
|
||||||
CSSStyleDeclaration decl = parser
|
CSSStyleDeclaration decl = parser.parseStyleDeclaration(new InputSource(new StringReader(orig)));
|
||||||
.parseStyleDeclaration(new InputSource(new StringReader(
|
|
||||||
orig)));
|
|
||||||
|
|
||||||
for (int i = 0; i < decl.getLength(); i++) {
|
for (int i = 0; i < decl.getLength(); i++) {
|
||||||
String property = decl.item(i);
|
String property = decl.item(i);
|
||||||
@@ -226,11 +208,8 @@ public class FeedUtils {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ALLOWED_IFRAME_CSS_RULES.contains(property)
|
if (ALLOWED_IFRAME_CSS_RULES.contains(property) && StringUtils.containsNone(value, DISALLOWED_IFRAME_CSS_RULE_CHARACTERS)) {
|
||||||
&& StringUtils.containsNone(value,
|
rules.add(property + ":" + decl.getPropertyValue(property) + ";");
|
||||||
DISALLOWED_IFRAME_CSS_RULE_CHARACTERS)) {
|
|
||||||
rules.add(property + ":" + decl.getPropertyValue(property)
|
|
||||||
+ ";");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -278,8 +257,7 @@ public class FeedUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (c >= 32 || c == 9 || c == 10 || c == 13) {
|
if (c >= 32 || c == 9 || c == 10 || c == 13) {
|
||||||
if (!Character.isHighSurrogate(c)
|
if (!Character.isHighSurrogate(c) && !Character.isLowSurrogate(c)) {
|
||||||
&& !Character.isLowSurrogate(c)) {
|
|
||||||
sb.append(c);
|
sb.append(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,8 +284,7 @@ public class FeedUtils {
|
|||||||
/**
|
/**
|
||||||
* When the feed was refreshed successfully
|
* When the feed was refreshed successfully
|
||||||
*/
|
*/
|
||||||
public static Date buildDisabledUntil(Date publishedDate,
|
public static Date buildDisabledUntil(Date publishedDate, Long averageEntryInterval) {
|
||||||
Long averageEntryInterval) {
|
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
|
|
||||||
if (publishedDate == null) {
|
if (publishedDate == null) {
|
||||||
@@ -325,8 +302,7 @@ public class FeedUtils {
|
|||||||
} else if (averageEntryInterval != null) {
|
} else if (averageEntryInterval != null) {
|
||||||
// use average time between entries to decide when to refresh next
|
// use average time between entries to decide when to refresh next
|
||||||
int factor = 2;
|
int factor = 2;
|
||||||
return new Date(Math.min(DateUtils.addHours(now, 6).getTime(),
|
return new Date(Math.min(DateUtils.addHours(now, 6).getTime(), now.getTime() + averageEntryInterval / factor));
|
||||||
now.getTime() + averageEntryInterval / factor));
|
|
||||||
} else {
|
} else {
|
||||||
// unknown case, recheck in 24 hours
|
// unknown case, recheck in 24 hours
|
||||||
return DateUtils.addHours(now, 24);
|
return DateUtils.addHours(now, 24);
|
||||||
@@ -378,14 +354,11 @@ public class FeedUtils {
|
|||||||
return baseUrl + url;
|
return baseUrl + url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getFaviconUrl(FeedSubscription subscription,
|
public static String getFaviconUrl(FeedSubscription subscription, String publicUrl) {
|
||||||
String publicUrl) {
|
return removeTrailingSlash(publicUrl) + "/rest/feed/favicon/" + subscription.getId();
|
||||||
return removeTrailingSlash(publicUrl) + "/rest/feed/favicon/"
|
|
||||||
+ subscription.getId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String proxyImages(String content, String publicUrl,
|
public static String proxyImages(String content, String publicUrl, boolean proxyImages) {
|
||||||
boolean proxyImages) {
|
|
||||||
if (!proxyImages) {
|
if (!proxyImages) {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
@@ -398,8 +371,7 @@ public class FeedUtils {
|
|||||||
for (Element element : elements) {
|
for (Element element : elements) {
|
||||||
String href = element.attr("src");
|
String href = element.attr("src");
|
||||||
if (href != null) {
|
if (href != null) {
|
||||||
String proxy = removeTrailingSlash(publicUrl)
|
String proxy = removeTrailingSlash(publicUrl) + "/rest/server/proxy?u=" + imageProxyEncoder(href);
|
||||||
+ "/rest/server/proxy?u=" + imageProxyEncoder(href);
|
|
||||||
element.attr("src", proxy);
|
element.attr("src", proxy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,13 +28,11 @@ public class OPMLExporter {
|
|||||||
public Opml export(User user) {
|
public Opml export(User user) {
|
||||||
Opml opml = new Opml();
|
Opml opml = new Opml();
|
||||||
opml.setFeedType("opml_1.1");
|
opml.setFeedType("opml_1.1");
|
||||||
opml.setTitle(String.format("%s subscriptions in CommaFeed",
|
opml.setTitle(String.format("%s subscriptions in CommaFeed", user.getName()));
|
||||||
user.getName()));
|
|
||||||
opml.setCreated(new Date());
|
opml.setCreated(new Date());
|
||||||
|
|
||||||
List<FeedCategory> categories = feedCategoryDAO.findAll(user);
|
List<FeedCategory> categories = feedCategoryDAO.findAll(user);
|
||||||
List<FeedSubscription> subscriptions = feedSubscriptionDAO
|
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findAll(user);
|
||||||
.findAll(user);
|
|
||||||
|
|
||||||
for (FeedCategory cat : categories) {
|
for (FeedCategory cat : categories) {
|
||||||
opml.getOutlines().add(buildCategoryOutline(cat, subscriptions));
|
opml.getOutlines().add(buildCategoryOutline(cat, subscriptions));
|
||||||
@@ -50,20 +48,17 @@ public class OPMLExporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private Outline buildCategoryOutline(FeedCategory cat,
|
private Outline buildCategoryOutline(FeedCategory cat, List<FeedSubscription> subscriptions) {
|
||||||
List<FeedSubscription> subscriptions) {
|
|
||||||
Outline outline = new Outline();
|
Outline outline = new Outline();
|
||||||
outline.setText(cat.getName());
|
outline.setText(cat.getName());
|
||||||
outline.setTitle(cat.getName());
|
outline.setTitle(cat.getName());
|
||||||
|
|
||||||
for (FeedCategory child : cat.getChildren()) {
|
for (FeedCategory child : cat.getChildren()) {
|
||||||
outline.getChildren().add(
|
outline.getChildren().add(buildCategoryOutline(child, subscriptions));
|
||||||
buildCategoryOutline(child, subscriptions));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (FeedSubscription sub : subscriptions) {
|
for (FeedSubscription sub : subscriptions) {
|
||||||
if (sub.getCategory() != null
|
if (sub.getCategory() != null && sub.getCategory().getId().equals(cat.getId())) {
|
||||||
&& sub.getCategory().getId().equals(cat.getId())) {
|
|
||||||
outline.getChildren().add(buildSubscriptionOutline(sub));
|
outline.getChildren().add(buildSubscriptionOutline(sub));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,11 +71,9 @@ public class OPMLExporter {
|
|||||||
outline.setText(sub.getTitle());
|
outline.setText(sub.getTitle());
|
||||||
outline.setTitle(sub.getTitle());
|
outline.setTitle(sub.getTitle());
|
||||||
outline.setType("rss");
|
outline.setType("rss");
|
||||||
outline.getAttributes().add(
|
outline.getAttributes().add(new Attribute("xmlUrl", sub.getFeed().getUrl()));
|
||||||
new Attribute("xmlUrl", sub.getFeed().getUrl()));
|
|
||||||
if (sub.getFeed().getLink() != null) {
|
if (sub.getFeed().getLink() != null) {
|
||||||
outline.getAttributes().add(
|
outline.getAttributes().add(new Attribute("htmlUrl", sub.getFeed().getLink()));
|
||||||
new Attribute("htmlUrl", sub.getFeed().getLink()));
|
|
||||||
}
|
}
|
||||||
return outline;
|
return outline;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,13 +63,12 @@ public class OPMLImporter {
|
|||||||
if (name == null) {
|
if (name == null) {
|
||||||
name = FeedUtils.truncate(outline.getTitle(), 128);
|
name = FeedUtils.truncate(outline.getTitle(), 128);
|
||||||
}
|
}
|
||||||
FeedCategory category = feedCategoryDAO.findByName(user, name,
|
FeedCategory category = feedCategoryDAO.findByName(user, name, parent);
|
||||||
parent);
|
|
||||||
if (category == null) {
|
if (category == null) {
|
||||||
if (StringUtils.isBlank(name)) {
|
if (StringUtils.isBlank(name)) {
|
||||||
name = "Unnamed category";
|
name = "Unnamed category";
|
||||||
}
|
}
|
||||||
|
|
||||||
category = new FeedCategory();
|
category = new FeedCategory();
|
||||||
category.setName(name);
|
category.setName(name);
|
||||||
category.setParent(parent);
|
category.setParent(parent);
|
||||||
@@ -91,15 +90,13 @@ public class OPMLImporter {
|
|||||||
}
|
}
|
||||||
// make sure we continue with the import process even a feed failed
|
// make sure we continue with the import process even a feed failed
|
||||||
try {
|
try {
|
||||||
feedSubscriptionService.subscribe(user, outline.getXmlUrl(),
|
feedSubscriptionService.subscribe(user, outline.getXmlUrl(), name, parent);
|
||||||
name, parent);
|
|
||||||
} catch (FeedSubscriptionException e) {
|
} catch (FeedSubscriptionException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("error while importing {}: {}", outline.getXmlUrl(),
|
log.error("error while importing {}: {}", outline.getXmlUrl(), e.getMessage());
|
||||||
e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cache.invalidateUserData(user);
|
cache.invalidateUserRootCategory(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,12 @@ public abstract class AbstractModel implements Serializable {
|
|||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.TABLE, generator = "gen")
|
@GeneratedValue(strategy = GenerationType.TABLE, generator = "gen")
|
||||||
@TableGenerator(name = "gen", table = "hibernate_sequences", pkColumnName = "sequence_name", valueColumnName = "sequence_next_hi_value", allocationSize = 1000)
|
@TableGenerator(
|
||||||
|
name = "gen",
|
||||||
|
table = "hibernate_sequences",
|
||||||
|
pkColumnName = "sequence_name",
|
||||||
|
valueColumnName = "sequence_next_hi_value",
|
||||||
|
allocationSize = 1000)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
public Long getId() {
|
public Long getId() {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.commafeed.backend.model;
|
package com.commafeed.backend.model;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
@@ -7,8 +9,11 @@ import javax.xml.bind.annotation.XmlAccessType;
|
|||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
import org.apache.log4j.Level;
|
import org.apache.log4j.Level;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "APPLICATIONSETTINGS")
|
@Table(name = "APPLICATIONSETTINGS")
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
@@ -35,10 +40,19 @@ public class ApplicationSettings extends AbstractModel {
|
|||||||
private boolean imageProxyEnabled;
|
private boolean imageProxyEnabled;
|
||||||
private int queryTimeout;
|
private int queryTimeout;
|
||||||
private boolean crawlingPaused;
|
private boolean crawlingPaused;
|
||||||
|
private int keepStatusDays = 0;
|
||||||
|
|
||||||
@Column(length = 255)
|
@Column(length = 255)
|
||||||
private String announcement;
|
private String announcement;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public Date getUnreadThreshold() {
|
||||||
|
int keepStatusDays = getKeepStatusDays();
|
||||||
|
return keepStatusDays > 0 ? DateUtils.addDays(new Date(), -1 * keepStatusDays) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* getters and setters below */
|
||||||
|
|
||||||
public String getPublicUrl() {
|
public String getPublicUrl() {
|
||||||
return publicUrl;
|
return publicUrl;
|
||||||
}
|
}
|
||||||
@@ -123,8 +137,7 @@ public class ApplicationSettings extends AbstractModel {
|
|||||||
return googleAnalyticsTrackingCode;
|
return googleAnalyticsTrackingCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGoogleAnalyticsTrackingCode(
|
public void setGoogleAnalyticsTrackingCode(String googleAnalyticsTrackingCode) {
|
||||||
String googleAnalyticsTrackingCode) {
|
|
||||||
this.googleAnalyticsTrackingCode = googleAnalyticsTrackingCode;
|
this.googleAnalyticsTrackingCode = googleAnalyticsTrackingCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,4 +213,12 @@ public class ApplicationSettings extends AbstractModel {
|
|||||||
this.crawlingPaused = crawlingPaused;
|
this.crawlingPaused = crawlingPaused;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getKeepStatusDays() {
|
||||||
|
return keepStatusDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKeepStatusDays(int keepStatusDays) {
|
||||||
|
this.keepStatusDays = keepStatusDays;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.Date;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.persistence.Cacheable;
|
import javax.persistence.Cacheable;
|
||||||
|
import javax.persistence.CascadeType;
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.OneToMany;
|
import javax.persistence.OneToMany;
|
||||||
@@ -107,8 +108,8 @@ public class Feed extends AbstractModel {
|
|||||||
@Column(length = 40)
|
@Column(length = 40)
|
||||||
private String lastContentHash;
|
private String lastContentHash;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "feed")
|
@OneToMany(mappedBy = "feed", cascade = CascadeType.REMOVE)
|
||||||
private Set<FeedFeedEntry> entryRelationships;
|
private Set<FeedEntry> entries;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "feed")
|
@OneToMany(mappedBy = "feed")
|
||||||
private Set<FeedSubscription> subscriptions;
|
private Set<FeedSubscription> subscriptions;
|
||||||
@@ -135,8 +136,7 @@ public class Feed extends AbstractModel {
|
|||||||
private Date pushLastPing;
|
private Date pushLastPing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Denotes a feed that needs to be refreshed before others. Currently used
|
* Denotes a feed that needs to be refreshed before others. Currently used when a feed is queued manually for refresh. Not persisted.
|
||||||
* when a feed is queued manually for refresh. Not persisted.
|
|
||||||
*/
|
*/
|
||||||
@Transient
|
@Transient
|
||||||
private boolean urgent;
|
private boolean urgent;
|
||||||
@@ -325,12 +325,12 @@ public class Feed extends AbstractModel {
|
|||||||
this.normalizedUrlHash = normalizedUrlHash;
|
this.normalizedUrlHash = normalizedUrlHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<FeedFeedEntry> getEntryRelationships() {
|
public Set<FeedEntry> getEntries() {
|
||||||
return entryRelationships;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEntryRelationships(Set<FeedFeedEntry> entryRelationships) {
|
public void setEntries(Set<FeedEntry> entries) {
|
||||||
this.entryRelationships = entryRelationships;
|
this.entries = entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import javax.persistence.Column;
|
|||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.FetchType;
|
import javax.persistence.FetchType;
|
||||||
import javax.persistence.JoinColumn;
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
import javax.persistence.OneToMany;
|
import javax.persistence.OneToMany;
|
||||||
import javax.persistence.OneToOne;
|
import javax.persistence.OneToOne;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
@@ -32,8 +33,8 @@ public class FeedEntry extends AbstractModel {
|
|||||||
@Column(length = 40, nullable = false)
|
@Column(length = 40, nullable = false)
|
||||||
private String guidHash;
|
private String guidHash;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "entry", cascade = CascadeType.REMOVE)
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
private Set<FeedFeedEntry> feedRelationships;
|
private Feed feed;
|
||||||
|
|
||||||
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
|
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
|
||||||
@JoinColumn(nullable = false, updatable = false)
|
@JoinColumn(nullable = false, updatable = false)
|
||||||
@@ -42,9 +43,6 @@ public class FeedEntry extends AbstractModel {
|
|||||||
@Column(length = 2048)
|
@Column(length = 2048)
|
||||||
private String url;
|
private String url;
|
||||||
|
|
||||||
@Column(name = "author", length = 128)
|
|
||||||
private String author;
|
|
||||||
|
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
private Date inserted;
|
private Date inserted;
|
||||||
|
|
||||||
@@ -116,20 +114,12 @@ public class FeedEntry extends AbstractModel {
|
|||||||
this.guidHash = guidHash;
|
this.guidHash = guidHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAuthor() {
|
public Feed getFeed() {
|
||||||
return author;
|
return feed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAuthor(String author) {
|
public void setFeed(Feed feed) {
|
||||||
this.author = author;
|
this.feed = feed;
|
||||||
}
|
|
||||||
|
|
||||||
public Set<FeedFeedEntry> getFeedRelationships() {
|
|
||||||
return feedRelationships;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFeedRelationships(Set<FeedFeedEntry> feedRelationships) {
|
|
||||||
this.feedRelationships = feedRelationships;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public FeedSubscription getSubscription() {
|
public FeedSubscription getSubscription() {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package com.commafeed.backend.model;
|
package com.commafeed.backend.model;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.persistence.Cacheable;
|
import javax.persistence.Cacheable;
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.Lob;
|
import javax.persistence.Lob;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
|
||||||
import org.hibernate.annotations.Cache;
|
import org.hibernate.annotations.Cache;
|
||||||
@@ -18,17 +21,29 @@ public class FeedEntryContent extends AbstractModel {
|
|||||||
|
|
||||||
@Column(length = 2048)
|
@Column(length = 2048)
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
|
@Column(length = 40)
|
||||||
|
private String titleHash;
|
||||||
|
|
||||||
@Lob
|
@Lob
|
||||||
@Column(length = Integer.MAX_VALUE)
|
@Column(length = Integer.MAX_VALUE)
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
|
@Column(length = 40)
|
||||||
|
private String contentHash;
|
||||||
|
|
||||||
|
@Column(name = "author", length = 128)
|
||||||
|
private String author;
|
||||||
|
|
||||||
@Column(length = 2048)
|
@Column(length = 2048)
|
||||||
private String enclosureUrl;
|
private String enclosureUrl;
|
||||||
|
|
||||||
@Column(length = 255)
|
@Column(length = 255)
|
||||||
private String enclosureType;
|
private String enclosureType;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "content")
|
||||||
|
private Set<FeedEntry> entries;
|
||||||
|
|
||||||
public String getContent() {
|
public String getContent() {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
@@ -61,4 +76,36 @@ public class FeedEntryContent extends AbstractModel {
|
|||||||
this.title = title;
|
this.title = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getContentHash() {
|
||||||
|
return contentHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContentHash(String contentHash) {
|
||||||
|
this.contentHash = contentHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(String author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<FeedEntry> getEntries() {
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntries(Set<FeedEntry> entries) {
|
||||||
|
this.entries = entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitleHash() {
|
||||||
|
return titleHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitleHash(String titleHash) {
|
||||||
|
this.titleHash = titleHash;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import javax.persistence.ManyToOne;
|
|||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
import javax.persistence.Temporal;
|
import javax.persistence.Temporal;
|
||||||
import javax.persistence.TemporalType;
|
import javax.persistence.TemporalType;
|
||||||
|
import javax.persistence.Transient;
|
||||||
|
|
||||||
import org.hibernate.annotations.Cache;
|
import org.hibernate.annotations.Cache;
|
||||||
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
||||||
@@ -34,6 +35,9 @@ public class FeedEntryStatus extends AbstractModel {
|
|||||||
private boolean read;
|
private boolean read;
|
||||||
private boolean starred;
|
private boolean starred;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private boolean markable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Denormalization starts here
|
* Denormalization starts here
|
||||||
*/
|
*/
|
||||||
@@ -116,4 +120,12 @@ public class FeedEntryStatus extends AbstractModel {
|
|||||||
this.user = user;
|
this.user = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isMarkable() {
|
||||||
|
return markable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMarkable(boolean markable) {
|
||||||
|
this.markable = markable;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
package com.commafeed.backend.model;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import javax.persistence.Cacheable;
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.FetchType;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.JoinColumn;
|
|
||||||
import javax.persistence.ManyToOne;
|
|
||||||
import javax.persistence.Table;
|
|
||||||
import javax.persistence.Temporal;
|
|
||||||
import javax.persistence.TemporalType;
|
|
||||||
|
|
||||||
import org.hibernate.annotations.Cache;
|
|
||||||
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "FEED_FEEDENTRIES")
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
@Cacheable
|
|
||||||
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
|
|
||||||
public class FeedFeedEntry implements Serializable {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
@JoinColumn(name = "FEED_ID")
|
|
||||||
private Feed feed;
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
@JoinColumn(name = "FEEDENTRY_ID")
|
|
||||||
private FeedEntry entry;
|
|
||||||
|
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
|
||||||
private Date entryUpdated;
|
|
||||||
|
|
||||||
public FeedFeedEntry() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public FeedFeedEntry(Feed feed, FeedEntry entry) {
|
|
||||||
this.feed = feed;
|
|
||||||
this.entry = entry;
|
|
||||||
this.entryUpdated = entry.getUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Feed getFeed() {
|
|
||||||
return feed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFeed(Feed feed) {
|
|
||||||
this.feed = feed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FeedEntry getEntry() {
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEntry(FeedEntry entry) {
|
|
||||||
this.entry = entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getEntryUpdated() {
|
|
||||||
return entryUpdated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEntryUpdated(Date entryUpdated) {
|
|
||||||
this.entryUpdated = entryUpdated;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -55,8 +55,7 @@ public class User extends AbstractModel {
|
|||||||
@Temporal(TemporalType.TIMESTAMP)
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
private Date recoverPasswordTokenDate;
|
private Date recoverPasswordTokenDate;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "user", cascade = { CascadeType.PERSIST,
|
@OneToMany(mappedBy = "user", cascade = { CascadeType.PERSIST, CascadeType.REMOVE })
|
||||||
CascadeType.REMOVE })
|
|
||||||
private Set<UserRole> roles = Sets.newHashSet();
|
private Set<UserRole> roles = Sets.newHashSet();
|
||||||
|
|
||||||
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
|
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ import com.google.common.collect.Lists;
|
|||||||
|
|
||||||
public class SubscriptionHandler {
|
public class SubscriptionHandler {
|
||||||
|
|
||||||
private static Logger log = LoggerFactory
|
private static Logger log = LoggerFactory.getLogger(SubscriptionHandler.class);
|
||||||
.getLogger(SubscriptionHandler.class);
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ApplicationSettingsService applicationSettingsService;
|
ApplicationSettingsService applicationSettingsService;
|
||||||
@@ -47,16 +46,13 @@ public class SubscriptionHandler {
|
|||||||
|
|
||||||
String hub = feed.getPushHub();
|
String hub = feed.getPushHub();
|
||||||
String topic = feed.getPushTopic();
|
String topic = feed.getPushTopic();
|
||||||
String publicUrl = FeedUtils
|
String publicUrl = FeedUtils.removeTrailingSlash(applicationSettingsService.get().getPublicUrl());
|
||||||
.removeTrailingSlash(applicationSettingsService.get()
|
|
||||||
.getPublicUrl());
|
|
||||||
|
|
||||||
log.debug("sending new pubsub subscription to {} for {}", hub, topic);
|
log.debug("sending new pubsub subscription to {} for {}", hub, topic);
|
||||||
|
|
||||||
HttpPost post = new HttpPost(hub);
|
HttpPost post = new HttpPost(hub);
|
||||||
List<NameValuePair> nvp = Lists.newArrayList();
|
List<NameValuePair> nvp = Lists.newArrayList();
|
||||||
nvp.add(new BasicNameValuePair("hub.callback", publicUrl
|
nvp.add(new BasicNameValuePair("hub.callback", publicUrl + "/rest/push/callback"));
|
||||||
+ "/rest/push/callback"));
|
|
||||||
nvp.add(new BasicNameValuePair("hub.topic", topic));
|
nvp.add(new BasicNameValuePair("hub.topic", topic));
|
||||||
nvp.add(new BasicNameValuePair("hub.mode", "subscribe"));
|
nvp.add(new BasicNameValuePair("hub.mode", "subscribe"));
|
||||||
nvp.add(new BasicNameValuePair("hub.verify", "async"));
|
nvp.add(new BasicNameValuePair("hub.verify", "async"));
|
||||||
@@ -65,8 +61,7 @@ public class SubscriptionHandler {
|
|||||||
nvp.add(new BasicNameValuePair("hub.lease_seconds", ""));
|
nvp.add(new BasicNameValuePair("hub.lease_seconds", ""));
|
||||||
|
|
||||||
post.setHeader(HttpHeaders.USER_AGENT, "CommaFeed");
|
post.setHeader(HttpHeaders.USER_AGENT, "CommaFeed");
|
||||||
post.setHeader(HttpHeaders.CONTENT_TYPE,
|
post.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
MediaType.APPLICATION_FORM_URLENCODED);
|
|
||||||
|
|
||||||
HttpClient client = HttpGetter.newClient(20000);
|
HttpClient client = HttpGetter.newClient(20000);
|
||||||
try {
|
try {
|
||||||
@@ -77,23 +72,19 @@ public class SubscriptionHandler {
|
|||||||
if (code != 204 && code != 202 && code != 200) {
|
if (code != 204 && code != 202 && code != 200) {
|
||||||
String message = EntityUtils.toString(response.getEntity());
|
String message = EntityUtils.toString(response.getEntity());
|
||||||
String pushpressError = " is value is not allowed. You may only subscribe to";
|
String pushpressError = " is value is not allowed. You may only subscribe to";
|
||||||
if (code == 400
|
if (code == 400 && StringUtils.contains(message, pushpressError)) {
|
||||||
&& StringUtils.contains(message, pushpressError)) {
|
|
||||||
String[] tokens = message.split(" ");
|
String[] tokens = message.split(" ");
|
||||||
feed.setPushTopic(tokens[tokens.length - 1]);
|
feed.setPushTopic(tokens[tokens.length - 1]);
|
||||||
taskGiver.giveBack(feed);
|
taskGiver.giveBack(feed);
|
||||||
log.debug("handled pushpress subfeed {} : {}", topic,
|
log.debug("handled pushpress subfeed {} : {}", topic, feed.getPushTopic());
|
||||||
feed.getPushTopic());
|
|
||||||
} else {
|
} else {
|
||||||
throw new Exception("Unexpected response code: " + code
|
throw new Exception("Unexpected response code: " + code + " " + response.getStatusLine().getReasonPhrase() + " - "
|
||||||
+ " " + response.getStatusLine().getReasonPhrase()
|
+ message);
|
||||||
+ " - " + message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.debug("subscribed to {} for {}", hub, topic);
|
log.debug("subscribed to {} for {}", hub, topic);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Could not subscribe to {} for {} : " + e.getMessage(),
|
log.error("Could not subscribe to {} for {} : " + e.getMessage(), hub, topic);
|
||||||
hub, topic);
|
|
||||||
} finally {
|
} finally {
|
||||||
client.getConnectionManager().shutdown();
|
client.getConnectionManager().shutdown();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import org.jdom.Element;
|
|||||||
|
|
||||||
import com.sun.syndication.feed.opml.Opml;
|
import com.sun.syndication.feed.opml.Opml;
|
||||||
|
|
||||||
public class OPML11Generator extends
|
public class OPML11Generator extends com.sun.syndication.io.impl.OPML10Generator {
|
||||||
com.sun.syndication.io.impl.OPML10Generator {
|
|
||||||
|
|
||||||
public OPML11Generator() {
|
public OPML11Generator() {
|
||||||
super("opml_1.1");
|
super("opml_1.1");
|
||||||
|
|||||||
@@ -6,20 +6,17 @@ import org.jdom.Element;
|
|||||||
import com.sun.syndication.io.impl.OPML10Parser;
|
import com.sun.syndication.io.impl.OPML10Parser;
|
||||||
|
|
||||||
public class OPML11Parser extends OPML10Parser {
|
public class OPML11Parser extends OPML10Parser {
|
||||||
|
|
||||||
public OPML11Parser() {
|
public OPML11Parser() {
|
||||||
super("opml_1.1");
|
super("opml_1.1");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isMyType(Document document) {
|
public boolean isMyType(Document document) {
|
||||||
Element e = document.getRootElement();
|
Element e = document.getRootElement();
|
||||||
|
|
||||||
if (e.getName().equals("opml")
|
if (e.getName().equals("opml") && (e.getChild("head") == null || e.getChild("head").getChild("docs") == null)
|
||||||
&& (e.getChild("head") == null || e.getChild("head").getChild(
|
&& (e.getAttributeValue("version") == null || e.getAttributeValue("version").equals("1.1"))) {
|
||||||
"docs") == null)
|
|
||||||
&& (e.getAttributeValue("version") == null || e
|
|
||||||
.getAttributeValue("version").equals("1.1"))) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ public class RSSRDF10Parser extends RSS10Parser {
|
|||||||
|
|
||||||
Element rssRoot = document.getRootElement();
|
Element rssRoot = document.getRootElement();
|
||||||
Namespace defaultNS = rssRoot.getNamespace();
|
Namespace defaultNS = rssRoot.getNamespace();
|
||||||
List additionalNSs = Lists.newArrayList(rssRoot
|
List additionalNSs = Lists.newArrayList(rssRoot.getAdditionalNamespaces());
|
||||||
.getAdditionalNamespaces());
|
|
||||||
List<Element> children = rssRoot.getChildren();
|
List<Element> children = rssRoot.getChildren();
|
||||||
if (CollectionUtils.isNotEmpty(children)) {
|
if (CollectionUtils.isNotEmpty(children)) {
|
||||||
Element child = children.get(0);
|
Element child = children.get(0);
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ public class ApplicationSettingsService {
|
|||||||
|
|
||||||
public ApplicationSettings get() {
|
public ApplicationSettings get() {
|
||||||
if (settings == null) {
|
if (settings == null) {
|
||||||
settings = Iterables.getFirst(applicationSettingsDAO.findAll(),
|
settings = Iterables.getFirst(applicationSettingsDAO.findAll(), null);
|
||||||
null);
|
|
||||||
}
|
}
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.commafeed.backend.services;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
|
import com.commafeed.backend.dao.FeedEntryContentDAO;
|
||||||
|
import com.commafeed.backend.feeds.FeedUtils;
|
||||||
|
import com.commafeed.backend.model.FeedEntryContent;
|
||||||
|
|
||||||
|
public class FeedEntryContentService {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FeedEntryContentDAO feedEntryContentDAO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this is NOT thread-safe
|
||||||
|
*/
|
||||||
|
public FeedEntryContent findOrCreate(FeedEntryContent content, String baseUrl) {
|
||||||
|
|
||||||
|
String contentHash = DigestUtils.sha1Hex(StringUtils.trimToEmpty(content.getContent()));
|
||||||
|
String titleHash = DigestUtils.sha1Hex(StringUtils.trimToEmpty(content.getTitle()));
|
||||||
|
FeedEntryContent existing = feedEntryContentDAO.findExisting(contentHash, titleHash);
|
||||||
|
if (existing == null) {
|
||||||
|
content.setContentHash(contentHash);
|
||||||
|
content.setTitleHash(titleHash);
|
||||||
|
|
||||||
|
content.setAuthor(FeedUtils.truncate(FeedUtils.handleContent(content.getAuthor(), baseUrl, true), 128));
|
||||||
|
content.setTitle(FeedUtils.truncate(FeedUtils.handleContent(content.getTitle(), baseUrl, true), 2048));
|
||||||
|
content.setContent(FeedUtils.handleContent(content.getContent(), baseUrl, false));
|
||||||
|
existing = content;
|
||||||
|
feedEntryContentDAO.saveOrUpdate(existing);
|
||||||
|
}
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
package com.commafeed.backend.services;
|
package com.commafeed.backend.services;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.ejb.Stateless;
|
import javax.ejb.Stateless;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import com.commafeed.backend.cache.CacheService;
|
||||||
import com.commafeed.backend.dao.FeedEntryDAO;
|
import com.commafeed.backend.dao.FeedEntryDAO;
|
||||||
import com.commafeed.backend.dao.FeedEntryStatusDAO;
|
import com.commafeed.backend.dao.FeedEntryStatusDAO;
|
||||||
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||||
@@ -10,6 +14,7 @@ import com.commafeed.backend.model.FeedEntry;
|
|||||||
import com.commafeed.backend.model.FeedEntryStatus;
|
import com.commafeed.backend.model.FeedEntryStatus;
|
||||||
import com.commafeed.backend.model.FeedSubscription;
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
import com.commafeed.backend.model.User;
|
import com.commafeed.backend.model.User;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
@Stateless
|
@Stateless
|
||||||
public class FeedEntryService {
|
public class FeedEntryService {
|
||||||
@@ -19,14 +24,15 @@ public class FeedEntryService {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FeedSubscriptionDAO feedSubscriptionDAO;
|
FeedSubscriptionDAO feedSubscriptionDAO;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FeedEntryDAO feedEntryDAO;
|
FeedEntryDAO feedEntryDAO;
|
||||||
|
|
||||||
public void markEntry(User user, Long entryId, Long subscriptionId,
|
@Inject
|
||||||
boolean read) {
|
CacheService cache;
|
||||||
FeedSubscription sub = feedSubscriptionDAO.findById(user,
|
|
||||||
subscriptionId);
|
public void markEntry(User user, Long entryId, Long subscriptionId, boolean read) {
|
||||||
|
FeedSubscription sub = feedSubscriptionDAO.findById(user, subscriptionId);
|
||||||
if (sub == null) {
|
if (sub == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -37,32 +43,17 @@ public class FeedEntryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FeedEntryStatus status = feedEntryStatusDAO.getStatus(sub, entry);
|
FeedEntryStatus status = feedEntryStatusDAO.getStatus(sub, entry);
|
||||||
|
if (status.isMarkable()) {
|
||||||
if (read) {
|
status.setRead(read);
|
||||||
if (status.getId() != null) {
|
|
||||||
if (status.isStarred()) {
|
|
||||||
status.setRead(true);
|
|
||||||
feedEntryStatusDAO.saveOrUpdate(status);
|
|
||||||
} else {
|
|
||||||
feedEntryStatusDAO.delete(status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (status.getId() == null) {
|
|
||||||
status = new FeedEntryStatus(user, sub, entry);
|
|
||||||
status.setSubscription(sub);
|
|
||||||
}
|
|
||||||
status.setRead(false);
|
|
||||||
feedEntryStatusDAO.saveOrUpdate(status);
|
feedEntryStatusDAO.saveOrUpdate(status);
|
||||||
|
cache.invalidateUnreadCount(sub);
|
||||||
|
cache.invalidateUserRootCategory(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void starEntry(User user, Long entryId, Long subscriptionId,
|
public void starEntry(User user, Long entryId, Long subscriptionId, boolean starred) {
|
||||||
boolean starred) {
|
|
||||||
|
|
||||||
FeedSubscription sub = feedSubscriptionDAO.findById(user,
|
FeedSubscription sub = feedSubscriptionDAO.findById(user, subscriptionId);
|
||||||
subscriptionId);
|
|
||||||
if (sub == null) {
|
if (sub == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -73,24 +64,33 @@ public class FeedEntryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FeedEntryStatus status = feedEntryStatusDAO.getStatus(sub, entry);
|
FeedEntryStatus status = feedEntryStatusDAO.getStatus(sub, entry);
|
||||||
|
status.setStarred(starred);
|
||||||
|
feedEntryStatusDAO.saveOrUpdate(status);
|
||||||
|
}
|
||||||
|
|
||||||
if (!starred) {
|
public void markSubscriptionEntries(User user, List<FeedSubscription> subscriptions, Date olderThan) {
|
||||||
if (status.getId() != null) {
|
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(subscriptions, true, null, null, -1, -1, null, false);
|
||||||
if (!status.isRead()) {
|
markList(statuses, olderThan);
|
||||||
status.setStarred(false);
|
cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0]));
|
||||||
feedEntryStatusDAO.saveOrUpdate(status);
|
cache.invalidateUserRootCategory(user);
|
||||||
} else {
|
}
|
||||||
feedEntryStatusDAO.delete(status);
|
|
||||||
|
public void markStarredEntries(User user, Date olderThan) {
|
||||||
|
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findStarred(user, null, -1, -1, null, false);
|
||||||
|
markList(statuses, olderThan);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markList(List<FeedEntryStatus> statuses, Date olderThan) {
|
||||||
|
List<FeedEntryStatus> list = Lists.newArrayList();
|
||||||
|
for (FeedEntryStatus status : statuses) {
|
||||||
|
if (!status.isRead()) {
|
||||||
|
Date inserted = status.getEntry().getInserted();
|
||||||
|
if (olderThan == null || inserted == null || olderThan.after(inserted)) {
|
||||||
|
status.setRead(true);
|
||||||
|
list.add(status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (status.getId() == null) {
|
|
||||||
status = new FeedEntryStatus(user, sub, entry);
|
|
||||||
status.setSubscription(sub);
|
|
||||||
status.setRead(true);
|
|
||||||
}
|
|
||||||
status.setStarred(true);
|
|
||||||
feedEntryStatusDAO.saveOrUpdate(status);
|
|
||||||
}
|
}
|
||||||
|
feedEntryStatusDAO.saveOrUpdate(list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,17 +18,14 @@ import com.commafeed.backend.feeds.FeedRefreshTaskGiver;
|
|||||||
import com.commafeed.backend.feeds.FeedUtils;
|
import com.commafeed.backend.feeds.FeedUtils;
|
||||||
import com.commafeed.backend.model.Feed;
|
import com.commafeed.backend.model.Feed;
|
||||||
import com.commafeed.backend.model.FeedCategory;
|
import com.commafeed.backend.model.FeedCategory;
|
||||||
import com.commafeed.backend.model.FeedEntry;
|
|
||||||
import com.commafeed.backend.model.FeedEntryStatus;
|
|
||||||
import com.commafeed.backend.model.FeedSubscription;
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
import com.commafeed.backend.model.Models;
|
import com.commafeed.backend.model.Models;
|
||||||
import com.commafeed.backend.model.User;
|
import com.commafeed.backend.model.User;
|
||||||
import com.google.api.client.util.Lists;
|
import com.google.api.client.util.Maps;
|
||||||
|
|
||||||
public class FeedSubscriptionService {
|
public class FeedSubscriptionService {
|
||||||
|
|
||||||
private static Logger log = LoggerFactory
|
private static Logger log = LoggerFactory.getLogger(FeedSubscriptionService.class);
|
||||||
.getLogger(FeedSubscriptionService.class);
|
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
@ApplicationException
|
@ApplicationException
|
||||||
@@ -59,63 +56,62 @@ public class FeedSubscriptionService {
|
|||||||
@Inject
|
@Inject
|
||||||
CacheService cache;
|
CacheService cache;
|
||||||
|
|
||||||
public Feed subscribe(User user, String url, String title,
|
public Feed subscribe(User user, String url, String title, FeedCategory category) {
|
||||||
FeedCategory category) {
|
|
||||||
|
|
||||||
final String pubUrl = applicationSettingsService.get().getPublicUrl();
|
final String pubUrl = applicationSettingsService.get().getPublicUrl();
|
||||||
if (StringUtils.isBlank(pubUrl)) {
|
if (StringUtils.isBlank(pubUrl)) {
|
||||||
throw new FeedSubscriptionException(
|
throw new FeedSubscriptionException("Public URL of this CommaFeed instance is not set");
|
||||||
"Public URL of this CommaFeed instance is not set");
|
|
||||||
}
|
}
|
||||||
if (url.startsWith(pubUrl)) {
|
if (url.startsWith(pubUrl)) {
|
||||||
throw new FeedSubscriptionException(
|
throw new FeedSubscriptionException("Could not subscribe to a feed from this CommaFeed instance");
|
||||||
"Could not subscribe to a feed from this CommaFeed instance");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Feed feed = feedService.findOrCreate(url);
|
Feed feed = feedService.findOrCreate(url);
|
||||||
|
|
||||||
FeedSubscription sub = feedSubscriptionDAO.findByFeed(user, feed);
|
FeedSubscription sub = feedSubscriptionDAO.findByFeed(user, feed);
|
||||||
boolean newSubscription = false;
|
|
||||||
if (sub == null) {
|
if (sub == null) {
|
||||||
sub = new FeedSubscription();
|
sub = new FeedSubscription();
|
||||||
sub.setFeed(feed);
|
sub.setFeed(feed);
|
||||||
sub.setUser(user);
|
sub.setUser(user);
|
||||||
newSubscription = true;
|
|
||||||
}
|
}
|
||||||
sub.setCategory(category);
|
sub.setCategory(category);
|
||||||
sub.setPosition(0);
|
sub.setPosition(0);
|
||||||
sub.setTitle(FeedUtils.truncate(title, 128));
|
sub.setTitle(FeedUtils.truncate(title, 128));
|
||||||
feedSubscriptionDAO.saveOrUpdate(sub);
|
feedSubscriptionDAO.saveOrUpdate(sub);
|
||||||
|
|
||||||
if (newSubscription) {
|
|
||||||
try {
|
|
||||||
List<FeedEntryStatus> statuses = Lists.newArrayList();
|
|
||||||
List<FeedEntry> allEntries = feedEntryDAO.findByFeed(feed, 0,
|
|
||||||
10);
|
|
||||||
for (FeedEntry entry : allEntries) {
|
|
||||||
FeedEntryStatus status = new FeedEntryStatus(user, sub, entry);
|
|
||||||
status.setRead(false);
|
|
||||||
status.setSubscription(sub);
|
|
||||||
statuses.add(status);
|
|
||||||
}
|
|
||||||
feedEntryStatusDAO.saveOrUpdate(statuses);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error(
|
|
||||||
"could not fetch initial statuses when importing {} : {}",
|
|
||||||
feed.getUrl(), e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
taskGiver.add(feed);
|
taskGiver.add(feed);
|
||||||
|
cache.invalidateUserRootCategory(user);
|
||||||
return feed;
|
return feed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean unsubscribe(User user, Long subId) {
|
||||||
|
FeedSubscription sub = feedSubscriptionDAO.findById(user, subId);
|
||||||
|
if (sub != null) {
|
||||||
|
feedSubscriptionDAO.delete(sub);
|
||||||
|
cache.invalidateUserRootCategory(user);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getUnreadCount(FeedSubscription sub) {
|
||||||
|
Long count = cache.getUnreadCount(sub);
|
||||||
|
if (count == null) {
|
||||||
|
log.debug("unread count cache miss for {}", Models.getId(sub));
|
||||||
|
count = feedEntryStatusDAO.getUnreadCount(sub);
|
||||||
|
cache.setUnreadCount(sub, count);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<Long, Long> getUnreadCount(User user) {
|
public Map<Long, Long> getUnreadCount(User user) {
|
||||||
Map<Long, Long> map = cache.getUnreadCounts(user);
|
Map<Long, Long> map = Maps.newHashMap();
|
||||||
if (map == null) {
|
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
|
||||||
log.debug("unread count cache miss for {}", Models.getId(user));
|
for (FeedSubscription sub : subs) {
|
||||||
map = feedEntryStatusDAO.getUnreadCount(user);
|
map.put(sub.getId(), getUnreadCount(sub));
|
||||||
cache.setUnreadCounts(user, map);
|
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,25 +8,23 @@ import javax.inject.Inject;
|
|||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
import javax.persistence.PersistenceContext;
|
import javax.persistence.PersistenceContext;
|
||||||
|
|
||||||
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
|
|
||||||
import com.commafeed.backend.MetricsBean;
|
import com.commafeed.backend.MetricsBean;
|
||||||
import com.commafeed.backend.cache.CacheService;
|
import com.commafeed.backend.cache.CacheService;
|
||||||
import com.commafeed.backend.dao.FeedEntryDAO;
|
import com.commafeed.backend.dao.FeedEntryDAO;
|
||||||
import com.commafeed.backend.dao.FeedEntryDAO.EntryWithFeed;
|
|
||||||
import com.commafeed.backend.dao.FeedEntryStatusDAO;
|
import com.commafeed.backend.dao.FeedEntryStatusDAO;
|
||||||
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||||
import com.commafeed.backend.feeds.FeedUtils;
|
|
||||||
import com.commafeed.backend.model.Feed;
|
import com.commafeed.backend.model.Feed;
|
||||||
import com.commafeed.backend.model.FeedEntry;
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
import com.commafeed.backend.model.FeedEntryContent;
|
import com.commafeed.backend.model.FeedEntryContent;
|
||||||
import com.commafeed.backend.model.FeedEntryStatus;
|
|
||||||
import com.commafeed.backend.model.FeedFeedEntry;
|
|
||||||
import com.commafeed.backend.model.FeedSubscription;
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
import com.commafeed.backend.model.User;
|
import com.commafeed.backend.model.User;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
@Stateless
|
@Stateless
|
||||||
public class FeedUpdateService {
|
public class FeedUpdateService {
|
||||||
|
|
||||||
@PersistenceContext
|
@PersistenceContext
|
||||||
protected EntityManager em;
|
protected EntityManager em;
|
||||||
|
|
||||||
@@ -45,47 +43,33 @@ public class FeedUpdateService {
|
|||||||
@Inject
|
@Inject
|
||||||
CacheService cache;
|
CacheService cache;
|
||||||
|
|
||||||
public void updateEntry(Feed feed, FeedEntry entry,
|
@Inject
|
||||||
List<FeedSubscription> subscriptions) {
|
FeedEntryContentService feedEntryContentService;
|
||||||
|
|
||||||
EntryWithFeed existing = feedEntryDAO.findExisting(entry.getGuid(),
|
/**
|
||||||
entry.getUrl(), feed.getId());
|
* this is NOT thread-safe
|
||||||
|
*/
|
||||||
|
public void updateEntry(Feed feed, FeedEntry entry, List<FeedSubscription> subscriptions) {
|
||||||
|
|
||||||
FeedEntry update = null;
|
FeedEntry existing = feedEntryDAO.findExisting(entry.getGuid(), entry.getUrl(), feed.getId());
|
||||||
FeedFeedEntry ffe = null;
|
if (existing != null) {
|
||||||
if (existing == null) {
|
return;
|
||||||
entry.setAuthor(FeedUtils.truncate(FeedUtils.handleContent(
|
|
||||||
entry.getAuthor(), feed.getLink(), true), 128));
|
|
||||||
FeedEntryContent content = entry.getContent();
|
|
||||||
content.setTitle(FeedUtils.truncate(FeedUtils.handleContent(
|
|
||||||
content.getTitle(), feed.getLink(), true), 2048));
|
|
||||||
content.setContent(FeedUtils.handleContent(content.getContent(),
|
|
||||||
feed.getLink(), false));
|
|
||||||
|
|
||||||
entry.setInserted(new Date());
|
|
||||||
ffe = new FeedFeedEntry(feed, entry);
|
|
||||||
|
|
||||||
update = entry;
|
|
||||||
} else if (existing.ffe == null) {
|
|
||||||
ffe = new FeedFeedEntry(feed, existing.entry);
|
|
||||||
update = existing.entry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (update != null) {
|
FeedEntryContent content = feedEntryContentService.findOrCreate(entry.getContent(), feed.getLink());
|
||||||
List<FeedEntryStatus> statusUpdateList = Lists.newArrayList();
|
entry.setGuidHash(DigestUtils.sha1Hex(entry.getGuid()));
|
||||||
List<User> users = Lists.newArrayList();
|
entry.setContent(content);
|
||||||
for (FeedSubscription sub : subscriptions) {
|
entry.setInserted(new Date());
|
||||||
User user = sub.getUser();
|
entry.setFeed(feed);
|
||||||
FeedEntryStatus status = new FeedEntryStatus(user, sub, update);
|
|
||||||
status.setSubscription(sub);
|
List<User> users = Lists.newArrayList();
|
||||||
statusUpdateList.add(status);
|
for (FeedSubscription sub : subscriptions) {
|
||||||
users.add(user);
|
User user = sub.getUser();
|
||||||
}
|
users.add(user);
|
||||||
cache.invalidateUserData(users.toArray(new User[0]));
|
|
||||||
feedEntryDAO.saveOrUpdate(update);
|
|
||||||
feedEntryStatusDAO.saveOrUpdate(statusUpdateList);
|
|
||||||
em.persist(ffe);
|
|
||||||
metricsBean.entryUpdated(statusUpdateList.size());
|
|
||||||
}
|
}
|
||||||
|
feedEntryDAO.saveOrUpdate(entry);
|
||||||
|
metricsBean.entryInserted();
|
||||||
|
cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0]));
|
||||||
|
cache.invalidateUserRootCategory(users.toArray(new User[0]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ public class MailService implements Serializable {
|
|||||||
@Inject
|
@Inject
|
||||||
ApplicationSettingsService applicationSettingsService;
|
ApplicationSettingsService applicationSettingsService;
|
||||||
|
|
||||||
public void sendMail(User user, String subject, String content)
|
public void sendMail(User user, String subject, String content) throws Exception {
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
ApplicationSettings settings = applicationSettingsService.get();
|
ApplicationSettings settings = applicationSettingsService.get();
|
||||||
|
|
||||||
@@ -50,8 +49,7 @@ public class MailService implements Serializable {
|
|||||||
|
|
||||||
Message message = new MimeMessage(session);
|
Message message = new MimeMessage(session);
|
||||||
message.setFrom(new InternetAddress(username, "CommaFeed"));
|
message.setFrom(new InternetAddress(username, "CommaFeed"));
|
||||||
message.setRecipients(Message.RecipientType.TO,
|
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(dest));
|
||||||
InternetAddress.parse(dest));
|
|
||||||
message.setSubject("CommaFeed - " + subject);
|
message.setSubject("CommaFeed - " + subject);
|
||||||
message.setContent(content, "text/html; charset=utf-8");
|
message.setContent(content, "text/html; charset=utf-8");
|
||||||
|
|
||||||
|
|||||||
@@ -53,8 +53,7 @@ public class PasswordEncryptionService implements Serializable {
|
|||||||
// http://blog.crackpassword.com/2010/09/smartphone-forensics-cracking-blackberry-backup-passwords/
|
// http://blog.crackpassword.com/2010/09/smartphone-forensics-cracking-blackberry-backup-passwords/
|
||||||
int iterations = 20000;
|
int iterations = 20000;
|
||||||
|
|
||||||
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations,
|
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, derivedKeyLength);
|
||||||
derivedKeyLength);
|
|
||||||
|
|
||||||
byte[] bytes = null;
|
byte[] bytes = null;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -48,15 +48,13 @@ public class UserService {
|
|||||||
|
|
||||||
User user = userDAO.findByName(name);
|
User user = userDAO.findByName(name);
|
||||||
if (user != null && !user.isDisabled()) {
|
if (user != null && !user.isDisabled()) {
|
||||||
boolean authenticated = encryptionService.authenticate(password,
|
boolean authenticated = encryptionService.authenticate(password, user.getPassword(), user.getSalt());
|
||||||
user.getPassword(), user.getSalt());
|
|
||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
Date lastLogin = user.getLastLogin();
|
Date lastLogin = user.getLastLogin();
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
// only update lastLogin field every hour in order to not
|
// only update lastLogin field every hour in order to not
|
||||||
// invalidate the cache everytime someone logs in
|
// invalidate the cache everytime someone logs in
|
||||||
if (lastLogin == null
|
if (lastLogin == null || lastLogin.before(DateUtils.addHours(now, -1))) {
|
||||||
|| lastLogin.before(DateUtils.addHours(now, -1))) {
|
|
||||||
user.setLastLogin(now);
|
user.setLastLogin(now);
|
||||||
userDAO.saveOrUpdate(user);
|
userDAO.saveOrUpdate(user);
|
||||||
}
|
}
|
||||||
@@ -66,39 +64,30 @@ public class UserService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public User register(String name, String password, String email,
|
public User register(String name, String password, String email, Collection<Role> roles) {
|
||||||
Collection<Role> roles) {
|
|
||||||
return register(name, password, email, roles, false);
|
return register(name, password, email, roles, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public User register(String name, String password, String email,
|
public User register(String name, String password, String email, Collection<Role> roles, boolean forceRegistration) {
|
||||||
Collection<Role> roles, boolean forceRegistration) {
|
|
||||||
|
|
||||||
Preconditions.checkNotNull(name);
|
Preconditions.checkNotNull(name);
|
||||||
Preconditions.checkArgument(StringUtils.length(name) <= 32,
|
Preconditions.checkArgument(StringUtils.length(name) <= 32, "Name too long (32 characters maximum)");
|
||||||
"Name too long (32 characters maximum)");
|
|
||||||
Preconditions.checkNotNull(password);
|
Preconditions.checkNotNull(password);
|
||||||
|
|
||||||
if (!forceRegistration) {
|
if (!forceRegistration) {
|
||||||
Preconditions.checkState(applicationSettingsService.get()
|
Preconditions.checkState(applicationSettingsService.get().isAllowRegistrations(),
|
||||||
.isAllowRegistrations(),
|
|
||||||
"Registrations are closed on this CommaFeed instance");
|
"Registrations are closed on this CommaFeed instance");
|
||||||
|
|
||||||
Preconditions.checkNotNull(email);
|
Preconditions.checkNotNull(email);
|
||||||
Preconditions.checkArgument(StringUtils.length(name) >= 3,
|
Preconditions.checkArgument(StringUtils.length(name) >= 3, "Name too short (3 characters minimum)");
|
||||||
"Name too short (3 characters minimum)");
|
Preconditions
|
||||||
Preconditions.checkArgument(
|
.checkArgument(forceRegistration || StringUtils.length(password) >= 6, "Password too short (6 characters maximum)");
|
||||||
forceRegistration || StringUtils.length(password) >= 6,
|
Preconditions.checkArgument(StringUtils.contains(email, "@"), "Invalid email address");
|
||||||
"Password too short (6 characters maximum)");
|
|
||||||
Preconditions.checkArgument(StringUtils.contains(email, "@"),
|
|
||||||
"Invalid email address");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Preconditions.checkArgument(userDAO.findByName(name) == null,
|
Preconditions.checkArgument(userDAO.findByName(name) == null, "Name already taken");
|
||||||
"Name already taken");
|
|
||||||
if (StringUtils.isNotBlank(email)) {
|
if (StringUtils.isNotBlank(email)) {
|
||||||
Preconditions.checkArgument(userDAO.findByEmail(email) == null,
|
Preconditions.checkArgument(userDAO.findByEmail(email) == null, "Email already taken");
|
||||||
"Email already taken");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
User user = new User();
|
User user = new User();
|
||||||
@@ -122,8 +111,7 @@ public class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String generateApiKey(User user) {
|
public String generateApiKey(User user) {
|
||||||
byte[] key = encryptionService.getEncryptedPassword(UUID.randomUUID()
|
byte[] key = encryptionService.getEncryptedPassword(UUID.randomUUID().toString(), user.getSalt());
|
||||||
.toString(), user.getSalt());
|
|
||||||
return DigestUtils.sha1Hex(key);
|
return DigestUtils.sha1Hex(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,15 +49,12 @@ import com.commafeed.frontend.utils.exception.DisplayExceptionPage;
|
|||||||
|
|
||||||
public class CommaFeedApplication extends AuthenticatedWebApplication {
|
public class CommaFeedApplication extends AuthenticatedWebApplication {
|
||||||
|
|
||||||
private static Logger log = LoggerFactory
|
private static Logger log = LoggerFactory.getLogger(CommaFeedApplication.class);
|
||||||
.getLogger(CommaFeedApplication.class);
|
|
||||||
|
|
||||||
public CommaFeedApplication() {
|
public CommaFeedApplication() {
|
||||||
super();
|
super();
|
||||||
String prod = ResourceBundle.getBundle("application").getString(
|
String prod = ResourceBundle.getBundle("application").getString("production");
|
||||||
"production");
|
setConfigurationType(Boolean.valueOf(prod) ? RuntimeConfigurationType.DEPLOYMENT : RuntimeConfigurationType.DEVELOPMENT);
|
||||||
setConfigurationType(Boolean.valueOf(prod) ? RuntimeConfigurationType.DEPLOYMENT
|
|
||||||
: RuntimeConfigurationType.DEVELOPMENT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -66,16 +63,16 @@ public class CommaFeedApplication extends AuthenticatedWebApplication {
|
|||||||
|
|
||||||
mountPage("welcome", WelcomePage.class);
|
mountPage("welcome", WelcomePage.class);
|
||||||
mountPage("demo", DemoLoginPage.class);
|
mountPage("demo", DemoLoginPage.class);
|
||||||
|
|
||||||
mountPage("recover", PasswordRecoveryPage.class);
|
mountPage("recover", PasswordRecoveryPage.class);
|
||||||
mountPage("recover2", PasswordRecoveryCallbackPage.class);
|
mountPage("recover2", PasswordRecoveryCallbackPage.class);
|
||||||
|
|
||||||
mountPage("logout", LogoutPage.class);
|
mountPage("logout", LogoutPage.class);
|
||||||
mountPage("error", DisplayExceptionPage.class);
|
mountPage("error", DisplayExceptionPage.class);
|
||||||
|
|
||||||
// mountPage("google/import/redirect", GoogleImportRedirectPage.class);
|
// mountPage("google/import/redirect", GoogleImportRedirectPage.class);
|
||||||
// mountPage(GoogleImportCallbackPage.PAGE_PATH,
|
// mountPage(GoogleImportCallbackPage.PAGE_PATH,
|
||||||
// GoogleImportCallbackPage.class);
|
// GoogleImportCallbackPage.class);
|
||||||
|
|
||||||
mountPage("next", NextUnreadRedirectPage.class);
|
mountPage("next", NextUnreadRedirectPage.class);
|
||||||
|
|
||||||
@@ -89,8 +86,7 @@ public class CommaFeedApplication extends AuthenticatedWebApplication {
|
|||||||
setHeaderResponseDecorator(new IHeaderResponseDecorator() {
|
setHeaderResponseDecorator(new IHeaderResponseDecorator() {
|
||||||
@Override
|
@Override
|
||||||
public IHeaderResponse decorate(IHeaderResponse response) {
|
public IHeaderResponse decorate(IHeaderResponse response) {
|
||||||
return new JavaScriptFilteredIntoFooterHeaderResponse(response,
|
return new JavaScriptFilteredIntoFooterHeaderResponse(response, "footer-container");
|
||||||
"footer-container");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -100,61 +96,52 @@ public class CommaFeedApplication extends AuthenticatedWebApplication {
|
|||||||
AjaxRequestTarget target = cycle.find(AjaxRequestTarget.class);
|
AjaxRequestTarget target = cycle.find(AjaxRequestTarget.class);
|
||||||
// redirect to the error page if ajax request, render error on
|
// redirect to the error page if ajax request, render error on
|
||||||
// current page otherwise
|
// current page otherwise
|
||||||
RedirectPolicy policy = target == null ? RedirectPolicy.NEVER_REDIRECT
|
RedirectPolicy policy = target == null ? RedirectPolicy.NEVER_REDIRECT : RedirectPolicy.AUTO_REDIRECT;
|
||||||
: RedirectPolicy.AUTO_REDIRECT;
|
return new RenderPageRequestHandler(new PageProvider(new DisplayExceptionPage(ex)), policy);
|
||||||
return new RenderPageRequestHandler(new PageProvider(
|
|
||||||
new DisplayExceptionPage(ex)), policy);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSecurity() {
|
private void setupSecurity() {
|
||||||
getSecuritySettings().setAuthenticationStrategy(
|
getSecuritySettings().setAuthenticationStrategy(new DefaultAuthenticationStrategy("LoggedIn") {
|
||||||
new DefaultAuthenticationStrategy("LoggedIn") {
|
|
||||||
|
|
||||||
private CookieUtils cookieUtils = null;
|
private CookieUtils cookieUtils = null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CookieUtils getCookieUtils() {
|
protected CookieUtils getCookieUtils() {
|
||||||
|
|
||||||
if (cookieUtils == null) {
|
if (cookieUtils == null) {
|
||||||
cookieUtils = new CookieUtils() {
|
cookieUtils = new CookieUtils() {
|
||||||
@Override
|
@Override
|
||||||
protected void initializeCookie(Cookie cookie) {
|
protected void initializeCookie(Cookie cookie) {
|
||||||
super.initializeCookie(cookie);
|
super.initializeCookie(cookie);
|
||||||
cookie.setHttpOnly(true);
|
cookie.setHttpOnly(true);
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return cookieUtils;
|
};
|
||||||
}
|
}
|
||||||
});
|
return cookieUtils;
|
||||||
getSecuritySettings().setAuthorizationStrategy(
|
}
|
||||||
new IAuthorizationStrategy() {
|
});
|
||||||
|
getSecuritySettings().setAuthorizationStrategy(new IAuthorizationStrategy() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T extends IRequestableComponent> boolean isInstantiationAuthorized(
|
public <T extends IRequestableComponent> boolean isInstantiationAuthorized(Class<T> componentClass) {
|
||||||
Class<T> componentClass) {
|
boolean authorized = true;
|
||||||
boolean authorized = true;
|
|
||||||
|
|
||||||
boolean restricted = componentClass
|
boolean restricted = componentClass.isAnnotationPresent(SecurityCheck.class);
|
||||||
.isAnnotationPresent(SecurityCheck.class);
|
if (restricted) {
|
||||||
if (restricted) {
|
SecurityCheck annotation = componentClass.getAnnotation(SecurityCheck.class);
|
||||||
SecurityCheck annotation = componentClass
|
Roles roles = CommaFeedSession.get().getRoles();
|
||||||
.getAnnotation(SecurityCheck.class);
|
authorized = roles.hasAnyRole(new Roles(annotation.value().name()));
|
||||||
Roles roles = CommaFeedSession.get().getRoles();
|
}
|
||||||
authorized = roles.hasAnyRole(new Roles(annotation
|
return authorized;
|
||||||
.value().name()));
|
}
|
||||||
}
|
|
||||||
return authorized;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActionAuthorized(Component component,
|
public boolean isActionAuthorized(Component component, Action action) {
|
||||||
Action action) {
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -164,10 +151,8 @@ public class CommaFeedApplication extends AuthenticatedWebApplication {
|
|||||||
|
|
||||||
protected void setupInjection() {
|
protected void setupInjection() {
|
||||||
try {
|
try {
|
||||||
BeanManager beanManager = (BeanManager) new InitialContext()
|
BeanManager beanManager = (BeanManager) new InitialContext().lookup("java:comp/BeanManager");
|
||||||
.lookup("java:comp/BeanManager");
|
new CdiConfiguration(beanManager).setPropagation(ConversationPropagation.NONE).configure(this);
|
||||||
new CdiConfiguration(beanManager).setPropagation(
|
|
||||||
ConversationPropagation.NONE).configure(this);
|
|
||||||
} catch (NamingException e) {
|
} catch (NamingException e) {
|
||||||
log.warn("Could not locate bean manager. CDI is disabled.");
|
log.warn("Could not locate bean manager. CDI is disabled.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ import com.wordnik.swagger.annotations.ApiProperty;
|
|||||||
@ApiClass("Entry details")
|
@ApiClass("Entry details")
|
||||||
public class Entry implements Serializable {
|
public class Entry implements Serializable {
|
||||||
|
|
||||||
public static Entry build(FeedEntryStatus status, String publicUrl,
|
public static Entry build(FeedEntryStatus status, String publicUrl, boolean proxyImages) {
|
||||||
boolean proxyImages) {
|
|
||||||
Entry entry = new Entry();
|
Entry entry = new Entry();
|
||||||
|
|
||||||
FeedEntry feedEntry = status.getEntry();
|
FeedEntry feedEntry = status.getEntry();
|
||||||
@@ -33,14 +32,14 @@ public class Entry implements Serializable {
|
|||||||
|
|
||||||
entry.setRead(status.isRead());
|
entry.setRead(status.isRead());
|
||||||
entry.setStarred(status.isStarred());
|
entry.setStarred(status.isStarred());
|
||||||
|
entry.setMarkable(status.isMarkable());
|
||||||
|
|
||||||
entry.setId(String.valueOf(feedEntry.getId()));
|
entry.setId(String.valueOf(feedEntry.getId()));
|
||||||
entry.setGuid(feedEntry.getGuid());
|
entry.setGuid(feedEntry.getGuid());
|
||||||
entry.setTitle(feedEntry.getContent().getTitle());
|
entry.setTitle(feedEntry.getContent().getTitle());
|
||||||
entry.setContent(FeedUtils.proxyImages(feedEntry.getContent()
|
entry.setContent(FeedUtils.proxyImages(feedEntry.getContent().getContent(), publicUrl, proxyImages));
|
||||||
.getContent(), publicUrl, proxyImages));
|
|
||||||
entry.setRtl(FeedUtils.isRTL(feedEntry));
|
entry.setRtl(FeedUtils.isRTL(feedEntry));
|
||||||
entry.setAuthor(feedEntry.getAuthor());
|
entry.setAuthor(feedEntry.getContent().getAuthor());
|
||||||
entry.setEnclosureUrl(feedEntry.getContent().getEnclosureUrl());
|
entry.setEnclosureUrl(feedEntry.getContent().getEnclosureUrl());
|
||||||
entry.setEnclosureType(feedEntry.getContent().getEnclosureType());
|
entry.setEnclosureType(feedEntry.getContent().getEnclosureType());
|
||||||
entry.setDate(feedEntry.getUpdated());
|
entry.setDate(feedEntry.getUpdated());
|
||||||
@@ -124,6 +123,9 @@ public class Entry implements Serializable {
|
|||||||
@ApiProperty("starred status")
|
@ApiProperty("starred status")
|
||||||
private boolean starred;
|
private boolean starred;
|
||||||
|
|
||||||
|
@ApiProperty("wether the entry is still markable (old entry statuses are discarded)")
|
||||||
|
private boolean markable;
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -268,4 +270,12 @@ public class Entry implements Serializable {
|
|||||||
this.insertedDate = insertedDate;
|
this.insertedDate = insertedDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isMarkable() {
|
||||||
|
return markable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMarkable(boolean markable) {
|
||||||
|
this.markable = markable;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ import com.wordnik.swagger.annotations.ApiProperty;
|
|||||||
@ApiClass("User information")
|
@ApiClass("User information")
|
||||||
public class Subscription implements Serializable {
|
public class Subscription implements Serializable {
|
||||||
|
|
||||||
public static Subscription build(FeedSubscription subscription,
|
public static Subscription build(FeedSubscription subscription, String publicUrl, long unreadCount) {
|
||||||
String publicUrl, long unreadCount) {
|
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
FeedCategory category = subscription.getCategory();
|
FeedCategory category = subscription.getCategory();
|
||||||
Feed feed = subscription.getFeed();
|
Feed feed = subscription.getFeed();
|
||||||
@@ -35,12 +34,9 @@ public class Subscription implements Serializable {
|
|||||||
sub.setFeedLink(feed.getLink());
|
sub.setFeedLink(feed.getLink());
|
||||||
sub.setIconUrl(FeedUtils.getFaviconUrl(subscription, publicUrl));
|
sub.setIconUrl(FeedUtils.getFaviconUrl(subscription, publicUrl));
|
||||||
sub.setLastRefresh(feed.getLastUpdated());
|
sub.setLastRefresh(feed.getLastUpdated());
|
||||||
sub.setNextRefresh((feed.getDisabledUntil() != null && feed
|
sub.setNextRefresh((feed.getDisabledUntil() != null && feed.getDisabledUntil().before(now)) ? null : feed.getDisabledUntil());
|
||||||
.getDisabledUntil().before(now)) ? null : feed
|
|
||||||
.getDisabledUntil());
|
|
||||||
sub.setUnread(unreadCount);
|
sub.setUnread(unreadCount);
|
||||||
sub.setCategoryId(category == null ? null : String.valueOf(category
|
sub.setCategoryId(category == null ? null : String.valueOf(category.getId()));
|
||||||
.getId()));
|
|
||||||
return sub;
|
return sub;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import com.wordnik.swagger.annotations.ApiProperty;
|
|||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
@ApiClass("Feed information request")
|
@ApiClass("Feed information request")
|
||||||
public class FeedInfoRequest implements Serializable {
|
public class FeedInfoRequest implements Serializable {
|
||||||
|
|
||||||
@ApiProperty(value = "feed url", required = true)
|
@ApiProperty(value = "feed url", required = true)
|
||||||
private String url;
|
private String url;
|
||||||
|
|
||||||
@@ -25,7 +25,5 @@ public class FeedInfoRequest implements Serializable {
|
|||||||
public void setUrl(String url) {
|
public void setUrl(String url) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ public class MarkRequest implements Serializable {
|
|||||||
@ApiProperty(value = "mark as read or unread")
|
@ApiProperty(value = "mark as read or unread")
|
||||||
private boolean read;
|
private boolean read;
|
||||||
|
|
||||||
@ApiProperty(value = "only entries older than this, pass the timestamp you got from the entry list to prevent marking an entry that was not retrieved", required = false)
|
@ApiProperty(
|
||||||
|
value = "only entries older than this, pass the timestamp you got from the entry list to prevent marking an entry that was not retrieved",
|
||||||
|
required = false)
|
||||||
private Long olderThan;
|
private Long olderThan;
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
|
|||||||
@@ -79,15 +79,12 @@ public abstract class BasePage extends WebPage {
|
|||||||
if (user != null) {
|
if (user != null) {
|
||||||
UserSettings settings = userSettingsDAO.findByUser(user);
|
UserSettings settings = userSettingsDAO.findByUser(user);
|
||||||
if (settings != null) {
|
if (settings != null) {
|
||||||
lang = settings.getLanguage() == null ? "en" : settings
|
lang = settings.getLanguage() == null ? "en" : settings.getLanguage();
|
||||||
.getLanguage();
|
theme = settings.getTheme() == null ? "default" : settings.getTheme();
|
||||||
theme = settings.getTheme() == null ? "default" : settings
|
|
||||||
.getTheme();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
add(new TransparentWebMarkupContainer("html").setMarkupId(
|
add(new TransparentWebMarkupContainer("html").setMarkupId("theme-" + theme).add(new AttributeModifier("lang", lang)));
|
||||||
"theme-" + theme).add(new AttributeModifier("lang", lang)));
|
|
||||||
|
|
||||||
settings = applicationSettingsService.get();
|
settings = applicationSettingsService.get();
|
||||||
add(new HeaderResponseContainer("footer-container", "footer-container"));
|
add(new HeaderResponseContainer("footer-container", "footer-container"));
|
||||||
@@ -107,8 +104,7 @@ public abstract class BasePage extends WebPage {
|
|||||||
if (getApplication().getConfigurationType() == RuntimeConfigurationType.DEPLOYMENT) {
|
if (getApplication().getConfigurationType() == RuntimeConfigurationType.DEPLOYMENT) {
|
||||||
long startupTime = startupBean.getStartupTime();
|
long startupTime = startupBean.getStartupTime();
|
||||||
String suffix = "?" + startupTime;
|
String suffix = "?" + startupTime;
|
||||||
response.render(JavaScriptHeaderItem.forUrl("static/all.js"
|
response.render(JavaScriptHeaderItem.forUrl("static/all.js" + suffix));
|
||||||
+ suffix));
|
|
||||||
response.render(CssHeaderItem.forUrl("static/all.css" + suffix));
|
response.render(CssHeaderItem.forUrl("static/all.css" + suffix));
|
||||||
} else {
|
} else {
|
||||||
response.render(JavaScriptHeaderItem.forUrl("wro/lib.js"));
|
response.render(JavaScriptHeaderItem.forUrl("wro/lib.js"));
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ public class DemoLoginPage extends WebPage {
|
|||||||
UserService userService;
|
UserService userService;
|
||||||
|
|
||||||
public DemoLoginPage() {
|
public DemoLoginPage() {
|
||||||
CommaFeedSession.get().authenticate(StartupBean.USERNAME_DEMO,
|
CommaFeedSession.get().authenticate(StartupBean.USERNAME_DEMO, StartupBean.USERNAME_DEMO);
|
||||||
StartupBean.USERNAME_DEMO);
|
|
||||||
setResponsePage(getApplication().getHomePage());
|
setResponsePage(getApplication().getHomePage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,8 +55,7 @@ public class GoogleImportCallbackPage extends WebPage {
|
|||||||
if (request.getQueryString() != null) {
|
if (request.getQueryString() != null) {
|
||||||
urlBuffer.append('?').append(request.getQueryString());
|
urlBuffer.append('?').append(request.getQueryString());
|
||||||
}
|
}
|
||||||
AuthorizationCodeResponseUrl responseUrl = new AuthorizationCodeResponseUrl(
|
AuthorizationCodeResponseUrl responseUrl = new AuthorizationCodeResponseUrl(urlBuffer.toString());
|
||||||
urlBuffer.toString());
|
|
||||||
String code = responseUrl.getCode();
|
String code = responseUrl.getCode();
|
||||||
|
|
||||||
if (responseUrl.getError() != null) {
|
if (responseUrl.getError() != null) {
|
||||||
@@ -73,8 +72,8 @@ public class GoogleImportCallbackPage extends WebPage {
|
|||||||
HttpTransport httpTransport = new NetHttpTransport();
|
HttpTransport httpTransport = new NetHttpTransport();
|
||||||
JacksonFactory jsonFactory = new JacksonFactory();
|
JacksonFactory jsonFactory = new JacksonFactory();
|
||||||
|
|
||||||
AuthorizationCodeTokenRequest tokenRequest = new AuthorizationCodeTokenRequest(
|
AuthorizationCodeTokenRequest tokenRequest = new AuthorizationCodeTokenRequest(httpTransport, jsonFactory, new GenericUrl(
|
||||||
httpTransport, jsonFactory, new GenericUrl(TOKEN_URL), code);
|
TOKEN_URL), code);
|
||||||
tokenRequest.setRedirectUri(redirectUri);
|
tokenRequest.setRedirectUri(redirectUri);
|
||||||
tokenRequest.put("client_id", clientId);
|
tokenRequest.put("client_id", clientId);
|
||||||
tokenRequest.put("client_secret", clientSecret);
|
tokenRequest.put("client_secret", clientSecret);
|
||||||
@@ -87,16 +86,13 @@ public class GoogleImportCallbackPage extends WebPage {
|
|||||||
TokenResponse tokenResponse = tokenRequest.execute();
|
TokenResponse tokenResponse = tokenRequest.execute();
|
||||||
String accessToken = tokenResponse.getAccessToken();
|
String accessToken = tokenResponse.getAccessToken();
|
||||||
|
|
||||||
HttpRequest httpRequest = httpTransport.createRequestFactory()
|
HttpRequest httpRequest = httpTransport.createRequestFactory().buildGetRequest(new GenericUrl(EXPORT_URL));
|
||||||
.buildGetRequest(new GenericUrl(EXPORT_URL));
|
BearerToken.authorizationHeaderAccessMethod().intercept(httpRequest, accessToken);
|
||||||
BearerToken.authorizationHeaderAccessMethod().intercept(
|
|
||||||
httpRequest, accessToken);
|
|
||||||
String opml = httpRequest.execute().parseAsString();
|
String opml = httpRequest.execute().parseAsString();
|
||||||
User user = CommaFeedSession.get().getUser();
|
User user = CommaFeedSession.get().getUser();
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
if (StartupBean.USERNAME_DEMO.equals(user.getName())) {
|
if (StartupBean.USERNAME_DEMO.equals(user.getName())) {
|
||||||
throw new DisplayException(
|
throw new DisplayException("Import is disabled for the demo account");
|
||||||
"Import is disabled for the demo account");
|
|
||||||
}
|
}
|
||||||
importer.importOpml(CommaFeedSession.get().getUser(), opml);
|
importer.importOpml(CommaFeedSession.get().getUser(), opml);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ import com.commafeed.backend.services.ApplicationSettingsService;
|
|||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public class GoogleImportRedirectPage extends WebPage {
|
public class GoogleImportRedirectPage extends WebPage {
|
||||||
|
|
||||||
private static Logger log = Logger
|
private static Logger log = Logger.getLogger(GoogleImportRedirectPage.class);
|
||||||
.getLogger(GoogleImportRedirectPage.class);
|
|
||||||
|
|
||||||
private static final String SCOPE = "https://www.google.com/reader/subscriptions/export email profile";
|
private static final String SCOPE = "https://www.google.com/reader/subscriptions/export email profile";
|
||||||
private static final String AUTH_URL = "https://accounts.google.com/o/oauth2/auth";
|
private static final String AUTH_URL = "https://accounts.google.com/o/oauth2/auth";
|
||||||
|
|||||||
@@ -18,16 +18,12 @@ public class HomePage extends BasePage {
|
|||||||
public void renderHead(IHeaderResponse response) {
|
public void renderHead(IHeaderResponse response) {
|
||||||
super.renderHead(response);
|
super.renderHead(response);
|
||||||
|
|
||||||
response.render(CssHeaderItem.forReference(
|
response.render(CssHeaderItem.forReference(new UserCustomCssReference() {
|
||||||
new UserCustomCssReference() {
|
@Override
|
||||||
@Override
|
protected String getCss() {
|
||||||
protected String getCss() {
|
UserSettings settings = userSettingsDAO.findByUser(CommaFeedSession.get().getUser());
|
||||||
UserSettings settings = userSettingsDAO
|
return settings == null ? null : settings.getCustomCss();
|
||||||
.findByUser(CommaFeedSession.get().getUser());
|
}
|
||||||
return settings == null ? null : settings
|
}, new PageParameters().add("_t", System.currentTimeMillis()), null));
|
||||||
.getCustomCss();
|
|
||||||
}
|
|
||||||
}, new PageParameters().add("_t", System.currentTimeMillis()),
|
|
||||||
null));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,20 +52,15 @@ public class NextUnreadRedirectPage extends WebPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<FeedEntryStatus> statuses = null;
|
List<FeedEntryStatus> statuses = null;
|
||||||
if (StringUtils.isBlank(categoryId)
|
if (StringUtils.isBlank(categoryId) || CategoryREST.ALL.equals(categoryId)) {
|
||||||
|| CategoryREST.ALL.equals(categoryId)) {
|
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
|
||||||
statuses = feedEntryStatusDAO.findAllUnread(user, null, 0, 1,
|
statuses = feedEntryStatusDAO.findBySubscriptions(subs, true, null, null, 0, 1, order, true);
|
||||||
order, true);
|
|
||||||
} else {
|
} else {
|
||||||
FeedCategory category = feedCategoryDAO.findById(user,
|
FeedCategory category = feedCategoryDAO.findById(user, Long.valueOf(categoryId));
|
||||||
Long.valueOf(categoryId));
|
|
||||||
if (category != null) {
|
if (category != null) {
|
||||||
List<FeedCategory> children = feedCategoryDAO
|
List<FeedCategory> children = feedCategoryDAO.findAllChildrenCategories(user, category);
|
||||||
.findAllChildrenCategories(user, category);
|
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findByCategories(user, children);
|
||||||
List<FeedSubscription> subscriptions = feedSubscriptionDAO
|
statuses = feedEntryStatusDAO.findBySubscriptions(subscriptions, true, null, null, 0, 1, order, true);
|
||||||
.findByCategories(user, children);
|
|
||||||
statuses = feedEntryStatusDAO.findUnreadBySubscriptions(
|
|
||||||
subscriptions, null, 0, 1, order, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,12 +40,10 @@ public class PasswordRecoveryCallbackPage extends BasePage {
|
|||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new DisplayException("email not found");
|
throw new DisplayException("email not found");
|
||||||
}
|
}
|
||||||
if (user.getRecoverPasswordToken() == null
|
if (user.getRecoverPasswordToken() == null || !user.getRecoverPasswordToken().equals(token)) {
|
||||||
|| !user.getRecoverPasswordToken().equals(token)) {
|
|
||||||
throw new DisplayException("invalid token");
|
throw new DisplayException("invalid token");
|
||||||
}
|
}
|
||||||
if (user.getRecoverPasswordTokenDate().before(
|
if (user.getRecoverPasswordTokenDate().before(DateUtils.addDays(new Date(), -2))) {
|
||||||
DateUtils.addDays(new Date(), -2))) {
|
|
||||||
throw new DisplayException("token expired");
|
throw new DisplayException("token expired");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,8 +55,7 @@ public class PasswordRecoveryCallbackPage extends BasePage {
|
|||||||
protected void onSubmit() {
|
protected void onSubmit() {
|
||||||
String passwd = password.getObject();
|
String passwd = password.getObject();
|
||||||
if (StringUtils.equals(passwd, confirm.getObject())) {
|
if (StringUtils.equals(passwd, confirm.getObject())) {
|
||||||
byte[] password = encryptionService.getEncryptedPassword(
|
byte[] password = encryptionService.getEncryptedPassword(passwd, user.getSalt());
|
||||||
passwd, user.getSalt());
|
|
||||||
user.setPassword(password);
|
user.setPassword(password);
|
||||||
user.setApiKey(userService.generateApiKey(user));
|
user.setApiKey(userService.generateApiKey(user));
|
||||||
user.setRecoverPasswordToken(null);
|
user.setRecoverPasswordToken(null);
|
||||||
@@ -71,10 +68,8 @@ public class PasswordRecoveryCallbackPage extends BasePage {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
add(form);
|
add(form);
|
||||||
form.add(new PasswordTextField("password", password).setResetPassword(
|
form.add(new PasswordTextField("password", password).setResetPassword(true).add(StringValidator.minimumLength(6)));
|
||||||
true).add(StringValidator.minimumLength(6)));
|
form.add(new PasswordTextField("confirm", confirm).setResetPassword(true).add(StringValidator.minimumLength(6)));
|
||||||
form.add(new PasswordTextField("confirm", confirm).setResetPassword(
|
|
||||||
true).add(StringValidator.minimumLength(6)));
|
|
||||||
|
|
||||||
form.add(new BookmarkablePageLink<Void>("cancel", HomePage.class));
|
form.add(new BookmarkablePageLink<Void>("cancel", HomePage.class));
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ import com.commafeed.frontend.pages.components.BootstrapFeedbackPanel;
|
|||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public class PasswordRecoveryPage extends BasePage {
|
public class PasswordRecoveryPage extends BasePage {
|
||||||
|
|
||||||
private static Logger log = LoggerFactory
|
private static Logger log = LoggerFactory.getLogger(PasswordRecoveryPage.class);
|
||||||
.getLogger(PasswordRecoveryPage.class);
|
|
||||||
|
|
||||||
public PasswordRecoveryPage() {
|
public PasswordRecoveryPage() {
|
||||||
|
|
||||||
@@ -37,12 +36,10 @@ public class PasswordRecoveryPage extends BasePage {
|
|||||||
error("Email not found.");
|
error("Email not found.");
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
user.setRecoverPasswordToken(DigestUtils.sha1Hex(UUID
|
user.setRecoverPasswordToken(DigestUtils.sha1Hex(UUID.randomUUID().toString()));
|
||||||
.randomUUID().toString()));
|
|
||||||
user.setRecoverPasswordTokenDate(new Date());
|
user.setRecoverPasswordTokenDate(new Date());
|
||||||
userDAO.saveOrUpdate(user);
|
userDAO.saveOrUpdate(user);
|
||||||
mailService.sendMail(user, "Password recovery",
|
mailService.sendMail(user, "Password recovery", buildEmailContent(user));
|
||||||
buildEmailContent(user));
|
|
||||||
info("Email sent.");
|
info("Email sent.");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
@@ -67,9 +64,7 @@ public class PasswordRecoveryPage extends BasePage {
|
|||||||
|
|
||||||
private String buildEmailContent(User user) throws Exception {
|
private String buildEmailContent(User user) throws Exception {
|
||||||
|
|
||||||
String publicUrl = FeedUtils
|
String publicUrl = FeedUtils.removeTrailingSlash(applicationSettingsService.get().getPublicUrl());
|
||||||
.removeTrailingSlash(applicationSettingsService.get()
|
|
||||||
.getPublicUrl());
|
|
||||||
publicUrl += "/recover2";
|
publicUrl += "/recover2";
|
||||||
|
|
||||||
return String
|
return String
|
||||||
@@ -78,11 +73,7 @@ public class PasswordRecoveryPage extends BasePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String callbackUrl(User user, String publicUrl) throws Exception {
|
private String callbackUrl(User user, String publicUrl) throws Exception {
|
||||||
return new URIBuilder(publicUrl)
|
return new URIBuilder(publicUrl).addParameter(PasswordRecoveryCallbackPage.PARAM_EMAIL, user.getEmail())
|
||||||
.addParameter(PasswordRecoveryCallbackPage.PARAM_EMAIL,
|
.addParameter(PasswordRecoveryCallbackPage.PARAM_TOKEN, user.getRecoverPasswordToken()).build().toURL().toString();
|
||||||
user.getEmail())
|
|
||||||
.addParameter(PasswordRecoveryCallbackPage.PARAM_TOKEN,
|
|
||||||
user.getRecoverPasswordToken()).build().toURL()
|
|
||||||
.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,8 +53,7 @@ public class TestRssPage extends WebPage {
|
|||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
getRequestCycle().scheduleRequestHandlerAfterCurrent(
|
getRequestCycle().scheduleRequestHandlerAfterCurrent(new TextRequestHandler("text/xml", "UTF-8", writer.toString()));
|
||||||
new TextRequestHandler("text/xml", "UTF-8", writer.toString()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,16 +15,14 @@ public class WelcomePage extends BasePage {
|
|||||||
ApplicationSettingsService applicationSettingsService;
|
ApplicationSettingsService applicationSettingsService;
|
||||||
|
|
||||||
public WelcomePage() {
|
public WelcomePage() {
|
||||||
add(new BookmarkablePageLink<Void>("logo-link", getApplication()
|
add(new BookmarkablePageLink<Void>("logo-link", getApplication().getHomePage()));
|
||||||
.getHomePage()));
|
|
||||||
add(new BookmarkablePageLink<Void>("demo-login", DemoLoginPage.class));
|
add(new BookmarkablePageLink<Void>("demo-login", DemoLoginPage.class));
|
||||||
add(new LoginPanel("login"));
|
add(new LoginPanel("login"));
|
||||||
add(new RegisterPanel("register") {
|
add(new RegisterPanel("register") {
|
||||||
@Override
|
@Override
|
||||||
protected void onConfigure() {
|
protected void onConfigure() {
|
||||||
super.onConfigure();
|
super.onConfigure();
|
||||||
setVisibilityAllowed(applicationSettingsService.get()
|
setVisibilityAllowed(applicationSettingsService.get().isAllowRegistrations());
|
||||||
.isAllowRegistrations());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,9 @@ public class LoginPanel extends SignInPanel {
|
|||||||
|
|
||||||
public LoginPanel(String id) {
|
public LoginPanel(String id) {
|
||||||
super(id);
|
super(id);
|
||||||
replace(new BootstrapFeedbackPanel("feedback",
|
replace(new BootstrapFeedbackPanel("feedback", new ContainerFeedbackMessageFilter(this)));
|
||||||
new ContainerFeedbackMessageFilter(this)));
|
|
||||||
Form<?> form = (Form<?>) get("signInForm");
|
Form<?> form = (Form<?>) get("signInForm");
|
||||||
form.add(new BookmarkablePageLink<Void>("recover",
|
form.add(new BookmarkablePageLink<Void>("recover", PasswordRecoveryPage.class) {
|
||||||
PasswordRecoveryPage.class){
|
|
||||||
@Override
|
@Override
|
||||||
protected void onConfigure() {
|
protected void onConfigure() {
|
||||||
super.onConfigure();
|
super.onConfigure();
|
||||||
|
|||||||
@@ -45,62 +45,51 @@ public class RegisterPanel extends Panel {
|
|||||||
|
|
||||||
IModel<RegistrationRequest> model = Model.of(new RegistrationRequest());
|
IModel<RegistrationRequest> model = Model.of(new RegistrationRequest());
|
||||||
|
|
||||||
Form<RegistrationRequest> form = new StatelessForm<RegistrationRequest>(
|
Form<RegistrationRequest> form = new StatelessForm<RegistrationRequest>("form", model) {
|
||||||
"form", model) {
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSubmit() {
|
protected void onSubmit() {
|
||||||
if (applicationSettingsService.get().isAllowRegistrations()) {
|
if (applicationSettingsService.get().isAllowRegistrations()) {
|
||||||
RegistrationRequest req = getModelObject();
|
RegistrationRequest req = getModelObject();
|
||||||
userService.register(req.getName(), req.getPassword(),
|
userService.register(req.getName(), req.getPassword(), req.getEmail(), Arrays.asList(Role.USER));
|
||||||
req.getEmail(), Arrays.asList(Role.USER));
|
|
||||||
|
|
||||||
IAuthenticationStrategy strategy = getApplication()
|
IAuthenticationStrategy strategy = getApplication().getSecuritySettings().getAuthenticationStrategy();
|
||||||
.getSecuritySettings().getAuthenticationStrategy();
|
|
||||||
strategy.save(req.getName(), req.getPassword());
|
strategy.save(req.getName(), req.getPassword());
|
||||||
CommaFeedSession.get().signIn(req.getName(),
|
CommaFeedSession.get().signIn(req.getName(), req.getPassword());
|
||||||
req.getPassword());
|
|
||||||
}
|
}
|
||||||
setResponsePage(getApplication().getHomePage());
|
setResponsePage(getApplication().getHomePage());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
add(form);
|
add(form);
|
||||||
add(new BootstrapFeedbackPanel("feedback",
|
add(new BootstrapFeedbackPanel("feedback", new ContainerFeedbackMessageFilter(form)));
|
||||||
new ContainerFeedbackMessageFilter(form)));
|
|
||||||
|
|
||||||
RegistrationRequest p = MF.p(RegistrationRequest.class);
|
RegistrationRequest p = MF.p(RegistrationRequest.class);
|
||||||
form.add(new RequiredTextField<String>("name", MF.m(model, p.getName()))
|
form.add(new RequiredTextField<String>("name", MF.m(model, p.getName())).add(StringValidator.lengthBetween(3, 32)).add(
|
||||||
.add(StringValidator.lengthBetween(3, 32)).add(
|
new IValidator<String>() {
|
||||||
new IValidator<String>() {
|
@Override
|
||||||
@Override
|
public void validate(IValidatable<String> validatable) {
|
||||||
public void validate(
|
String name = validatable.getValue();
|
||||||
IValidatable<String> validatable) {
|
User user = userDAO.findByName(name);
|
||||||
String name = validatable.getValue();
|
if (user != null) {
|
||||||
User user = userDAO.findByName(name);
|
validatable.error(new ValidationError("Name is already taken."));
|
||||||
if (user != null) {
|
}
|
||||||
validatable.error(new ValidationError(
|
}
|
||||||
"Name is already taken."));
|
}));
|
||||||
}
|
form.add(new PasswordTextField("password", MF.m(model, p.getPassword())).setResetPassword(false).add(
|
||||||
}
|
StringValidator.minimumLength(6)));
|
||||||
}));
|
form.add(new RequiredTextField<String>("email", MF.m(model, p.getEmail())) {
|
||||||
form.add(new PasswordTextField("password", MF.m(model, p.getPassword()))
|
|
||||||
.setResetPassword(false).add(StringValidator.minimumLength(6)));
|
|
||||||
form.add(new RequiredTextField<String>("email", MF.m(model,
|
|
||||||
p.getEmail())) {
|
|
||||||
@Override
|
@Override
|
||||||
protected String getInputType() {
|
protected String getInputType() {
|
||||||
return "email";
|
return "email";
|
||||||
}
|
}
|
||||||
}.add(RfcCompliantEmailAddressValidator.getInstance()).add(
|
}.add(RfcCompliantEmailAddressValidator.getInstance()).add(new IValidator<String>() {
|
||||||
new IValidator<String>() {
|
@Override
|
||||||
@Override
|
public void validate(IValidatable<String> validatable) {
|
||||||
public void validate(IValidatable<String> validatable) {
|
String email = validatable.getValue();
|
||||||
String email = validatable.getValue();
|
User user = userDAO.findByEmail(email);
|
||||||
User user = userDAO.findByEmail(email);
|
if (user != null) {
|
||||||
if (user != null) {
|
validatable.error(new ValidationError("Email is already taken."));
|
||||||
validatable.error(new ValidationError(
|
}
|
||||||
"Email is already taken."));
|
}
|
||||||
}
|
}));
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ import ro.isdc.wro.model.resource.processor.impl.css.CssImportPreProcessor;
|
|||||||
public class SassImportProcessor extends CssImportPreProcessor {
|
public class SassImportProcessor extends CssImportPreProcessor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String doTransform(String cssContent, List<Resource> foundImports)
|
protected String doTransform(String cssContent, List<Resource> foundImports) throws IOException {
|
||||||
throws IOException {
|
|
||||||
for (Resource resource : foundImports) {
|
for (Resource resource : foundImports) {
|
||||||
String uri = resource.getUri();
|
String uri = resource.getUri();
|
||||||
int lastSlash = uri.lastIndexOf('/');
|
int lastSlash = uri.lastIndexOf('/');
|
||||||
|
|||||||
@@ -15,10 +15,8 @@ import ro.isdc.wro.model.resource.SupportedResourceType;
|
|||||||
public class SassOnlyProcessor extends RubySassCssProcessor {
|
public class SassOnlyProcessor extends RubySassCssProcessor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void process(Resource resource, Reader reader, Writer writer)
|
public void process(Resource resource, Reader reader, Writer writer) throws IOException {
|
||||||
throws IOException {
|
if (resource.getUri().endsWith(".sass") || resource.getUri().endsWith(".scss")) {
|
||||||
if (resource.getUri().endsWith(".sass")
|
|
||||||
|| resource.getUri().endsWith(".scss")) {
|
|
||||||
super.process(resource, reader, writer);
|
super.process(resource, reader, writer);
|
||||||
} else {
|
} else {
|
||||||
writer.write(IOUtils.toString(reader));
|
writer.write(IOUtils.toString(reader));
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ public class TimestampProcessor implements ResourcePreProcessor {
|
|||||||
private static final String NOW = "" + System.currentTimeMillis();
|
private static final String NOW = "" + System.currentTimeMillis();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void process(Resource resource, Reader reader, Writer writer)
|
public void process(Resource resource, Reader reader, Writer writer) throws IOException {
|
||||||
throws IOException {
|
|
||||||
String content = IOUtils.toString(reader);
|
String content = IOUtils.toString(reader);
|
||||||
content = content.replace("${timestamp}", NOW);
|
content = content.replace("${timestamp}", NOW);
|
||||||
writer.write(content);
|
writer.write(content);
|
||||||
|
|||||||
@@ -24,10 +24,8 @@ public abstract class UserCustomCssReference extends ResourceReference {
|
|||||||
resourceResponse.setTextEncoding("UTF-8");
|
resourceResponse.setTextEncoding("UTF-8");
|
||||||
resourceResponse.setWriteCallback(new WriteCallback() {
|
resourceResponse.setWriteCallback(new WriteCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void writeData(Attributes attributes)
|
public void writeData(Attributes attributes) throws IOException {
|
||||||
throws IOException {
|
attributes.getResponse().write(StringUtils.trimToEmpty(getCss()));
|
||||||
attributes.getResponse().write(
|
|
||||||
StringUtils.trimToEmpty(getCss()));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return resourceResponse;
|
return resourceResponse;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import com.google.api.client.util.Maps;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Build-time solution
|
* Build-time solution
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class WroAdditionalProvider implements ProcessorProvider {
|
public class WroAdditionalProvider implements ProcessorProvider {
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Runtime solution
|
* Runtime solution
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class WroManagerFactory extends ConfigurableWroManagerFactory {
|
public class WroManagerFactory extends ConfigurableWroManagerFactory {
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.commafeed.frontend.rest;
|
package com.commafeed.frontend.rest;
|
||||||
|
|
||||||
|
|
||||||
public class Enums {
|
public class Enums {
|
||||||
|
|
||||||
public enum Type {
|
public enum Type {
|
||||||
|
|||||||
@@ -17,18 +17,14 @@ import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
|
|||||||
public class JsonProvider extends JacksonJsonProvider {
|
public class JsonProvider extends JacksonJsonProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(Object value, Class<?> type, Type genericType,
|
public void writeTo(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
|
||||||
Annotation[] annotations, MediaType mediaType,
|
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException {
|
||||||
MultivaluedMap<String, Object> httpHeaders,
|
|
||||||
OutputStream entityStream) throws IOException {
|
|
||||||
|
|
||||||
httpHeaders.putSingle(HttpHeaders.CONTENT_TYPE, mediaType.toString()
|
httpHeaders.putSingle(HttpHeaders.CONTENT_TYPE, mediaType.toString() + ";charset=UTF-8");
|
||||||
+ ";charset=UTF-8");
|
|
||||||
httpHeaders.putSingle(HttpHeaders.CACHE_CONTROL, "no-cache");
|
httpHeaders.putSingle(HttpHeaders.CACHE_CONTROL, "no-cache");
|
||||||
httpHeaders.putSingle(HttpHeaders.PRAGMA, "no-cache");
|
httpHeaders.putSingle(HttpHeaders.PRAGMA, "no-cache");
|
||||||
|
|
||||||
super.writeTo(value, type, genericType, annotations, mediaType,
|
super.writeTo(value, type, genericType, annotations, mediaType, httpHeaders, entityStream);
|
||||||
httpHeaders, entityStream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,8 +51,7 @@ public abstract class AbstractREST {
|
|||||||
ServletWebResponse swresp = new ServletWebResponse(swreq, response);
|
ServletWebResponse swresp = new ServletWebResponse(swreq, response);
|
||||||
RequestCycle cycle = app.createRequestCycle(swreq, swresp);
|
RequestCycle cycle = app.createRequestCycle(swreq, swresp);
|
||||||
ThreadContext.setRequestCycle(cycle);
|
ThreadContext.setRequestCycle(cycle);
|
||||||
CommaFeedSession session = (CommaFeedSession) app
|
CommaFeedSession session = (CommaFeedSession) app.fetchCreateAndSetSession(cycle);
|
||||||
.fetchCreateAndSetSession(cycle);
|
|
||||||
|
|
||||||
if (session.getUser() == null) {
|
if (session.getUser() == null) {
|
||||||
cookieLogin(app, session);
|
cookieLogin(app, session);
|
||||||
@@ -63,8 +62,7 @@ public abstract class AbstractREST {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void cookieLogin(CommaFeedApplication app, CommaFeedSession session) {
|
private void cookieLogin(CommaFeedApplication app, CommaFeedSession session) {
|
||||||
IAuthenticationStrategy authenticationStrategy = app
|
IAuthenticationStrategy authenticationStrategy = app.getSecuritySettings().getAuthenticationStrategy();
|
||||||
.getSecuritySettings().getAuthenticationStrategy();
|
|
||||||
String[] data = authenticationStrategy.load();
|
String[] data = authenticationStrategy.load();
|
||||||
if (data != null && data.length > 1) {
|
if (data != null && data.length > 1) {
|
||||||
session.signIn(data[0], data[1]);
|
session.signIn(data[0], data[1]);
|
||||||
@@ -98,8 +96,7 @@ public abstract class AbstractREST {
|
|||||||
boolean allowed = true;
|
boolean allowed = true;
|
||||||
User user = null;
|
User user = null;
|
||||||
Method method = context.getMethod();
|
Method method = context.getMethod();
|
||||||
SecurityCheck check = method.isAnnotationPresent(SecurityCheck.class) ? method
|
SecurityCheck check = method.isAnnotationPresent(SecurityCheck.class) ? method.getAnnotation(SecurityCheck.class) : method
|
||||||
.getAnnotation(SecurityCheck.class) : method
|
|
||||||
.getDeclaringClass().getAnnotation(SecurityCheck.class);
|
.getDeclaringClass().getAnnotation(SecurityCheck.class);
|
||||||
|
|
||||||
if (check != null) {
|
if (check != null) {
|
||||||
@@ -113,11 +110,9 @@ public abstract class AbstractREST {
|
|||||||
}
|
}
|
||||||
if (!allowed) {
|
if (!allowed) {
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
return Response.status(Status.UNAUTHORIZED)
|
return Response.status(Status.UNAUTHORIZED).entity("You are not authorized to do this.").build();
|
||||||
.entity("You are not authorized to do this.").build();
|
|
||||||
} else {
|
} else {
|
||||||
return Response.status(Status.FORBIDDEN)
|
return Response.status(Status.FORBIDDEN).entity("You are not authorized to do this.").build();
|
||||||
.entity("You are not authorized to do this.").build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,40 +33,33 @@ public abstract class AbstractResourceREST extends AbstractREST {
|
|||||||
@GET
|
@GET
|
||||||
@SecurityCheck(value = Role.NONE)
|
@SecurityCheck(value = Role.NONE)
|
||||||
@ApiOperation(value = "Returns information about API parameters", responseClass = "com.wordnik.swagger.core.Documentation")
|
@ApiOperation(value = "Returns information about API parameters", responseClass = "com.wordnik.swagger.core.Documentation")
|
||||||
public Response getHelp(@Context Application app,
|
public Response getHelp(@Context Application app, @Context HttpHeaders headers, @Context UriInfo uriInfo) {
|
||||||
@Context HttpHeaders headers, @Context UriInfo uriInfo) {
|
|
||||||
|
|
||||||
TypeUtil.addAllowablePackage(Entries.class.getPackage().getName());
|
TypeUtil.addAllowablePackage(Entries.class.getPackage().getName());
|
||||||
TypeUtil.addAllowablePackage(MarkRequest.class.getPackage().getName());
|
TypeUtil.addAllowablePackage(MarkRequest.class.getPackage().getName());
|
||||||
|
|
||||||
String apiVersion = ApiDocumentationREST.API_VERSION;
|
String apiVersion = ApiDocumentationREST.API_VERSION;
|
||||||
String swaggerVersion = SwaggerSpec.version();
|
String swaggerVersion = SwaggerSpec.version();
|
||||||
String basePath = ApiDocumentationREST
|
String basePath = ApiDocumentationREST.getBasePath(applicationSettingsService.get().getPublicUrl());
|
||||||
.getBasePath(applicationSettingsService.get().getPublicUrl());
|
|
||||||
|
|
||||||
Class<?> resource = null;
|
Class<?> resource = null;
|
||||||
String path = prependSlash(uriInfo.getPath());
|
String path = prependSlash(uriInfo.getPath());
|
||||||
for (Class<?> klass : app.getClasses()) {
|
for (Class<?> klass : app.getClasses()) {
|
||||||
Api api = klass.getAnnotation(Api.class);
|
Api api = klass.getAnnotation(Api.class);
|
||||||
if (api != null && api.value() != null
|
if (api != null && api.value() != null && StringUtils.equals(prependSlash(api.value()), path)) {
|
||||||
&& StringUtils.equals(prependSlash(api.value()), path)) {
|
|
||||||
resource = klass;
|
resource = klass;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
return Response
|
return Response.status(Status.NOT_FOUND).entity("Api annotation not found on class " + getClass().getName()).build();
|
||||||
.status(Status.NOT_FOUND)
|
|
||||||
.entity("Api annotation not found on class "
|
|
||||||
+ getClass().getName()).build();
|
|
||||||
}
|
}
|
||||||
Api api = resource.getAnnotation(Api.class);
|
Api api = resource.getAnnotation(Api.class);
|
||||||
String apiPath = api.value();
|
String apiPath = api.value();
|
||||||
String apiListingPath = api.value();
|
String apiListingPath = api.value();
|
||||||
|
|
||||||
Documentation doc = new HelpApi(null).filterDocs(JaxrsApiReader.read(
|
Documentation doc = new HelpApi(null).filterDocs(JaxrsApiReader.read(resource, apiVersion, swaggerVersion, basePath, apiPath),
|
||||||
resource, apiVersion, swaggerVersion, basePath, apiPath),
|
|
||||||
headers, uriInfo, apiListingPath, apiPath);
|
headers, uriInfo, apiListingPath, apiPath);
|
||||||
|
|
||||||
doc.setSwaggerVersion(swaggerVersion);
|
doc.setSwaggerVersion(swaggerVersion);
|
||||||
|
|||||||
@@ -102,24 +102,18 @@ public class AdminREST extends AbstractResourceREST {
|
|||||||
roles.add(Role.ADMIN);
|
roles.add(Role.ADMIN);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
userService.register(userModel.getName(),
|
userService.register(userModel.getName(), userModel.getPassword(), userModel.getEmail(), roles, true);
|
||||||
userModel.getPassword(), userModel.getEmail(), roles,
|
|
||||||
true);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Response.status(Status.CONFLICT).entity(e.getMessage())
|
return Response.status(Status.CONFLICT).entity(e.getMessage()).build();
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
User user = userDAO.findById(id);
|
User user = userDAO.findById(id);
|
||||||
if (StartupBean.USERNAME_ADMIN.equals(user.getName())
|
if (StartupBean.USERNAME_ADMIN.equals(user.getName()) && !userModel.isEnabled()) {
|
||||||
&& !userModel.isEnabled()) {
|
return Response.status(Status.FORBIDDEN).entity("You cannot disable the admin user.").build();
|
||||||
return Response.status(Status.FORBIDDEN)
|
|
||||||
.entity("You cannot disable the admin user.").build();
|
|
||||||
}
|
}
|
||||||
user.setName(userModel.getName());
|
user.setName(userModel.getName());
|
||||||
if (StringUtils.isNotBlank(userModel.getPassword())) {
|
if (StringUtils.isNotBlank(userModel.getPassword())) {
|
||||||
user.setPassword(encryptionService.getEncryptedPassword(
|
user.setPassword(encryptionService.getEncryptedPassword(userModel.getPassword(), user.getSalt()));
|
||||||
userModel.getPassword(), user.getSalt()));
|
|
||||||
}
|
}
|
||||||
user.setEmail(userModel.getEmail());
|
user.setEmail(userModel.getEmail());
|
||||||
user.setDisabled(!userModel.isEnabled());
|
user.setDisabled(!userModel.isEnabled());
|
||||||
@@ -130,10 +124,7 @@ public class AdminREST extends AbstractResourceREST {
|
|||||||
userRoleDAO.saveOrUpdate(new UserRole(user, Role.ADMIN));
|
userRoleDAO.saveOrUpdate(new UserRole(user, Role.ADMIN));
|
||||||
} else if (!userModel.isAdmin() && roles.contains(Role.ADMIN)) {
|
} else if (!userModel.isAdmin() && roles.contains(Role.ADMIN)) {
|
||||||
if (StartupBean.USERNAME_ADMIN.equals(user.getName())) {
|
if (StartupBean.USERNAME_ADMIN.equals(user.getName())) {
|
||||||
return Response
|
return Response.status(Status.FORBIDDEN).entity("You cannot remove the admin role from the admin user.").build();
|
||||||
.status(Status.FORBIDDEN)
|
|
||||||
.entity("You cannot remove the admin role from the admin user.")
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
for (UserRole userRole : userRoleDAO.findAll(user)) {
|
for (UserRole userRole : userRoleDAO.findAll(user)) {
|
||||||
if (userRole.getRole() == Role.ADMIN) {
|
if (userRole.getRole() == Role.ADMIN) {
|
||||||
@@ -150,8 +141,7 @@ public class AdminREST extends AbstractResourceREST {
|
|||||||
@Path("/user/get/{id}")
|
@Path("/user/get/{id}")
|
||||||
@GET
|
@GET
|
||||||
@ApiOperation(value = "Get user information", notes = "Get user information", responseClass = "com.commafeed.frontend.model.UserModel")
|
@ApiOperation(value = "Get user information", notes = "Get user information", responseClass = "com.commafeed.frontend.model.UserModel")
|
||||||
public Response getUser(
|
public Response getUser(@ApiParam(value = "user id", required = true) @PathParam("id") Long id) {
|
||||||
@ApiParam(value = "user id", required = true) @PathParam("id") Long id) {
|
|
||||||
Preconditions.checkNotNull(id);
|
Preconditions.checkNotNull(id);
|
||||||
User user = userDAO.findById(id);
|
User user = userDAO.findById(id);
|
||||||
UserModel userModel = new UserModel();
|
UserModel userModel = new UserModel();
|
||||||
@@ -205,8 +195,7 @@ public class AdminREST extends AbstractResourceREST {
|
|||||||
return Response.status(Status.NOT_FOUND).build();
|
return Response.status(Status.NOT_FOUND).build();
|
||||||
}
|
}
|
||||||
if (StartupBean.USERNAME_ADMIN.equals(user.getName())) {
|
if (StartupBean.USERNAME_ADMIN.equals(user.getName())) {
|
||||||
return Response.status(Status.FORBIDDEN)
|
return Response.status(Status.FORBIDDEN).entity("You cannot delete the admin user.").build();
|
||||||
.entity("You cannot delete the admin user.").build();
|
|
||||||
}
|
}
|
||||||
userService.unregister(user);
|
userService.unregister(user);
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
@@ -214,7 +203,10 @@ public class AdminREST extends AbstractResourceREST {
|
|||||||
|
|
||||||
@Path("/settings")
|
@Path("/settings")
|
||||||
@GET
|
@GET
|
||||||
@ApiOperation(value = "Retrieve application settings", notes = "Retrieve application settings", responseClass = "com.commafeed.backend.model.ApplicationSettings")
|
@ApiOperation(
|
||||||
|
value = "Retrieve application settings",
|
||||||
|
notes = "Retrieve application settings",
|
||||||
|
responseClass = "com.commafeed.backend.model.ApplicationSettings")
|
||||||
public Response getSettings() {
|
public Response getSettings() {
|
||||||
return Response.ok(applicationSettingsService.get()).build();
|
return Response.ok(applicationSettingsService.get()).build();
|
||||||
}
|
}
|
||||||
@@ -222,8 +214,7 @@ public class AdminREST extends AbstractResourceREST {
|
|||||||
@Path("/settings")
|
@Path("/settings")
|
||||||
@POST
|
@POST
|
||||||
@ApiOperation(value = "Save application settings", notes = "Save application settings")
|
@ApiOperation(value = "Save application settings", notes = "Save application settings")
|
||||||
public Response saveSettings(
|
public Response saveSettings(@ApiParam(required = true) ApplicationSettings settings) {
|
||||||
@ApiParam(required = true) ApplicationSettings settings) {
|
|
||||||
Preconditions.checkNotNull(settings);
|
Preconditions.checkNotNull(settings);
|
||||||
applicationSettingsService.save(settings);
|
applicationSettingsService.save(settings);
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
@@ -232,8 +223,7 @@ public class AdminREST extends AbstractResourceREST {
|
|||||||
@Path("/metrics")
|
@Path("/metrics")
|
||||||
@GET
|
@GET
|
||||||
@ApiOperation(value = "Retrieve server metrics")
|
@ApiOperation(value = "Retrieve server metrics")
|
||||||
public Response getMetrics(
|
public Response getMetrics(@QueryParam("backlog") @DefaultValue("false") boolean backlog) {
|
||||||
@QueryParam("backlog") @DefaultValue("false") boolean backlog) {
|
|
||||||
Map<String, Object> map = Maps.newLinkedHashMap();
|
Map<String, Object> map = Maps.newLinkedHashMap();
|
||||||
map.put("lastMinute", metricsBean.getLastMinute());
|
map.put("lastMinute", metricsBean.getLastMinute());
|
||||||
map.put("lastHour", metricsBean.getLastHour());
|
map.put("lastHour", metricsBean.getLastHour());
|
||||||
@@ -254,43 +244,44 @@ public class AdminREST extends AbstractResourceREST {
|
|||||||
@ApiOperation(value = "Feeds cleanup", notes = "Delete feeds without subscriptions and entries without feeds")
|
@ApiOperation(value = "Feeds cleanup", notes = "Delete feeds without subscriptions and entries without feeds")
|
||||||
public Response cleanupFeeds() {
|
public Response cleanupFeeds() {
|
||||||
Map<String, Long> map = Maps.newHashMap();
|
Map<String, Long> map = Maps.newHashMap();
|
||||||
map.put("feeds_without_subscriptions",
|
map.put("feeds_without_subscriptions", cleaner.cleanFeedsWithoutSubscriptions());
|
||||||
cleaner.cleanFeedsWithoutSubscriptions());
|
return Response.ok(map).build();
|
||||||
map.put("entries_without_feeds", cleaner.cleanEntriesWithoutFeeds());
|
}
|
||||||
|
|
||||||
|
@Path("/cleanup/content")
|
||||||
|
@GET
|
||||||
|
@ApiOperation(value = "Content cleanup", notes = "Delete contents without entries")
|
||||||
|
public Response cleanupContents() {
|
||||||
|
Map<String, Long> map = Maps.newHashMap();
|
||||||
|
map.put("contents_without_entries", cleaner.cleanContentsWithoutEntries());
|
||||||
return Response.ok(map).build();
|
return Response.ok(map).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("/cleanup/entries")
|
@Path("/cleanup/entries")
|
||||||
@GET
|
@GET
|
||||||
@ApiOperation(value = "Entries cleanup", notes = "Delete entries older than given date")
|
@ApiOperation(value = "Entries cleanup", notes = "Delete entries older than given date")
|
||||||
public Response cleanupEntries(
|
public Response cleanupEntries(@QueryParam("days") @DefaultValue("30") int days) {
|
||||||
@QueryParam("days") @DefaultValue("30") int days) {
|
|
||||||
Map<String, Long> map = Maps.newHashMap();
|
Map<String, Long> map = Maps.newHashMap();
|
||||||
map.put("old entries",
|
map.put("old_entries", cleaner.cleanEntriesOlderThan(days, TimeUnit.DAYS));
|
||||||
cleaner.cleanEntriesOlderThan(days, TimeUnit.DAYS));
|
|
||||||
return Response.ok(map).build();
|
return Response.ok(map).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("/cleanup/findDuplicateFeeds")
|
@Path("/cleanup/findDuplicateFeeds")
|
||||||
@GET
|
@GET
|
||||||
@ApiOperation(value = "Find duplicate feeds")
|
@ApiOperation(value = "Find duplicate feeds")
|
||||||
public Response findDuplicateFeeds(@QueryParam("mode") DuplicateMode mode,
|
public Response findDuplicateFeeds(@QueryParam("mode") DuplicateMode mode, @QueryParam("page") int page,
|
||||||
@QueryParam("page") int page, @QueryParam("limit") int limit,
|
@QueryParam("limit") int limit, @QueryParam("minCount") long minCount) {
|
||||||
@QueryParam("minCount") long minCount) {
|
List<FeedCount> list = feedDAO.findDuplicates(mode, limit * page, limit, minCount);
|
||||||
List<FeedCount> list = feedDAO.findDuplicates(mode, limit * page,
|
|
||||||
limit, minCount);
|
|
||||||
return Response.ok(list).build();
|
return Response.ok(list).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("/cleanup/merge")
|
@Path("/cleanup/merge")
|
||||||
@POST
|
@POST
|
||||||
@ApiOperation(value = "Merge feeds", notes = "Merge feeds together")
|
@ApiOperation(value = "Merge feeds", notes = "Merge feeds together")
|
||||||
public Response mergeFeeds(
|
public Response mergeFeeds(@ApiParam(required = true) FeedMergeRequest request) {
|
||||||
@ApiParam(required = true) FeedMergeRequest request) {
|
|
||||||
Feed into = feedDAO.findById(request.getIntoFeedId());
|
Feed into = feedDAO.findById(request.getIntoFeedId());
|
||||||
if (into == null) {
|
if (into == null) {
|
||||||
return Response.status(Status.BAD_REQUEST)
|
return Response.status(Status.BAD_REQUEST).entity("'into feed' not found").build();
|
||||||
.entity("'into feed' not found").build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Feed> feeds = Lists.newArrayList();
|
List<Feed> feeds = Lists.newArrayList();
|
||||||
@@ -300,8 +291,7 @@ public class AdminREST extends AbstractResourceREST {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (feeds.isEmpty()) {
|
if (feeds.isEmpty()) {
|
||||||
return Response.status(Status.BAD_REQUEST)
|
return Response.status(Status.BAD_REQUEST).entity("'from feeds' empty").build();
|
||||||
.entity("'from feeds' empty").build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cleaner.mergeFeeds(into, feeds);
|
cleaner.mergeFeeds(into, feeds);
|
||||||
|
|||||||
@@ -40,14 +40,12 @@ public class ApiDocumentationREST extends AbstractREST {
|
|||||||
}
|
}
|
||||||
Api api = resource.getAnnotation(Api.class);
|
Api api = resource.getAnnotation(Api.class);
|
||||||
if (api != null) {
|
if (api != null) {
|
||||||
doc.addApi(new DocumentationEndPoint(api.value(), api
|
doc.addApi(new DocumentationEndPoint(api.value(), api.description()));
|
||||||
.description()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doc.setSwaggerVersion(SwaggerSpec.version());
|
doc.setSwaggerVersion(SwaggerSpec.version());
|
||||||
doc.setBasePath(getBasePath(applicationSettingsService.get()
|
doc.setBasePath(getBasePath(applicationSettingsService.get().getPublicUrl()));
|
||||||
.getPublicUrl()));
|
|
||||||
doc.setApiVersion(API_VERSION);
|
doc.setApiVersion(API_VERSION);
|
||||||
|
|
||||||
return Response.ok().entity(doc).build();
|
return Response.ok().entity(doc).build();
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import com.commafeed.backend.model.FeedSubscription;
|
|||||||
import com.commafeed.backend.model.User;
|
import com.commafeed.backend.model.User;
|
||||||
import com.commafeed.backend.model.UserRole.Role;
|
import com.commafeed.backend.model.UserRole.Role;
|
||||||
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
||||||
|
import com.commafeed.backend.services.FeedEntryService;
|
||||||
import com.commafeed.backend.services.FeedSubscriptionService;
|
import com.commafeed.backend.services.FeedSubscriptionService;
|
||||||
import com.commafeed.frontend.SecurityCheck;
|
import com.commafeed.frontend.SecurityCheck;
|
||||||
import com.commafeed.frontend.model.Category;
|
import com.commafeed.frontend.model.Category;
|
||||||
@@ -68,6 +69,9 @@ public class CategoryREST extends AbstractResourceREST {
|
|||||||
@Inject
|
@Inject
|
||||||
FeedEntryStatusDAO feedEntryStatusDAO;
|
FeedEntryStatusDAO feedEntryStatusDAO;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FeedEntryService feedEntryService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FeedCategoryDAO feedCategoryDAO;
|
FeedCategoryDAO feedCategoryDAO;
|
||||||
|
|
||||||
@@ -82,14 +86,20 @@ public class CategoryREST extends AbstractResourceREST {
|
|||||||
|
|
||||||
@Path("/entries")
|
@Path("/entries")
|
||||||
@GET
|
@GET
|
||||||
@ApiOperation(value = "Get category entries", notes = "Get a list of category entries", responseClass = "com.commafeed.frontend.model.Entries")
|
@ApiOperation(
|
||||||
|
value = "Get category entries",
|
||||||
|
notes = "Get a list of category entries",
|
||||||
|
responseClass = "com.commafeed.frontend.model.Entries")
|
||||||
public Response getCategoryEntries(
|
public Response getCategoryEntries(
|
||||||
@ApiParam(value = "id of the category, 'all' or 'starred'", required = true) @QueryParam("id") String id,
|
@ApiParam(value = "id of the category, 'all' or 'starred'", required = true) @QueryParam("id") String id, @ApiParam(
|
||||||
@ApiParam(value = "all entries or only unread ones", allowableValues = "all,unread", required = true) @DefaultValue("unread") @QueryParam("readType") ReadType readType,
|
value = "all entries or only unread ones",
|
||||||
@ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan,
|
allowableValues = "all,unread",
|
||||||
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset,
|
required = true) @DefaultValue("unread") @QueryParam("readType") ReadType readType, @ApiParam(
|
||||||
@ApiParam(value = "limit for paging, default 20, maximum 50") @DefaultValue("20") @QueryParam("limit") int limit,
|
value = "only entries newer than this") @QueryParam("newerThan") Long newerThan,
|
||||||
@ApiParam(value = "date ordering", allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order) {
|
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset, @ApiParam(
|
||||||
|
value = "limit for paging, default 20, maximum 50") @DefaultValue("20") @QueryParam("limit") int limit, @ApiParam(
|
||||||
|
value = "date ordering",
|
||||||
|
allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order) {
|
||||||
|
|
||||||
Preconditions.checkNotNull(readType);
|
Preconditions.checkNotNull(readType);
|
||||||
limit = Math.min(limit, 50);
|
limit = Math.min(limit, 50);
|
||||||
@@ -101,60 +111,38 @@ public class CategoryREST extends AbstractResourceREST {
|
|||||||
id = ALL;
|
id = ALL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Date newerThanDate = newerThan == null ? null : new Date(
|
Date newerThanDate = newerThan == null ? null : new Date(Long.valueOf(newerThan));
|
||||||
Long.valueOf(newerThan));
|
|
||||||
|
|
||||||
if (ALL.equals(id)) {
|
if (ALL.equals(id)) {
|
||||||
entries.setName("All");
|
entries.setName("All");
|
||||||
List<FeedEntryStatus> list = null;
|
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findAll(getUser());
|
||||||
List<FeedSubscription> subscriptions = feedSubscriptionDAO
|
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(subscriptions, unreadOnly, null, newerThanDate, offset,
|
||||||
.findAll(getUser());
|
limit + 1, order, true);
|
||||||
if (unreadOnly) {
|
|
||||||
list = feedEntryStatusDAO.findAllUnread(getUser(),
|
|
||||||
newerThanDate, offset, limit + 1, order, true);
|
|
||||||
} else {
|
|
||||||
list = feedEntryStatusDAO.findBySubscriptions(subscriptions,
|
|
||||||
null, newerThanDate, offset, limit + 1, order, true);
|
|
||||||
}
|
|
||||||
for (FeedEntryStatus status : list) {
|
for (FeedEntryStatus status : list) {
|
||||||
entries.getEntries().add(
|
entries.getEntries().add(
|
||||||
Entry.build(status, applicationSettingsService.get()
|
Entry.build(status, applicationSettingsService.get().getPublicUrl(), applicationSettingsService.get()
|
||||||
.getPublicUrl(), applicationSettingsService
|
.isImageProxyEnabled()));
|
||||||
.get().isImageProxyEnabled()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (STARRED.equals(id)) {
|
} else if (STARRED.equals(id)) {
|
||||||
entries.setName("Starred");
|
entries.setName("Starred");
|
||||||
List<FeedEntryStatus> starred = feedEntryStatusDAO.findStarred(
|
List<FeedEntryStatus> starred = feedEntryStatusDAO.findStarred(getUser(), newerThanDate, offset, limit + 1, order, true);
|
||||||
getUser(), newerThanDate, offset, limit + 1, order, true);
|
|
||||||
for (FeedEntryStatus status : starred) {
|
for (FeedEntryStatus status : starred) {
|
||||||
entries.getEntries().add(
|
entries.getEntries().add(
|
||||||
Entry.build(status, applicationSettingsService.get()
|
Entry.build(status, applicationSettingsService.get().getPublicUrl(), applicationSettingsService.get()
|
||||||
.getPublicUrl(), applicationSettingsService
|
.isImageProxyEnabled()));
|
||||||
.get().isImageProxyEnabled()));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
FeedCategory parent = feedCategoryDAO.findById(getUser(),
|
FeedCategory parent = feedCategoryDAO.findById(getUser(), Long.valueOf(id));
|
||||||
Long.valueOf(id));
|
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
List<FeedCategory> categories = feedCategoryDAO
|
List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(getUser(), parent);
|
||||||
.findAllChildrenCategories(getUser(), parent);
|
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(getUser(), categories);
|
||||||
List<FeedSubscription> subs = feedSubscriptionDAO
|
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(subs, unreadOnly, null, newerThanDate, offset,
|
||||||
.findByCategories(getUser(), categories);
|
limit + 1, order, true);
|
||||||
List<FeedEntryStatus> list = null;
|
|
||||||
if (unreadOnly) {
|
|
||||||
list = feedEntryStatusDAO.findUnreadBySubscriptions(subs,
|
|
||||||
newerThanDate, offset, limit + 1, order, true);
|
|
||||||
} else {
|
|
||||||
list = feedEntryStatusDAO.findBySubscriptions(subs, null,
|
|
||||||
newerThanDate, offset, limit + 1, order, true);
|
|
||||||
}
|
|
||||||
for (FeedEntryStatus status : list) {
|
for (FeedEntryStatus status : list) {
|
||||||
entries.getEntries().add(
|
entries.getEntries().add(
|
||||||
Entry.build(status, applicationSettingsService
|
Entry.build(status, applicationSettingsService.get().getPublicUrl(), applicationSettingsService.get()
|
||||||
.get().getPublicUrl(),
|
.isImageProxyEnabled()));
|
||||||
applicationSettingsService.get()
|
|
||||||
.isImageProxyEnabled()));
|
|
||||||
}
|
}
|
||||||
entries.setName(parent.getName());
|
entries.setName(parent.getName());
|
||||||
}
|
}
|
||||||
@@ -186,8 +174,7 @@ public class CategoryREST extends AbstractResourceREST {
|
|||||||
int offset = 0;
|
int offset = 0;
|
||||||
int limit = 20;
|
int limit = 20;
|
||||||
|
|
||||||
Entries entries = (Entries) getCategoryEntries(id, readType, null,
|
Entries entries = (Entries) getCategoryEntries(id, readType, null, offset, limit, order).getEntity();
|
||||||
offset, limit, order).getEntity();
|
|
||||||
|
|
||||||
SyndFeed feed = new SyndFeedImpl();
|
SyndFeed feed = new SyndFeedImpl();
|
||||||
feed.setFeedType("rss_2.0");
|
feed.setFeedType("rss_2.0");
|
||||||
@@ -216,36 +203,30 @@ public class CategoryREST extends AbstractResourceREST {
|
|||||||
@Path("/mark")
|
@Path("/mark")
|
||||||
@POST
|
@POST
|
||||||
@ApiOperation(value = "Mark category entries", notes = "Mark feed entries of this category as read")
|
@ApiOperation(value = "Mark category entries", notes = "Mark feed entries of this category as read")
|
||||||
public Response markCategoryEntries(
|
public Response markCategoryEntries(@ApiParam(value = "category id, or 'all'", required = true) MarkRequest req) {
|
||||||
@ApiParam(value = "category id, or 'all'", required = true) MarkRequest req) {
|
|
||||||
Preconditions.checkNotNull(req);
|
Preconditions.checkNotNull(req);
|
||||||
Preconditions.checkNotNull(req.getId());
|
Preconditions.checkNotNull(req.getId());
|
||||||
|
|
||||||
Date olderThan = req.getOlderThan() == null ? null : new Date(
|
Date olderThan = req.getOlderThan() == null ? null : new Date(req.getOlderThan());
|
||||||
req.getOlderThan());
|
|
||||||
|
|
||||||
if (ALL.equals(req.getId())) {
|
if (ALL.equals(req.getId())) {
|
||||||
feedEntryStatusDAO.markAllEntries(getUser(), olderThan);
|
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findAll(getUser());
|
||||||
|
feedEntryService.markSubscriptionEntries(getUser(), subscriptions, olderThan);
|
||||||
} else if (STARRED.equals(req.getId())) {
|
} else if (STARRED.equals(req.getId())) {
|
||||||
feedEntryStatusDAO.markStarredEntries(getUser(), olderThan);
|
feedEntryService.markStarredEntries(getUser(), olderThan);
|
||||||
} else {
|
} else {
|
||||||
FeedCategory parent = feedCategoryDAO.findById(getUser(),
|
FeedCategory parent = feedCategoryDAO.findById(getUser(), Long.valueOf(req.getId()));
|
||||||
Long.valueOf(req.getId()));
|
List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(getUser(), parent);
|
||||||
List<FeedCategory> categories = feedCategoryDAO
|
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(getUser(), categories);
|
||||||
.findAllChildrenCategories(getUser(), parent);
|
feedEntryService.markSubscriptionEntries(getUser(), subs, olderThan);
|
||||||
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(
|
|
||||||
getUser(), categories);
|
|
||||||
feedEntryStatusDAO.markSubscriptionEntries(subs, olderThan);
|
|
||||||
}
|
}
|
||||||
cache.invalidateUserData(getUser());
|
|
||||||
return Response.ok(Status.OK).build();
|
return Response.ok(Status.OK).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("/add")
|
@Path("/add")
|
||||||
@POST
|
@POST
|
||||||
@ApiOperation(value = "Add a category", notes = "Add a new feed category")
|
@ApiOperation(value = "Add a category", notes = "Add a new feed category")
|
||||||
public Response addCategory(
|
public Response addCategory(@ApiParam(required = true) AddCategoryRequest req) {
|
||||||
@ApiParam(required = true) AddCategoryRequest req) {
|
|
||||||
Preconditions.checkNotNull(req);
|
Preconditions.checkNotNull(req);
|
||||||
Preconditions.checkNotNull(req.getName());
|
Preconditions.checkNotNull(req.getName());
|
||||||
|
|
||||||
@@ -260,7 +241,7 @@ public class CategoryREST extends AbstractResourceREST {
|
|||||||
cat.setParent(parent);
|
cat.setParent(parent);
|
||||||
}
|
}
|
||||||
feedCategoryDAO.saveOrUpdate(cat);
|
feedCategoryDAO.saveOrUpdate(cat);
|
||||||
cache.invalidateUserData(getUser());
|
cache.invalidateUserRootCategory(getUser());
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,24 +255,21 @@ public class CategoryREST extends AbstractResourceREST {
|
|||||||
|
|
||||||
FeedCategory cat = feedCategoryDAO.findById(getUser(), req.getId());
|
FeedCategory cat = feedCategoryDAO.findById(getUser(), req.getId());
|
||||||
if (cat != null) {
|
if (cat != null) {
|
||||||
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategory(
|
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategory(getUser(), cat);
|
||||||
getUser(), cat);
|
|
||||||
for (FeedSubscription sub : subs) {
|
for (FeedSubscription sub : subs) {
|
||||||
sub.setCategory(null);
|
sub.setCategory(null);
|
||||||
}
|
}
|
||||||
feedSubscriptionDAO.saveOrUpdate(subs);
|
feedSubscriptionDAO.saveOrUpdate(subs);
|
||||||
List<FeedCategory> categories = feedCategoryDAO
|
List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(getUser(), cat);
|
||||||
.findAllChildrenCategories(getUser(), cat);
|
|
||||||
for (FeedCategory child : categories) {
|
for (FeedCategory child : categories) {
|
||||||
if (!child.getId().equals(cat.getId())
|
if (!child.getId().equals(cat.getId()) && child.getParent().getId().equals(cat.getId())) {
|
||||||
&& child.getParent().getId().equals(cat.getId())) {
|
|
||||||
child.setParent(null);
|
child.setParent(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
feedCategoryDAO.saveOrUpdate(categories);
|
feedCategoryDAO.saveOrUpdate(categories);
|
||||||
|
|
||||||
feedCategoryDAO.delete(cat);
|
feedCategoryDAO.delete(cat);
|
||||||
cache.invalidateUserData(getUser());
|
cache.invalidateUserRootCategory(getUser());
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
} else {
|
} else {
|
||||||
return Response.status(Status.NOT_FOUND).build();
|
return Response.status(Status.NOT_FOUND).build();
|
||||||
@@ -301,43 +279,35 @@ public class CategoryREST extends AbstractResourceREST {
|
|||||||
@POST
|
@POST
|
||||||
@Path("/modify")
|
@Path("/modify")
|
||||||
@ApiOperation(value = "Rename a category", notes = "Rename an existing feed category")
|
@ApiOperation(value = "Rename a category", notes = "Rename an existing feed category")
|
||||||
public Response modifyCategory(
|
public Response modifyCategory(@ApiParam(required = true) CategoryModificationRequest req) {
|
||||||
@ApiParam(required = true) CategoryModificationRequest req) {
|
|
||||||
Preconditions.checkNotNull(req);
|
Preconditions.checkNotNull(req);
|
||||||
Preconditions.checkNotNull(req.getId());
|
Preconditions.checkNotNull(req.getId());
|
||||||
|
|
||||||
FeedCategory category = feedCategoryDAO
|
FeedCategory category = feedCategoryDAO.findById(getUser(), req.getId());
|
||||||
.findById(getUser(), req.getId());
|
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(req.getName())) {
|
if (StringUtils.isNotBlank(req.getName())) {
|
||||||
category.setName(req.getName());
|
category.setName(req.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
FeedCategory parent = null;
|
FeedCategory parent = null;
|
||||||
if (req.getParentId() != null
|
if (req.getParentId() != null && !CategoryREST.ALL.equals(req.getParentId())
|
||||||
&& !CategoryREST.ALL.equals(req.getParentId())
|
&& !StringUtils.equals(req.getParentId(), String.valueOf(req.getId()))) {
|
||||||
&& !StringUtils.equals(req.getParentId(),
|
parent = feedCategoryDAO.findById(getUser(), Long.valueOf(req.getParentId()));
|
||||||
String.valueOf(req.getId()))) {
|
|
||||||
parent = feedCategoryDAO.findById(getUser(),
|
|
||||||
Long.valueOf(req.getParentId()));
|
|
||||||
}
|
}
|
||||||
category.setParent(parent);
|
category.setParent(parent);
|
||||||
|
|
||||||
if (req.getPosition() != null) {
|
if (req.getPosition() != null) {
|
||||||
List<FeedCategory> categories = feedCategoryDAO.findByParent(
|
List<FeedCategory> categories = feedCategoryDAO.findByParent(getUser(), parent);
|
||||||
getUser(), parent);
|
|
||||||
Collections.sort(categories, new Comparator<FeedCategory>() {
|
Collections.sort(categories, new Comparator<FeedCategory>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(FeedCategory o1, FeedCategory o2) {
|
public int compare(FeedCategory o1, FeedCategory o2) {
|
||||||
return ObjectUtils.compare(o1.getPosition(),
|
return ObjectUtils.compare(o1.getPosition(), o2.getPosition());
|
||||||
o2.getPosition());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
int existingIndex = -1;
|
int existingIndex = -1;
|
||||||
for (int i = 0; i < categories.size(); i++) {
|
for (int i = 0; i < categories.size(); i++) {
|
||||||
if (ObjectUtils.equals(categories.get(i).getId(),
|
if (ObjectUtils.equals(categories.get(i).getId(), category.getId())) {
|
||||||
category.getId())) {
|
|
||||||
existingIndex = i;
|
existingIndex = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,8 +315,7 @@ public class CategoryREST extends AbstractResourceREST {
|
|||||||
categories.remove(existingIndex);
|
categories.remove(existingIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
categories.add(Math.min(req.getPosition(), categories.size()),
|
categories.add(Math.min(req.getPosition(), categories.size()), category);
|
||||||
category);
|
|
||||||
for (int i = 0; i < categories.size(); i++) {
|
for (int i = 0; i < categories.size(); i++) {
|
||||||
categories.get(i).setPosition(i);
|
categories.get(i).setPosition(i);
|
||||||
}
|
}
|
||||||
@@ -356,7 +325,7 @@ public class CategoryREST extends AbstractResourceREST {
|
|||||||
}
|
}
|
||||||
|
|
||||||
feedCategoryDAO.saveOrUpdate(category);
|
feedCategoryDAO.saveOrUpdate(category);
|
||||||
cache.invalidateUserData(getUser());
|
cache.invalidateUserRootCategory(getUser());
|
||||||
return Response.ok(Status.OK).build();
|
return Response.ok(Status.OK).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,14 +336,13 @@ public class CategoryREST extends AbstractResourceREST {
|
|||||||
Preconditions.checkNotNull(req);
|
Preconditions.checkNotNull(req);
|
||||||
Preconditions.checkNotNull(req.getId());
|
Preconditions.checkNotNull(req.getId());
|
||||||
|
|
||||||
FeedCategory category = feedCategoryDAO.findById(getUser(),
|
FeedCategory category = feedCategoryDAO.findById(getUser(), Long.valueOf(req.getId()));
|
||||||
Long.valueOf(req.getId()));
|
|
||||||
if (category == null) {
|
if (category == null) {
|
||||||
return Response.status(Status.NOT_FOUND).build();
|
return Response.status(Status.NOT_FOUND).build();
|
||||||
}
|
}
|
||||||
category.setCollapsed(req.isCollapse());
|
category.setCollapsed(req.isCollapse());
|
||||||
feedCategoryDAO.saveOrUpdate(category);
|
feedCategoryDAO.saveOrUpdate(category);
|
||||||
cache.invalidateUserData(getUser());
|
cache.invalidateUserRootCategory(getUser());
|
||||||
return Response.ok(Status.OK).build();
|
return Response.ok(Status.OK).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,8 +351,7 @@ public class CategoryREST extends AbstractResourceREST {
|
|||||||
@ApiOperation(value = "Get unread count for feed subscriptions", responseClass = "List[com.commafeed.frontend.model.UnreadCount]")
|
@ApiOperation(value = "Get unread count for feed subscriptions", responseClass = "List[com.commafeed.frontend.model.UnreadCount]")
|
||||||
public Response getUnreadCount() {
|
public Response getUnreadCount() {
|
||||||
List<UnreadCount> list = Lists.newArrayList();
|
List<UnreadCount> list = Lists.newArrayList();
|
||||||
Map<Long, Long> unreadCount = feedSubscriptionService
|
Map<Long, Long> unreadCount = feedSubscriptionService.getUnreadCount(getUser());
|
||||||
.getUnreadCount(getUser());
|
|
||||||
for (Map.Entry<Long, Long> e : unreadCount.entrySet()) {
|
for (Map.Entry<Long, Long> e : unreadCount.entrySet()) {
|
||||||
list.add(new UnreadCount(e.getKey(), e.getValue()));
|
list.add(new UnreadCount(e.getKey(), e.getValue()));
|
||||||
}
|
}
|
||||||
@@ -393,39 +360,37 @@ public class CategoryREST extends AbstractResourceREST {
|
|||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/get")
|
@Path("/get")
|
||||||
@ApiOperation(value = "Get feed categories", notes = "Get all categories and subscriptions of the user", responseClass = "com.commafeed.frontend.model.Category")
|
@ApiOperation(
|
||||||
|
value = "Get feed categories",
|
||||||
|
notes = "Get all categories and subscriptions of the user",
|
||||||
|
responseClass = "com.commafeed.frontend.model.Category")
|
||||||
public Response getSubscriptions() {
|
public Response getSubscriptions() {
|
||||||
User user = getUser();
|
User user = getUser();
|
||||||
|
|
||||||
Category root = cache.getRootCategory(user);
|
Category root = cache.getUserRootCategory(user);
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
log.debug("root category cache miss for {}", user.getName());
|
log.debug("tree cache miss for {}", user.getId());
|
||||||
List<FeedCategory> categories = feedCategoryDAO.findAll(user);
|
List<FeedCategory> categories = feedCategoryDAO.findAll(user);
|
||||||
List<FeedSubscription> subscriptions = feedSubscriptionDAO
|
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findAll(user);
|
||||||
.findAll(getUser());
|
Map<Long, Long> unreadCount = feedSubscriptionService.getUnreadCount(user);
|
||||||
Map<Long, Long> unreadCount = feedSubscriptionService
|
|
||||||
.getUnreadCount(getUser());
|
|
||||||
|
|
||||||
root = buildCategory(null, categories, subscriptions, unreadCount);
|
root = buildCategory(null, categories, subscriptions, unreadCount);
|
||||||
root.setId("all");
|
root.setId("all");
|
||||||
root.setName("All");
|
root.setName("All");
|
||||||
cache.setRootCategory(user, root);
|
cache.setUserRootCategory(user, root);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.ok(root).build();
|
return Response.ok(root).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Category buildCategory(Long id, List<FeedCategory> categories,
|
private Category buildCategory(Long id, List<FeedCategory> categories, List<FeedSubscription> subscriptions, Map<Long, Long> unreadCount) {
|
||||||
List<FeedSubscription> subscriptions, Map<Long, Long> unreadCount) {
|
|
||||||
Category category = new Category();
|
Category category = new Category();
|
||||||
category.setId(String.valueOf(id));
|
category.setId(String.valueOf(id));
|
||||||
category.setExpanded(true);
|
category.setExpanded(true);
|
||||||
|
|
||||||
for (FeedCategory c : categories) {
|
for (FeedCategory c : categories) {
|
||||||
if ((id == null && c.getParent() == null)
|
if ((id == null && c.getParent() == null) || (c.getParent() != null && ObjectUtils.equals(c.getParent().getId(), id))) {
|
||||||
|| (c.getParent() != null && ObjectUtils.equals(c
|
Category child = buildCategory(c.getId(), categories, subscriptions, unreadCount);
|
||||||
.getParent().getId(), id))) {
|
|
||||||
Category child = buildCategory(c.getId(), categories,
|
|
||||||
subscriptions, unreadCount);
|
|
||||||
child.setId(String.valueOf(c.getId()));
|
child.setId(String.valueOf(c.getId()));
|
||||||
child.setName(c.getName());
|
child.setName(c.getName());
|
||||||
child.setPosition(c.getPosition());
|
child.setPosition(c.getPosition());
|
||||||
@@ -445,13 +410,10 @@ public class CategoryREST extends AbstractResourceREST {
|
|||||||
|
|
||||||
for (FeedSubscription subscription : subscriptions) {
|
for (FeedSubscription subscription : subscriptions) {
|
||||||
if ((id == null && subscription.getCategory() == null)
|
if ((id == null && subscription.getCategory() == null)
|
||||||
|| (subscription.getCategory() != null && ObjectUtils
|
|| (subscription.getCategory() != null && ObjectUtils.equals(subscription.getCategory().getId(), id))) {
|
||||||
.equals(subscription.getCategory().getId(), id))) {
|
|
||||||
Long size = unreadCount.get(subscription.getId());
|
Long size = unreadCount.get(subscription.getId());
|
||||||
long unread = size == null ? 0 : size;
|
long unread = size == null ? 0 : size;
|
||||||
Subscription sub = Subscription
|
Subscription sub = Subscription.build(subscription, applicationSettingsService.get().getPublicUrl(), unread);
|
||||||
.build(subscription, applicationSettingsService.get()
|
|
||||||
.getPublicUrl(), unread);
|
|
||||||
category.getFeeds().add(sub);
|
category.getFeeds().add(sub);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.commafeed.frontend.rest.resources;
|
package com.commafeed.frontend.rest.resources;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -12,8 +13,8 @@ import javax.ws.rs.core.Response;
|
|||||||
import javax.ws.rs.core.Response.Status;
|
import javax.ws.rs.core.Response.Status;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
|
||||||
import com.commafeed.backend.cache.CacheService;
|
|
||||||
import com.commafeed.backend.dao.FeedEntryStatusDAO;
|
import com.commafeed.backend.dao.FeedEntryStatusDAO;
|
||||||
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||||
import com.commafeed.backend.model.FeedEntryStatus;
|
import com.commafeed.backend.model.FeedEntryStatus;
|
||||||
@@ -26,7 +27,6 @@ import com.commafeed.frontend.model.request.MarkRequest;
|
|||||||
import com.commafeed.frontend.model.request.MultipleMarkRequest;
|
import com.commafeed.frontend.model.request.MultipleMarkRequest;
|
||||||
import com.commafeed.frontend.model.request.StarRequest;
|
import com.commafeed.frontend.model.request.StarRequest;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.wordnik.swagger.annotations.Api;
|
import com.wordnik.swagger.annotations.Api;
|
||||||
import com.wordnik.swagger.annotations.ApiOperation;
|
import com.wordnik.swagger.annotations.ApiOperation;
|
||||||
import com.wordnik.swagger.annotations.ApiParam;
|
import com.wordnik.swagger.annotations.ApiParam;
|
||||||
@@ -44,29 +44,22 @@ public class EntryREST extends AbstractResourceREST {
|
|||||||
@Inject
|
@Inject
|
||||||
FeedSubscriptionDAO feedSubscriptionDAO;
|
FeedSubscriptionDAO feedSubscriptionDAO;
|
||||||
|
|
||||||
@Inject
|
|
||||||
CacheService cache;
|
|
||||||
|
|
||||||
@Path("/mark")
|
@Path("/mark")
|
||||||
@POST
|
@POST
|
||||||
@ApiOperation(value = "Mark a feed entry", notes = "Mark a feed entry as read/unread")
|
@ApiOperation(value = "Mark a feed entry", notes = "Mark a feed entry as read/unread")
|
||||||
public Response markFeedEntry(
|
public Response markFeedEntry(@ApiParam(value = "Mark Request", required = true) MarkRequest req) {
|
||||||
@ApiParam(value = "Mark Request", required = true) MarkRequest req) {
|
|
||||||
Preconditions.checkNotNull(req);
|
Preconditions.checkNotNull(req);
|
||||||
Preconditions.checkNotNull(req.getId());
|
Preconditions.checkNotNull(req.getId());
|
||||||
Preconditions.checkNotNull(req.getFeedId());
|
Preconditions.checkNotNull(req.getFeedId());
|
||||||
|
|
||||||
feedEntryService.markEntry(getUser(), Long.valueOf(req.getId()),
|
feedEntryService.markEntry(getUser(), Long.valueOf(req.getId()), req.getFeedId(), req.isRead());
|
||||||
req.getFeedId(), req.isRead());
|
|
||||||
cache.invalidateUserData(getUser());
|
|
||||||
return Response.ok(Status.OK).build();
|
return Response.ok(Status.OK).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("/markMultiple")
|
@Path("/markMultiple")
|
||||||
@POST
|
@POST
|
||||||
@ApiOperation(value = "Mark multiple feed entries", notes = "Mark feed entries as read/unread")
|
@ApiOperation(value = "Mark multiple feed entries", notes = "Mark feed entries as read/unread")
|
||||||
public Response markFeedEntries(
|
public Response markFeedEntries(@ApiParam(value = "Multiple Mark Request", required = true) MultipleMarkRequest req) {
|
||||||
@ApiParam(value = "Multiple Mark Request", required = true) MultipleMarkRequest req) {
|
|
||||||
Preconditions.checkNotNull(req);
|
Preconditions.checkNotNull(req);
|
||||||
Preconditions.checkNotNull(req.getRequests());
|
Preconditions.checkNotNull(req.getRequests());
|
||||||
|
|
||||||
@@ -80,45 +73,76 @@ public class EntryREST extends AbstractResourceREST {
|
|||||||
@Path("/star")
|
@Path("/star")
|
||||||
@POST
|
@POST
|
||||||
@ApiOperation(value = "Mark a feed entry", notes = "Mark a feed entry as read/unread")
|
@ApiOperation(value = "Mark a feed entry", notes = "Mark a feed entry as read/unread")
|
||||||
public Response starFeedEntry(
|
public Response starFeedEntry(@ApiParam(value = "Star Request", required = true) StarRequest req) {
|
||||||
@ApiParam(value = "Star Request", required = true) StarRequest req) {
|
|
||||||
Preconditions.checkNotNull(req);
|
Preconditions.checkNotNull(req);
|
||||||
Preconditions.checkNotNull(req.getId());
|
Preconditions.checkNotNull(req.getId());
|
||||||
Preconditions.checkNotNull(req.getFeedId());
|
Preconditions.checkNotNull(req.getFeedId());
|
||||||
|
|
||||||
feedEntryService.starEntry(getUser(), Long.valueOf(req.getId()),
|
feedEntryService.starEntry(getUser(), Long.valueOf(req.getId()), req.getFeedId(), req.isStarred());
|
||||||
req.getFeedId(), req.isStarred());
|
|
||||||
|
|
||||||
return Response.ok(Status.OK).build();
|
return Response.ok(Status.OK).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("/search")
|
@Path("/search")
|
||||||
@GET
|
@GET
|
||||||
@ApiOperation(value = "Search for entries", notes = "Look through title and content of entries by keywords", responseClass = "com.commafeed.frontend.model.Entries")
|
@ApiOperation(
|
||||||
|
value = "Search for entries",
|
||||||
|
notes = "Look through title and content of entries by keywords",
|
||||||
|
responseClass = "com.commafeed.frontend.model.Entries")
|
||||||
public Response searchEntries(
|
public Response searchEntries(
|
||||||
@ApiParam(value = "keywords separated by spaces, 3 characters minimum", required = true) @QueryParam("keywords") String keywords,
|
@ApiParam(value = "keywords separated by spaces, 3 characters minimum", required = true) @QueryParam("keywords") String keywords,
|
||||||
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset,
|
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset, @ApiParam(
|
||||||
@ApiParam(value = "limit for paging") @DefaultValue("-1") @QueryParam("limit") int limit) {
|
value = "limit for paging") @DefaultValue("-1") @QueryParam("limit") int limit) {
|
||||||
keywords = StringUtils.trimToEmpty(keywords);
|
keywords = StringUtils.trimToEmpty(keywords);
|
||||||
limit = Math.min(limit, 50);
|
limit = Math.min(limit, 50);
|
||||||
Preconditions.checkArgument(StringUtils.length(keywords) >= 3);
|
Preconditions.checkArgument(StringUtils.length(keywords) >= 3);
|
||||||
|
|
||||||
Entries entries = new Entries();
|
Entries entries = new Entries();
|
||||||
|
|
||||||
List<Entry> list = Lists.newArrayList();
|
|
||||||
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(getUser());
|
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(getUser());
|
||||||
List<FeedEntryStatus> entriesStatus = feedEntryStatusDAO
|
List<FeedEntryStatus> entriesStatus = feedEntryStatusDAO.findBySubscriptions(subs, false, keywords, null, offset, limit + 1,
|
||||||
.findBySubscriptions(subs, keywords, null, offset, limit,
|
ReadingOrder.desc, true);
|
||||||
ReadingOrder.desc, true);
|
|
||||||
for (FeedEntryStatus status : entriesStatus) {
|
for (FeedEntryStatus status : entriesStatus) {
|
||||||
list.add(Entry.build(status, applicationSettingsService.get()
|
entries.getEntries().add(
|
||||||
.getPublicUrl(), applicationSettingsService.get()
|
Entry.build(status, applicationSettingsService.get().getPublicUrl(), applicationSettingsService.get()
|
||||||
.isImageProxyEnabled()));
|
.isImageProxyEnabled()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean hasMore = entries.getEntries().size() > limit;
|
||||||
|
if (hasMore) {
|
||||||
|
entries.setHasMore(true);
|
||||||
|
entries.getEntries().remove(entries.getEntries().size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeUnwanted(entries.getEntries(), keywords);
|
||||||
|
|
||||||
entries.setName("Search for : " + keywords);
|
entries.setName("Search for : " + keywords);
|
||||||
entries.getEntries().addAll(list);
|
entries.setTimestamp(System.currentTimeMillis());
|
||||||
return Response.ok(entries).build();
|
return Response.ok(entries).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeUnwanted(List<Entry> entries, String keywords) {
|
||||||
|
Iterator<Entry> it = entries.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Entry entry = it.next();
|
||||||
|
boolean keep = true;
|
||||||
|
for (String keyword : keywords.split(" ")) {
|
||||||
|
String content = Jsoup.parse(entry.getContent()).text();
|
||||||
|
if (!StringUtils.containsIgnoreCase(content, keyword)) {
|
||||||
|
keep = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
String title = Jsoup.parse(entry.getTitle()).text();
|
||||||
|
if (!StringUtils.containsIgnoreCase(title, keyword)) {
|
||||||
|
keep = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!keep) {
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import com.commafeed.backend.model.FeedEntryStatus;
|
|||||||
import com.commafeed.backend.model.FeedSubscription;
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
import com.commafeed.backend.model.UserRole.Role;
|
import com.commafeed.backend.model.UserRole.Role;
|
||||||
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
||||||
|
import com.commafeed.backend.services.FeedEntryService;
|
||||||
import com.commafeed.backend.services.FeedSubscriptionService;
|
import com.commafeed.backend.services.FeedSubscriptionService;
|
||||||
import com.commafeed.frontend.SecurityCheck;
|
import com.commafeed.frontend.SecurityCheck;
|
||||||
import com.commafeed.frontend.model.Entries;
|
import com.commafeed.frontend.model.Entries;
|
||||||
@@ -97,6 +98,9 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
@Inject
|
@Inject
|
||||||
FeedSubscriptionDAO feedSubscriptionDAO;
|
FeedSubscriptionDAO feedSubscriptionDAO;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FeedEntryService feedEntryService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FeedSubscriptionService feedSubscriptionService;
|
FeedSubscriptionService feedSubscriptionService;
|
||||||
|
|
||||||
@@ -124,13 +128,15 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
@Path("/entries")
|
@Path("/entries")
|
||||||
@GET
|
@GET
|
||||||
@ApiOperation(value = "Get feed entries", notes = "Get a list of feed entries", responseClass = "com.commafeed.frontend.model.Entries")
|
@ApiOperation(value = "Get feed entries", notes = "Get a list of feed entries", responseClass = "com.commafeed.frontend.model.Entries")
|
||||||
public Response getFeedEntries(
|
public Response getFeedEntries(@ApiParam(value = "id of the feed", required = true) @QueryParam("id") String id, @ApiParam(
|
||||||
@ApiParam(value = "id of the feed", required = true) @QueryParam("id") String id,
|
value = "all entries or only unread ones",
|
||||||
@ApiParam(value = "all entries or only unread ones", allowableValues = "all,unread", required = true) @DefaultValue("unread") @QueryParam("readType") ReadType readType,
|
allowableValues = "all,unread",
|
||||||
@ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan,
|
required = true) @DefaultValue("unread") @QueryParam("readType") ReadType readType, @ApiParam(
|
||||||
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset,
|
value = "only entries newer than this") @QueryParam("newerThan") Long newerThan,
|
||||||
@ApiParam(value = "limit for paging, default 20, maximum 50") @DefaultValue("20") @QueryParam("limit") int limit,
|
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset, @ApiParam(
|
||||||
@ApiParam(value = "date ordering", allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order) {
|
value = "limit for paging, default 20, maximum 50") @DefaultValue("20") @QueryParam("limit") int limit, @ApiParam(
|
||||||
|
value = "date ordering",
|
||||||
|
allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order) {
|
||||||
|
|
||||||
Preconditions.checkNotNull(id);
|
Preconditions.checkNotNull(id);
|
||||||
Preconditions.checkNotNull(readType);
|
Preconditions.checkNotNull(readType);
|
||||||
@@ -141,33 +147,22 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
Entries entries = new Entries();
|
Entries entries = new Entries();
|
||||||
boolean unreadOnly = readType == ReadType.unread;
|
boolean unreadOnly = readType == ReadType.unread;
|
||||||
|
|
||||||
Date newerThanDate = newerThan == null ? null : new Date(
|
Date newerThanDate = newerThan == null ? null : new Date(Long.valueOf(newerThan));
|
||||||
Long.valueOf(newerThan));
|
|
||||||
|
|
||||||
FeedSubscription subscription = feedSubscriptionDAO.findById(getUser(),
|
FeedSubscription subscription = feedSubscriptionDAO.findById(getUser(), Long.valueOf(id));
|
||||||
Long.valueOf(id));
|
|
||||||
if (subscription != null) {
|
if (subscription != null) {
|
||||||
entries.setName(subscription.getTitle());
|
entries.setName(subscription.getTitle());
|
||||||
entries.setMessage(subscription.getFeed().getMessage());
|
entries.setMessage(subscription.getFeed().getMessage());
|
||||||
entries.setErrorCount(subscription.getFeed().getErrorCount());
|
entries.setErrorCount(subscription.getFeed().getErrorCount());
|
||||||
entries.setFeedLink(subscription.getFeed().getLink());
|
entries.setFeedLink(subscription.getFeed().getLink());
|
||||||
|
|
||||||
List<FeedEntryStatus> list = null;
|
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(Arrays.asList(subscription), unreadOnly, null,
|
||||||
if (unreadOnly) {
|
newerThanDate, offset, limit + 1, order, true);
|
||||||
list = feedEntryStatusDAO.findUnreadBySubscriptions(
|
|
||||||
Arrays.asList(subscription), newerThanDate, offset,
|
|
||||||
limit + 1, order, true);
|
|
||||||
} else {
|
|
||||||
list = feedEntryStatusDAO.findBySubscriptions(
|
|
||||||
Arrays.asList(subscription), null, newerThanDate,
|
|
||||||
offset, limit + 1, order, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (FeedEntryStatus status : list) {
|
for (FeedEntryStatus status : list) {
|
||||||
entries.getEntries().add(
|
entries.getEntries().add(
|
||||||
Entry.build(status, applicationSettingsService.get()
|
Entry.build(status, applicationSettingsService.get().getPublicUrl(), applicationSettingsService.get()
|
||||||
.getPublicUrl(), applicationSettingsService
|
.isImageProxyEnabled()));
|
||||||
.get().isImageProxyEnabled()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasMore = entries.getEntries().size() > limit;
|
boolean hasMore = entries.getEntries().size() > limit;
|
||||||
@@ -186,8 +181,7 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
@ApiOperation(value = "Get feed entries as a feed", notes = "Get a feed of feed entries")
|
@ApiOperation(value = "Get feed entries as a feed", notes = "Get a feed of feed entries")
|
||||||
@Produces(MediaType.APPLICATION_XML)
|
@Produces(MediaType.APPLICATION_XML)
|
||||||
@SecurityCheck(value = Role.USER, apiKeyAllowed = true)
|
@SecurityCheck(value = Role.USER, apiKeyAllowed = true)
|
||||||
public Response getFeedEntriesAsFeed(
|
public Response getFeedEntriesAsFeed(@ApiParam(value = "id of the feed", required = true) @QueryParam("id") String id) {
|
||||||
@ApiParam(value = "id of the feed", required = true) @QueryParam("id") String id) {
|
|
||||||
|
|
||||||
Preconditions.checkNotNull(id);
|
Preconditions.checkNotNull(id);
|
||||||
|
|
||||||
@@ -196,8 +190,7 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
int offset = 0;
|
int offset = 0;
|
||||||
int limit = 20;
|
int limit = 20;
|
||||||
|
|
||||||
Entries entries = (Entries) getFeedEntries(id, readType, null, offset,
|
Entries entries = (Entries) getFeedEntries(id, readType, null, offset, limit, order).getEntity();
|
||||||
limit, order).getEntity();
|
|
||||||
|
|
||||||
SyndFeed feed = new SyndFeedImpl();
|
SyndFeed feed = new SyndFeedImpl();
|
||||||
feed.setFeedType("rss_2.0");
|
feed.setFeedType("rss_2.0");
|
||||||
@@ -228,16 +221,13 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
url = StringUtils.trimToEmpty(url);
|
url = StringUtils.trimToEmpty(url);
|
||||||
url = prependHttp(url);
|
url = prependHttp(url);
|
||||||
try {
|
try {
|
||||||
FetchedFeed feed = feedFetcher.fetch(url, true, null, null, null,
|
FetchedFeed feed = feedFetcher.fetch(url, true, null, null, null, null);
|
||||||
null);
|
|
||||||
info = new FeedInfo();
|
info = new FeedInfo();
|
||||||
info.setUrl(feed.getFeed().getUrl());
|
info.setUrl(feed.getFeed().getUrl());
|
||||||
info.setTitle(feed.getTitle());
|
info.setTitle(feed.getTitle());
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new WebApplicationException(e, Response
|
throw new WebApplicationException(e, Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build());
|
||||||
.status(Status.INTERNAL_SERVER_ERROR)
|
|
||||||
.entity(e.getMessage()).build());
|
|
||||||
}
|
}
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
@@ -245,8 +235,7 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
@POST
|
@POST
|
||||||
@Path("/fetch")
|
@Path("/fetch")
|
||||||
@ApiOperation(value = "Fetch a feed", notes = "Fetch a feed by its url", responseClass = "com.commafeed.frontend.model.FeedInfo")
|
@ApiOperation(value = "Fetch a feed", notes = "Fetch a feed by its url", responseClass = "com.commafeed.frontend.model.FeedInfo")
|
||||||
public Response fetchFeed(
|
public Response fetchFeed(@ApiParam(value = "feed url", required = true) FeedInfoRequest req) {
|
||||||
@ApiParam(value = "feed url", required = true) FeedInfoRequest req) {
|
|
||||||
Preconditions.checkNotNull(req);
|
Preconditions.checkNotNull(req);
|
||||||
Preconditions.checkNotNull(req.getUrl());
|
Preconditions.checkNotNull(req.getUrl());
|
||||||
|
|
||||||
@@ -254,8 +243,7 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
try {
|
try {
|
||||||
info = fetchFeedInternal(req.getUrl());
|
info = fetchFeedInternal(req.getUrl());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Response.status(Status.INTERNAL_SERVER_ERROR)
|
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
|
||||||
.entity(e.getMessage()).build();
|
|
||||||
}
|
}
|
||||||
return Response.ok(info).build();
|
return Response.ok(info).build();
|
||||||
}
|
}
|
||||||
@@ -268,8 +256,7 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
Preconditions.checkNotNull(req);
|
Preconditions.checkNotNull(req);
|
||||||
Preconditions.checkNotNull(req.getId());
|
Preconditions.checkNotNull(req.getId());
|
||||||
|
|
||||||
FeedSubscription sub = feedSubscriptionDAO.findById(getUser(),
|
FeedSubscription sub = feedSubscriptionDAO.findById(getUser(), req.getId());
|
||||||
req.getId());
|
|
||||||
if (sub != null) {
|
if (sub != null) {
|
||||||
Feed feed = sub.getFeed();
|
Feed feed = sub.getFeed();
|
||||||
feed.setUrgent(true);
|
feed.setUrgent(true);
|
||||||
@@ -283,54 +270,43 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
@Path("/mark")
|
@Path("/mark")
|
||||||
@POST
|
@POST
|
||||||
@ApiOperation(value = "Mark feed entries", notes = "Mark feed entries as read (unread is not supported)")
|
@ApiOperation(value = "Mark feed entries", notes = "Mark feed entries as read (unread is not supported)")
|
||||||
public Response markFeedEntries(
|
public Response markFeedEntries(@ApiParam(value = "Mark request") MarkRequest req) {
|
||||||
@ApiParam(value = "Mark request") MarkRequest req) {
|
|
||||||
Preconditions.checkNotNull(req);
|
Preconditions.checkNotNull(req);
|
||||||
Preconditions.checkNotNull(req.getId());
|
Preconditions.checkNotNull(req.getId());
|
||||||
|
|
||||||
Date olderThan = req.getOlderThan() == null ? null : new Date(
|
Date olderThan = req.getOlderThan() == null ? null : new Date(req.getOlderThan());
|
||||||
req.getOlderThan());
|
|
||||||
|
|
||||||
FeedSubscription subscription = feedSubscriptionDAO.findById(getUser(),
|
FeedSubscription subscription = feedSubscriptionDAO.findById(getUser(), Long.valueOf(req.getId()));
|
||||||
Long.valueOf(req.getId()));
|
|
||||||
if (subscription != null) {
|
if (subscription != null) {
|
||||||
feedEntryStatusDAO.markSubscriptionEntries(
|
feedEntryService.markSubscriptionEntries(getUser(), Arrays.asList(subscription), olderThan);
|
||||||
Arrays.asList(subscription), olderThan);
|
|
||||||
}
|
}
|
||||||
cache.invalidateUserData(getUser());
|
|
||||||
return Response.ok(Status.OK).build();
|
return Response.ok(Status.OK).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/get/{id}")
|
@Path("/get/{id}")
|
||||||
@ApiOperation(value = "", notes = "")
|
@ApiOperation(value = "", notes = "")
|
||||||
public Response get(
|
public Response get(@ApiParam(value = "user id", required = true) @PathParam("id") Long id) {
|
||||||
@ApiParam(value = "user id", required = true) @PathParam("id") Long id) {
|
|
||||||
|
|
||||||
Preconditions.checkNotNull(id);
|
Preconditions.checkNotNull(id);
|
||||||
FeedSubscription sub = feedSubscriptionDAO.findById(getUser(), id);
|
FeedSubscription sub = feedSubscriptionDAO.findById(getUser(), id);
|
||||||
if (sub == null) {
|
if (sub == null) {
|
||||||
return Response.status(Status.NOT_FOUND).build();
|
return Response.status(Status.NOT_FOUND).build();
|
||||||
}
|
}
|
||||||
Long unreadCount = feedSubscriptionService.getUnreadCount(getUser())
|
Long unreadCount = feedSubscriptionService.getUnreadCount(getUser()).get(id);
|
||||||
.get(id);
|
|
||||||
if (unreadCount == null) {
|
if (unreadCount == null) {
|
||||||
unreadCount = new Long(0);
|
unreadCount = new Long(0);
|
||||||
}
|
}
|
||||||
return Response.ok(
|
return Response.ok(Subscription.build(sub, applicationSettingsService.get().getPublicUrl(), unreadCount)).build();
|
||||||
Subscription.build(sub, applicationSettingsService.get()
|
|
||||||
.getPublicUrl(), unreadCount)).build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/favicon/{id}")
|
@Path("/favicon/{id}")
|
||||||
@ApiOperation(value = "Fetch a feed's icon", notes = "Fetch a feed's icon")
|
@ApiOperation(value = "Fetch a feed's icon", notes = "Fetch a feed's icon")
|
||||||
public Response getFavicon(
|
public Response getFavicon(@ApiParam(value = "subscription id") @PathParam("id") Long id) {
|
||||||
@ApiParam(value = "subscription id") @PathParam("id") Long id) {
|
|
||||||
|
|
||||||
Preconditions.checkNotNull(id);
|
Preconditions.checkNotNull(id);
|
||||||
FeedSubscription subscription = feedSubscriptionDAO.findById(getUser(),
|
FeedSubscription subscription = feedSubscriptionDAO.findById(getUser(), id);
|
||||||
id);
|
|
||||||
if (subscription == null) {
|
if (subscription == null) {
|
||||||
return Response.status(Status.NOT_FOUND).build();
|
return Response.status(Status.NOT_FOUND).build();
|
||||||
}
|
}
|
||||||
@@ -340,11 +316,8 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
|
|
||||||
ResponseBuilder builder = null;
|
ResponseBuilder builder = null;
|
||||||
if (icon == null) {
|
if (icon == null) {
|
||||||
String baseUrl = FeedUtils
|
String baseUrl = FeedUtils.removeTrailingSlash(applicationSettingsService.get().getPublicUrl());
|
||||||
.removeTrailingSlash(applicationSettingsService.get()
|
builder = Response.status(Status.MOVED_PERMANENTLY).location(URI.create(baseUrl + "/images/default_favicon.gif"));
|
||||||
.getPublicUrl());
|
|
||||||
builder = Response.status(Status.MOVED_PERMANENTLY).location(
|
|
||||||
URI.create(baseUrl + "/images/default_favicon.gif"));
|
|
||||||
} else {
|
} else {
|
||||||
builder = Response.ok(icon, "image/x-icon");
|
builder = Response.ok(icon, "image/x-icon");
|
||||||
}
|
}
|
||||||
@@ -366,8 +339,7 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
@POST
|
@POST
|
||||||
@Path("/subscribe")
|
@Path("/subscribe")
|
||||||
@ApiOperation(value = "Subscribe to a feed", notes = "Subscribe to a feed")
|
@ApiOperation(value = "Subscribe to a feed", notes = "Subscribe to a feed")
|
||||||
public Response subscribe(
|
public Response subscribe(@ApiParam(value = "subscription request", required = true) SubscribeRequest req) {
|
||||||
@ApiParam(value = "subscription request", required = true) SubscribeRequest req) {
|
|
||||||
Preconditions.checkNotNull(req);
|
Preconditions.checkNotNull(req);
|
||||||
Preconditions.checkNotNull(req.getTitle());
|
Preconditions.checkNotNull(req.getTitle());
|
||||||
Preconditions.checkNotNull(req.getUrl());
|
Preconditions.checkNotNull(req.getUrl());
|
||||||
@@ -376,28 +348,21 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
try {
|
try {
|
||||||
url = fetchFeedInternal(url).getUrl();
|
url = fetchFeedInternal(url).getUrl();
|
||||||
|
|
||||||
FeedCategory category = CategoryREST.ALL
|
FeedCategory category = CategoryREST.ALL.equals(req.getCategoryId()) ? null : feedCategoryDAO.findById(Long.valueOf(req
|
||||||
.equals(req.getCategoryId()) ? null : feedCategoryDAO
|
.getCategoryId()));
|
||||||
.findById(Long.valueOf(req.getCategoryId()));
|
|
||||||
FeedInfo info = fetchFeedInternal(url);
|
FeedInfo info = fetchFeedInternal(url);
|
||||||
feedSubscriptionService.subscribe(getUser(), info.getUrl(),
|
feedSubscriptionService.subscribe(getUser(), info.getUrl(), req.getTitle(), category);
|
||||||
req.getTitle(), category);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.info("Failed to subscribe to URL {}: {}", url, e.getMessage());
|
log.info("Failed to subscribe to URL {}: {}", url, e.getMessage());
|
||||||
return Response
|
return Response.status(Status.SERVICE_UNAVAILABLE).entity("Failed to subscribe to URL " + url + ": " + e.getMessage()).build();
|
||||||
.status(Status.SERVICE_UNAVAILABLE)
|
|
||||||
.entity("Failed to subscribe to URL " + url + ": "
|
|
||||||
+ e.getMessage()).build();
|
|
||||||
}
|
}
|
||||||
cache.invalidateUserData(getUser());
|
|
||||||
return Response.ok(Status.OK).build();
|
return Response.ok(Status.OK).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/subscribe")
|
@Path("/subscribe")
|
||||||
@ApiOperation(value = "Subscribe to a feed", notes = "Subscribe to a feed")
|
@ApiOperation(value = "Subscribe to a feed", notes = "Subscribe to a feed")
|
||||||
public Response subscribe(
|
public Response subscribe(@ApiParam(value = "feed url", required = true) @QueryParam("url") String url) {
|
||||||
@ApiParam(value = "feed url", required = true) @QueryParam("url") String url) {
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Preconditions.checkNotNull(url);
|
Preconditions.checkNotNull(url);
|
||||||
@@ -406,14 +371,11 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
url = fetchFeedInternal(url).getUrl();
|
url = fetchFeedInternal(url).getUrl();
|
||||||
|
|
||||||
FeedInfo info = fetchFeedInternal(url);
|
FeedInfo info = fetchFeedInternal(url);
|
||||||
feedSubscriptionService.subscribe(getUser(), info.getUrl(),
|
feedSubscriptionService.subscribe(getUser(), info.getUrl(), info.getTitle(), null);
|
||||||
info.getTitle(), null);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.info("Could not subscribe to url {} : {}", url, e.getMessage());
|
log.info("Could not subscribe to url {} : {}", url, e.getMessage());
|
||||||
}
|
}
|
||||||
return Response.temporaryRedirect(
|
return Response.temporaryRedirect(URI.create(applicationSettingsService.get().getPublicUrl())).build();
|
||||||
URI.create(applicationSettingsService.get().getPublicUrl()))
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String prependHttp(String url) {
|
private String prependHttp(String url) {
|
||||||
@@ -430,11 +392,8 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
Preconditions.checkNotNull(req);
|
Preconditions.checkNotNull(req);
|
||||||
Preconditions.checkNotNull(req.getId());
|
Preconditions.checkNotNull(req.getId());
|
||||||
|
|
||||||
FeedSubscription sub = feedSubscriptionDAO.findById(getUser(),
|
boolean deleted = feedSubscriptionService.unsubscribe(getUser(), req.getId());
|
||||||
req.getId());
|
if (deleted) {
|
||||||
if (sub != null) {
|
|
||||||
feedSubscriptionDAO.delete(sub);
|
|
||||||
cache.invalidateUserData(getUser());
|
|
||||||
return Response.ok(Status.OK).build();
|
return Response.ok(Status.OK).build();
|
||||||
} else {
|
} else {
|
||||||
return Response.status(Status.NOT_FOUND).build();
|
return Response.status(Status.NOT_FOUND).build();
|
||||||
@@ -444,41 +403,34 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
@POST
|
@POST
|
||||||
@Path("/modify")
|
@Path("/modify")
|
||||||
@ApiOperation(value = "Modify a subscription", notes = "Modify a feed subscription")
|
@ApiOperation(value = "Modify a subscription", notes = "Modify a feed subscription")
|
||||||
public Response modify(
|
public Response modify(@ApiParam(value = "subscription id", required = true) FeedModificationRequest req) {
|
||||||
@ApiParam(value = "subscription id", required = true) FeedModificationRequest req) {
|
|
||||||
Preconditions.checkNotNull(req);
|
Preconditions.checkNotNull(req);
|
||||||
Preconditions.checkNotNull(req.getId());
|
Preconditions.checkNotNull(req.getId());
|
||||||
|
|
||||||
FeedSubscription subscription = feedSubscriptionDAO.findById(getUser(),
|
FeedSubscription subscription = feedSubscriptionDAO.findById(getUser(), req.getId());
|
||||||
req.getId());
|
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(req.getName())) {
|
if (StringUtils.isNotBlank(req.getName())) {
|
||||||
subscription.setTitle(req.getName());
|
subscription.setTitle(req.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
FeedCategory parent = null;
|
FeedCategory parent = null;
|
||||||
if (req.getCategoryId() != null
|
if (req.getCategoryId() != null && !CategoryREST.ALL.equals(req.getCategoryId())) {
|
||||||
&& !CategoryREST.ALL.equals(req.getCategoryId())) {
|
parent = feedCategoryDAO.findById(getUser(), Long.valueOf(req.getCategoryId()));
|
||||||
parent = feedCategoryDAO.findById(getUser(),
|
|
||||||
Long.valueOf(req.getCategoryId()));
|
|
||||||
}
|
}
|
||||||
subscription.setCategory(parent);
|
subscription.setCategory(parent);
|
||||||
|
|
||||||
if (req.getPosition() != null) {
|
if (req.getPosition() != null) {
|
||||||
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategory(
|
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategory(getUser(), parent);
|
||||||
getUser(), parent);
|
|
||||||
Collections.sort(subs, new Comparator<FeedSubscription>() {
|
Collections.sort(subs, new Comparator<FeedSubscription>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(FeedSubscription o1, FeedSubscription o2) {
|
public int compare(FeedSubscription o1, FeedSubscription o2) {
|
||||||
return ObjectUtils.compare(o1.getPosition(),
|
return ObjectUtils.compare(o1.getPosition(), o2.getPosition());
|
||||||
o2.getPosition());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
int existingIndex = -1;
|
int existingIndex = -1;
|
||||||
for (int i = 0; i < subs.size(); i++) {
|
for (int i = 0; i < subs.size(); i++) {
|
||||||
if (ObjectUtils.equals(subs.get(i).getId(),
|
if (ObjectUtils.equals(subs.get(i).getId(), subscription.getId())) {
|
||||||
subscription.getId())) {
|
|
||||||
existingIndex = i;
|
existingIndex = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -494,7 +446,7 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
} else {
|
} else {
|
||||||
feedSubscriptionDAO.saveOrUpdate(subscription);
|
feedSubscriptionDAO.saveOrUpdate(subscription);
|
||||||
}
|
}
|
||||||
cache.invalidateUserData(getUser());
|
cache.invalidateUserRootCategory(getUser());
|
||||||
return Response.ok(Status.OK).build();
|
return Response.ok(Status.OK).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,22 +458,19 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
|
|
||||||
String publicUrl = applicationSettingsService.get().getPublicUrl();
|
String publicUrl = applicationSettingsService.get().getPublicUrl();
|
||||||
if (StringUtils.isBlank(publicUrl)) {
|
if (StringUtils.isBlank(publicUrl)) {
|
||||||
throw new WebApplicationException(Response
|
throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR)
|
||||||
.status(Status.INTERNAL_SERVER_ERROR)
|
|
||||||
.entity("Set the public URL in the admin section.").build());
|
.entity("Set the public URL in the admin section.").build());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StartupBean.USERNAME_DEMO.equals(getUser().getName())) {
|
if (StartupBean.USERNAME_DEMO.equals(getUser().getName())) {
|
||||||
return Response.status(Status.FORBIDDEN)
|
return Response.status(Status.FORBIDDEN).entity("Import is disabled for the demo account").build();
|
||||||
.entity("Import is disabled for the demo account").build();
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
FileItemFactory factory = new DiskFileItemFactory(1000000, null);
|
FileItemFactory factory = new DiskFileItemFactory(1000000, null);
|
||||||
ServletFileUpload upload = new ServletFileUpload(factory);
|
ServletFileUpload upload = new ServletFileUpload(factory);
|
||||||
for (FileItem item : upload.parseRequest(request)) {
|
for (FileItem item : upload.parseRequest(request)) {
|
||||||
if ("file".equals(item.getFieldName())) {
|
if ("file".equals(item.getFieldName())) {
|
||||||
String opml = IOUtils.toString(item.getInputStream(),
|
String opml = IOUtils.toString(item.getInputStream(), "UTF-8");
|
||||||
"UTF-8");
|
|
||||||
if (StringUtils.isNotBlank(opml)) {
|
if (StringUtils.isNotBlank(opml)) {
|
||||||
opmlImporter.importOpml(getUser(), opml);
|
opmlImporter.importOpml(getUser(), opml);
|
||||||
}
|
}
|
||||||
@@ -529,14 +478,9 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new WebApplicationException(Response
|
throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build());
|
||||||
.status(Status.INTERNAL_SERVER_ERROR)
|
|
||||||
.entity(e.getMessage()).build());
|
|
||||||
}
|
}
|
||||||
cache.invalidateUserData(getUser());
|
return Response.temporaryRedirect(URI.create(applicationSettingsService.get().getPublicUrl())).build();
|
||||||
return Response.temporaryRedirect(
|
|
||||||
URI.create(applicationSettingsService.get().getPublicUrl()))
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@@ -550,8 +494,7 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
try {
|
try {
|
||||||
opmlString = output.outputString(opml);
|
opmlString = output.outputString(opml);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e)
|
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e).build();
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
return Response.ok(opmlString).build();
|
return Response.ok(opmlString).build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ import com.google.api.client.repackaged.com.google.common.base.Preconditions;
|
|||||||
@Path("/push")
|
@Path("/push")
|
||||||
public class PubSubHubbubCallbackREST {
|
public class PubSubHubbubCallbackREST {
|
||||||
|
|
||||||
private static Logger log = LoggerFactory
|
private static Logger log = LoggerFactory.getLogger(PubSubHubbubCallbackREST.class);
|
||||||
.getLogger(PubSubHubbubCallbackREST.class);
|
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
HttpServletRequest request;
|
HttpServletRequest request;
|
||||||
@@ -57,13 +56,10 @@ public class PubSubHubbubCallbackREST {
|
|||||||
@Path("/callback")
|
@Path("/callback")
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.TEXT_PLAIN)
|
@Produces(MediaType.TEXT_PLAIN)
|
||||||
public Response verify(@QueryParam("hub.mode") String mode,
|
public Response verify(@QueryParam("hub.mode") String mode, @QueryParam("hub.topic") String topic,
|
||||||
@QueryParam("hub.topic") String topic,
|
@QueryParam("hub.challenge") String challenge, @QueryParam("hub.lease_seconds") String leaseSeconds,
|
||||||
@QueryParam("hub.challenge") String challenge,
|
|
||||||
@QueryParam("hub.lease_seconds") String leaseSeconds,
|
|
||||||
@QueryParam("hub.verify_token") String verifyToken) {
|
@QueryParam("hub.verify_token") String verifyToken) {
|
||||||
if (!applicationSettingsService.get()
|
if (!applicationSettingsService.get().isPubsubhubbub()) {
|
||||||
.isPubsubhubbub()) {
|
|
||||||
return Response.status(Status.FORBIDDEN).entity("pubsubhubbub is disabled").build();
|
return Response.status(Status.FORBIDDEN).entity("pubsubhubbub is disabled").build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,8 +72,7 @@ public class PubSubHubbubCallbackREST {
|
|||||||
|
|
||||||
if (feeds.isEmpty() == false) {
|
if (feeds.isEmpty() == false) {
|
||||||
for (Feed feed : feeds) {
|
for (Feed feed : feeds) {
|
||||||
log.debug("activated push notifications for {}",
|
log.debug("activated push notifications for {}", feed.getPushTopic());
|
||||||
feed.getPushTopic());
|
|
||||||
feed.setPushLastPing(new Date());
|
feed.setPushLastPing(new Date());
|
||||||
}
|
}
|
||||||
feedDAO.saveOrUpdate(feeds);
|
feedDAO.saveOrUpdate(feeds);
|
||||||
@@ -92,8 +87,7 @@ public class PubSubHubbubCallbackREST {
|
|||||||
@POST
|
@POST
|
||||||
@Consumes({ MediaType.APPLICATION_ATOM_XML, "application/rss+xml" })
|
@Consumes({ MediaType.APPLICATION_ATOM_XML, "application/rss+xml" })
|
||||||
public Response callback() {
|
public Response callback() {
|
||||||
if (!applicationSettingsService.get()
|
if (!applicationSettingsService.get().isPubsubhubbub()) {
|
||||||
.isPubsubhubbub()) {
|
|
||||||
return Response.status(Status.FORBIDDEN).entity("pubsubhubbub is disabled").build();
|
return Response.status(Status.FORBIDDEN).entity("pubsubhubbub is disabled").build();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -34,10 +34,8 @@ public class ServerREST extends AbstractResourceREST {
|
|||||||
ApplicationPropertiesService properties = ApplicationPropertiesService.get();
|
ApplicationPropertiesService properties = ApplicationPropertiesService.get();
|
||||||
|
|
||||||
ServerInfo infos = new ServerInfo();
|
ServerInfo infos = new ServerInfo();
|
||||||
infos.setAnnouncement(applicationSettingsService.get()
|
infos.setAnnouncement(applicationSettingsService.get().getAnnouncement());
|
||||||
.getAnnouncement());
|
infos.getSupportedLanguages().putAll(startupBean.getSupportedLanguages());
|
||||||
infos.getSupportedLanguages().putAll(
|
|
||||||
startupBean.getSupportedLanguages());
|
|
||||||
infos.setVersion(properties.getVersion());
|
infos.setVersion(properties.getVersion());
|
||||||
infos.setGitCommit(properties.getGitCommit());
|
infos.setGitCommit(properties.getGitCommit());
|
||||||
return Response.ok(infos).build();
|
return Response.ok(infos).build();
|
||||||
@@ -57,8 +55,7 @@ public class ServerREST extends AbstractResourceREST {
|
|||||||
HttpResult result = httpGetter.getBinary(url, 20000);
|
HttpResult result = httpGetter.getBinary(url, 20000);
|
||||||
return Response.ok(result.getContent()).build();
|
return Response.ok(result.getContent()).build();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Response.status(Status.SERVICE_UNAVAILABLE)
|
return Response.status(Status.SERVICE_UNAVAILABLE).entity(e.getMessage()).build();
|
||||||
.entity(e.getMessage()).build();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import javax.ws.rs.core.Response.Status;
|
|||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
import com.commafeed.backend.StartupBean;
|
import com.commafeed.backend.StartupBean;
|
||||||
import com.commafeed.backend.cache.CacheService;
|
|
||||||
import com.commafeed.backend.dao.UserDAO;
|
import com.commafeed.backend.dao.UserDAO;
|
||||||
import com.commafeed.backend.dao.UserRoleDAO;
|
import com.commafeed.backend.dao.UserRoleDAO;
|
||||||
import com.commafeed.backend.dao.UserSettingsDAO;
|
import com.commafeed.backend.dao.UserSettingsDAO;
|
||||||
@@ -58,15 +57,15 @@ public class UserREST extends AbstractResourceREST {
|
|||||||
@Inject
|
@Inject
|
||||||
PasswordEncryptionService encryptionService;
|
PasswordEncryptionService encryptionService;
|
||||||
|
|
||||||
@Inject
|
|
||||||
CacheService cache;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ApplicationSettingsService applicationSettingsService;
|
ApplicationSettingsService applicationSettingsService;
|
||||||
|
|
||||||
@Path("/settings")
|
@Path("/settings")
|
||||||
@GET
|
@GET
|
||||||
@ApiOperation(value = "Retrieve user settings", notes = "Retrieve user settings", responseClass = "com.commafeed.frontend.model.Settings")
|
@ApiOperation(
|
||||||
|
value = "Retrieve user settings",
|
||||||
|
notes = "Retrieve user settings",
|
||||||
|
responseClass = "com.commafeed.frontend.model.Settings")
|
||||||
public Response getSettings() {
|
public Response getSettings() {
|
||||||
Settings s = new Settings();
|
Settings s = new Settings();
|
||||||
UserSettings settings = userSettingsDAO.findByUser(getUser());
|
UserSettings settings = userSettingsDAO.findByUser(getUser());
|
||||||
@@ -149,16 +148,13 @@ public class UserREST extends AbstractResourceREST {
|
|||||||
@Path("/profile")
|
@Path("/profile")
|
||||||
@POST
|
@POST
|
||||||
@ApiOperation(value = "Save user's profile")
|
@ApiOperation(value = "Save user's profile")
|
||||||
public Response save(
|
public Response save(@ApiParam(required = true) ProfileModificationRequest request) {
|
||||||
@ApiParam(required = true) ProfileModificationRequest request) {
|
|
||||||
User user = getUser();
|
User user = getUser();
|
||||||
|
|
||||||
Preconditions.checkArgument(StringUtils.isBlank(request.getPassword())
|
Preconditions.checkArgument(StringUtils.isBlank(request.getPassword()) || request.getPassword().length() >= 6);
|
||||||
|| request.getPassword().length() >= 6);
|
|
||||||
if (StringUtils.isNotBlank(request.getEmail())) {
|
if (StringUtils.isNotBlank(request.getEmail())) {
|
||||||
User u = userDAO.findByEmail(request.getEmail());
|
User u = userDAO.findByEmail(request.getEmail());
|
||||||
Preconditions.checkArgument(u == null
|
Preconditions.checkArgument(u == null || user.getId().equals(u.getId()));
|
||||||
|| user.getId().equals(u.getId()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StartupBean.USERNAME_DEMO.equals(user.getName())) {
|
if (StartupBean.USERNAME_DEMO.equals(user.getName())) {
|
||||||
@@ -167,8 +163,7 @@ public class UserREST extends AbstractResourceREST {
|
|||||||
|
|
||||||
user.setEmail(StringUtils.trimToNull(request.getEmail()));
|
user.setEmail(StringUtils.trimToNull(request.getEmail()));
|
||||||
if (StringUtils.isNotBlank(request.getPassword())) {
|
if (StringUtils.isNotBlank(request.getPassword())) {
|
||||||
byte[] password = encryptionService.getEncryptedPassword(
|
byte[] password = encryptionService.getEncryptedPassword(request.getPassword(), user.getSalt());
|
||||||
request.getPassword(), user.getSalt());
|
|
||||||
user.setPassword(password);
|
user.setPassword(password);
|
||||||
user.setApiKey(userService.generateApiKey(user));
|
user.setApiKey(userService.generateApiKey(user));
|
||||||
}
|
}
|
||||||
@@ -185,12 +180,10 @@ public class UserREST extends AbstractResourceREST {
|
|||||||
@SecurityCheck(Role.NONE)
|
@SecurityCheck(Role.NONE)
|
||||||
public Response register(@ApiParam(required = true) RegistrationRequest req) {
|
public Response register(@ApiParam(required = true) RegistrationRequest req) {
|
||||||
try {
|
try {
|
||||||
userService.register(req.getName(), req.getPassword(),
|
userService.register(req.getName(), req.getPassword(), req.getEmail(), Arrays.asList(Role.USER));
|
||||||
req.getEmail(), Arrays.asList(Role.USER));
|
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Response.status(Status.INTERNAL_SERVER_ERROR)
|
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
|
||||||
.entity(e.getMessage()).build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -199,12 +192,10 @@ public class UserREST extends AbstractResourceREST {
|
|||||||
@POST
|
@POST
|
||||||
@ApiOperation(value = "Delete the user account")
|
@ApiOperation(value = "Delete the user account")
|
||||||
public Response delete() {
|
public Response delete() {
|
||||||
if (StartupBean.USERNAME_ADMIN.equals(getUser().getName())
|
if (StartupBean.USERNAME_ADMIN.equals(getUser().getName()) || StartupBean.USERNAME_DEMO.equals(getUser().getName())) {
|
||||||
|| StartupBean.USERNAME_DEMO.equals(getUser().getName())) {
|
|
||||||
return Response.status(Status.FORBIDDEN).build();
|
return Response.status(Status.FORBIDDEN).build();
|
||||||
}
|
}
|
||||||
userService.unregister(getUser());
|
userService.unregister(getUser());
|
||||||
cache.invalidateUserData(getUser());
|
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,14 +29,12 @@ import com.commafeed.backend.services.ApplicationPropertiesService;
|
|||||||
import com.commafeed.frontend.CommaFeedSession;
|
import com.commafeed.frontend.CommaFeedSession;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace variables from templates on the fly in dev mode only. In production
|
* Replace variables from templates on the fly in dev mode only. In production the substitution is done at build-time.
|
||||||
* the substitution is done at build-time.
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class InternationalizationDevelopmentFilter implements Filter {
|
public class InternationalizationDevelopmentFilter implements Filter {
|
||||||
|
|
||||||
private static Logger log = LoggerFactory
|
private static Logger log = LoggerFactory.getLogger(InternationalizationDevelopmentFilter.class);
|
||||||
.getLogger(InternationalizationDevelopmentFilter.class);
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UserSettingsDAO userSettingsDAO;
|
UserSettingsDAO userSettingsDAO;
|
||||||
@@ -55,8 +53,7 @@ public class InternationalizationDevelopmentFilter implements Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doFilter(ServletRequest request, ServletResponse response,
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||||
FilterChain chain) throws IOException, ServletException {
|
|
||||||
|
|
||||||
if (production) {
|
if (production) {
|
||||||
chain.doFilter(request, response);
|
chain.doFilter(request, response);
|
||||||
@@ -64,8 +61,7 @@ public class InternationalizationDevelopmentFilter implements Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final ServletOutputStream wrapper = new ServletOutputStreamWrapper();
|
final ServletOutputStream wrapper = new ServletOutputStreamWrapper();
|
||||||
ServletResponse interceptor = new HttpServletResponseWrapper(
|
ServletResponse interceptor = new HttpServletResponseWrapper((HttpServletResponse) response) {
|
||||||
(HttpServletResponse) response) {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ServletOutputStream getOutputStream() throws IOException {
|
public ServletOutputStream getOutputStream() throws IOException {
|
||||||
@@ -74,10 +70,8 @@ public class InternationalizationDevelopmentFilter implements Filter {
|
|||||||
};
|
};
|
||||||
chain.doFilter(request, interceptor);
|
chain.doFilter(request, interceptor);
|
||||||
|
|
||||||
UserSettings settings = userSettingsDAO.findByUser(CommaFeedSession
|
UserSettings settings = userSettingsDAO.findByUser(CommaFeedSession.get().getUser());
|
||||||
.get().getUser());
|
String lang = (settings == null || settings.getLanguage() == null) ? "en" : settings.getLanguage();
|
||||||
String lang = (settings == null || settings.getLanguage() == null) ? "en"
|
|
||||||
: settings.getLanguage();
|
|
||||||
|
|
||||||
byte[] bytes = translate(wrapper.toString(), lang).getBytes("UTF-8");
|
byte[] bytes = translate(wrapper.toString(), lang).getBytes("UTF-8");
|
||||||
response.setContentLength(bytes.length);
|
response.setContentLength(bytes.length);
|
||||||
@@ -91,8 +85,7 @@ public class InternationalizationDevelopmentFilter implements Filter {
|
|||||||
Properties props = new Properties();
|
Properties props = new Properties();
|
||||||
InputStream is = null;
|
InputStream is = null;
|
||||||
try {
|
try {
|
||||||
is = getClass()
|
is = getClass().getResourceAsStream("/i18n/" + lang + ".properties");
|
||||||
.getResourceAsStream("/i18n/" + lang + ".properties");
|
|
||||||
props.load(new InputStreamReader(is, "UTF-8"));
|
props.load(new InputStreamReader(is, "UTF-8"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
@@ -111,8 +104,7 @@ public class InternationalizationDevelopmentFilter implements Filter {
|
|||||||
while (m.find()) {
|
while (m.find()) {
|
||||||
String var = m.group(1);
|
String var = m.group(1);
|
||||||
Object replacement = props.get(var);
|
Object replacement = props.get(var);
|
||||||
String replacementValue = replacement == null ? var : replacement
|
String replacementValue = replacement == null ? var : replacement.toString().split("#")[0];
|
||||||
.toString().split("#")[0];
|
|
||||||
m.appendReplacement(sb, replacementValue);
|
m.appendReplacement(sb, replacementValue);
|
||||||
}
|
}
|
||||||
m.appendTail(sb);
|
m.appendTail(sb);
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public class ModelFactory {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public static class MF {
|
public static class MF {
|
||||||
|
|
||||||
public static <T> String i(T proxiedValue) {
|
public static <T> String i(T proxiedValue) {
|
||||||
return ModelFactory.invokedProperty(proxiedValue);
|
return ModelFactory.invokedProperty(proxiedValue);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,16 +17,12 @@ import org.apache.wicket.util.template.PackageTextTemplate;
|
|||||||
|
|
||||||
public class WicketUtils {
|
public class WicketUtils {
|
||||||
|
|
||||||
public static void loadJS(IHeaderResponse response, Class<?> klass,
|
public static void loadJS(IHeaderResponse response, Class<?> klass, String fileName) {
|
||||||
String fileName) {
|
HeaderItem result = JavaScriptHeaderItem.forReference(new JavaScriptResourceReference(klass, fileName + ".js"));
|
||||||
HeaderItem result = JavaScriptHeaderItem
|
|
||||||
.forReference(new JavaScriptResourceReference(klass, fileName
|
|
||||||
+ ".js"));
|
|
||||||
response.render(result);
|
response.render(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void loadJS(IHeaderResponse response, Class<?> klass,
|
public static void loadJS(IHeaderResponse response, Class<?> klass, String fileName, Map<String, ? extends Object> variables) {
|
||||||
String fileName, Map<String, ? extends Object> variables) {
|
|
||||||
HeaderItem result = null;
|
HeaderItem result = null;
|
||||||
PackageTextTemplate template = null;
|
PackageTextTemplate template = null;
|
||||||
try {
|
try {
|
||||||
@@ -40,14 +36,12 @@ public class WicketUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static HttpServletRequest getHttpServletRequest() {
|
public static HttpServletRequest getHttpServletRequest() {
|
||||||
ServletWebRequest servletWebRequest = (ServletWebRequest) RequestCycle
|
ServletWebRequest servletWebRequest = (ServletWebRequest) RequestCycle.get().getRequest();
|
||||||
.get().getRequest();
|
|
||||||
return servletWebRequest.getContainerRequest();
|
return servletWebRequest.getContainerRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HttpServletResponse getHttpServletResponse() {
|
public static HttpServletResponse getHttpServletResponse() {
|
||||||
WebResponse webResponse = (WebResponse) RequestCycle.get()
|
WebResponse webResponse = (WebResponse) RequestCycle.get().getResponse();
|
||||||
.getResponse();
|
|
||||||
return (HttpServletResponse) webResponse.getContainerResponse();
|
return (HttpServletResponse) webResponse.getContainerResponse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ public class DisplayExceptionPage extends BasePage {
|
|||||||
|
|
||||||
add(new Label("message", t.getMessage()));
|
add(new Label("message", t.getMessage()));
|
||||||
|
|
||||||
add(new BookmarkablePageLink<Void>("homepage", getApplication()
|
add(new BookmarkablePageLink<Void>("homepage", getApplication().getHomePage()));
|
||||||
.getHomePage()));
|
|
||||||
|
|
||||||
StringWriter stringWriter = new StringWriter();
|
StringWriter stringWriter = new StringWriter();
|
||||||
t.printStackTrace(new PrintWriter(stringWriter));
|
t.printStackTrace(new PrintWriter(stringWriter));
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ public class CDIBootstrap implements Extension {
|
|||||||
void afterBeanDiscovery(@Observes AfterBeanDiscovery abd, BeanManager bm) {
|
void afterBeanDiscovery(@Observes AfterBeanDiscovery abd, BeanManager bm) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void afterDeploymentValidation(@Observes AfterDeploymentValidation event,
|
void afterDeploymentValidation(@Observes AfterDeploymentValidation event, BeanManager manager) {
|
||||||
BeanManager manager) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Produces
|
@Produces
|
||||||
|
|||||||
@@ -5,20 +5,8 @@
|
|||||||
http://java.sun.com/xml/ns/persistence/orm
|
http://java.sun.com/xml/ns/persistence/orm
|
||||||
http://java.sun.com/xml/ns/persistence/orm_2_0.xsd">
|
http://java.sun.com/xml/ns/persistence/orm_2_0.xsd">
|
||||||
|
|
||||||
<named-query name="EntryStatus.unreadCounts">
|
<named-query name="Statuses.deleteOld">
|
||||||
<query>select s.subscription.id, count(s) from FeedEntryStatus s where s.user=:user and s.read=false group by s.subscription.id</query>
|
<query>delete from FeedEntryStatus s where s.entryInserted < :date and s.starred = false</query>
|
||||||
</named-query>
|
|
||||||
|
|
||||||
<named-query name="EntryStatus.existing">
|
|
||||||
<query>select new com.commafeed.backend.dao.FeedEntryDAO$EntryWithFeed(e, f) FROM FeedEntry e LEFT JOIN e.feedRelationships f WITH f.feed.id = :feedId where e.guidHash = :guidHash and e.url = :url</query>
|
|
||||||
</named-query>
|
|
||||||
|
|
||||||
<named-query name="EntryStatus.deleteByIds">
|
|
||||||
<query>delete from FeedEntryStatus s where s.id in :ids</query>
|
|
||||||
</named-query>
|
|
||||||
|
|
||||||
<named-query name="Feed.deleteEntryRelationships">
|
|
||||||
<query>delete from FeedFeedEntry ffe where ffe.feed.id = :feedId</query>
|
|
||||||
</named-query>
|
</named-query>
|
||||||
|
|
||||||
</entity-mappings>
|
</entity-mappings>
|
||||||
@@ -346,4 +346,20 @@
|
|||||||
<column name="subscription_id" />
|
<column name="subscription_id" />
|
||||||
</createIndex>
|
</createIndex>
|
||||||
</changeSet>
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet author="athou" id="add-trim-status-setting">
|
||||||
|
<addColumn tableName="APPLICATIONSETTINGS">
|
||||||
|
<column name="keepStatusDays" type="INT" />
|
||||||
|
</addColumn>
|
||||||
|
<update tableName="APPLICATIONSETTINGS">
|
||||||
|
<column name="keepStatusDays" valueNumeric="0"></column>
|
||||||
|
</update>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet author="athou" id="status-cleanup">
|
||||||
|
<delete tableName="FEEDENTRYSTATUSES">
|
||||||
|
<where>read_status = false and starred = false</where>
|
||||||
|
</delete>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
104
src/main/resources/changelogs/db.changelog-1.2.xml
Normal file
104
src/main/resources/changelogs/db.changelog-1.2.xml
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
|
||||||
|
|
||||||
|
<changeSet author="athou" id="change-entries-model">
|
||||||
|
<dropTable tableName="FEED_FEEDENTRIES" />
|
||||||
|
<delete tableName="FEEDENTRYSTATUSES"></delete>
|
||||||
|
<delete tableName="FEEDENTRIES"></delete>
|
||||||
|
<delete tableName="FEEDENTRYCONTENTS"></delete>
|
||||||
|
|
||||||
|
<addColumn tableName="FEEDENTRIES">
|
||||||
|
<column name="feed_id" type="BIGINT" defaultValue="1">
|
||||||
|
<constraints nullable="false" />
|
||||||
|
</column>
|
||||||
|
</addColumn>
|
||||||
|
<addForeignKeyConstraint constraintName="fk_feed_id"
|
||||||
|
baseTableName="FEEDENTRIES" baseColumnNames="feed_id"
|
||||||
|
referencedTableName="FEEDS" referencedColumnNames="id" />
|
||||||
|
<createIndex tableName="FEEDENTRIES" indexName="feed_updated_index">
|
||||||
|
<column name="feed_id" />
|
||||||
|
<column name="updated" />
|
||||||
|
</createIndex>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet author="athou" id="add-content-hashes">
|
||||||
|
<addColumn tableName="FEEDENTRYCONTENTS">
|
||||||
|
<column name="author" type="VARCHAR(128)" />
|
||||||
|
<column name="contentHash" type="VARCHAR(40)" />
|
||||||
|
</addColumn>
|
||||||
|
<createIndex tableName="FEEDENTRYCONTENTS" indexName="content_hash_index">
|
||||||
|
<column name="contentHash" />
|
||||||
|
</createIndex>
|
||||||
|
<dropColumn tableName="FEEDENTRIES" columnName="author"/>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet author="athou" id="add-new-index">
|
||||||
|
<createIndex tableName="FEEDENTRYSTATUSES" indexName="user_entry_index">
|
||||||
|
<column name="user_id" />
|
||||||
|
<column name="entry_id" />
|
||||||
|
</createIndex>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet author="athou" id="drop-old-index">
|
||||||
|
<dropIndex tableName="FEEDENTRYSTATUSES" indexName="sub_entry_index" />
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet author="athou" id="force-feed-refresh">
|
||||||
|
<update tableName="FEEDS">
|
||||||
|
<column name="lastUpdated" valueComputed="null"></column>
|
||||||
|
<column name="lastPublishedDate" valueComputed="null"></column>
|
||||||
|
<column name="lastEntryDate" valueComputed="null"></column>
|
||||||
|
<column name="lastUpdateSuccess" valueComputed="null"></column>
|
||||||
|
<column name="message" valueComputed="null"></column>
|
||||||
|
<column name="errorCount" valueNumeric="0"></column>
|
||||||
|
<column name="disabledUntil" valueComputed="null"></column>
|
||||||
|
<column name="lastModifiedHeader" valueComputed="null"></column>
|
||||||
|
<column name="etagHeader" valueComputed="null"></column>
|
||||||
|
<column name="averageEntryInterval" valueNumeric="null"></column>
|
||||||
|
<column name="lastContentHash" valueComputed="null"></column>
|
||||||
|
</update>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet author="athou" id="revamp-status-indexes">
|
||||||
|
<createIndex tableName="FEEDENTRYSTATUSES" indexName="user_starred_updated">
|
||||||
|
<column name="user_id"></column>
|
||||||
|
<column name="starred"></column>
|
||||||
|
<column name="entryUpdated"></column>
|
||||||
|
</createIndex>
|
||||||
|
|
||||||
|
<createIndex tableName="FEEDENTRYSTATUSES" indexName="sub_index">
|
||||||
|
<column name="subscription_id"></column>
|
||||||
|
</createIndex>
|
||||||
|
|
||||||
|
<dropIndex tableName="FEEDENTRYSTATUSES" indexName="sub_read_updated_index" />
|
||||||
|
<dropIndex tableName="FEEDENTRYSTATUSES" indexName="user_read_updated_index" />
|
||||||
|
<dropIndex tableName="FEEDENTRYSTATUSES" indexName="user_read_sub_index" />
|
||||||
|
<dropIndex tableName="FEEDENTRYSTATUSES" indexName="user_entry_index" />
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet author="athou" id="revamp-entries-indexes">
|
||||||
|
<dropIndex tableName="FEEDENTRIES" indexName="updated_index" />
|
||||||
|
<dropIndex tableName="FEEDENTRIES" indexName="inserted_index" />
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet author="athou" id="add-starred-index-for-cleanup">
|
||||||
|
<createIndex tableName="FEEDENTRYSTATUSES" indexName="insterted_starred_index">
|
||||||
|
<column name="entryInserted"></column>
|
||||||
|
<column name="starred"></column>
|
||||||
|
</createIndex>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet author="athou" id="add-title-hashes">
|
||||||
|
<addColumn tableName="FEEDENTRYCONTENTS">
|
||||||
|
<column name="titleHash" type="VARCHAR(40)" />
|
||||||
|
</addColumn>
|
||||||
|
<createIndex tableName="FEEDENTRYCONTENTS" indexName="content_title_index">
|
||||||
|
<column name="contentHash" />
|
||||||
|
<column name="titleHash" />
|
||||||
|
</createIndex>
|
||||||
|
<dropIndex tableName="FEEDENTRYCONTENTS" indexName="content_hash_index" />
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
@@ -5,5 +5,6 @@
|
|||||||
|
|
||||||
<include file="changelogs/db.changelog-1.0.xml" />
|
<include file="changelogs/db.changelog-1.0.xml" />
|
||||||
<include file="changelogs/db.changelog-1.1.xml" />
|
<include file="changelogs/db.changelog-1.1.xml" />
|
||||||
|
<include file="changelogs/db.changelog-1.2.xml" />
|
||||||
|
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
@@ -46,7 +46,7 @@ function($scope, FeedService, CategoryService, MobileService) {
|
|||||||
|
|
||||||
$scope.open = function() {
|
$scope.open = function() {
|
||||||
$scope.sub = {
|
$scope.sub = {
|
||||||
categoryId: $scope.sub.categoryId
|
categoryId: $scope.sub.categoryId || 'all'
|
||||||
};
|
};
|
||||||
$scope.isOpen = true;
|
$scope.isOpen = true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -163,6 +163,14 @@
|
|||||||
ng-model="settings.queryTimeout" />
|
ng-model="settings.queryTimeout" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="keepStatusDays">Keep read status for (days)</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="number" name="keepStatusDays" class="input-block-level"
|
||||||
|
ng-model="settings.keepStatusDays" />
|
||||||
|
<span class="help-inline">0 = keep forever</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="control-label" for="crawlingPaused">Pause crawling</label>
|
<label class="control-label" for="crawlingPaused">Pause crawling</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
<i ng-class="{'icon-star icon-star-yellow': entry.starred, 'icon-star-empty': !entry.starred}"
|
<i ng-class="{'icon-star icon-star-yellow': entry.starred, 'icon-star-empty': !entry.starred}"
|
||||||
class="pointer"></i>
|
class="pointer"></i>
|
||||||
</span>
|
</span>
|
||||||
<label class="checkbox inline">
|
<label class="checkbox inline" ui-if="entry.markable">
|
||||||
<input type="checkbox" ng-checked="!entry.read" ng-click="mark(entry, !entry.read)" class="mousetrap"></input>
|
<input type="checkbox" ng-checked="!entry.read" ng-click="mark(entry, !entry.read)" class="mousetrap"></input>
|
||||||
${view.keep_unread}
|
${view.keep_unread}
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user