Compare commits

..

164 Commits
1.0.0 ... 1.2.0

Author SHA1 Message Date
Athou
81a4b36c08 we don't need to set the default port manually as wicket does that for us 2013-08-16 21:33:59 +02:00
Athou
bf154cf83d set port too 2013-08-16 21:23:11 +02:00
Athou
0d1234ca4b use protocol from the publicUrl when rendering urls 2013-08-16 21:10:25 +02:00
Athou
a1c42f2709 preserve https across redirections if it was set 2013-08-16 19:27:13 +02:00
Athou
7608921684 readme update 2013-08-16 18:03:07 +02:00
Athou
24f2b17416 reduce session size and prevent potential session loss (and exceptions) when server restarts 2013-08-16 17:40:45 +02:00
Athou
33eb469520 silence swagger, generated warnings are distracting 2013-08-16 17:06:52 +02:00
Athou
90eef904f9 import cleanup 2013-08-16 16:40:04 +02:00
Athou
d1f72ee53a if we fail to parse the rule, return an empty rule instead 2013-08-16 16:31:43 +02:00
Athou
e0e212dfc4 trust encoding declared as windows codepages (fix #491) 2013-08-16 13:41:49 +02:00
Athou
ef0a03cb3b added link to news+ extension 2013-08-16 13:40:14 +02:00
Athou
221eeddab8 don't add already existing entries (#477) 2013-08-16 13:32:58 +02:00
Athou
1076527b62 make chrome think every bookmark click is a different url in order to create history entries (fix #488) 2013-08-14 10:43:10 +02:00
Athou
1e13c11061 prevent NPE 2013-08-13 15:35:02 +02:00
Athou
440922380d use jackson for json only 2013-08-13 12:49:57 +02:00
Athou
969a199a8e new label 2013-08-13 12:21:21 +02:00
Athou
21b0176a49 display message on details page 2013-08-13 12:15:30 +02:00
Athou
06a996cd81 added ability to exclude subscriptions from getEntries 2013-08-13 10:10:25 +02:00
Athou
e1fc33626e added ability to exclude subscriptions from markAll 2013-08-13 09:40:13 +02:00
Athou
b331626e8f don't send the OK int constant as entity 2013-08-13 09:25:55 +02:00
Athou
95d4f725f9 feed id not required anymore 2013-08-13 09:14:41 +02:00
Athou
45b54a75db annotations not needed anymore 2013-08-13 09:06:08 +02:00
Athou
e000bb05c4 don't handle byte arrays with jackson (feed icons, ...) 2013-08-12 20:53:19 +02:00
Athou
d66ca05dca don't wrap String responses with double quotes 2013-08-12 20:50:03 +02:00
Athou
321260b0a5 set disabledUntil to now instead of null when the error count threshold has not been reached yet 2013-08-12 16:20:55 +02:00
Athou
5ef8fd18ca annotations not needed anymore 2013-08-12 13:42:18 +02:00
Athou
5b5d5cca1c jackson update 2013-08-12 12:45:25 +02:00
Athou
51ac16a9e1 small refactoring 2013-08-12 10:03:34 +02:00
Athou
cf185c3877 only fetch status when we know it's there 2013-08-12 10:01:24 +02:00
Athou
71368fba62 fix reply message 2013-08-11 17:22:17 +02:00
Athou
6bae50a56a wicket update 2013-08-11 17:12:00 +02:00
Athou
27681603cd generate accessors only 2013-08-11 14:01:16 +02:00
Athou
e1be05711b use lombok data instead of getters and setters 2013-08-11 12:09:05 +02:00
Athou
7b1bb9072e modelgen upgrade 2013-08-11 12:01:08 +02:00
Athou
a58b0a0806 use lombok data instead of getters and setters 2013-08-11 11:59:24 +02:00
Athou
e26950671c clarify documentation: keywords are not required 2013-08-11 11:50:30 +02:00
Athou
0d730128f7 use lombok slf4j annotation 2013-08-11 11:45:32 +02:00
Athou
174664619b use lombok 2013-08-11 11:11:09 +02:00
Athou
f8738f10af added optional 'onlyIds' api parameter 2013-08-09 12:53:21 +02:00
Athou
677fb87f71 increase limit 2013-08-09 12:18:35 +02:00
Athou
c980e5dd67 return newly created category id 2013-08-09 09:36:06 +02:00
Athou
474995c8dd send date of the newest subscriptions item 2013-08-08 17:00:52 +02:00
Athou
74ee810757 Merge pull request #486 from swoga/patch-1
Update de.properties
2013-08-08 07:27:36 -07:00
swoga
c5b56b47ae Update de.properties 2013-08-08 16:26:47 +02:00
Athou
dccaca4972 file writing is now java vendor independent (#485) 2013-08-08 15:31:31 +02:00
Athou
92f53a0034 double click in the tree opens the edit page 2013-08-07 16:41:37 +02:00
Athou
15eb00b1ba new labels 2013-08-07 16:32:13 +02:00
Athou
041b5ad2c0 tweaking display for search results 2013-08-07 16:31:44 +02:00
Athou
4520ef4078 search is now context aware (will only search in selected category or feed) 2013-08-07 15:26:43 +02:00
Athou
701a1903ba move feedcount in its own file 2013-08-07 10:08:03 +02:00
Athou
ff7458dfc1 make sure statuses are unique 2013-08-06 16:20:50 +02:00
Athou
a72e08c0c6 try to parse given url before using embedded links 2013-08-06 13:49:03 +02:00
Athou
2bff335698 show exception in debug log 2013-08-06 13:07:50 +02:00
Athou
b8a256ac7d allow some css rules for images (#478) 2013-08-06 13:00:28 +02:00
Athou
2168c0039a Merge pull request #479 from Athou/disk-io
stay in indexes as long as possible
2013-08-06 03:48:58 -07:00
Athou
d225884ec3 stay in indexes as long as possible 2013-08-06 12:39:12 +02:00
Athou
9934c4a169 reset current entry tracking when reloading (#476) 2013-08-06 11:46:39 +02:00
Athou
9e75f23d8f display more of the feed name 2013-08-06 11:14:12 +02:00
Athou
f36471bbf3 in unread mode, use the actual number of unread items as offset instead of the size of the list of items (#462) 2013-08-03 16:32:18 +02:00
Athou
4664bef4d8 not needed anymore 2013-08-01 21:16:01 +02:00
Athou
71403d4174 if under heavy load, don't refresh feeds for users who logged in more than a month ago for the last time 2013-08-01 21:11:45 +02:00
Athou
cb1b99815c copy generated api docs even when prod flag is false 2013-08-01 18:26:10 +02:00
Athou
5668efc8a8 don't expose documentation class in the method signature as it's not available at runtime 2013-08-01 18:16:44 +02:00
Athou
d3223ec8b4 exclude api generator from war 2013-08-01 17:20:06 +02:00
Athou
bfbe39993f use an annotation processor instead of a groovy script because of classloading issues 2013-08-01 17:11:57 +02:00
Athou
e90747fd08 display the base url statically 2013-08-01 14:38:24 +02:00
Athou
8926f9784d documentation is now generated during build time and swagger is not needed for runtime anymore 2013-08-01 13:35:04 +02:00
Athou
0ff1d58dfb code formatting 2013-08-01 11:18:06 +02:00
Athou
8df587aaad don't display entries twice when refreshing during loading (fix #473) 2013-08-01 11:18:06 +02:00
Athou
10fdffc378 Merge pull request #475 from res87th/patch-1
Light changes in ru.properties
2013-07-31 23:11:35 -07:00
res87th
a7d7335970 Update ru.properties 2013-07-31 22:55:59 -07:00
Athou
bb5244c118 return headers on all requests 2013-08-01 05:33:23 +02:00
Athou
f20a5e92e2 extracted needed classes and remove crawler4j dependency and java7 requirement 2013-08-01 05:24:05 +02:00
Athou
5ce0428b15 Merge pull request #472 from gabrielrcp/translation
New labels for ptuguese translation
2013-07-31 09:56:21 -07:00
Athou
a43e738365 set default value 2013-07-31 18:47:20 +02:00
Gabriel Peixoto
f4a4eab32d New labels for portuguese translation. 2013-07-31 13:44:00 -03:00
Athou
7ffc58892a marker already present in the openshift repository, delete to avoid merge conflicts 2013-07-31 18:26:35 +02:00
Athou
8f85637bb8 invalidate cache once per feed instead of once per entry 2013-07-31 16:03:52 +02:00
Athou
a37925396a mobile view tweaks 2013-07-31 15:51:00 +02:00
Athou
b66749264a added http headers for cross origin resource sharing 2013-07-31 15:31:06 +02:00
Athou
6dcf2aabd1 pass a context object around instead of creating transient fields in model objects 2013-07-31 13:06:57 +02:00
Athou
71bb33d710 small cleanup 2013-07-31 12:07:29 +02:00
Athou
365c235e1f fix jslint warning 2013-07-31 11:35:53 +02:00
Athou
da65e85081 apply new js formatter 2013-07-31 11:16:50 +02:00
Athou
7497b88c26 added javascript formatter 2013-07-31 11:16:40 +02:00
Athou
5d5c955451 list filtering wasn't working correctly 2013-07-31 11:07:48 +02:00
Athou
c17cc5bd1c highlight search results 2013-07-31 11:02:39 +02:00
Athou
54e5621267 more infos in entry header in mobile view 2013-07-31 09:18:08 +02:00
Athou
13534b5f44 hide feedback button in mobile view 2013-07-31 08:59:30 +02:00
Athou
43a0c7be81 relationships are not needed for that query 2013-07-30 21:12:52 +02:00
Athou
8ec9705dd6 .gitkeep files not needed 2013-07-30 16:17:16 +02:00
Athou
a82e6f3402 hibernate timeout is in seconds 2013-07-30 14:38:50 +02:00
Athou
704081656e increase batch size again 2013-07-30 13:23:32 +02:00
Athou
201a3ae96f Merge pull request #468 from swoga/patch-1
Added translations to de.properties
2013-07-30 03:35:10 -07:00
swoga
2d54ec9efb Added translations to de.properties 2013-07-30 12:33:57 +02:00
Athou
c1ac273749 increase maximum allowed favicon size 2013-07-30 09:20:52 +02:00
Athou
9bc5fdf02f small batch size seems better than a large one 2013-07-30 06:18:22 +02:00
Athou
8d340e0f52 optimization not needed anymore 2013-07-29 21:31:08 +02:00
Athou
8628ac9e9a use ApplicationPropertiesService 2013-07-29 16:42:52 +02:00
Athou
737e24e7dc more push callback checks 2013-07-29 12:24:03 +02:00
Athou
f5943889ec increase batch size 2013-07-29 12:10:22 +02:00
Athou
61bdd484d3 Merge pull request #467 from yxd-works/patch-3
Update zh.properties
2013-07-29 01:44:57 -07:00
Athou
0ed901ffb6 fix error when emitting event without payload 2013-07-29 10:43:57 +02:00
Athou
5fe5b97130 configurable feed refresh interval 2013-07-29 10:40:55 +02:00
Athou
ef79cf1748 added comments for refill() 2013-07-29 09:47:21 +02:00
YANG Xudong
0f00161b93 Update zh.properties 2013-07-29 11:31:44 +09:00
Athou
0809021c25 Merge pull request #466 from JKakku/patch-1
Update fi.properties
2013-07-28 11:29:45 -07:00
JKakku
ad28b26e72 Update fi.properties 2013-07-28 21:02:22 +03:00
Athou
7827cf49d6 limit queue sizes 2013-07-28 16:36:21 +02:00
Athou
8ed58a8aa5 reduce cache size 2013-07-28 16:14:34 +02:00
Athou
068bb1a0d8 Merge pull request #465 from LpSamuelm/patch-16
Translated new labels to Swedish
2013-07-28 07:11:42 -07:00
LpSamuelm
4f1b458458 Translated new labels to Swedish
woop woop in da boop
2013-07-28 15:41:58 +02:00
Athou
7ad9c24879 fix feed cleanup 2013-07-28 14:35:10 +02:00
Athou
e3e476555a use default thread factory 2013-07-28 12:44:24 +02:00
Athou
223c2f464e index for entry lookup 2013-07-27 17:22:31 +02:00
Athou
6d396e1982 don't fetch the whole content, just return the id if it does 2013-07-27 16:42:50 +02:00
Athou
60bf96411c removed unused urlHash field 2013-07-27 15:45:15 +02:00
Athou
3dd4f140e2 refactored the way we handle feed refresh queue 2013-07-27 15:45:03 +02:00
Athou
1131d70645 move utility method to service (fix #463) 2013-07-27 13:01:28 +02:00
Athou
4b080510e7 new label 2013-07-27 11:22:04 +02:00
Athou
37437877e1 option to force refresh all feeds 2013-07-27 11:21:26 +02:00
Athou
da94880c53 added a little doc 2013-07-26 16:00:02 +02:00
Athou
68ad6d8b55 reuse existing random object 2013-07-26 15:34:14 +02:00
Athou
080c0b48d0 prevent possible NPE 2013-07-26 15:34:02 +02:00
Athou
e8bfecc07d Merge pull request #461 from Athou/statuses-revamp
Statuses revamp
2013-07-26 06:23:06 -07:00
Athou
8e43a7fa00 added update warning 2013-07-26 14:34:00 +02:00
Athou
fa45d1bfad version bump 2013-07-26 14:13:38 +02:00
Athou
9cdc364fde index title hash 2013-07-26 08:15:23 +02:00
Athou
6f29af1710 better locking mechanism 2013-07-25 16:41:48 +02:00
Athou
e77787e2cd ignore case 2013-07-25 14:37:21 +02:00
Athou
84159a3a2d filter out entries that were matched from html code 2013-07-25 14:15:30 +02:00
Athou
68e531ed0c log phrasing 2013-07-25 11:31:49 +02:00
Athou
72bdf2573c tests for fixedsizesortedset 2013-07-25 11:15:57 +02:00
Athou
00159ce1c5 moved methods around 2013-07-25 11:02:49 +02:00
Athou
d293e972f2 added logging 2013-07-25 10:55:19 +02:00
Athou
c618e22c52 revert part of caching mechanism 2013-07-25 10:48:52 +02:00
Athou
73f2871235 revamp cache service 2013-07-25 10:21:11 +02:00
Athou
bdb30a60c3 ignore utility method for (de)serialization 2013-07-25 09:27:09 +02:00
Athou
7da630ed6d run every day at midnight 2013-07-25 09:19:05 +02:00
Athou
8845c54d0c apply formatter 2013-07-25 09:17:33 +02:00
Athou
02f1090fe7 set parameters for delete query 2013-07-25 09:12:56 +02:00
Athou
9bac3f424f index for faster status deletes 2013-07-25 09:12:45 +02:00
Athou
db2264023f added code formatter 2013-07-25 09:12:33 +02:00
Athou
ec8eb4bd1f faster startup 2013-07-24 20:05:58 +02:00
Athou
ed5596636a rewrote with a backing arraylist 2013-07-24 19:41:29 +02:00
Athou
dd0fdfc89e prevent exception when hashing content 2013-07-24 19:19:50 +02:00
Athou
d212e96664 set initial capacity to minimum if unknown capacity 2013-07-24 17:34:46 +02:00
Athou
1a720e6a29 change underlying implementation of the fixedsizesortedset to priority queue 2013-07-24 17:28:10 +02:00
Athou
7316a6e07d revert comparators 2013-07-24 17:25:14 +02:00
Athou
84a75db464 treeset uses compareTo instead of equals 2013-07-24 16:57:01 +02:00
Athou
dde3d8e405 was not meant to be committed 2013-07-24 16:43:00 +02:00
Athou
9e0a39981f use capacity as limit 2013-07-24 16:42:43 +02:00
Athou
dab9f53743 fix metrics 2013-07-24 15:53:21 +02:00
Athou
645164997d move author to content 2013-07-24 15:50:05 +02:00
Athou
c2b53b117c remove many to many relationship between entries and feeds 2013-07-24 15:40:59 +02:00
Athou
6e52f60e85 Merge pull request #458 from JKakku/patch-4
Update fi.properties
2013-07-24 06:02:54 -07:00
JKakku
1498e24037 Update fi.properties
Added version label
2013-07-24 15:41:34 +03:00
Athou
fdacac74cc cleanup unneeded statuses 2013-07-24 12:13:18 +02:00
Athou
3defd982e7 only mark if markable 2013-07-24 12:13:06 +02:00
Athou
f4eb9e2a09 always join for statuses 2013-07-23 16:45:13 +02:00
Athou
08693e16f0 move method around 2013-07-23 16:32:20 +02:00
Athou
d95e1522d8 less database calls 2013-07-23 16:19:19 +02:00
Athou
150920e0c8 delete old statuses 2013-07-23 15:27:56 +02:00
Athou
074ecbf159 persist read status instead of unread status 2013-07-22 16:31:29 +02:00
181 changed files with 5416 additions and 5808 deletions

View File

@@ -6,15 +6,12 @@ Google Reader inspired self-hosted RSS reader, based on JAX-RS, Wicket and Angul
Deploy on your own server (using TomEE, a lightweight JavaEE6 container based on Tomcat) or even in the cloud for free on OpenShift. Deploy on your own server (using TomEE, a lightweight JavaEE6 container based on Tomcat) or even in the cloud for free on OpenShift.
[Android app](https://github.com/doomrobo/CommaFeed-Android-Reader) Related open-source projects
----------------------------
[Chrome extension](https://github.com/Athou/commafeed-chrome) Android apps: [News+ extension](https://github.com/Athou/commafeed-newsplus) - [Android app](https://github.com/doomrobo/CommaFeed-Android-Reader)
[Firefox extension](https://github.com/Athou/commafeed-firefox) Browser extensions: [Chrome](https://github.com/Athou/commafeed-chrome) - [Firefox](https://github.com/Athou/commafeed-firefox) - [Opera](https://github.com/Athou/commafeed-opera) - [Safari](https://github.com/Athou/commafeed-safari)
[Opera extension](https://github.com/Athou/commafeed-opera)
[Safari extension](https://github.com/Athou/commafeed-safari)
Deployment on OpenShift Deployment on OpenShift
----------------------- -----------------------

View 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>

View File

@@ -0,0 +1,267 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="11">
<profile kind="CodeFormatterProfile" name="Eclipse [built-in] 140 chars" version="11">
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_case" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_compact_if" value="52"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_after_annotation" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_header" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_block_comments" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_object_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_between_type_declarations" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_assignment" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_default" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.continuation_indentation_for_objlit_initializer" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.compiler.compliance" value="1.5"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_closing_brace_in_objlit_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_source_code" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.wrap_before_binary_operator" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_after_package" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_after_comma_in_objlit_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_objlit_initializer" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.compiler.source" value="1.5"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.continuation_indentation" value="2"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_conditional_expression" value="48"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.indent_parameter_description" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_after_imports" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="64"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.indent_root_tags" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_member_type" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_enum_constants" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_imports" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_binary_expression" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.lineSplit" value="140"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_method" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_superclass_in_type_declaration" value="64"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.compiler.codegen.targetPlatform" value="1.5"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.keep_empty_objlit_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_line_comments" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="64"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.compiler.problem.assertIdentifier" value="error"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.insert_new_line_for_parameter" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_object_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.line_length" value="80"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_block" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_new_chunk" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.tabulation.char" value="tab"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.put_empty_statement_on_new_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_after_opening_brace_in_objlit_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
</profile>
</profiles>

101
pom.xml
View File

@@ -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>
@@ -46,9 +46,8 @@
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version> <version>3.1</version>
<configuration> <configuration>
<source>1.7</source> <source>1.6</source>
<target>1.7</target> <target>1.6</target>
<compilerArgument>-proc:none</compilerArgument>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
@@ -65,6 +64,13 @@
<include>**/beans.xml</include> <include>**/beans.xml</include>
</includes> </includes>
</resource> </resource>
<resource>
<directory>target/generated-sources/api-docs/</directory>
<targetPath>api/api-docs</targetPath>
<includes>
<include>**/*</include>
</includes>
</resource>
</webResources> </webResources>
</configuration> </configuration>
</plugin> </plugin>
@@ -138,12 +144,25 @@
<outputDirectory>target/generated-sources/metamodel</outputDirectory> <outputDirectory>target/generated-sources/metamodel</outputDirectory>
</configuration> </configuration>
</execution> </execution>
<execution>
<id>doc</id>
<phase>process-classes</phase>
<goals>
<goal>process</goal>
</goals>
<configuration>
<processors>
<processor>com.commafeed.frontend.APIGenerator</processor>
</processors>
<outputDirectory>target/generated-sources/api-docs</outputDirectory>
</configuration>
</execution>
</executions> </executions>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.hibernate</groupId> <groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId> <artifactId>hibernate-jpamodelgen</artifactId>
<version>1.2.0.Final</version> <version>1.3.0.Final</version>
</dependency> </dependency>
</dependencies> </dependencies>
</plugin> </plugin>
@@ -167,6 +186,12 @@
</build> </build>
<dependencies> <dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>0.12.0</version>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>org.jboss.spec</groupId> <groupId>org.jboss.spec</groupId>
<artifactId>jboss-javaee-6.0</artifactId> <artifactId>jboss-javaee-6.0</artifactId>
@@ -237,16 +262,6 @@
<artifactId>commons-fileupload</artifactId> <artifactId>commons-fileupload</artifactId>
<version>1.3</version> <version>1.3</version>
</dependency> </dependency>
<dependency>
<groupId>com.googlecode.lambdaj</groupId>
<artifactId>lambdaj</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.2</version>
</dependency>
<dependency> <dependency>
<groupId>net.java.dev.rome</groupId> <groupId>net.java.dev.rome</groupId>
@@ -270,11 +285,6 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>edu.uci.ics</groupId>
<artifactId>crawler4j</artifactId>
<version>3.5</version>
</dependency>
<dependency> <dependency>
<groupId>org.jdom</groupId> <groupId>org.jdom</groupId>
<artifactId>jdom</artifactId> <artifactId>jdom</artifactId>
@@ -296,17 +306,6 @@
<version>0.9.9</version> <version>0.9.9</version>
</dependency> </dependency>
<dependency>
<groupId>com.google.oauth-client</groupId>
<artifactId>google-oauth-client-servlet</artifactId>
<version>1.14.1-beta</version>
</dependency>
<dependency>
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client-jackson2</artifactId>
<version>1.14.1-beta</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.httpcomponents</groupId> <groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId> <artifactId>httpclient</artifactId>
@@ -319,9 +318,9 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId> <artifactId>jackson-databind</artifactId>
<version>2.1.4</version> <version>2.2.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
@@ -337,23 +336,23 @@
<dependency> <dependency>
<groupId>org.apache.wicket</groupId> <groupId>org.apache.wicket</groupId>
<artifactId>wicket-core</artifactId> <artifactId>wicket-core</artifactId>
<version>6.8.0</version> <version>6.9.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.wicket</groupId> <groupId>org.apache.wicket</groupId>
<artifactId>wicket-auth-roles</artifactId> <artifactId>wicket-auth-roles</artifactId>
<version>6.8.0</version> <version>6.9.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.wicket</groupId> <groupId>org.apache.wicket</groupId>
<artifactId>wicket-extensions</artifactId> <artifactId>wicket-extensions</artifactId>
<version>6.8.0</version> <version>6.9.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.wicket</groupId> <groupId>org.apache.wicket</groupId>
<artifactId>wicket-cdi</artifactId> <artifactId>wicket-cdi</artifactId>
<version>6.8.0</version> <version>6.9.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ro.isdc.wro4j</groupId> <groupId>ro.isdc.wro4j</groupId>
@@ -361,28 +360,16 @@
<version>1.6.3</version> <version>1.6.3</version>
</dependency> </dependency>
<dependency>
<groupId>com.wordnik</groupId>
<artifactId>swagger-annotations_2.9.1</artifactId>
<version>1.2.5</version>
</dependency>
<dependency> <dependency>
<groupId>com.wordnik</groupId> <groupId>com.wordnik</groupId>
<artifactId>swagger-jaxrs_2.9.1</artifactId> <artifactId>swagger-jaxrs_2.9.1</artifactId>
<version>1.2.5</version> <version>1.2.5</version>
<exclusions> <scope>provided</scope>
<exclusion>
<artifactId>jersey-server</artifactId>
<groupId>com.sun.jersey</groupId>
</exclusion>
<exclusion>
<artifactId>jersey-servlet</artifactId>
<groupId>com.sun.jersey</groupId>
</exclusion>
<exclusion>
<artifactId>jersey-client</artifactId>
<groupId>com.sun.jersey</groupId>
</exclusion>
<exclusion>
<artifactId>jersey-core</artifactId>
<groupId>com.sun.jersey</groupId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
@@ -518,7 +505,7 @@
</dependencies> </dependencies>
<executions> <executions>
<execution> <execution>
<phase>generate-sources</phase> <phase>process-classes</phase>
<goals> <goals>
<goal>execute</goal> <goal>execute</goal>
</goals> </goals>

View File

View File

@@ -1,25 +1,30 @@
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;
import javax.inject.Inject; import javax.inject.Inject;
import org.slf4j.Logger; import lombok.extern.slf4j.Slf4j;
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;
import com.commafeed.backend.services.ApplicationSettingsService; import com.commafeed.backend.services.ApplicationSettingsService;
/**
* Contains utility methods for cleaning the database
*
*/
@Slf4j
public class DatabaseCleaner { public class DatabaseCleaner {
private static Logger log = LoggerFactory.getLogger(DatabaseCleaner.class);
@Inject @Inject
FeedDAO feedDAO; FeedDAO feedDAO;
@@ -29,6 +34,12 @@ public class DatabaseCleaner {
@Inject @Inject
FeedSubscriptionDAO feedSubscriptionDAO; FeedSubscriptionDAO feedSubscriptionDAO;
@Inject
FeedEntryContentDAO feedEntryContentDAO;
@Inject
FeedEntryStatusDAO feedEntryStatusDAO;
@Inject @Inject
ApplicationSettingsService applicationSettingsService; ApplicationSettingsService applicationSettingsService;
@@ -45,16 +56,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;
} }
@@ -83,9 +94,14 @@ public class DatabaseCleaner {
sub.setFeed(into); sub.setFeed(into);
} }
feedSubscriptionDAO.saveOrUpdate(subs); feedSubscriptionDAO.saveOrUpdate(subs);
feedDAO.deleteRelationships(feed);
feedDAO.delete(feed); feedDAO.delete(feed);
} }
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);
}
} }

View File

@@ -20,6 +20,10 @@ import liquibase.structure.DatabaseObject;
import com.commafeed.backend.services.ApplicationPropertiesService; import com.commafeed.backend.services.ApplicationPropertiesService;
/**
* Executes needed liquibase database schema upgrades
*
*/
@Stateless @Stateless
@TransactionManagement(TransactionManagementType.BEAN) @TransactionManagement(TransactionManagementType.BEAN)
public class DatabaseUpdater { public class DatabaseUpdater {
@@ -33,18 +37,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 +56,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) {

View File

@@ -1,62 +1,49 @@
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; /**
* List wrapper that sorts its elements in the order provided by given comparator and ensure a maximum capacity.
*
*
*/
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;
} }
} }

View File

@@ -13,6 +13,8 @@ import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
@@ -38,13 +40,14 @@ import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams; import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams; import org.apache.http.params.HttpProtocolParams;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Smart HTTP getter: handles gzip, ssl, last modified and etag headers
*
*/
@Slf4j
public class HttpGetter { public class HttpGetter {
private static Logger log = LoggerFactory.getLogger(HttpGetter.class);
private static final String USER_AGENT = "CommaFeed/1.0 (http://www.commafeed.com)"; private static final String USER_AGENT = "CommaFeed/1.0 (http://www.commafeed.com)";
private static final String ACCEPT_LANGUAGE = "en"; private static final String ACCEPT_LANGUAGE = "en";
private static final String PRAGMA_NO_CACHE = "No-cache"; private static final String PRAGMA_NO_CACHE = "No-cache";
@@ -56,9 +59,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 +67,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 +85,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();
@@ -110,27 +110,23 @@ public class HttpGetter {
response = client.execute(httpget); response = client.execute(httpget);
int code = response.getStatusLine().getStatusCode(); int code = response.getStatusLine().getStatusCode();
if (code == HttpStatus.SC_NOT_MODIFIED) { if (code == HttpStatus.SC_NOT_MODIFIED) {
throw new NotModifiedException("304 http code"); throw new NotModifiedException("'304 - not modified' http code received");
} 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) {
if (e.getStatusCode() == HttpStatus.SC_NOT_MODIFIED) { if (e.getStatusCode() == HttpStatus.SC_NOT_MODIFIED) {
throw new NotModifiedException("304 http code"); throw new NotModifiedException("'304 - not modified' http code received");
} else { } else {
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");
} }
@@ -141,16 +137,17 @@ public class HttpGetter {
HttpEntity entity = response.getEntity(); HttpEntity entity = response.getEntity();
byte[] content = null; byte[] content = null;
String contentType = null;
if (entity != null) { if (entity != null) {
content = EntityUtils.toByteArray(entity); content = EntityUtils.toByteArray(entity);
if (entity.getContentType() != null) {
contentType = entity.getContentType().getValue();
}
} }
long duration = System.currentTimeMillis() - start; long duration = System.currentTimeMillis() - start;
Header contentType = entity.getContentType(); result = new HttpResult(content, contentType, lastModifiedHeader == null ? null : lastModifiedHeader.getValue(),
result = new HttpResult(content, contentType == null ? null eTagHeader == null ? null : eTagHeader.getValue(), duration);
: contentType.getValue(), lastModifiedHeader == null ? null
: lastModifiedHeader.getValue(), eTagHeader == null ? null
: eTagHeader.getValue(), duration);
} finally { } finally {
client.getConnectionManager().shutdown(); client.getConnectionManager().shutdown();
} }
@@ -165,8 +162,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 +205,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 +220,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 +233,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

View File

@@ -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;
} }

View File

@@ -0,0 +1,39 @@
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 com.commafeed.backend.services.ApplicationSettingsService;
/**
* Contains all scheduled tasks
*
*/
@Stateless
public class ScheduledTasks {
@Inject
ApplicationSettingsService applicationSettingsService;
@Inject
DatabaseCleaner cleaner;
@PersistenceContext
EntityManager em;
/**
* clean old read statuses, runs every day at midnight
*/
@Schedule(hour = "0", persistent = false)
private void cleanupOldStatuses() {
Date threshold = applicationSettingsService.getUnreadThreshold();
if (threshold != null) {
cleaner.cleanStatusesOlderThan(threshold);
}
}
}

View File

@@ -13,24 +13,28 @@ import javax.ejb.Singleton;
import javax.ejb.Startup; import javax.ejb.Startup;
import javax.inject.Inject; import javax.inject.Inject;
import org.apache.commons.io.IOUtils; import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.commafeed.backend.dao.UserDAO; import org.apache.commons.io.IOUtils;
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;
import com.commafeed.backend.services.ApplicationSettingsService; import com.commafeed.backend.services.ApplicationSettingsService;
import com.commafeed.backend.services.UserService; import com.commafeed.backend.services.UserService;
import com.google.api.client.util.Maps; import com.google.common.collect.Maps;
/**
* Starting point of the application
*
*/
@Startup @Startup
@Singleton @Singleton
@ConcurrencyManagement(ConcurrencyManagementType.BEAN) @ConcurrencyManagement(ConcurrencyManagementType.BEAN)
@Slf4j
public class StartupBean { public class StartupBean {
private static Logger log = LoggerFactory.getLogger(StartupBean.class);
public static final String USERNAME_ADMIN = "admin"; public static final String USERNAME_ADMIN = "admin";
public static final String USERNAME_DEMO = "demo"; public static final String USERNAME_DEMO = "demo";
@@ -38,7 +42,7 @@ public class StartupBean {
DatabaseUpdater databaseUpdater; DatabaseUpdater databaseUpdater;
@Inject @Inject
UserDAO userDAO; ApplicationSettingsDAO applicationSettingsDAO;
@Inject @Inject
UserService userService; UserService userService;
@@ -56,14 +60,19 @@ public class StartupBean {
private void init() { private void init() {
startupTime = System.currentTimeMillis(); startupTime = System.currentTimeMillis();
// update database schema
databaseUpdater.update(); databaseUpdater.update();
if (userDAO.getCount() == 0) { if (applicationSettingsDAO.getCount() == 0) {
// import initial data
initialData(); initialData();
} }
applicationSettingsService.applyLogLevel(); applicationSettingsService.applyLogLevel();
initSupportedLanguages(); initSupportedLanguages();
// start fetching feeds
taskGiver.start(); taskGiver.start();
} }
@@ -79,11 +88,13 @@ 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()));
} }
} }
/**
* create default users
*/
private void initialData() { private void initialData() {
log.info("Populating database with default values"); log.info("Populating database with default values");
@@ -92,11 +103,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);
} }

View File

@@ -1,34 +1,39 @@
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;
import com.commafeed.frontend.model.UnreadCount;
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 UnreadCount getUnreadCount(FeedSubscription sub);
public abstract void setUnreadCount(FeedSubscription sub, UnreadCount count);
public abstract void invalidateUnreadCount(FeedSubscription... subs);
} }

View File

@@ -2,14 +2,15 @@ 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;
import com.commafeed.frontend.model.UnreadCount;
@Alternative @Alternative
@ApplicationScoped @ApplicationScoped
@@ -25,27 +26,32 @@ public class NoopCacheService extends CacheService {
} }
@Override @Override
public Category getRootCategory(User user) { public UnreadCount getUnreadCount(FeedSubscription sub) {
return null; return null;
} }
@Override @Override
public void setRootCategory(User user, Category category) { public void setUnreadCount(FeedSubscription sub, UnreadCount 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) {
} }

View File

@@ -1,39 +1,36 @@
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;
import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Alternative; import javax.enterprise.inject.Alternative;
import org.slf4j.Logger; import lombok.extern.slf4j.Slf4j;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis; import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig; 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.commafeed.frontend.model.UnreadCount;
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.common.collect.Lists;
import com.google.api.client.util.Lists;
@Alternative @Alternative
@ApplicationScoped @ApplicationScoped
@Slf4j
public class RedisCacheService extends CacheService { public class RedisCacheService extends CacheService {
private static final Logger log = LoggerFactory private static ObjectMapper mapper = new ObjectMapper();
.getLogger(RedisCacheService.class);
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 +67,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 +85,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 +103,35 @@ public class RedisCacheService extends CacheService {
} }
@Override @Override
public Map<Long, Long> getUnreadCounts(User user) { public UnreadCount getUnreadCount(FeedSubscription sub) {
Map<Long, Long> map = null; UnreadCount 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 json = jedis.get(key);
if (json != null) { if (json != null) {
MapType type = mapper.getTypeFactory().constructMapType( count = mapper.readValue(json, UnreadCount.class);
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, UnreadCount 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, mapper.writeValueAsString(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 +139,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 +155,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);
} }
} }

View File

@@ -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) {

View File

@@ -4,19 +4,17 @@ import java.util.Date;
import java.util.List; import java.util.List;
import javax.ejb.Stateless; import javax.ejb.Stateless;
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.Expression; import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType; import javax.persistence.criteria.JoinType;
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 javax.persistence.criteria.SetJoin; import javax.persistence.criteria.SetJoin;
import javax.persistence.criteria.Subquery;
import javax.persistence.metamodel.SingularAttribute; import javax.persistence.metamodel.SingularAttribute;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@@ -26,59 +24,55 @@ import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.FeedSubscription_; import com.commafeed.backend.model.FeedSubscription_;
import com.commafeed.backend.model.Feed_; import com.commafeed.backend.model.Feed_;
import com.commafeed.backend.model.User;
import com.commafeed.backend.model.User_;
import com.commafeed.frontend.model.FeedCount;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@Stateless @Stateless
public class FeedDAO extends GenericDAO<Feed> { public class FeedDAO extends GenericDAO<Feed> {
@XmlRootElement private List<Predicate> getUpdatablePredicates(CriteriaQuery<?> query, Root<Feed> root, Date lastLoginThreshold) {
@XmlAccessorType(XmlAccessType.FIELD)
public static class FeedCount { List<Predicate> preds = Lists.newArrayList();
public String value; Predicate isNull = builder.isNull(root.get(Feed_.disabledUntil));
public List<Feed> feeds; Predicate lessThan = builder.lessThan(root.get(Feed_.disabledUntil), new Date());
preds.add(builder.or(isNull, lessThan));
if (lastLoginThreshold != null) {
Subquery<Long> subquery = query.subquery(Long.class);
Root<FeedSubscription> subroot = subquery.from(FeedSubscription.class);
subquery.select(builder.count(subroot.get(FeedSubscription_.id)));
Join<FeedSubscription, User> userJoin = subroot.join(FeedSubscription_.user);
Predicate p1 = builder.equal(subroot.get(FeedSubscription_.feed), root);
Predicate p2 = builder.greaterThanOrEqualTo(userJoin.get(User_.lastLogin), lastLoginThreshold);
subquery.where(p1, p2);
preds.add(builder.exists(subquery));
}
return preds;
} }
private List<Predicate> getUpdatablePredicates(Root<Feed> root, public Long getUpdatableCount(Date lastLoginThreshold) {
Date threshold) {
Predicate hasSubscriptions = builder.isNotEmpty(root
.get(Feed_.subscriptions));
Predicate neverUpdated = builder.isNull(root.get(Feed_.lastUpdated));
Predicate updatedBeforeThreshold = builder.lessThan(
root.get(Feed_.lastUpdated), threshold);
Predicate disabledDateIsNull = builder.isNull(root
.get(Feed_.disabledUntil));
Predicate disabledDateIsInPast = builder.lessThan(
root.get(Feed_.disabledUntil), new Date());
return Lists.newArrayList(hasSubscriptions,
builder.or(neverUpdated, updatedBeforeThreshold),
builder.or(disabledDateIsNull, disabledDateIsInPast));
}
public Long getUpdatableCount(Date threshold) {
CriteriaQuery<Long> query = builder.createQuery(Long.class); CriteriaQuery<Long> query = builder.createQuery(Long.class);
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(query, root, lastLoginThreshold).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();
} }
public List<Feed> findNextUpdatable(int count, Date threshold) { public List<Feed> findNextUpdatable(int count, Date lastLoginThreshold) {
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(query, root, lastLoginThreshold).toArray(new Predicate[0]));
new Predicate[0])); query.orderBy(builder.asc(root.get(Feed_.disabledUntil)));
query.orderBy(builder.asc(root.get(Feed_.lastUpdated)));
TypedQuery<Feed> q = em.createQuery(query); TypedQuery<Feed> q = em.createQuery(query);
q.setMaxResults(count); q.setMaxResults(count);
@@ -87,18 +81,11 @@ public class FeedDAO extends GenericDAO<Feed> {
} }
public Feed findByUrl(String url) { public Feed findByUrl(String url) {
List<Feed> feeds = findByField(Feed_.urlHash, DigestUtils.sha1Hex(url));
Feed feed = Iterables.getFirst(feeds, null);
if (feed != null && StringUtils.equals(url, feed.getUrl())) {
return feed;
}
String normalized = FeedUtils.normalizeURL(url); String normalized = FeedUtils.normalizeURL(url);
feeds = findByField(Feed_.normalizedUrlHash, List<Feed> feeds = findByField(Feed_.normalizedUrlHash, DigestUtils.sha1Hex(normalized));
DigestUtils.sha1Hex(normalized)); Feed feed = Iterables.getFirst(feeds, null);
feed = Iterables.getFirst(feeds, null); if (feed != null && StringUtils.equals(normalized, feed.getNormalizedUrl())) {
if (feed != null
&& StringUtils.equals(normalized, feed.getNormalizedUrl())) {
return feed; return feed;
} }
@@ -109,19 +96,11 @@ public class FeedDAO extends GenericDAO<Feed> {
return findByField(Feed_.pushTopicHash, DigestUtils.sha1Hex(topic)); return findByField(Feed_.pushTopicHash, DigestUtils.sha1Hex(topic));
} }
public void deleteRelationships(Feed feed) {
Query relationshipDeleteQuery = em
.createNamedQuery("Feed.deleteEntryRelationships");
relationshipDeleteQuery.setParameter("feedId", feed.getId());
relationshipDeleteQuery.executeUpdate();
}
public int deleteWithoutSubscriptions(int max) { public int deleteWithoutSubscriptions(int max) {
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);
@@ -129,17 +108,13 @@ public class FeedDAO extends GenericDAO<Feed> {
List<Feed> list = q.getResultList(); List<Feed> list = q.getResultList();
int deleted = list.size(); int deleted = list.size();
for (Feed feed : list) { delete(list);
deleteRelationships(feed);
delete(feed);
}
return deleted; return deleted;
} }
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 +126,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());
@@ -170,14 +144,12 @@ public class FeedDAO extends GenericDAO<Feed> {
List<FeedCount> result = Lists.newArrayList(); List<FeedCount> result = Lists.newArrayList();
for (String pathValue : pathValues) { for (String pathValue : pathValues) {
FeedCount fc = new FeedCount(); FeedCount fc = new FeedCount(pathValue);
fc.value = pathValue;
fc.feeds = Lists.newArrayList();
for (Feed feed : findByField(mode.getPath(), pathValue)) { for (Feed feed : findByField(mode.getPath(), pathValue)) {
Feed f = new Feed(); Feed f = new Feed();
f.setId(feed.getId()); f.setId(feed.getId());
f.setUrl(feed.getUrl()); f.setUrl(feed.getUrl());
fc.feeds.add(f); fc.getFeeds().add(f);
} }
result.add(fc); result.add(fc);
} }

View File

@@ -0,0 +1,50 @@
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 Long findExisting(String contentHash, String titleHash) {
CriteriaQuery<Long> query = builder.createQuery(Long.class);
Root<FeedEntryContent> root = query.from(getType());
query.select(root.get(FeedEntryContent_.id));
Predicate p1 = builder.equal(root.get(FeedEntryContent_.contentHash), contentHash);
Predicate p2 = builder.equal(root.get(FeedEntryContent_.titleHash), titleHash);
query.where(p1, p2);
TypedQuery<Long> q = em.createQuery(query);
limit(q, 0, 1);
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;
}
}

View File

@@ -4,77 +4,36 @@ 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.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 public Long findExisting(String guid, Long feedId) {
ApplicationSettingsService applicationSettingsService;
protected static final Logger log = LoggerFactory CriteriaQuery<Long> query = builder.createQuery(Long.class);
.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());
Root<FeedEntry> root = query.from(getType()); Root<FeedEntry> root = query.from(getType());
SetJoin<FeedEntry, FeedFeedEntry> feedsJoin = root.join(FeedEntry_.feedRelationships); query.select(root.get(FeedEntry_.id));
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_.feed).get(Feed_.id), feedId);
TypedQuery<FeedEntry> q = em.createQuery(query);
limit(q, offset, limit); query.where(p1, p2);
setTimeout(q, applicationSettingsService.get().getQueryTimeout());
return q.getResultList(); TypedQuery<Long> q = em.createQuery(query);
limit(q, 0, 1);
List<Long> list = q.getResultList();
return Iterables.getFirst(list, null);
} }
public int delete(Date olderThan, int max) { public int delete(Date olderThan, int max) {
@@ -90,20 +49,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;
}
} }

View File

@@ -10,53 +10,42 @@ 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.lang.StringUtils;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger; import org.hibernate.Criteria;
import org.slf4j.LoggerFactory; 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 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;
import com.commafeed.backend.model.UserSettings.ReadingOrder; import com.commafeed.backend.model.UserSettings.ReadingOrder;
import com.commafeed.backend.services.ApplicationSettingsService; import com.commafeed.backend.services.ApplicationSettingsService;
import com.commafeed.frontend.model.UnreadCount;
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 private static final String ALIAS_STATUS = "status";
.getLogger(FeedEntryStatusDAO.class); private static final String ALIAS_ENTRY = "entry";
private static final Comparator<FeedEntry> ENTRY_COMPARATOR_DESC = new Comparator<FeedEntry>() {
@Override
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 +70,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.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 +105,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 +113,156 @@ 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, 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 : StringUtils.split(keywords)) {
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.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) {
// hibernate timeout is in seconds, jpa timeout is in millis
criteria.setTimeout(timeout / 1000);
}
return criteria;
} }
private List<FeedEntryStatus> lazyLoadContent(boolean includeContent, @SuppressWarnings("unchecked")
List<FeedEntryStatus> results) { public List<FeedEntryStatus> findBySubscriptions(List<FeedSubscription> subs, boolean unreadOnly, String keywords, Date newerThan,
int offset, int limit, ReadingOrder order, boolean includeContent, boolean onlyIds) {
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 : subs) {
Date last = (order != null && set.isFull()) ? set.last().getEntryUpdated() : null;
Criteria criteria = buildSearchCriteria(sub, unreadOnly, keywords, newerThan, -1, capacity, order, last);
ProjectionList projection = Projections.projectionList();
projection.add(Projections.property("id"), "id");
projection.add(Projections.property("updated"), "updated");
projection.add(Projections.property(ALIAS_STATUS + ".id"), "status_id");
criteria.setProjection(projection);
criteria.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
List<Map<String, Object>> list = criteria.list();
for (Map<String, Object> map : list) {
Long id = (Long) map.get("id");
Date updated = (Date) map.get("updated");
Long statusId = (Long) map.get("status_id");
FeedEntry entry = new FeedEntry();
entry.setId(id);
entry.setUpdated(updated);
FeedEntryStatus status = new FeedEntryStatus();
status.setId(statusId);
status.setEntryUpdated(updated);
status.setEntry(entry);
status.setSubscription(sub);
set.add(status);
}
}
List<FeedEntryStatus> placeholders = set.asList();
int size = placeholders.size();
if (size < offset) {
return Lists.newArrayList();
}
placeholders = placeholders.subList(Math.max(offset, 0), size);
List<FeedEntryStatus> statuses = null;
if (onlyIds) {
statuses = placeholders;
} else {
statuses = Lists.newArrayList();
for (FeedEntryStatus placeholder : placeholders) {
Long statusId = placeholder.getId();
FeedEntry entry = em.find(FeedEntry.class, placeholder.getEntry().getId());
statuses.add(handleStatus(statusId == null ? null : findById(statusId), placeholder.getSubscription(), entry));
}
statuses = lazyLoadContent(includeContent, statuses);
}
return statuses;
}
@SuppressWarnings("unchecked")
public UnreadCount getUnreadCount(FeedSubscription subscription) {
UnreadCount uc = null;
Criteria criteria = buildSearchCriteria(subscription, true, null, null, -1, -1, null, null);
ProjectionList projection = Projections.projectionList();
projection.add(Projections.rowCount(), "count");
projection.add(Projections.max(FeedEntry_.updated.getName()), "updated");
criteria.setProjection(projection);
criteria.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
List<Map<String, Object>> list = criteria.list();
for (Map<String, Object> row : list) {
Long count = (Long) row.get("count");
Date updated = (Date) row.get("updated");
uc = new UnreadCount(subscription.getId(), count, updated);
}
return uc;
}
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 +272,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 +290,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);
} }
} }

View File

@@ -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,11 +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), feed));
.get(Feed_.id), feed.getId())); List<FeedSubscription> list = cache(em.createQuery(query)).getResultList();
List<FeedSubscription> list = cache(em.createQuery(query))
.getResultList();
initRelations(list);
return list; return list;
} }
@@ -60,18 +55,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 +76,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);
} }
} }

View File

@@ -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);

View File

@@ -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();

View File

@@ -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();
} }

View File

@@ -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 {

View File

@@ -5,12 +5,12 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.commafeed.backend.HttpGetter; import com.commafeed.backend.HttpGetter;
import com.commafeed.backend.HttpGetter.HttpResult; import com.commafeed.backend.HttpGetter.HttpResult;
@@ -19,20 +19,16 @@ import com.commafeed.backend.HttpGetter.HttpResult;
* Inspired/Ported from https://github.com/potatolondon/getfavicon * Inspired/Ported from https://github.com/potatolondon/getfavicon
* *
*/ */
@Slf4j
public class FaviconFetcher { public class FaviconFetcher {
private static Logger log = LoggerFactory.getLogger(FeedFetcher.class);
private static long MIN_ICON_LENGTH = 100; private static long MIN_ICON_LENGTH = 100;
private static long MAX_ICON_LENGTH = 20000; private static long MAX_ICON_LENGTH = 100000;
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;
@@ -84,7 +80,7 @@ public class FaviconFetcher {
return bytes; return bytes;
} }
boolean isValidIconResponse(byte[] content, String contentType) { private boolean isValidIconResponse(byte[] content, String contentType) {
if (content == null) { if (content == null) {
return false; return false;
} }
@@ -101,14 +97,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 +120,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);

View File

@@ -5,14 +5,14 @@ import java.util.Date;
import javax.inject.Inject; import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.StringUtils; import org.apache.commons.codec.binary.StringUtils;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.client.ClientProtocolException; import org.apache.http.client.ClientProtocolException;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.commafeed.backend.HttpGetter; import com.commafeed.backend.HttpGetter;
import com.commafeed.backend.HttpGetter.HttpResult; import com.commafeed.backend.HttpGetter.HttpResult;
@@ -20,52 +20,54 @@ import com.commafeed.backend.HttpGetter.NotModifiedException;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
import com.sun.syndication.io.FeedException; import com.sun.syndication.io.FeedException;
@Slf4j
public class FeedFetcher { public class FeedFetcher {
private static Logger log = LoggerFactory.getLogger(FeedFetcher.class);
@Inject @Inject
FeedParser parser; FeedParser parser;
@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) { byte[] content = result.getContent();
String extractedUrl = extractFeedUrl(
StringUtils.newStringUtf8(result.getContent()), feedUrl); try {
if (org.apache.commons.lang.StringUtils.isNotBlank(extractedUrl)) { fetchedFeed = parser.parse(feedUrl, content);
result = getter.getBinary(extractedUrl, lastModified, eTag, timeout); } catch (FeedException e) {
feedUrl = extractedUrl; if (extractFeedUrlFromHtml) {
String extractedUrl = extractFeedUrl(StringUtils.newStringUtf8(result.getContent()), feedUrl);
if (org.apache.commons.lang.StringUtils.isNotBlank(extractedUrl)) {
feedUrl = extractedUrl;
result = getter.getBinary(extractedUrl, lastModified, eTag, timeout);
content = result.getContent();
fetchedFeed = parser.parse(feedUrl, content);
}
} else {
throw e;
} }
} }
byte[] content = result.getContent();
if (content == null) { if (content == null) {
throw new IOException("Feed content is empty."); throw new IOException("Feed content is empty.");
} }
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); if (lastPublishedDate != null && fetchedFeed.getFeed().getLastPublishedDate() != null
&& lastPublishedDate.getTime() == fetchedFeed.getFeed().getLastPublishedDate().getTime()) {
if (lastPublishedDate != null
&& fetchedFeed.getFeed().getLastPublishedDate() != null
&& 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");
} }

View File

@@ -5,13 +5,12 @@ 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 lombok.extern.slf4j.Slf4j;
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;
import org.jdom.Namespace; import org.jdom.Namespace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
@@ -29,17 +28,14 @@ import com.sun.syndication.feed.synd.SyndLinkImpl;
import com.sun.syndication.io.FeedException; import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedInput; import com.sun.syndication.io.SyndFeedInput;
@Slf4j
public class FeedParser { public class FeedParser {
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) {
@@ -52,15 +48,12 @@ public class FeedParser {
FetchedFeed fetchedFeed = new FetchedFeed(); FetchedFeed fetchedFeed = new FetchedFeed();
Feed feed = fetchedFeed.getFeed(); Feed feed = fetchedFeed.getFeed();
List<FeedEntry> entries = fetchedFeed.getEntries(); List<FeedEntry> entries = fetchedFeed.getEntries();
feed.setLastUpdated(new Date());
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 +82,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 +101,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 = (publishedDate == null || publishedDate.before(lastEntryDate)) ? lastEntryDate : publishedDate;
} }
feed.setLastPublishedDate(validateDate(publishedDate, true)); feed.setLastPublishedDate(publishedDate);
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 +130,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,17 +141,6 @@ public class FeedParser {
} }
} }
private Date getFeedPublishedDate(Date publishedDate,
List<FeedEntry> entries) {
for (FeedEntry entry : entries) {
if (publishedDate == null || entry.getUpdated().getTime() > publishedDate.getTime()) {
publishedDate = entry.getUpdated();
}
}
return publishedDate;
}
private Date getEntryUpdateDate(SyndEntry item) { private Date getEntryUpdateDate(SyndEntry item) {
Date date = item.getUpdatedDate(); Date date = item.getUpdatedDate();
if (date == null) { if (date == null) {
@@ -199,14 +171,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 +188,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 +206,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();
} }
} }

View File

@@ -0,0 +1,42 @@
package com.commafeed.backend.feeds;
import java.util.List;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry;
public class FeedRefreshContext {
private Feed feed;
private List<FeedEntry> entries;
private boolean isUrgent;
public FeedRefreshContext(Feed feed, boolean isUrgent) {
this.feed = feed;
this.isUrgent = isUrgent;
}
public Feed getFeed() {
return feed;
}
public void setFeed(Feed feed) {
this.feed = feed;
}
public boolean isUrgent() {
return isUrgent;
}
public void setUrgent(boolean isUrgent) {
this.isUrgent = isUrgent;
}
public List<FeedEntry> getEntries() {
return entries;
}
public void setEntries(List<FeedEntry> entries) {
this.entries = entries;
}
}

View File

@@ -2,42 +2,39 @@ package com.commafeed.backend.feeds;
import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger; import lombok.extern.slf4j.Slf4j;
import org.slf4j.LoggerFactory;
/**
* Wraps a {@link ThreadPoolExecutor} instance. Blocks when queue is full instead of rejecting the task. Allow priority queueing by using
* {@link Task} instead of {@link Runnable}
*
*/
@Slf4j
public class FeedRefreshExecutor { public class FeedRefreshExecutor {
private static Logger log = LoggerFactory
.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,12 +47,10 @@ 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);
} }
} }
}); });
pool.setThreadFactory(new NamedThreadFactory(poolName));
} }
public void execute(Task task) { public void execute(Task task) {
@@ -80,34 +75,8 @@ 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 final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
private NamedThreadFactory(String poolName) {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = poolName + "-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
} }

View File

@@ -12,24 +12,28 @@ import javax.annotation.PreDestroy;
import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject; import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.time.DateUtils; import org.apache.commons.lang.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.commafeed.backend.MetricsBean; import com.commafeed.backend.MetricsBean;
import com.commafeed.backend.dao.FeedDAO; import com.commafeed.backend.dao.FeedDAO;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
import com.commafeed.backend.services.ApplicationSettingsService; import com.commafeed.backend.services.ApplicationSettingsService;
import com.google.api.client.util.Maps;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues; import com.google.common.collect.Queues;
/**
* Infinite loop fetching feeds from the database and queuing them to the {@link FeedRefreshWorker} pool. Also handles feed database updates
* at the end of the cycle through {@link #giveBack(Feed)}.
*
*/
@ApplicationScoped @ApplicationScoped
@Slf4j
public class FeedRefreshTaskGiver { public class FeedRefreshTaskGiver {
protected static final Logger log = LoggerFactory.getLogger(FeedRefreshTaskGiver.class);
@Inject @Inject
FeedDAO feedDAO; FeedDAO feedDAO;
@@ -44,16 +48,15 @@ public class FeedRefreshTaskGiver {
private int backgroundThreads; private int backgroundThreads;
private Queue<Feed> addQueue = Queues.newConcurrentLinkedQueue(); private Queue<FeedRefreshContext> addQueue = Queues.newConcurrentLinkedQueue();
private Queue<Feed> takeQueue = Queues.newConcurrentLinkedQueue(); private Queue<FeedRefreshContext> takeQueue = Queues.newConcurrentLinkedQueue();
private Queue<Feed> giveBackQueue = Queues.newConcurrentLinkedQueue(); private Queue<Feed> giveBackQueue = Queues.newConcurrentLinkedQueue();
private ExecutorService executor; private ExecutorService executor;
@PostConstruct @PostConstruct
public void init() { public void init() {
backgroundThreads = applicationSettingsService.get() backgroundThreads = applicationSettingsService.get().getBackgroundThreads();
.getBackgroundThreads();
executor = Executors.newFixedThreadPool(1); executor = Executors.newFixedThreadPool(1);
} }
@@ -83,12 +86,13 @@ public class FeedRefreshTaskGiver {
public void run() { public void run() {
while (!executor.isShutdown()) { while (!executor.isShutdown()) {
try { try {
Feed feed = take(); FeedRefreshContext context = take();
if (feed != null) { if (context != null) {
metricsBean.feedRefreshed(); metricsBean.feedRefreshed();
worker.updateFeed(feed); worker.updateFeed(context);
} else { } else {
log.debug("nothing to do, sleeping for 15s"); log.debug("nothing to do, sleeping for 15s");
metricsBean.threadWaited();
try { try {
Thread.sleep(15000); Thread.sleep(15000);
} catch (InterruptedException e) { } catch (InterruptedException e) {
@@ -103,72 +107,99 @@ public class FeedRefreshTaskGiver {
}); });
} }
private Feed take() { /**
Feed feed = takeQueue.poll(); * take a feed from the refresh queue
*/
private FeedRefreshContext take() {
FeedRefreshContext context = takeQueue.poll();
if (feed == null) { if (context == null) {
refill(); refill();
feed = takeQueue.poll(); context = takeQueue.poll();
} }
return feed; return context;
} }
public Long getUpdatableCount() { public Long getUpdatableCount() {
return feedDAO.getUpdatableCount(getThreshold()); return feedDAO.getUpdatableCount(getLastLoginThreshold());
} }
private Date getThreshold() { /**
boolean heavyLoad = applicationSettingsService.get().isHeavyLoad(); * add a feed to the refresh queue
Date threshold = DateUtils.addMinutes(new Date(), heavyLoad ? -15 : -5); */
return threshold; public void add(Feed feed, boolean urgent) {
} int refreshInterval = applicationSettingsService.get().getRefreshIntervalMinutes();
if (feed.getLastUpdated() == null || feed.getLastUpdated().before(DateUtils.addMinutes(new Date(), -1 * refreshInterval))) {
public void add(Feed feed) { addQueue.add(new FeedRefreshContext(feed, urgent));
Date threshold = getThreshold();
if (feed.getLastUpdated() == null
|| feed.getLastUpdated().before(threshold)) {
addQueue.add(feed);
} }
} }
/**
* refills the refresh queue and empties the giveBack queue while at it
*/
private void refill() { private void refill() {
Date now = new Date();
int count = Math.min(100, 3 * backgroundThreads); int count = Math.min(100, 3 * backgroundThreads);
List<Feed> feeds = null;
if (applicationSettingsService.get().isCrawlingPaused()) { // first, get feeds that are up to refresh from the database
feeds = Lists.newArrayList(); List<FeedRefreshContext> contexts = Lists.newArrayList();
} else { if (!applicationSettingsService.get().isCrawlingPaused()) {
feeds = feedDAO.findNextUpdatable(count, getThreshold()); List<Feed> feeds = feedDAO.findNextUpdatable(count, getLastLoginThreshold());
for (Feed feed : feeds) {
contexts.add(new FeedRefreshContext(feed, false));
}
} }
// then, add to those the feeds we got from the add() method. We add them at the beginning of the list as they probably have a
// higher priority
int size = addQueue.size(); int size = addQueue.size();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
feeds.add(0, addQueue.poll()); contexts.add(0, addQueue.poll());
} }
Map<Long, Feed> map = Maps.newLinkedHashMap(); // set the disabledDate to now as we use the disabledDate in feedDAO to decide what to refresh next. We also use a map to remove
for (Feed f : feeds) { // duplicates.
f.setLastUpdated(now); Map<Long, FeedRefreshContext> map = Maps.newLinkedHashMap();
map.put(f.getId(), f); for (FeedRefreshContext context : contexts) {
Feed feed = context.getFeed();
feed.setDisabledUntil(new Date());
map.put(feed.getId(), context);
} }
// refill the queue
takeQueue.addAll(map.values()); takeQueue.addAll(map.values());
// add feeds from the giveBack queue to the map, overriding duplicates
size = giveBackQueue.size(); size = giveBackQueue.size();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
Feed f = giveBackQueue.poll(); Feed feed = giveBackQueue.poll();
f.setLastUpdated(now); map.put(feed.getId(), new FeedRefreshContext(feed, false));
map.put(f.getId(), f);
} }
feedDAO.saveOrUpdate(map.values()); // update all feeds in the database
List<Feed> feeds = Lists.newArrayList();
for (FeedRefreshContext context : map.values()) {
feeds.add(context.getFeed());
}
feedDAO.saveOrUpdate(feeds);
} }
/**
* give a feed back, updating it to the database during the next refill()
*/
public void giveBack(Feed feed) { public void giveBack(Feed feed) {
String normalized = FeedUtils.normalizeURL(feed.getUrl()); String normalized = FeedUtils.normalizeURL(feed.getUrl());
feed.setNormalizedUrl(normalized); feed.setNormalizedUrl(normalized);
feed.setNormalizedUrlHash(DigestUtils.sha1Hex(normalized)); feed.setNormalizedUrlHash(DigestUtils.sha1Hex(normalized));
feed.setLastUpdated(new Date());
giveBackQueue.add(feed); giveBackQueue.add(feed);
} }
private Date getLastLoginThreshold() {
if (applicationSettingsService.get().isHeavyLoad()) {
return DateUtils.addDays(new Date(), -30);
} else {
return null;
}
}
} }

View File

@@ -1,7 +1,8 @@
package com.commafeed.backend.feeds; package com.commafeed.backend.feeds;
import java.util.Collection; import java.util.Arrays;
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,10 +12,12 @@ import javax.annotation.PreDestroy;
import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject; import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections.CollectionUtils;
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.LoggerFactory;
import com.commafeed.backend.MetricsBean; import com.commafeed.backend.MetricsBean;
import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.cache.CacheService;
@@ -25,19 +28,19 @@ 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.model.User;
import com.commafeed.backend.pubsubhubbub.SubscriptionHandler; import com.commafeed.backend.pubsubhubbub.SubscriptionHandler;
import com.commafeed.backend.services.ApplicationSettingsService; import com.commafeed.backend.services.ApplicationSettingsService;
import com.commafeed.backend.services.FeedUpdateService; import com.commafeed.backend.services.FeedUpdateService;
import com.google.api.client.util.Lists; import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Striped; import com.google.common.util.concurrent.Striped;
@ApplicationScoped @ApplicationScoped
@Slf4j
public class FeedRefreshUpdater { public class FeedRefreshUpdater {
protected static Logger log = LoggerFactory
.getLogger(FeedRefreshUpdater.class);
@Inject @Inject
FeedUpdateService feedUpdateService; FeedUpdateService feedUpdateService;
@@ -72,7 +75,7 @@ public class FeedRefreshUpdater {
public void init() { public void init() {
ApplicationSettings settings = applicationSettingsService.get(); ApplicationSettings settings = applicationSettingsService.get();
int threads = Math.max(settings.getDatabaseUpdateThreads(), 1); int threads = Math.max(settings.getDatabaseUpdateThreads(), 1);
pool = new FeedRefreshExecutor("feed-refresh-updater", threads, 500 * threads); pool = new FeedRefreshExecutor("feed-refresh-updater", threads, Math.min(50 * threads, 1000));
locks = Striped.lazyWeakLock(threads * 100000); locks = Striped.lazyWeakLock(threads * 100000);
} }
@@ -81,25 +84,26 @@ public class FeedRefreshUpdater {
pool.shutdown(); pool.shutdown();
} }
public void updateFeed(Feed feed, Collection<FeedEntry> entries) { public void updateFeed(FeedRefreshContext context) {
pool.execute(new EntryTask(feed, entries)); pool.execute(new EntryTask(context));
} }
private class EntryTask implements Task { private class EntryTask implements Task {
private Feed feed; private FeedRefreshContext context;
private Collection<FeedEntry> entries;
public EntryTask(Feed feed, Collection<FeedEntry> entries) { public EntryTask(FeedRefreshContext context) {
this.feed = feed; this.context = context;
this.entries = entries;
} }
@Override @Override
public void run() { public void run() {
boolean ok = true; boolean ok = true;
if (entries.isEmpty() == false) { Feed feed = context.getFeed();
List<FeedEntry> entries = context.getEntries();
if (entries.isEmpty()) {
feed.setMessage("Feed has no entries");
} else {
List<String> lastEntries = cache.getLastEntries(feed); List<String> lastEntries = cache.getLastEntries(feed);
List<String> currentEntries = Lists.newArrayList(); List<String> currentEntries = Lists.newArrayList();
@@ -109,25 +113,39 @@ 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());
metricsBean.entryCacheHit(); metricsBean.entryCacheHit();
} }
currentEntries.add(cacheKey); currentEntries.add(cacheKey);
} }
cache.setLastEntries(feed, currentEntries); cache.setLastEntries(feed, currentEntries);
if (subscriptions == null) {
feed.setMessage("No new entries found");
}
if (CollectionUtils.isNotEmpty(subscriptions)) {
List<User> users = Lists.newArrayList();
for (FeedSubscription sub : subscriptions) {
users.add(sub.getUser());
}
cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0]));
cache.invalidateUserRootCategory(users.toArray(new User[0]));
}
} }
if (applicationSettingsService.get().isPubsubhubbub()) { if (applicationSettingsService.get().isPubsubhubbub()) {
handlePubSub(feed); handlePubSub(feed);
} }
if (!ok) { if (!ok) {
feed.setDisabledUntil(null); // requeue asap
feed.setDisabledUntil(new Date(0));
} }
metricsBean.feedUpdated(); metricsBean.feedUpdated();
taskGiver.giveBack(feed); taskGiver.giveBack(feed);
@@ -135,31 +153,47 @@ public class FeedRefreshUpdater {
@Override @Override
public boolean isUrgent() { public boolean isUrgent() {
return feed.isUrgent(); return context.isUrgent();
} }
} }
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);
feedUpdateService.updateEntry(feed, entry, subscriptions); if (locked1 && locked2) {
boolean inserted = feedUpdateService.addEntry(feed, entry);
if (inserted) {
metricsBean.entryInserted();
}
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;

View File

@@ -8,26 +8,26 @@ import javax.annotation.PreDestroy;
import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject; import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger; import org.apache.commons.lang.time.DateUtils;
import org.slf4j.LoggerFactory;
import com.commafeed.backend.HttpGetter.NotModifiedException; import com.commafeed.backend.HttpGetter.NotModifiedException;
import com.commafeed.backend.MetricsBean;
import com.commafeed.backend.dao.FeedEntryDAO;
import com.commafeed.backend.feeds.FeedRefreshExecutor.Task; 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.services.ApplicationSettingsService; import com.commafeed.backend.services.ApplicationSettingsService;
import com.sun.syndication.io.FeedException;
/**
* Calls {@link FeedFetcher} and handles its outcome
*
*/
@ApplicationScoped @ApplicationScoped
@Slf4j
public class FeedRefreshWorker { public class FeedRefreshWorker {
private static Logger log = LoggerFactory
.getLogger(FeedRefreshWorker.class);
@Inject @Inject
FeedRefreshUpdater feedRefreshUpdater; FeedRefreshUpdater feedRefreshUpdater;
@@ -40,20 +40,13 @@ public class FeedRefreshWorker {
@Inject @Inject
ApplicationSettingsService applicationSettingsService; ApplicationSettingsService applicationSettingsService;
@Inject
MetricsBean metricsBean;
@Inject
FeedEntryDAO feedEntryDAO;
private FeedRefreshExecutor pool; private FeedRefreshExecutor pool;
@PostConstruct @PostConstruct
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, Math.min(20 * threads, 1000));
20 * threads);
} }
@PreDestroy @PreDestroy
@@ -61,8 +54,8 @@ public class FeedRefreshWorker {
pool.shutdown(); pool.shutdown();
} }
public void updateFeed(Feed feed) { public void updateFeed(FeedRefreshContext context) {
pool.execute(new FeedTask(feed)); pool.execute(new FeedTask(context));
} }
public int getQueueSize() { public int getQueueSize() {
@@ -75,50 +68,45 @@ public class FeedRefreshWorker {
private class FeedTask implements Task { private class FeedTask implements Task {
private Feed feed; private FeedRefreshContext context;
public FeedTask(Feed feed) { public FeedTask(FeedRefreshContext context) {
this.feed = feed; this.context = context;
} }
@Override @Override
public void run() { public void run() {
update(feed); update(context);
} }
@Override @Override
public boolean isUrgent() { public boolean isUrgent() {
return feed.isUrgent(); return context.isUrgent();
} }
} }
private void update(Feed feed) { private void update(FeedRefreshContext context) {
Date now = new Date(); Feed feed = context.getFeed();
int refreshInterval = applicationSettingsService.get().getRefreshIntervalMinutes();
Date disabledUntil = DateUtils.addMinutes(new Date(), refreshInterval);
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
List<FeedEntry> entries = fetchedFeed.getEntries(); List<FeedEntry> entries = fetchedFeed.getEntries();
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(), disabledUntil);
.getAverageEntryInterval());
} }
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);
@@ -126,36 +114,27 @@ public class FeedRefreshWorker {
feed.setDisabledUntil(disabledUntil); feed.setDisabledUntil(disabledUntil);
handlePubSub(feed, fetchedFeed.getFeed()); handlePubSub(feed, fetchedFeed.getFeed());
feedRefreshUpdater.updateFeed(feed, entries); context.setEntries(entries);
feedRefreshUpdater.updateFeed(context);
} 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;
if (applicationSettingsService.get().isHeavyLoad()) { if (applicationSettingsService.get().isHeavyLoad()) {
disabledUntil = FeedUtils disabledUntil = FeedUtils.buildDisabledUntil(feed.getLastEntryDate(), feed.getAverageEntryInterval(), disabledUntil);
.buildDisabledUntil(feed.getLastEntryDate(),
feed.getAverageEntryInterval());
} }
feed.setErrorCount(0); feed.setErrorCount(0);
feed.setMessage(null); feed.setMessage(e.getMessage());
feed.setDisabledUntil(disabledUntil); feed.setDisabledUntil(disabledUntil);
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(); log.debug(e.getClass().getName() + " " + message, e);
if (e instanceof FeedException) {
log.debug(e.getClass().getName() + " " + message, e);
} else {
log.debug(e.getClass().getName() + " " + message, e);
}
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);
} }

View File

@@ -1,13 +1,16 @@
package com.commafeed.backend.feeds; package com.commafeed.backend.feeds;
import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.ArrayUtils;
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;
@@ -21,30 +24,31 @@ import org.jsoup.safety.Cleaner;
import org.jsoup.safety.Whitelist; import org.jsoup.safety.Whitelist;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
import org.mozilla.universalchardet.UniversalDetector; import org.mozilla.universalchardet.UniversalDetector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.css.sac.InputSource; import org.w3c.css.sac.InputSource;
import org.w3c.dom.css.CSSStyleDeclaration; import org.w3c.dom.css.CSSStyleDeclaration;
import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.FeedSubscription;
import com.google.api.client.util.Base64; import com.commafeed.frontend.model.Entry;
import com.google.api.client.util.Lists; import com.google.common.collect.Lists;
import com.google.gwt.i18n.client.HasDirection.Direction; import com.google.gwt.i18n.client.HasDirection.Direction;
import com.google.gwt.i18n.shared.BidiUtils; import com.google.gwt.i18n.shared.BidiUtils;
import com.steadystate.css.parser.CSSOMParser; import com.steadystate.css.parser.CSSOMParser;
import edu.uci.ics.crawler4j.url.URLCanonicalizer; import edu.uci.ics.crawler4j.url.URLCanonicalizer;
/**
* Utility methods related to feed handling
*
*/
@Slf4j
public class FeedUtils { public class FeedUtils {
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(
"height", "width", "border"); private static final List<String> ALLOWED_IFRAME_CSS_RULES = Arrays.asList("height", "width", "border");
private static final char[] DISALLOWED_IFRAME_CSS_RULE_CHARACTERS = new char[] { private static final List<String> ALLOWED_IMG_CSS_RULES = Arrays.asList("display", "width", "height");
'(', ')' }; private static final char[] FORBIDDEN_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 +58,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) {
@@ -64,6 +68,8 @@ public class FeedUtils {
if (StringUtils.endsWith(extracted, "1") == false) { if (StringUtils.endsWith(extracted, "1") == false) {
return extracted; return extracted;
} }
} else if (StringUtils.startsWithIgnoreCase(extracted, "windows-")) {
return extracted;
} }
return detectEncoding(bytes); return detectEncoding(bytes);
} }
@@ -87,8 +93,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 +118,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 +149,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 +166,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", "style");
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 +192,13 @@ public class FeedUtils {
e.attr("style", escaped); e.attr("style", escaped);
} }
clean.outputSettings(new OutputSettings().escapeMode( for (Element e : clean.select("img[style]")) {
EscapeMode.base).prettyPrint(false)); String style = e.attr("style");
String escaped = escapeImgCss(style);
e.attr("style", escaped);
}
clean.outputSettings(new OutputSettings().escapeMode(EscapeMode.base).prettyPrint(false));
Element body = clean.body(); Element body = clean.body();
if (keepTextOnly) { if (keepTextOnly) {
content = body.text(); content = body.text();
@@ -212,12 +210,11 @@ public class FeedUtils {
} }
public static String escapeIFrameCss(String orig) { public static String escapeIFrameCss(String orig) {
List<String> rules = Lists.newArrayList(); String rule = "";
CSSOMParser parser = new CSSOMParser(); CSSOMParser parser = new CSSOMParser();
try { try {
CSSStyleDeclaration decl = parser List<String> rules = Lists.newArrayList();
.parseStyleDeclaration(new InputSource(new StringReader( CSSStyleDeclaration decl = parser.parseStyleDeclaration(new InputSource(new StringReader(orig)));
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,17 +223,40 @@ public class FeedUtils {
continue; continue;
} }
if (ALLOWED_IFRAME_CSS_RULES.contains(property) if (ALLOWED_IFRAME_CSS_RULES.contains(property) && StringUtils.containsNone(value, FORBIDDEN_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) { rule = StringUtils.join(rules, "");
} catch (Exception e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
} }
return StringUtils.join(rules, ""); return rule;
}
public static String escapeImgCss(String orig) {
String rule = "";
CSSOMParser parser = new CSSOMParser();
try {
List<String> rules = Lists.newArrayList();
CSSStyleDeclaration decl = parser.parseStyleDeclaration(new InputSource(new StringReader(orig)));
for (int i = 0; i < decl.getLength(); i++) {
String property = decl.item(i);
String value = decl.getPropertyValue(property);
if (StringUtils.isBlank(property) || StringUtils.isBlank(value)) {
continue;
}
if (ALLOWED_IMG_CSS_RULES.contains(property) && StringUtils.containsNone(value, FORBIDDEN_CSS_RULE_CHARACTERS)) {
rules.add(property + ":" + decl.getPropertyValue(property) + ";");
}
}
rule = StringUtils.join(rules, "");
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return rule;
} }
public static boolean isRTL(FeedEntry entry) { public static boolean isRTL(FeedEntry entry) {
@@ -278,8 +298,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);
} }
} }
@@ -300,14 +319,13 @@ public class FeedUtils {
disabledHours = Math.min(24 * 7, disabledHours); disabledHours = Math.min(24 * 7, disabledHours);
return DateUtils.addHours(now, disabledHours); return DateUtils.addHours(now, disabledHours);
} }
return null; return now;
} }
/** /**
* 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, Date defaultRefreshInterval) {
Long averageEntryInterval) {
Date now = new Date(); Date now = new Date();
if (publishedDate == null) { if (publishedDate == null) {
@@ -323,10 +341,16 @@ public class FeedUtils {
// older than a week, recheck in 6 hours // older than a week, recheck in 6 hours
return DateUtils.addHours(now, 6); return DateUtils.addHours(now, 6);
} 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, divided by factor
int factor = 2; int factor = 2;
return new Date(Math.min(DateUtils.addHours(now, 6).getTime(),
now.getTime() + averageEntryInterval / factor)); // not more than 6 hours
long date = Math.min(DateUtils.addHours(now, 6).getTime(), now.getTime() + averageEntryInterval / factor);
// not less than default refresh interval
date = Math.max(defaultRefreshInterval.getTime(), date);
return new Date(date);
} 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 +402,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 +419,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);
} }
} }
@@ -433,4 +453,27 @@ public class FeedUtils {
return rot13(new String(Base64.decodeBase64(code))); return rot13(new String(Base64.decodeBase64(code)));
} }
public static void removeUnwantedFromSearch(List<Entry> entries, String keywords) {
if (StringUtils.isBlank(keywords)) {
return;
}
Iterator<Entry> it = entries.iterator();
while (it.hasNext()) {
Entry entry = it.next();
boolean keep = true;
for (String keyword : keywords.split(" ")) {
String title = Jsoup.parse(entry.getTitle()).text();
String content = Jsoup.parse(entry.getContent()).text();
if (!StringUtils.containsIgnoreCase(content, keyword) && !StringUtils.containsIgnoreCase(title, keyword)) {
keep = false;
break;
}
}
if (!keep) {
it.remove();
}
}
}
} }

View File

@@ -4,7 +4,7 @@ import java.util.List;
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.google.api.client.util.Lists; import com.google.common.collect.Lists;
public class FetchedFeed { public class FetchedFeed {

View File

@@ -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;
} }

View File

@@ -9,9 +9,9 @@ import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType; import javax.ejb.TransactionAttributeType;
import javax.inject.Inject; import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.cache.CacheService;
import com.commafeed.backend.dao.FeedCategoryDAO; import com.commafeed.backend.dao.FeedCategoryDAO;
@@ -25,10 +25,9 @@ import com.sun.syndication.io.WireFeedInput;
@Stateless @Stateless
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
@Slf4j
public class OPMLImporter { public class OPMLImporter {
private static Logger log = LoggerFactory.getLogger(OPMLImporter.class);
@Inject @Inject
FeedSubscriptionService feedSubscriptionService; FeedSubscriptionService feedSubscriptionService;
@@ -63,13 +62,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 +89,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);
} }
} }

View File

@@ -8,21 +8,26 @@ import javax.persistence.Id;
import javax.persistence.MappedSuperclass; import javax.persistence.MappedSuperclass;
import javax.persistence.TableGenerator; import javax.persistence.TableGenerator;
import lombok.Getter;
import lombok.Setter;
/**
* Abstract model for all entities, defining id and table generator
*
*/
@SuppressWarnings("serial") @SuppressWarnings("serial")
@MappedSuperclass @MappedSuperclass
@Getter
@Setter
public abstract class AbstractModel implements Serializable { 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() {
return id;
}
public void setId(Long id) {
this.id = id;
}
} }

View File

@@ -3,17 +3,17 @@ package com.commafeed.backend.model;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Table; import javax.persistence.Table;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import lombok.Getter;
import javax.xml.bind.annotation.XmlRootElement; import lombok.Setter;
import org.apache.log4j.Level; import org.apache.log4j.Level;
@Entity @Entity
@Table(name = "APPLICATIONSETTINGS") @Table(name = "APPLICATIONSETTINGS")
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement @Getter
@XmlAccessorType(XmlAccessType.FIELD) @Setter
public class ApplicationSettings extends AbstractModel { public class ApplicationSettings extends AbstractModel {
private String publicUrl; private String publicUrl;
@@ -35,169 +35,9 @@ 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;
private int refreshIntervalMinutes = 5;
@Column(length = 255) @Column(length = 255)
private String announcement; private String announcement;
public String getPublicUrl() {
return publicUrl;
}
public void setPublicUrl(String publicUrl) {
this.publicUrl = publicUrl;
}
public boolean isAllowRegistrations() {
return allowRegistrations;
}
public void setAllowRegistrations(boolean allowRegistrations) {
this.allowRegistrations = allowRegistrations;
}
public String getGoogleClientId() {
return googleClientId;
}
public void setGoogleClientId(String googleClientId) {
this.googleClientId = googleClientId;
}
public String getGoogleClientSecret() {
return googleClientSecret;
}
public void setGoogleClientSecret(String googleClientSecret) {
this.googleClientSecret = googleClientSecret;
}
public int getBackgroundThreads() {
return backgroundThreads;
}
public void setBackgroundThreads(int backgroundThreads) {
this.backgroundThreads = backgroundThreads;
}
public String getSmtpHost() {
return smtpHost;
}
public void setSmtpHost(String smtpHost) {
this.smtpHost = smtpHost;
}
public int getSmtpPort() {
return smtpPort;
}
public void setSmtpPort(int smtpPort) {
this.smtpPort = smtpPort;
}
public boolean isSmtpTls() {
return smtpTls;
}
public void setSmtpTls(boolean smtpTls) {
this.smtpTls = smtpTls;
}
public String getSmtpUserName() {
return smtpUserName;
}
public void setSmtpUserName(String smtpUserName) {
this.smtpUserName = smtpUserName;
}
public String getSmtpPassword() {
return smtpPassword;
}
public void setSmtpPassword(String smtpPassword) {
this.smtpPassword = smtpPassword;
}
public String getGoogleAnalyticsTrackingCode() {
return googleAnalyticsTrackingCode;
}
public void setGoogleAnalyticsTrackingCode(
String googleAnalyticsTrackingCode) {
this.googleAnalyticsTrackingCode = googleAnalyticsTrackingCode;
}
public String getAnnouncement() {
return announcement;
}
public void setAnnouncement(String announcement) {
this.announcement = announcement;
}
public boolean isFeedbackButton() {
return feedbackButton;
}
public void setFeedbackButton(boolean feedbackButton) {
this.feedbackButton = feedbackButton;
}
public boolean isPubsubhubbub() {
return pubsubhubbub;
}
public void setPubsubhubbub(boolean pubsubhubbub) {
this.pubsubhubbub = pubsubhubbub;
}
public boolean isHeavyLoad() {
return heavyLoad;
}
public void setHeavyLoad(boolean heavyLoad) {
this.heavyLoad = heavyLoad;
}
public int getDatabaseUpdateThreads() {
return databaseUpdateThreads;
}
public void setDatabaseUpdateThreads(int databaseUpdateThreads) {
this.databaseUpdateThreads = databaseUpdateThreads;
}
public String getLogLevel() {
return logLevel;
}
public void setLogLevel(String logLevel) {
this.logLevel = logLevel;
}
public boolean isImageProxyEnabled() {
return imageProxyEnabled;
}
public void setImageProxyEnabled(boolean imageProxyEnabled) {
this.imageProxyEnabled = imageProxyEnabled;
}
public int getQueryTimeout() {
return queryTimeout;
}
public void setQueryTimeout(int queryTimeout) {
this.queryTimeout = queryTimeout;
}
public boolean isCrawlingPaused() {
return crawlingPaused;
}
public void setCrawlingPaused(boolean crawlingPaused) {
this.crawlingPaused = crawlingPaused;
}
} }

View File

@@ -4,13 +4,16 @@ 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;
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 lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
@@ -20,6 +23,8 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@Cacheable @Cacheable
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
@Getter
@Setter
public class Feed extends AbstractModel { public class Feed extends AbstractModel {
/** /**
@@ -28,9 +33,6 @@ public class Feed extends AbstractModel {
@Column(length = 2048, nullable = false) @Column(length = 2048, nullable = false)
private String url; private String url;
@Column(length = 40, nullable = false)
private String urlHash;
@Column(length = 2048, nullable = false) @Column(length = 2048, nullable = false)
private String normalizedUrl; private String normalizedUrl;
@@ -61,12 +63,6 @@ public class Feed extends AbstractModel {
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
private Date lastEntryDate; private Date lastEntryDate;
/**
* Last time we successfully refreshed the feed
*/
@Temporal(TemporalType.TIMESTAMP)
private Date lastUpdateSuccess;
/** /**
* error message while retrieving the feed * error message while retrieving the feed
*/ */
@@ -107,8 +103,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;
@@ -134,13 +130,6 @@ public class Feed extends AbstractModel {
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
private Date pushLastPing; private Date pushLastPing;
/**
* Denotes a feed that needs to be refreshed before others. Currently used
* when a feed is queued manually for refresh. Not persisted.
*/
@Transient
private boolean urgent;
public Feed() { public Feed() {
} }
@@ -148,189 +137,4 @@ public class Feed extends AbstractModel {
public Feed(String url) { public Feed(String url) {
this.url = url; this.url = url;
} }
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Date getLastUpdated() {
return lastUpdated;
}
public void setLastUpdated(Date lastUpdated) {
this.lastUpdated = lastUpdated;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Set<FeedSubscription> getSubscriptions() {
return subscriptions;
}
public void setSubscriptions(Set<FeedSubscription> subscriptions) {
this.subscriptions = subscriptions;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public int getErrorCount() {
return errorCount;
}
public void setErrorCount(int errorCount) {
this.errorCount = errorCount;
}
public Date getDisabledUntil() {
return disabledUntil;
}
public void setDisabledUntil(Date disabledUntil) {
this.disabledUntil = disabledUntil;
}
public String getUrlHash() {
return urlHash;
}
public void setUrlHash(String urlHash) {
this.urlHash = urlHash;
}
public String getLastModifiedHeader() {
return lastModifiedHeader;
}
public void setLastModifiedHeader(String lastModifiedHeader) {
this.lastModifiedHeader = lastModifiedHeader;
}
public String getEtagHeader() {
return etagHeader;
}
public void setEtagHeader(String etagHeader) {
this.etagHeader = etagHeader;
}
public Date getLastUpdateSuccess() {
return lastUpdateSuccess;
}
public void setLastUpdateSuccess(Date lastUpdateSuccess) {
this.lastUpdateSuccess = lastUpdateSuccess;
}
public String getPushHub() {
return pushHub;
}
public void setPushHub(String pushHub) {
this.pushHub = pushHub;
}
public String getPushTopic() {
return pushTopic;
}
public void setPushTopic(String pushTopic) {
this.pushTopic = pushTopic;
}
public Date getPushLastPing() {
return pushLastPing;
}
public void setPushLastPing(Date pushLastPing) {
this.pushLastPing = pushLastPing;
}
public Date getLastPublishedDate() {
return lastPublishedDate;
}
public void setLastPublishedDate(Date lastPublishedDate) {
this.lastPublishedDate = lastPublishedDate;
}
public String getLastContentHash() {
return lastContentHash;
}
public void setLastContentHash(String lastContentHash) {
this.lastContentHash = lastContentHash;
}
public Long getAverageEntryInterval() {
return averageEntryInterval;
}
public void setAverageEntryInterval(Long averageEntryInterval) {
this.averageEntryInterval = averageEntryInterval;
}
public Date getLastEntryDate() {
return lastEntryDate;
}
public void setLastEntryDate(Date lastEntryDate) {
this.lastEntryDate = lastEntryDate;
}
public String getPushTopicHash() {
return pushTopicHash;
}
public void setPushTopicHash(String pushTopicHash) {
this.pushTopicHash = pushTopicHash;
}
public boolean isUrgent() {
return urgent;
}
public void setUrgent(boolean urgent) {
this.urgent = urgent;
}
public String getNormalizedUrl() {
return normalizedUrl;
}
public void setNormalizedUrl(String normalizedUrl) {
this.normalizedUrl = normalizedUrl;
}
public String getNormalizedUrlHash() {
return normalizedUrlHash;
}
public void setNormalizedUrlHash(String normalizedUrlHash) {
this.normalizedUrlHash = normalizedUrlHash;
}
public Set<FeedFeedEntry> getEntryRelationships() {
return entryRelationships;
}
public void setEntryRelationships(Set<FeedFeedEntry> entryRelationships) {
this.entryRelationships = entryRelationships;
}
} }

View File

@@ -11,16 +11,19 @@ import javax.persistence.ManyToOne;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
import com.google.common.collect.Sets;
@Entity @Entity
@Table(name = "FEEDCATEGORIES") @Table(name = "FEEDCATEGORIES")
@SuppressWarnings("serial") @SuppressWarnings("serial")
@Cacheable @Cacheable
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
@Getter
@Setter
public class FeedCategory extends AbstractModel { public class FeedCategory extends AbstractModel {
@Column(length = 128, nullable = false) @Column(length = 128, nullable = false)
@@ -45,63 +48,4 @@ public class FeedCategory extends AbstractModel {
private Integer position; private Integer position;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public FeedCategory getParent() {
return parent;
}
public void setParent(FeedCategory parent) {
this.parent = parent;
}
public Set<FeedSubscription> getSubscriptions() {
if (subscriptions == null) {
return Sets.newHashSet();
}
return subscriptions;
}
public void setSubscriptions(Set<FeedSubscription> subscriptions) {
this.subscriptions = subscriptions;
}
public Set<FeedCategory> getChildren() {
return children;
}
public void setChildren(Set<FeedCategory> children) {
this.children = children;
}
public boolean isCollapsed() {
return collapsed;
}
public void setCollapsed(boolean collapsed) {
this.collapsed = collapsed;
}
public Integer getPosition() {
return position;
}
public void setPosition(Integer position) {
this.position = position;
}
} }

View File

@@ -9,12 +9,15 @@ 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;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import javax.persistence.Transient;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
@@ -24,6 +27,8 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@Cacheable @Cacheable
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
@Getter
@Setter
public class FeedEntry extends AbstractModel { public class FeedEntry extends AbstractModel {
@Column(length = 2048, nullable = false) @Column(length = 2048, nullable = false)
@@ -32,19 +37,16 @@ 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(fetch = FetchType.LAZY, optional = false)
@JoinColumn(nullable = false, updatable = false) @JoinColumn(nullable = false, updatable = false)
private FeedEntryContent content; private FeedEntryContent content;
@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;
@@ -54,90 +56,4 @@ public class FeedEntry extends AbstractModel {
@OneToMany(mappedBy = "entry", cascade = CascadeType.REMOVE) @OneToMany(mappedBy = "entry", cascade = CascadeType.REMOVE)
private Set<FeedEntryStatus> statuses; private Set<FeedEntryStatus> statuses;
/**
* useful placeholder for the subscription, not persisted
*/
@Transient
private FeedSubscription subscription;
public String getGuid() {
return guid;
}
public void setGuid(String guid) {
this.guid = guid;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Date getUpdated() {
return updated;
}
public void setUpdated(Date updated) {
this.updated = updated;
}
public Set<FeedEntryStatus> getStatuses() {
return statuses;
}
public void setStatuses(Set<FeedEntryStatus> statuses) {
this.statuses = statuses;
}
public Date getInserted() {
return inserted;
}
public void setInserted(Date inserted) {
this.inserted = inserted;
}
public FeedEntryContent getContent() {
return content;
}
public void setContent(FeedEntryContent content) {
this.content = content;
}
public String getGuidHash() {
return guidHash;
}
public void setGuidHash(String guidHash) {
this.guidHash = guidHash;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Set<FeedFeedEntry> getFeedRelationships() {
return feedRelationships;
}
public void setFeedRelationships(Set<FeedFeedEntry> feedRelationships) {
this.feedRelationships = feedRelationships;
}
public FeedSubscription getSubscription() {
return subscription;
}
public void setSubscription(FeedSubscription subscription) {
this.subscription = subscription;
}
} }

View File

@@ -1,11 +1,17 @@
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 lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
@@ -14,51 +20,33 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@Cacheable @Cacheable
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
@Getter
@Setter
public class FeedEntryContent extends AbstractModel { 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;
public String getContent() { @OneToMany(mappedBy = "content")
return content; private Set<FeedEntry> entries;
}
public void setContent(String content) {
this.content = content;
}
public String getEnclosureUrl() {
return enclosureUrl;
}
public void setEnclosureUrl(String enclosureUrl) {
this.enclosureUrl = enclosureUrl;
}
public String getEnclosureType() {
return enclosureType;
}
public void setEnclosureType(String enclosureType) {
this.enclosureType = enclosureType;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
} }

View File

@@ -11,6 +11,10 @@ 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 lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
@@ -20,6 +24,8 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@Cacheable @Cacheable
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
@Getter
@Setter
public class FeedEntryStatus extends AbstractModel { public class FeedEntryStatus extends AbstractModel {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@@ -34,6 +40,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
*/ */
@@ -60,60 +69,4 @@ public class FeedEntryStatus extends AbstractModel {
setEntryUpdated(entry.getUpdated()); setEntryUpdated(entry.getUpdated());
} }
public FeedSubscription getSubscription() {
return subscription;
}
public void setSubscription(FeedSubscription subscription) {
this.subscription = subscription;
}
public FeedEntry getEntry() {
return entry;
}
public void setEntry(FeedEntry entry) {
this.entry = entry;
}
public boolean isRead() {
return read;
}
public void setRead(boolean read) {
this.read = read;
}
public boolean isStarred() {
return starred;
}
public void setStarred(boolean starred) {
this.starred = starred;
}
public Date getEntryInserted() {
return entryInserted;
}
public void setEntryInserted(Date entryInserted) {
this.entryInserted = entryInserted;
}
public Date getEntryUpdated() {
return entryUpdated;
}
public void setEntryUpdated(Date entryUpdated) {
this.entryUpdated = entryUpdated;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
} }

View File

@@ -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;
}
}

View File

@@ -12,6 +12,9 @@ import javax.persistence.ManyToOne;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
@@ -20,6 +23,8 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@Cacheable @Cacheable
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
@Getter
@Setter
public class FeedSubscription extends AbstractModel { public class FeedSubscription extends AbstractModel {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@@ -44,52 +49,4 @@ public class FeedSubscription extends AbstractModel {
private Integer position; private Integer position;
public Feed getFeed() {
return feed;
}
public void setFeed(Feed feed) {
this.feed = feed;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public FeedCategory getCategory() {
return category;
}
public void setCategory(FeedCategory category) {
this.category = category;
}
public Set<FeedEntryStatus> getStatuses() {
return statuses;
}
public void setStatuses(Set<FeedEntryStatus> statuses) {
this.statuses = statuses;
}
public Integer getPosition() {
return position;
}
public void setPosition(Integer position) {
this.position = position;
}
} }

View File

@@ -13,6 +13,9 @@ import javax.persistence.Table;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
@@ -23,6 +26,8 @@ import com.google.common.collect.Sets;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@Cacheable @Cacheable
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
@Getter
@Setter
public class User extends AbstractModel { public class User extends AbstractModel {
@Column(length = 32, nullable = false, unique = true) @Column(length = 32, nullable = false, unique = true)
@@ -55,107 +60,10 @@ 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)
private Set<FeedSubscription> subscriptions; private Set<FeedSubscription> subscriptions;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public byte[] getPassword() {
return password;
}
public void setPassword(byte[] password) {
this.password = password;
}
public byte[] getSalt() {
return salt;
}
public void setSalt(byte[] salt) {
this.salt = salt;
}
public Set<UserRole> getRoles() {
return roles;
}
public void setRoles(Set<UserRole> roles) {
this.roles = roles;
}
public boolean isDisabled() {
return disabled;
}
public void setDisabled(boolean disabled) {
this.disabled = disabled;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getLastLogin() {
return lastLogin;
}
public void setLastLogin(Date lastLogin) {
this.lastLogin = lastLogin;
}
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public String getRecoverPasswordToken() {
return recoverPasswordToken;
}
public void setRecoverPasswordToken(String recoverPasswordToken) {
this.recoverPasswordToken = recoverPasswordToken;
}
public Date getRecoverPasswordTokenDate() {
return recoverPasswordTokenDate;
}
public void setRecoverPasswordTokenDate(Date recoverPasswordTokenDate) {
this.recoverPasswordTokenDate = recoverPasswordTokenDate;
}
public Set<FeedSubscription> getSubscriptions() {
return subscriptions;
}
public void setSubscriptions(Set<FeedSubscription> subscriptions) {
this.subscriptions = subscriptions;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
} }

View File

@@ -10,6 +10,9 @@ import javax.persistence.JoinColumn;
import javax.persistence.OneToOne; import javax.persistence.OneToOne;
import javax.persistence.Table; import javax.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
@@ -18,6 +21,8 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@Cacheable @Cacheable
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
@Getter
@Setter
public class UserRole extends AbstractModel { public class UserRole extends AbstractModel {
public static enum Role { public static enum Role {
@@ -41,20 +46,4 @@ public class UserRole extends AbstractModel {
this.role = role; this.role = role;
} }
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
} }

View File

@@ -11,6 +11,9 @@ import javax.persistence.Lob;
import javax.persistence.OneToOne; import javax.persistence.OneToOne;
import javax.persistence.Table; import javax.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
@@ -19,6 +22,8 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@Cacheable @Cacheable
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
@Getter
@Setter
public class UserSettings extends AbstractModel { public class UserSettings extends AbstractModel {
public enum ReadingMode { public enum ReadingMode {
@@ -63,84 +68,4 @@ public class UserSettings extends AbstractModel {
@Column(length = Integer.MAX_VALUE) @Column(length = Integer.MAX_VALUE)
private String customCss; private String customCss;
public ReadingMode getReadingMode() {
return readingMode;
}
public void setReadingMode(ReadingMode readingMode) {
this.readingMode = readingMode;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getCustomCss() {
return customCss;
}
public void setCustomCss(String customCss) {
this.customCss = customCss;
}
public ReadingOrder getReadingOrder() {
return readingOrder;
}
public void setReadingOrder(ReadingOrder readingOrder) {
this.readingOrder = readingOrder;
}
public boolean isShowRead() {
return showRead;
}
public void setShowRead(boolean showRead) {
this.showRead = showRead;
}
public boolean isSocialButtons() {
return socialButtons;
}
public void setSocialButtons(boolean socialButtons) {
this.socialButtons = socialButtons;
}
public ViewMode getViewMode() {
return viewMode;
}
public void setViewMode(ViewMode viewMode) {
this.viewMode = viewMode;
}
public boolean isScrollMarks() {
return scrollMarks;
}
public void setScrollMarks(boolean scrollMarks) {
this.scrollMarks = scrollMarks;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getTheme() {
return theme;
}
public void setTheme(String theme) {
this.theme = theme;
}
} }

View File

@@ -5,6 +5,8 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpHeaders; import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
@@ -14,21 +16,22 @@ import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.commafeed.backend.HttpGetter; import com.commafeed.backend.HttpGetter;
import com.commafeed.backend.feeds.FeedRefreshTaskGiver; 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.services.ApplicationSettingsService; import com.commafeed.backend.services.ApplicationSettingsService;
import com.commafeed.frontend.rest.resources.PubSubHubbubCallbackREST;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
/**
* Sends push subscription requests. Callback is handled by {@link PubSubHubbubCallbackREST}
*
*/
@Slf4j
public class SubscriptionHandler { public class SubscriptionHandler {
private static Logger log = LoggerFactory
.getLogger(SubscriptionHandler.class);
@Inject @Inject
ApplicationSettingsService applicationSettingsService; ApplicationSettingsService applicationSettingsService;
@@ -47,16 +50,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 +65,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 +76,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();
} }

View File

@@ -4,8 +4,11 @@ import org.jdom.Element;
import com.sun.syndication.feed.opml.Opml; import com.sun.syndication.feed.opml.Opml;
public class OPML11Generator extends /**
com.sun.syndication.io.impl.OPML10Generator { * Add missing title to the generated OPML
*
*/
public class OPML11Generator extends com.sun.syndication.io.impl.OPML10Generator {
public OPML11Generator() { public OPML11Generator() {
super("opml_1.1"); super("opml_1.1");

View File

@@ -5,21 +5,22 @@ import org.jdom.Element;
import com.sun.syndication.io.impl.OPML10Parser; import com.sun.syndication.io.impl.OPML10Parser;
/**
* Support for OPML 1.1 parsing
*
*/
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;
} }

View File

@@ -6,6 +6,10 @@ import com.sun.syndication.feed.synd.SyndContentImpl;
import com.sun.syndication.feed.synd.SyndEntry; import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.impl.ConverterForRSS090; import com.sun.syndication.feed.synd.impl.ConverterForRSS090;
/**
* Support description tag for RSS09
*
*/
public class RSS090DescriptionConverter extends ConverterForRSS090 { public class RSS090DescriptionConverter extends ConverterForRSS090 {
@Override @Override

View File

@@ -6,6 +6,10 @@ import com.sun.syndication.feed.rss.Description;
import com.sun.syndication.feed.rss.Item; import com.sun.syndication.feed.rss.Item;
import com.sun.syndication.io.impl.RSS090Parser; import com.sun.syndication.io.impl.RSS090Parser;
/**
* Support description tag for RSS09
*
*/
public class RSS090DescriptionParser extends RSS090Parser { public class RSS090DescriptionParser extends RSS090Parser {
@Override @Override

View File

@@ -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);

View File

@@ -1,10 +1,12 @@
package com.commafeed.backend.services; package com.commafeed.backend.services;
import java.util.Date;
import java.util.Enumeration; import java.util.Enumeration;
import javax.ejb.Singleton; import javax.ejb.Singleton;
import javax.inject.Inject; import javax.inject.Inject;
import org.apache.commons.lang.time.DateUtils;
import org.apache.log4j.Level; import org.apache.log4j.Level;
import org.apache.log4j.LogManager; import org.apache.log4j.LogManager;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
@@ -29,12 +31,16 @@ 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;
} }
public Date getUnreadThreshold() {
int keepStatusDays = get().getKeepStatusDays();
return keepStatusDays > 0 ? DateUtils.addDays(new Date(), -1 * keepStatusDays) : null;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void applyLogLevel() { public void applyLogLevel() {
String logLevel = get().getLogLevel(); String logLevel = get().getLogLevel();

View File

@@ -0,0 +1,42 @@
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()));
Long existingId = feedEntryContentDAO.findExisting(contentHash, titleHash);
FeedEntryContent result = null;
if (existingId == 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));
result = content;
feedEntryContentDAO.saveOrUpdate(result);
} else {
result = new FeedEntryContent();
result.setId(existingId);
}
return result;
}
}

View File

@@ -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,50 +24,37 @@ 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, boolean read) {
if (sub == null) {
return;
}
FeedEntry entry = feedEntryDAO.findById(entryId); FeedEntry entry = feedEntryDAO.findById(entryId);
if (entry == null) { if (entry == null) {
return; return;
} }
FeedEntryStatus status = feedEntryStatusDAO.getStatus(sub, entry); FeedSubscription sub = feedSubscriptionDAO.findByFeed(user, entry.getFeed());
if (sub == null) {
if (read) { return;
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);
} }
FeedEntryStatus status = feedEntryStatusDAO.getStatus(sub, entry);
if (status.isMarkable()) {
status.setRead(read);
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 +65,34 @@ 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
if (!status.isRead()) { .findBySubscriptions(subscriptions, true, null, null, -1, -1, null, false, false);
status.setStarred(false); markList(statuses, olderThan);
feedEntryStatusDAO.saveOrUpdate(status); cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0]));
} else { cache.invalidateUserRootCategory(user);
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);
} }
} }

View File

@@ -1,5 +1,7 @@
package com.commafeed.backend.services; package com.commafeed.backend.services;
import java.util.Date;
import javax.ejb.Lock; import javax.ejb.Lock;
import javax.ejb.LockType; import javax.ejb.LockType;
import javax.ejb.Singleton; import javax.ejb.Singleton;
@@ -28,9 +30,9 @@ public class FeedService {
String normalized = FeedUtils.normalizeURL(url); String normalized = FeedUtils.normalizeURL(url);
feed = new Feed(); feed = new Feed();
feed.setUrl(url); feed.setUrl(url);
feed.setUrlHash(DigestUtils.sha1Hex(url));
feed.setNormalizedUrl(normalized); feed.setNormalizedUrl(normalized);
feed.setNormalizedUrlHash(DigestUtils.sha1Hex(normalized)); feed.setNormalizedUrlHash(DigestUtils.sha1Hex(normalized));
feed.setDisabledUntil(new Date(0));
feedDAO.saveOrUpdate(feed); feedDAO.saveOrUpdate(feed);
} }
return feed; return feed;

View File

@@ -6,9 +6,9 @@ import java.util.Map;
import javax.ejb.ApplicationException; import javax.ejb.ApplicationException;
import javax.inject.Inject; import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.cache.CacheService;
import com.commafeed.backend.dao.FeedEntryDAO; import com.commafeed.backend.dao.FeedEntryDAO;
@@ -18,18 +18,15 @@ 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.commafeed.frontend.model.UnreadCount;
import com.google.common.collect.Maps;
@Slf4j
public class FeedSubscriptionService { public class FeedSubscriptionService {
private static Logger log = LoggerFactory
.getLogger(FeedSubscriptionService.class);
@SuppressWarnings("serial") @SuppressWarnings("serial")
@ApplicationException @ApplicationException
public static class FeedSubscriptionException extends RuntimeException { public static class FeedSubscriptionException extends RuntimeException {
@@ -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) { taskGiver.add(feed, false);
try { cache.invalidateUserRootCategory(user);
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);
return feed; return feed;
} }
public Map<Long, Long> getUnreadCount(User user) { public boolean unsubscribe(User user, Long subId) {
Map<Long, Long> map = cache.getUnreadCounts(user); FeedSubscription sub = feedSubscriptionDAO.findById(user, subId);
if (map == null) { if (sub != null) {
log.debug("unread count cache miss for {}", Models.getId(user)); feedSubscriptionDAO.delete(sub);
map = feedEntryStatusDAO.getUnreadCount(user); cache.invalidateUserRootCategory(user);
cache.setUnreadCounts(user, map); return true;
} else {
return false;
}
}
public UnreadCount getUnreadCount(FeedSubscription sub) {
UnreadCount 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, UnreadCount> getUnreadCount(User user) {
Map<Long, UnreadCount> map = Maps.newHashMap();
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
for (FeedSubscription sub : subs) {
map.put(sub.getId(), getUnreadCount(sub));
} }
return map; return map;
} }
} }

View File

@@ -1,91 +1,43 @@
package com.commafeed.backend.services; package com.commafeed.backend.services;
import java.util.Date; 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 javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import com.commafeed.backend.MetricsBean; import org.apache.commons.codec.digest.DigestUtils;
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.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.User;
import com.google.common.collect.Lists;
@Stateless @Stateless
public class FeedUpdateService { public class FeedUpdateService {
@PersistenceContext
protected EntityManager em;
@Inject
FeedSubscriptionDAO feedSubscriptionDAO;
@Inject @Inject
FeedEntryDAO feedEntryDAO; FeedEntryDAO feedEntryDAO;
@Inject @Inject
FeedEntryStatusDAO feedEntryStatusDAO; FeedEntryContentService feedEntryContentService;
@Inject /**
MetricsBean metricsBean; * this is NOT thread-safe
*/
public boolean addEntry(Feed feed, FeedEntry entry) {
@Inject Long existing = feedEntryDAO.findExisting(entry.getGuid(), feed.getId());
CacheService cache; if (existing != null) {
return false;
public void updateEntry(Feed feed, FeedEntry entry,
List<FeedSubscription> subscriptions) {
EntryWithFeed existing = feedEntryDAO.findExisting(entry.getGuid(),
entry.getUrl(), feed.getId());
FeedEntry update = null;
FeedFeedEntry ffe = null;
if (existing == null) {
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); feedEntryDAO.saveOrUpdate(entry);
statusUpdateList.add(status); return true;
users.add(user);
}
cache.invalidateUserData(users.toArray(new User[0]));
feedEntryDAO.saveOrUpdate(update);
feedEntryStatusDAO.saveOrUpdate(statusUpdateList);
em.persist(ffe);
metricsBean.entryUpdated(statusUpdateList.size());
}
} }
} }

View File

@@ -12,22 +12,20 @@ import javax.mail.Transport;
import javax.mail.internet.InternetAddress; import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.commafeed.backend.model.ApplicationSettings; import com.commafeed.backend.model.ApplicationSettings;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
/**
* Mailing service
*
*/
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class MailService implements Serializable { public class MailService implements Serializable {
protected static Logger log = LoggerFactory.getLogger(MailService.class);
@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 +48,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");

View File

@@ -10,25 +10,19 @@ import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory; import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEKeySpec;
import org.slf4j.Logger; import lombok.extern.slf4j.Slf4j;
import org.slf4j.LoggerFactory;
import com.commafeed.backend.dao.UserDAO; // taken from http://www.javacodegeeks.com/2012/05/secure-password-storage-donts-dos-and.html
// http://www.javacodegeeks.com/2012/05/secure-password-storage-donts-dos-and.html
@SuppressWarnings("serial") @SuppressWarnings("serial")
@Slf4j
public class PasswordEncryptionService implements Serializable { public class PasswordEncryptionService implements Serializable {
private static final Logger log = LoggerFactory.getLogger(UserDAO.class); public boolean authenticate(String attemptedPassword, byte[] encryptedPassword, byte[] salt) {
public boolean authenticate(String attemptedPassword,
byte[] encryptedPassword, byte[] salt) {
// Encrypt the clear-text password using the same salt that was used to // Encrypt the clear-text password using the same salt that was used to
// encrypt the original password // encrypt the original password
byte[] encryptedAttemptedPassword = null; byte[] encryptedAttemptedPassword = null;
try { try {
encryptedAttemptedPassword = getEncryptedPassword( encryptedAttemptedPassword = getEncryptedPassword(attemptedPassword, salt);
attemptedPassword, salt);
} catch (Exception e) { } catch (Exception e) {
// should never happen // should never happen
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
@@ -53,8 +47,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 {

View File

@@ -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);
} }
} }

View File

@@ -0,0 +1,101 @@
package com.commafeed.frontend;
import java.io.OutputStream;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import org.apache.commons.lang.StringUtils;
import org.apache.wicket.util.io.IOUtils;
import com.commafeed.frontend.model.Entries;
import com.commafeed.frontend.model.request.MarkRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.core.Documentation;
import com.wordnik.swagger.core.DocumentationEndPoint;
import com.wordnik.swagger.core.SwaggerSpec;
import com.wordnik.swagger.core.util.TypeUtil;
import com.wordnik.swagger.jaxrs.HelpApi;
import com.wordnik.swagger.jaxrs.JaxrsApiReader;
@SupportedAnnotationTypes("com.wordnik.swagger.annotations.Api")
@SupportedOptions("outputDirectory")
public class APIGenerator extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
try {
return processInternal(annotations, roundEnv);
} catch (Exception e) {
e.printStackTrace();
processingEnv.getMessager().printMessage(Kind.ERROR, e.getMessage());
}
return false;
}
private boolean processInternal(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) throws Exception {
JaxrsApiReader.setFormatString("");
TypeUtil.addAllowablePackage(Entries.class.getPackage().getName());
TypeUtil.addAllowablePackage(MarkRequest.class.getPackage().getName());
String apiVersion = "1.0";
String swaggerVersion = SwaggerSpec.version();
String basePath = "../rest";
Documentation doc = new Documentation();
for (Element element : roundEnv.getElementsAnnotatedWith(Api.class)) {
TypeElement type = (TypeElement) element;
String fqn = type.getQualifiedName().toString();
Class<?> resource = Class.forName(fqn);
Api api = resource.getAnnotation(Api.class);
String apiPath = api.value();
Documentation apiDoc = JaxrsApiReader.read(resource, apiVersion, swaggerVersion, basePath, apiPath);
apiDoc = new HelpApi(null).filterDocs(apiDoc, null, null, null, null);
apiDoc.setSwaggerVersion(swaggerVersion);
apiDoc.setApiVersion(apiVersion);
write(apiDoc.getResourcePath(), apiDoc, element);
doc.addApi(new DocumentationEndPoint(api.value(), api.description()));
}
doc.setSwaggerVersion(swaggerVersion);
doc.setApiVersion(apiVersion);
write(doc.getResourcePath(), doc, null);
return true;
}
private void write(String resourcePath, Object doc, Element element) throws Exception {
String fileName = StringUtils.defaultString(resourcePath, "resources");
fileName = StringUtils.removeStart(fileName, "/");
FileObject resource = null;
try {
resource = processingEnv.getFiler().createResource(StandardLocation.SOURCE_OUTPUT, "", fileName, element);
} catch (Exception e) {
// already processed
}
if (resource != null) {
OutputStream os = resource.openOutputStream();
try {
IOUtils.write(new ObjectMapper().writeValueAsString(doc), os, "UTF-8");
} finally {
IOUtils.closeQuietly(os);
}
}
}
}

View File

@@ -1,14 +1,17 @@
package com.commafeed.frontend; package com.commafeed.frontend;
import java.util.ResourceBundle;
import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import javax.naming.InitialContext; import javax.naming.InitialContext;
import javax.naming.NamingException; import javax.naming.NamingException;
import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.wicket.Application; import org.apache.wicket.Application;
import org.apache.wicket.Component; import org.apache.wicket.Component;
import org.apache.wicket.IRequestCycleProvider;
import org.apache.wicket.Page; import org.apache.wicket.Page;
import org.apache.wicket.RuntimeConfigurationType; import org.apache.wicket.RuntimeConfigurationType;
import org.apache.wicket.Session; import org.apache.wicket.Session;
@@ -20,6 +23,7 @@ import org.apache.wicket.authroles.authentication.AbstractAuthenticatedWebSessio
import org.apache.wicket.authroles.authentication.AuthenticatedWebApplication; import org.apache.wicket.authroles.authentication.AuthenticatedWebApplication;
import org.apache.wicket.authroles.authorization.strategies.role.Roles; import org.apache.wicket.authroles.authorization.strategies.role.Roles;
import org.apache.wicket.cdi.CdiConfiguration; import org.apache.wicket.cdi.CdiConfiguration;
import org.apache.wicket.cdi.CdiContainer;
import org.apache.wicket.cdi.ConversationPropagation; import org.apache.wicket.cdi.ConversationPropagation;
import org.apache.wicket.core.request.handler.PageProvider; import org.apache.wicket.core.request.handler.PageProvider;
import org.apache.wicket.core.request.handler.RenderPageRequestHandler; import org.apache.wicket.core.request.handler.RenderPageRequestHandler;
@@ -31,13 +35,16 @@ import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.request.IRequestHandler; import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.Request; import org.apache.wicket.request.Request;
import org.apache.wicket.request.Response; import org.apache.wicket.request.Response;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.UrlRenderer;
import org.apache.wicket.request.component.IRequestableComponent; import org.apache.wicket.request.component.IRequestableComponent;
import org.apache.wicket.request.cycle.AbstractRequestCycleListener; import org.apache.wicket.request.cycle.AbstractRequestCycleListener;
import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.cycle.RequestCycleContext;
import org.apache.wicket.util.cookies.CookieUtils; import org.apache.wicket.util.cookies.CookieUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.commafeed.backend.services.ApplicationPropertiesService;
import com.commafeed.backend.services.ApplicationSettingsService;
import com.commafeed.frontend.pages.DemoLoginPage; import com.commafeed.frontend.pages.DemoLoginPage;
import com.commafeed.frontend.pages.HomePage; import com.commafeed.frontend.pages.HomePage;
import com.commafeed.frontend.pages.LogoutPage; import com.commafeed.frontend.pages.LogoutPage;
@@ -45,43 +52,38 @@ import com.commafeed.frontend.pages.NextUnreadRedirectPage;
import com.commafeed.frontend.pages.PasswordRecoveryCallbackPage; import com.commafeed.frontend.pages.PasswordRecoveryCallbackPage;
import com.commafeed.frontend.pages.PasswordRecoveryPage; import com.commafeed.frontend.pages.PasswordRecoveryPage;
import com.commafeed.frontend.pages.WelcomePage; import com.commafeed.frontend.pages.WelcomePage;
import com.commafeed.frontend.utils.WicketUtils;
import com.commafeed.frontend.utils.exception.DisplayExceptionPage; import com.commafeed.frontend.utils.exception.DisplayExceptionPage;
@Slf4j
public class CommaFeedApplication extends AuthenticatedWebApplication { public class CommaFeedApplication extends AuthenticatedWebApplication {
private static Logger log = LoggerFactory @Inject
.getLogger(CommaFeedApplication.class); ApplicationSettingsService applicationSettingsService;
public CommaFeedApplication() { public CommaFeedApplication() {
super(); super();
String prod = ResourceBundle.getBundle("application").getString( boolean prod = ApplicationPropertiesService.get().isProduction();
"production"); setConfigurationType(prod ? RuntimeConfigurationType.DEPLOYMENT : RuntimeConfigurationType.DEVELOPMENT);
setConfigurationType(Boolean.valueOf(prod) ? RuntimeConfigurationType.DEPLOYMENT
: RuntimeConfigurationType.DEVELOPMENT);
} }
@Override @Override
protected void init() { protected void init() {
super.init(); super.init();
setupInjection();
setupSecurity();
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(GoogleImportCallbackPage.PAGE_PATH,
// GoogleImportCallbackPage.class);
mountPage("next", NextUnreadRedirectPage.class); mountPage("next", NextUnreadRedirectPage.class);
setupInjection();
setupSecurity();
getMarkupSettings().setStripWicketTags(true); getMarkupSettings().setStripWicketTags(true);
getMarkupSettings().setCompressWhitespace(true); getMarkupSettings().setCompressWhitespace(true);
getMarkupSettings().setDefaultMarkupEncoding("UTF-8"); getMarkupSettings().setDefaultMarkupEncoding("UTF-8");
@@ -89,8 +91,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");
} }
}); });
@@ -98,63 +99,77 @@ public class CommaFeedApplication extends AuthenticatedWebApplication {
@Override @Override
public IRequestHandler onException(RequestCycle cycle, Exception ex) { public IRequestHandler onException(RequestCycle cycle, Exception ex) {
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.AUTO_REDIRECT;
RedirectPolicy policy = target == null ? RedirectPolicy.NEVER_REDIRECT return new RenderPageRequestHandler(new PageProvider(new DisplayExceptionPage(ex)), policy);
: RedirectPolicy.AUTO_REDIRECT; }
return new RenderPageRequestHandler(new PageProvider( });
new DisplayExceptionPage(ex)), policy);
setRequestCycleProvider(new IRequestCycleProvider() {
@Override
public RequestCycle get(RequestCycleContext context) {
return new RequestCycle(context) {
@Override
protected UrlRenderer newUrlRenderer() {
return new UrlRenderer(getRequest()) {
@Override
public String renderUrl(Url url) {
// override wicket's relative-to-absolute url conversion with what we know is the correct protocol
String publicUrl = applicationSettingsService.get().getPublicUrl();
if (StringUtils.isNotBlank(publicUrl)) {
Url parsed = Url.parse(publicUrl);
url.setProtocol(parsed.getProtocol());
url.setPort(parsed.getPort());
}
return super.renderUrl(url);
}
};
}
};
} }
}); });
} }
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,15 +179,21 @@ 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"); CdiContainer container = new CdiConfiguration(beanManager).setPropagation(ConversationPropagation.NONE).configure(this);
new CdiConfiguration(beanManager).setPropagation( container.getNonContextualManager().inject(this);
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.");
} }
} }
@Override
public void restartResponseAtSignInPage() {
// preserve https if it was set
RequestCycle.get().getUrlRenderer().setBaseUrl(Url.parse(WicketUtils.getClientFullUrl()));
super.restartResponseAtSignInPage();
}
@Override @Override
public Session newSession(Request request, Response response) { public Session newSession(Request request, Response response) {
return new CommaFeedSession(request); return new CommaFeedSession(request);

View File

@@ -2,32 +2,27 @@ package com.commafeed.frontend;
import java.util.Set; import java.util.Set;
import javax.inject.Inject; import lombok.Getter;
import org.apache.wicket.Session; import org.apache.wicket.Session;
import org.apache.wicket.authroles.authentication.AuthenticatedWebSession; import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
import org.apache.wicket.authroles.authorization.strategies.role.Roles; import org.apache.wicket.authroles.authorization.strategies.role.Roles;
import org.apache.wicket.request.Request; import org.apache.wicket.request.Request;
import com.commafeed.backend.dao.UserRoleDAO;
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.services.UserService;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
public class CommaFeedSession extends AuthenticatedWebSession { public class CommaFeedSession extends AuthenticatedWebSession {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Inject
UserService userService;
@Inject
UserRoleDAO userRoleDAO;
private User user; private User user;
private Roles roles = new Roles(); private Roles roles = new Roles();
@Getter(lazy = true)
private final CommaFeedSessionServices services = newServices();
public CommaFeedSession(Request request) { public CommaFeedSession(Request request) {
super(request); super(request);
} }
@@ -47,7 +42,7 @@ public class CommaFeedSession extends AuthenticatedWebSession {
@Override @Override
public boolean authenticate(String userName, String password) { public boolean authenticate(String userName, String password) {
User user = userService.login(userName, password); User user = getServices().getUserService().login(userName, password);
setUser(user); setUser(user);
return user != null; return user != null;
} }
@@ -59,7 +54,7 @@ public class CommaFeedSession extends AuthenticatedWebSession {
} else { } else {
Set<String> roleSet = Sets.newHashSet(); Set<String> roleSet = Sets.newHashSet();
for (Role role : userRoleDAO.findRoles(user)) { for (Role role : getServices().getUserRoleDAO().findRoles(user)) {
roleSet.add(role.name()); roleSet.add(role.name());
} }
this.user = user; this.user = user;
@@ -67,4 +62,8 @@ public class CommaFeedSession extends AuthenticatedWebSession {
} }
} }
private CommaFeedSessionServices newServices() {
return new CommaFeedSessionServices();
}
} }

View File

@@ -0,0 +1,35 @@
package com.commafeed.frontend;
import javax.inject.Inject;
import org.apache.wicket.Component;
import com.commafeed.backend.dao.UserRoleDAO;
import com.commafeed.backend.services.UserService;
// extend Component in order to benefit from injection
public class CommaFeedSessionServices extends Component {
@Inject
UserService userService;
@Inject
UserRoleDAO userRoleDAO;
public CommaFeedSessionServices() {
super("services");
}
public UserService getUserService() {
return userService;
}
public UserRoleDAO getUserRoleDAO() {
return userRoleDAO;
}
@Override
protected void onRender() {
// do nothing
}
}

View File

@@ -0,0 +1,53 @@
package com.commafeed.frontend;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
@WebFilter(urlPatterns = "/*")
public class InterceptingFilter implements Filter {
private static final String HEADER_CORS = "Access-Control-Allow-Origin";
private static final String HEADER_CORS_VALUE = "*";
private static final String HEADER_CORS_METHODS = "Access-Control-Allow-Methods";
private static final String HEADER_CORS_METHODS_VALUE = "POST, GET, OPTIONS";
private static final String HEADER_CORS_MAXAGE = "Access-Control-Max-Age";
private static final String HEADER_CORS_MAXAGE_VALUE = "2592000";
private static final String HEADER_CORS_ALLOW_HEADERS = "Access-Control-Allow-Headers";
private static final String HEADER_CORS_ALLOW_HEADERS_VALUE = "Authorization";
private static final String HEADER_CORS_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
private static final String HEADER_CORS_ALLOW_CREDENTIALS_VALUE = "true";
private static final String HEADER_X_UA_COMPATIBLE = "X-UA-Compatible";
private static final String HEADER_X_UA_COMPATIBLE_VALUE = "IE=Edge,chrome=1";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) response;
resp.addHeader(HEADER_CORS, HEADER_CORS_VALUE);
resp.addHeader(HEADER_CORS_METHODS, HEADER_CORS_METHODS_VALUE);
resp.addHeader(HEADER_CORS_MAXAGE, HEADER_CORS_MAXAGE_VALUE);
resp.addHeader(HEADER_CORS_ALLOW_HEADERS, HEADER_CORS_ALLOW_HEADERS_VALUE);
resp.addHeader(HEADER_CORS_ALLOW_CREDENTIALS, HEADER_CORS_ALLOW_CREDENTIALS_VALUE);
resp.addHeader(HEADER_X_UA_COMPATIBLE, HEADER_X_UA_COMPATIBLE_VALUE);
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}

View File

@@ -3,18 +3,15 @@ package com.commafeed.frontend.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("Entry details") @ApiClass("Entry details")
@Data
public class Category implements Serializable { public class Category implements Serializable {
@ApiProperty("category id") @ApiProperty("category id")
@@ -37,61 +34,4 @@ public class Category implements Serializable {
@ApiProperty("position of the category in the list") @ApiProperty("position of the category in the list")
private Integer position; private Integer position;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Category> getChildren() {
return children;
}
public void setChildren(List<Category> children) {
this.children = children;
}
public List<Subscription> getFeeds() {
return feeds;
}
public void setFeeds(List<Subscription> feeds) {
this.feeds = feeds;
}
public boolean isExpanded() {
return expanded;
}
public void setExpanded(boolean expanded) {
this.expanded = expanded;
}
public Integer getPosition() {
return position;
}
public void setPosition(Integer position) {
this.position = position;
}
} }

View File

@@ -3,18 +3,15 @@ package com.commafeed.frontend.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("List of entries with some metadata") @ApiClass("List of entries with some metadata")
@Data
public class Entries implements Serializable { public class Entries implements Serializable {
@ApiProperty("name of the feed or the category requested") @ApiProperty("name of the feed or the category requested")
@@ -35,63 +32,13 @@ public class Entries implements Serializable {
@ApiProperty("if the query has more elements") @ApiProperty("if the query has more elements")
private boolean hasMore; private boolean hasMore;
@ApiProperty("the requested offset")
private int offset;
@ApiProperty("the requested limit")
private int limit;
@ApiProperty("list of entries") @ApiProperty("list of entries")
private List<Entry> entries = Lists.newArrayList(); private List<Entry> entries = Lists.newArrayList();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Entry> getEntries() {
return entries;
}
public void setEntries(List<Entry> entries) {
this.entries = entries;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getErrorCount() {
return errorCount;
}
public void setErrorCount(int errorCount) {
this.errorCount = errorCount;
}
public String getFeedLink() {
return feedLink;
}
public void setFeedLink(String feedLink) {
this.feedLink = feedLink;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public boolean isHasMore() {
return hasMore;
}
public void setHasMore(boolean hasMore) {
this.hasMore = hasMore;
}
} }

View File

@@ -4,12 +4,11 @@ import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.commafeed.backend.feeds.FeedUtils; import com.commafeed.backend.feeds.FeedUtils;
import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryContent;
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.sun.syndication.feed.synd.SyndContentImpl; import com.sun.syndication.feed.synd.SyndContentImpl;
@@ -19,40 +18,40 @@ import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("Entry details") @ApiClass("Entry details")
@Data
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();
FeedSubscription sub = status.getSubscription(); FeedSubscription sub = status.getSubscription();
FeedEntryContent content = feedEntry.getContent();
entry.setRead(status.isRead());
entry.setStarred(status.isStarred());
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.setRead(status.isRead());
entry.setContent(FeedUtils.proxyImages(feedEntry.getContent() entry.setStarred(status.isStarred());
.getContent(), publicUrl, proxyImages)); entry.setMarkable(status.isMarkable());
entry.setRtl(FeedUtils.isRTL(feedEntry));
entry.setAuthor(feedEntry.getAuthor());
entry.setEnclosureUrl(feedEntry.getContent().getEnclosureUrl());
entry.setEnclosureType(feedEntry.getContent().getEnclosureType());
entry.setDate(feedEntry.getUpdated()); entry.setDate(feedEntry.getUpdated());
entry.setInsertedDate(feedEntry.getInserted()); entry.setInsertedDate(feedEntry.getInserted());
entry.setUrl(feedEntry.getUrl()); entry.setUrl(feedEntry.getUrl());
entry.setFeedName(sub.getTitle()); entry.setFeedName(sub.getTitle());
entry.setFeedId(String.valueOf(sub.getId())); entry.setFeedId(String.valueOf(sub.getId()));
entry.setFeedUrl(sub.getFeed().getUrl()); entry.setFeedUrl(sub.getFeed().getUrl());
entry.setFeedLink(sub.getFeed().getLink()); entry.setFeedLink(sub.getFeed().getLink());
entry.setIconUrl(FeedUtils.getFaviconUrl(sub, publicUrl)); entry.setIconUrl(FeedUtils.getFaviconUrl(sub, publicUrl));
if (content != null) {
entry.setRtl(FeedUtils.isRTL(feedEntry));
entry.setTitle(content.getTitle());
entry.setContent(FeedUtils.proxyImages(content.getContent(), publicUrl, proxyImages));
entry.setAuthor(content.getAuthor());
entry.setEnclosureUrl(content.getEnclosureUrl());
entry.setEnclosureType(content.getEnclosureType());
}
return entry; return entry;
} }
@@ -124,148 +123,6 @@ public class Entry implements Serializable {
@ApiProperty("starred status") @ApiProperty("starred status")
private boolean starred; private boolean starred;
public String getId() { @ApiProperty("wether the entry is still markable (old entry statuses are discarded)")
return id; private boolean markable;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getFeedId() {
return feedId;
}
public void setFeedId(String feedId) {
this.feedId = feedId;
}
public String getFeedName() {
return feedName;
}
public void setFeedName(String feedName) {
this.feedName = feedName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public boolean isRead() {
return read;
}
public void setRead(boolean read) {
this.read = read;
}
public boolean isStarred() {
return starred;
}
public void setStarred(boolean starred) {
this.starred = starred;
}
public String getFeedUrl() {
return feedUrl;
}
public void setFeedUrl(String feedUrl) {
this.feedUrl = feedUrl;
}
public String getEnclosureUrl() {
return enclosureUrl;
}
public void setEnclosureUrl(String enclosureUrl) {
this.enclosureUrl = enclosureUrl;
}
public String getEnclosureType() {
return enclosureType;
}
public void setEnclosureType(String enclosureType) {
this.enclosureType = enclosureType;
}
public String getGuid() {
return guid;
}
public void setGuid(String guid) {
this.guid = guid;
}
public String getFeedLink() {
return feedLink;
}
public void setFeedLink(String feedLink) {
this.feedLink = feedLink;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getIconUrl() {
return iconUrl;
}
public void setIconUrl(String iconUrl) {
this.iconUrl = iconUrl;
}
public boolean isRtl() {
return rtl;
}
public void setRtl(boolean rtl) {
this.rtl = rtl;
}
public Date getInsertedDate() {
return insertedDate;
}
public void setInsertedDate(Date insertedDate) {
this.insertedDate = insertedDate;
}
} }

View File

@@ -0,0 +1,25 @@
package com.commafeed.frontend.model;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
import com.commafeed.backend.model.Feed;
import com.google.common.collect.Lists;
import com.wordnik.swagger.annotations.ApiClass;
@ApiClass("Feed count")
@Data
public class FeedCount implements Serializable {
private static final long serialVersionUID = 1L;
private String value;
private List<Feed> feeds = Lists.newArrayList();;
public FeedCount(String value) {
this.value = value;
}
}

View File

@@ -2,35 +2,16 @@ package com.commafeed.frontend.model;
import java.io.Serializable; import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("Feed details") @ApiClass("Feed details")
@Data
public class FeedInfo implements Serializable { public class FeedInfo implements Serializable {
private String url; private String url;
private String title; private String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
} }

View File

@@ -3,17 +3,14 @@ package com.commafeed.frontend.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.Map; import java.util.Map;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.google.api.client.util.Maps; import com.google.common.collect.Maps;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("Server infos") @ApiClass("Server infos")
@Data
public class ServerInfo implements Serializable { public class ServerInfo implements Serializable {
private String announcement; private String announcement;
@@ -21,36 +18,4 @@ public class ServerInfo implements Serializable {
private String gitCommit; private String gitCommit;
private Map<String, String> supportedLanguages = Maps.newHashMap(); private Map<String, String> supportedLanguages = Maps.newHashMap();
public String getAnnouncement() {
return announcement;
}
public void setAnnouncement(String announcement) {
this.announcement = announcement;
}
public Map<String, String> getSupportedLanguages() {
return supportedLanguages;
}
public void setSupportedLanguages(Map<String, String> supportedLanguages) {
this.supportedLanguages = supportedLanguages;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getGitCommit() {
return gitCommit;
}
public void setGitCommit(String gitCommit) {
this.gitCommit = gitCommit;
}
} }

View File

@@ -2,17 +2,14 @@ package com.commafeed.frontend.model;
import java.io.Serializable; import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("User settings") @ApiClass("User settings")
@Data
public class Settings implements Serializable { public class Settings implements Serializable {
@ApiProperty(value = "user's preferred language, english if none") @ApiProperty(value = "user's preferred language, english if none")
@@ -42,76 +39,4 @@ public class Settings implements Serializable {
@ApiProperty(value = "user's custom css for the website") @ApiProperty(value = "user's custom css for the website")
private String customCss; private String customCss;
public String getReadingMode() {
return readingMode;
}
public void setReadingMode(String readingMode) {
this.readingMode = readingMode;
}
public String getCustomCss() {
return customCss;
}
public void setCustomCss(String customCss) {
this.customCss = customCss;
}
public String getReadingOrder() {
return readingOrder;
}
public void setReadingOrder(String readingOrder) {
this.readingOrder = readingOrder;
}
public boolean isShowRead() {
return showRead;
}
public void setShowRead(boolean showRead) {
this.showRead = showRead;
}
public boolean isSocialButtons() {
return socialButtons;
}
public void setSocialButtons(boolean socialButtons) {
this.socialButtons = socialButtons;
}
public String getViewMode() {
return viewMode;
}
public void setViewMode(String viewMode) {
this.viewMode = viewMode;
}
public boolean isScrollMarks() {
return scrollMarks;
}
public void setScrollMarks(boolean scrollMarks) {
this.scrollMarks = scrollMarks;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getTheme() {
return theme;
}
public void setTheme(String theme) {
this.theme = theme;
}
} }

View File

@@ -3,9 +3,7 @@ package com.commafeed.frontend.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.commafeed.backend.feeds.FeedUtils; import com.commafeed.backend.feeds.FeedUtils;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
@@ -15,13 +13,11 @@ import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("User information") @ApiClass("User information")
@Data
public class Subscription implements Serializable { public class Subscription implements Serializable {
public static Subscription build(FeedSubscription subscription, public static Subscription build(FeedSubscription subscription, String publicUrl, UnreadCount 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 +31,10 @@ 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 sub.setUnread(unreadCount.getUnreadCount());
.getDisabledUntil()); sub.setNewestItemTime(unreadCount.getNewestItemTime());
sub.setUnread(unreadCount); sub.setCategoryId(category == null ? null : String.valueOf(category.getId()));
sub.setCategoryId(category == null ? null : String.valueOf(category
.getId()));
return sub; return sub;
} }
@@ -80,100 +74,7 @@ public class Subscription implements Serializable {
@ApiProperty("position of the subscription's in the list") @ApiProperty("position of the subscription's in the list")
private Integer position; private Integer position;
public Long getId() { @ApiProperty("date of the newest item")
return id; private Date newestItemTime;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getUnread() {
return unread;
}
public void setUnread(long unread) {
this.unread = unread;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getFeedUrl() {
return feedUrl;
}
public void setFeedUrl(String feedUrl) {
this.feedUrl = feedUrl;
}
public int getErrorCount() {
return errorCount;
}
public void setErrorCount(int errorCount) {
this.errorCount = errorCount;
}
public String getFeedLink() {
return feedLink;
}
public void setFeedLink(String feedLink) {
this.feedLink = feedLink;
}
public Date getLastRefresh() {
return lastRefresh;
}
public void setLastRefresh(Date lastRefresh) {
this.lastRefresh = lastRefresh;
}
public String getCategoryId() {
return categoryId;
}
public void setCategoryId(String categoryId) {
this.categoryId = categoryId;
}
public Date getNextRefresh() {
return nextRefresh;
}
public void setNextRefresh(Date nextRefresh) {
this.nextRefresh = nextRefresh;
}
public String getIconUrl() {
return iconUrl;
}
public void setIconUrl(String iconUrl) {
this.iconUrl = iconUrl;
}
public Integer getPosition() {
return position;
}
public void setPosition(Integer position) {
this.position = position;
}
} }

View File

@@ -1,45 +1,29 @@
package com.commafeed.frontend.model; package com.commafeed.frontend.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("Unread count") @ApiClass("Unread count")
@Data
public class UnreadCount implements Serializable { public class UnreadCount implements Serializable {
private long feedId; private long feedId;
private long unreadCount; private long unreadCount;
private Date newestItemTime;
public UnreadCount() { public UnreadCount() {
} }
public UnreadCount(long feedId, long unreadCount) { public UnreadCount(long feedId, long unreadCount, Date newestItemTime) {
this.feedId = feedId; this.feedId = feedId;
this.unreadCount = unreadCount; this.unreadCount = unreadCount;
} this.newestItemTime = newestItemTime;
public long getFeedId() {
return feedId;
}
public void setFeedId(long feedId) {
this.feedId = feedId;
}
public long getUnreadCount() {
return unreadCount;
}
public void setUnreadCount(long unreadCount) {
this.unreadCount = unreadCount;
} }
} }

View File

@@ -3,17 +3,14 @@ package com.commafeed.frontend.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("User information") @ApiClass("User information")
@Data
public class UserModel implements Serializable { public class UserModel implements Serializable {
@ApiProperty(value = "user id", required = true) @ApiProperty(value = "user id", required = true)
@@ -43,76 +40,4 @@ public class UserModel implements Serializable {
@ApiProperty(value = "user is admin") @ApiProperty(value = "user is admin")
private boolean admin; private boolean admin;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isAdmin() {
return admin;
}
public void setAdmin(boolean admin) {
this.admin = admin;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getLastLogin() {
return lastLogin;
}
public void setLastLogin(Date lastLogin) {
this.lastLogin = lastLogin;
}
} }

View File

@@ -2,17 +2,14 @@ package com.commafeed.frontend.model.request;
import java.io.Serializable; import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("Add Category Request") @ApiClass("Add Category Request")
@Data
public class AddCategoryRequest implements Serializable { public class AddCategoryRequest implements Serializable {
@ApiProperty(value = "name", required = true) @ApiProperty(value = "name", required = true)
@@ -21,20 +18,4 @@ public class AddCategoryRequest implements Serializable {
@ApiProperty(value = "parent category id, if any") @ApiProperty(value = "parent category id, if any")
private String parentId; private String parentId;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
} }

View File

@@ -2,17 +2,14 @@ package com.commafeed.frontend.model.request;
import java.io.Serializable; import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("Category modification request") @ApiClass("Category modification request")
@Data
public class CategoryModificationRequest implements Serializable { public class CategoryModificationRequest implements Serializable {
@ApiProperty(value = "id", required = true) @ApiProperty(value = "id", required = true)
@@ -27,36 +24,4 @@ public class CategoryModificationRequest implements Serializable {
@ApiProperty(value = "new display position, null if not changed") @ApiProperty(value = "new display position, null if not changed")
private Integer position; private Integer position;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public Integer getPosition() {
return position;
}
public void setPosition(Integer position) {
this.position = position;
}
} }

View File

@@ -2,17 +2,14 @@ package com.commafeed.frontend.model.request;
import java.io.Serializable; import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("Mark Request") @ApiClass("Mark Request")
@Data
public class CollapseRequest implements Serializable { public class CollapseRequest implements Serializable {
@ApiProperty(value = "category id", required = true) @ApiProperty(value = "category id", required = true)
@@ -21,20 +18,4 @@ public class CollapseRequest implements Serializable {
@ApiProperty(value = "collapse", required = true) @ApiProperty(value = "collapse", required = true)
private boolean collapse; private boolean collapse;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public boolean isCollapse() {
return collapse;
}
public void setCollapse(boolean collapse) {
this.collapse = collapse;
}
} }

View File

@@ -2,30 +2,17 @@ package com.commafeed.frontend.model.request;
import java.io.Serializable; import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("Feed information request") @ApiClass("Feed information request")
@Data
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;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
} }

View File

@@ -3,17 +3,14 @@ package com.commafeed.frontend.model.request;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("Feed merge Request") @ApiClass("Feed merge Request")
@Data
public class FeedMergeRequest implements Serializable { public class FeedMergeRequest implements Serializable {
@ApiProperty(value = "merge into this feed", required = true) @ApiProperty(value = "merge into this feed", required = true)
@@ -22,20 +19,4 @@ public class FeedMergeRequest implements Serializable {
@ApiProperty(value = "id of the feeds to merge", required = true) @ApiProperty(value = "id of the feeds to merge", required = true)
private List<Long> feedIds; private List<Long> feedIds;
public Long getIntoFeedId() {
return intoFeedId;
}
public void setIntoFeedId(Long intoFeedId) {
this.intoFeedId = intoFeedId;
}
public List<Long> getFeedIds() {
return feedIds;
}
public void setFeedIds(List<Long> feedIds) {
this.feedIds = feedIds;
}
} }

View File

@@ -2,17 +2,14 @@ package com.commafeed.frontend.model.request;
import java.io.Serializable; import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("Feed modification request") @ApiClass("Feed modification request")
@Data
public class FeedModificationRequest implements Serializable { public class FeedModificationRequest implements Serializable {
@ApiProperty(value = "id", required = true) @ApiProperty(value = "id", required = true)
@@ -27,36 +24,4 @@ public class FeedModificationRequest implements Serializable {
@ApiProperty(value = "new display position, null if not changed") @ApiProperty(value = "new display position, null if not changed")
private Integer position; private Integer position;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCategoryId() {
return categoryId;
}
public void setCategoryId(String categoryId) {
this.categoryId = categoryId;
}
public Integer getPosition() {
return position;
}
public void setPosition(Integer position) {
this.position = position;
}
} }

View File

@@ -2,28 +2,17 @@ package com.commafeed.frontend.model.request;
import java.io.Serializable; import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass @ApiClass
@Data
public class IDRequest implements Serializable { public class IDRequest implements Serializable {
@ApiProperty @ApiProperty
private Long id; private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
} }

View File

@@ -1,62 +1,30 @@
package com.commafeed.frontend.model.request; package com.commafeed.frontend.model.request;
import java.io.Serializable; import java.io.Serializable;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("Mark Request") @ApiClass("Mark Request")
@Data
public class MarkRequest implements Serializable { public class MarkRequest implements Serializable {
@ApiProperty(value = "entry id, category id, 'all' or 'starred'", required = true) @ApiProperty(value = "entry id, category id, 'all' or 'starred'", required = true)
private String id; private String id;
@ApiProperty(value = "feed id, only required when marking an entry")
private Long feedId;
@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() { @ApiProperty(value = "if marking a category or 'all', exclude those subscriptions from the marking", required = false)
return id; private List<Long> excludedSubscriptions;
}
public void setId(String id) {
this.id = id;
}
public boolean isRead() {
return read;
}
public void setRead(boolean read) {
this.read = read;
}
public Long getOlderThan() {
return olderThan;
}
public void setOlderThan(Long olderThan) {
this.olderThan = olderThan;
}
public Long getFeedId() {
return feedId;
}
public void setFeedId(Long feedId) {
this.feedId = feedId;
}
} }

View File

@@ -3,28 +3,17 @@ package com.commafeed.frontend.model.request;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("Multiple Mark Request") @ApiClass("Multiple Mark Request")
@Data
public class MultipleMarkRequest implements Serializable { public class MultipleMarkRequest implements Serializable {
@ApiProperty(value = "list of mark requests", required = true) @ApiProperty(value = "list of mark requests", required = true)
private List<MarkRequest> requests; private List<MarkRequest> requests;
public List<MarkRequest> getRequests() {
return requests;
}
public void setRequests(List<MarkRequest> requests) {
this.requests = requests;
}
} }

View File

@@ -1,16 +1,16 @@
package com.commafeed.frontend.model.request; package com.commafeed.frontend.model.request;
import javax.xml.bind.annotation.XmlAccessType; import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement; import lombok.Data;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@XmlRootElement @SuppressWarnings("serial")
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("Profile modification request") @ApiClass("Profile modification request")
public class ProfileModificationRequest { @Data
public class ProfileModificationRequest implements Serializable {
@ApiProperty(value = "changes email of the user, if specified") @ApiProperty(value = "changes email of the user, if specified")
private String email; private String email;
@@ -21,28 +21,4 @@ public class ProfileModificationRequest {
@ApiProperty(value = "generate a new api key") @ApiProperty(value = "generate a new api key")
private boolean newApiKey; private boolean newApiKey;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isNewApiKey() {
return newApiKey;
}
public void setNewApiKey(boolean newApiKey) {
this.newApiKey = newApiKey;
}
} }

View File

@@ -2,15 +2,12 @@ package com.commafeed.frontend.model.request;
import java.io.Serializable; import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement @Data
@XmlAccessorType(XmlAccessType.FIELD)
public class RegistrationRequest implements Serializable { public class RegistrationRequest implements Serializable {
@ApiProperty(value = "username, between 3 and 32 characters", required = true) @ApiProperty(value = "username, between 3 and 32 characters", required = true)
@@ -22,27 +19,4 @@ public class RegistrationRequest implements Serializable {
@ApiProperty(value = "email address for password recovery", required = true) @ApiProperty(value = "email address for password recovery", required = true)
private String email; private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
} }

View File

@@ -2,17 +2,14 @@ package com.commafeed.frontend.model.request;
import java.io.Serializable; import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("Star Request") @ApiClass("Star Request")
@Data
public class StarRequest implements Serializable { public class StarRequest implements Serializable {
@ApiProperty(value = "id", required = true) @ApiProperty(value = "id", required = true)
@@ -24,28 +21,4 @@ public class StarRequest implements Serializable {
@ApiProperty(value = "starred or not") @ApiProperty(value = "starred or not")
private boolean starred; private boolean starred;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public boolean isStarred() {
return starred;
}
public void setStarred(boolean starred) {
this.starred = starred;
}
public Long getFeedId() {
return feedId;
}
public void setFeedId(Long feedId) {
this.feedId = feedId;
}
} }

View File

@@ -2,17 +2,14 @@ package com.commafeed.frontend.model.request;
import java.io.Serializable; import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType; import lombok.Data;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import com.wordnik.swagger.annotations.ApiClass; import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty; import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("Subscription request") @ApiClass("Subscription request")
@Data
public class SubscribeRequest implements Serializable { public class SubscribeRequest implements Serializable {
@ApiProperty(value = "url of the feed", required = true) @ApiProperty(value = "url of the feed", required = true)
@@ -24,28 +21,4 @@ public class SubscribeRequest implements Serializable {
@ApiProperty(value = "id of the user category to place the feed in") @ApiProperty(value = "id of the user category to place the feed in")
private String categoryId; private String categoryId;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getCategoryId() {
return categoryId;
}
public void setCategoryId(String categoryId) {
this.categoryId = categoryId;
}
} }

View File

@@ -31,7 +31,7 @@ import com.commafeed.backend.services.ApplicationSettingsService;
import com.commafeed.backend.services.MailService; import com.commafeed.backend.services.MailService;
import com.commafeed.frontend.CommaFeedSession; import com.commafeed.frontend.CommaFeedSession;
import com.commafeed.frontend.utils.WicketUtils; import com.commafeed.frontend.utils.WicketUtils;
import com.google.api.client.util.Maps; import com.google.common.collect.Maps;
@SuppressWarnings("serial") @SuppressWarnings("serial")
public abstract class BasePage extends WebPage { public abstract class BasePage extends WebPage {
@@ -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"));

View File

@@ -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());
} }
} }

View File

@@ -1,109 +0,0 @@
package com.commafeed.frontend.pages;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import org.apache.wicket.RestartResponseException;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import com.commafeed.backend.StartupBean;
import com.commafeed.backend.dao.UserDAO;
import com.commafeed.backend.feeds.FeedUtils;
import com.commafeed.backend.feeds.OPMLImporter;
import com.commafeed.backend.model.ApplicationSettings;
import com.commafeed.backend.model.User;
import com.commafeed.backend.services.ApplicationSettingsService;
import com.commafeed.frontend.CommaFeedSession;
import com.commafeed.frontend.utils.WicketUtils;
import com.commafeed.frontend.utils.exception.DisplayException;
import com.google.api.client.auth.oauth2.AuthorizationCodeResponseUrl;
import com.google.api.client.auth.oauth2.AuthorizationCodeTokenRequest;
import com.google.api.client.auth.oauth2.BearerToken;
import com.google.api.client.auth.oauth2.TokenResponse;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
@SuppressWarnings("serial")
public class GoogleImportCallbackPage extends WebPage {
private static final String TOKEN_URL = "https://accounts.google.com/o/oauth2/token";
private static final String EXPORT_URL = "https://www.google.com/reader/subscriptions/export";
public static final String PAGE_PATH = "google/import/callback";
@Inject
ApplicationSettingsService applicationSettingsService;
@Inject
OPMLImporter importer;
@Inject
UserDAO userDAO;
public static String getCallbackUrl(String publicUrl) {
return FeedUtils.removeTrailingSlash(publicUrl) + "/" + PAGE_PATH;
}
public GoogleImportCallbackPage(PageParameters params) {
HttpServletRequest request = WicketUtils.getHttpServletRequest();
StringBuffer urlBuffer = request.getRequestURL();
if (request.getQueryString() != null) {
urlBuffer.append('?').append(request.getQueryString());
}
AuthorizationCodeResponseUrl responseUrl = new AuthorizationCodeResponseUrl(
urlBuffer.toString());
String code = responseUrl.getCode();
if (responseUrl.getError() != null) {
// user declined
throw new RestartResponseException(getApplication().getHomePage());
} else if (code == null) {
throw new DisplayException("Missing authorization code");
} else {
ApplicationSettings settings = applicationSettingsService.get();
String redirectUri = getCallbackUrl(settings.getPublicUrl());
String clientId = settings.getGoogleClientId();
String clientSecret = settings.getGoogleClientSecret();
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
AuthorizationCodeTokenRequest tokenRequest = new AuthorizationCodeTokenRequest(
httpTransport, jsonFactory, new GenericUrl(TOKEN_URL), code);
tokenRequest.setRedirectUri(redirectUri);
tokenRequest.put("client_id", clientId);
tokenRequest.put("client_secret", clientSecret);
tokenRequest.setGrantType("authorization_code");
try {
// potential fix for invalid_grant error, happens if local
// system time is ahead of google servers time
Thread.sleep(1000);
TokenResponse tokenResponse = tokenRequest.execute();
String accessToken = tokenResponse.getAccessToken();
HttpRequest httpRequest = httpTransport.createRequestFactory()
.buildGetRequest(new GenericUrl(EXPORT_URL));
BearerToken.authorizationHeaderAccessMethod().intercept(
httpRequest, accessToken);
String opml = httpRequest.execute().parseAsString();
User user = CommaFeedSession.get().getUser();
if (user != null) {
if (StartupBean.USERNAME_DEMO.equals(user.getName())) {
throw new DisplayException(
"Import is disabled for the demo account");
}
importer.importOpml(CommaFeedSession.get().getUser(), opml);
}
} catch (Exception e) {
throw new DisplayException(e);
}
}
setResponsePage(getApplication().getHomePage());
}
}

View File

@@ -1,50 +0,0 @@
package com.commafeed.frontend.pages;
import java.net.URISyntaxException;
import javax.inject.Inject;
import org.apache.http.client.utils.URIBuilder;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.request.flow.RedirectToUrlException;
import org.jboss.logging.Logger;
import com.commafeed.backend.model.ApplicationSettings;
import com.commafeed.backend.services.ApplicationSettingsService;
@SuppressWarnings("serial")
public class GoogleImportRedirectPage extends WebPage {
private static Logger log = Logger
.getLogger(GoogleImportRedirectPage.class);
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";
@Inject
ApplicationSettingsService applicationSettingsService;
public GoogleImportRedirectPage() {
ApplicationSettings settings = applicationSettingsService.get();
String clientId = settings.getGoogleClientId();
String redirectUri = GoogleImportCallbackPage.getCallbackUrl(settings.getPublicUrl());
try {
URIBuilder builder = new URIBuilder(AUTH_URL);
builder.addParameter("redirect_uri", redirectUri);
builder.addParameter("response_type", "code");
builder.addParameter("scope", SCOPE);
builder.addParameter("approval_prompt", "force");
builder.addParameter("client_id", clientId);
builder.addParameter("access_type", "offline");
throw new RedirectToUrlException(builder.build().toString());
} catch (URISyntaxException e) {
log.error(e.getMessage(), e);
}
}
}

View File

@@ -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));
} }
} }

View File

@@ -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, false);
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, false);
.findByCategories(user, children);
statuses = feedEntryStatusDAO.findUnreadBySubscriptions(
subscriptions, null, 0, 1, order, true);
} }
} }

View File

@@ -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));

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