Compare commits

...

169 Commits
2.0.0 ... 2.0.3

Author SHA1 Message Date
Athou
969da0f2a6 let's release 2014-10-29 08:29:32 +01:00
Athou
2061b68a2f method not used anymore 2014-10-29 08:27:04 +01:00
Athou
443dea5055 huge perf boost 2014-10-29 08:26:53 +01:00
Athou
a4c6365ede pom cleanup 2014-10-28 14:27:15 +01:00
Athou
c9c044386e Merge pull request #659 from Hubcapp/master
update README to encompass all directions to set up development environment
2014-10-27 10:12:33 +01:00
Hubcapp
2744f8285c Merge pull request #2 from Athou/master
update README to encompass all directions to set up development environment
2014-10-27 05:01:06 -04:00
Tyler Gebhard
7bf5f20b06 Moving "Local development" to the bottom; it's become really long, and I think there are more contributors who make themes and translate than contributors who need to set up a development environment.. 2014-10-27 04:56:07 -04:00
Athou
b43aa84c2a enable wadl 2014-10-27 09:54:06 +01:00
Tyler Gebhard
dd27d88309 Nope, more revisions. But this time for sure it looks good. 2014-10-27 04:53:13 -04:00
Tyler Gebhard
8dc36a72b2 I think it's done this time. 2014-10-27 04:51:20 -04:00
Tyler Gebhard
d3ca301675 Replaced arrows, revised step 1 2014-10-27 04:47:30 -04:00
Tyler Gebhard
43e3469e63 Still not happy with the formatting; removed all "\r"s 2014-10-27 04:44:21 -04:00
Tyler Gebhard
cdc3dc6740 Trying to fix formatting. 2014-10-27 04:40:53 -04:00
Tyler Gebhard
6fba8b61e7 Updating local development section to be idiot-proof. Hopefully, it's not too specific. 2014-10-27 04:34:28 -04:00
Hubcapp
b34594a1dc Merge pull request #1 from Athou/master
Merge latest changes from Athou to Hubcapp
2014-10-27 04:19:12 -04:00
Athou
19964d253e fix youtube icons (#658) 2014-10-27 05:23:36 +01:00
Athou
165f3ed25a revert to using gif for default icon 2014-10-26 19:07:52 +01:00
Athou
5058290103 remove unused images 2014-10-26 18:27:22 +01:00
Athou
358a6029a1 cache default (missing) favicon too 2014-10-26 18:27:21 +01:00
Athou
fa4bfa729d fix favicon caching 2014-10-26 18:27:20 +01:00
Athou
9c9e43cf46 readme update (fix #655) 2014-10-26 12:34:33 +01:00
Athou
b7e5bd0144 changelog update 2014-10-26 12:34:21 +01:00
Athou
58dc6f5832 Merge branch 'Hubcapp-master' 2014-10-26 12:26:30 +01:00
Athou
f409af1c37 rewrite favicon fetcher 2014-10-26 12:25:44 +01:00
Tyler Gebhard
9e0c94f1a4 changes to the way favicons are retrieved for YouTube feeds. Now instead of fetching the YouTube logo, it fetches the YouTube user's custom thumbnail. 2014-10-26 03:03:02 -04:00
Athou
3794d61a77 readme update (fix #654) 2014-10-24 11:15:17 +02:00
Athou
d22da54d53 Merge pull request #652 from rationalrevolt/master
Refactor unit tests using DRY, add tests for api login
2014-10-23 05:36:11 +02:00
Sankaranarayanan Viswanathan
8e34c44e0d Refactor unit tests using DRY, add tests for api login 2014-10-22 22:31:36 -04:00
Athou
b71434acf6 use dropwizard built-in executor service facilities 2014-10-22 15:36:21 +02:00
Athou
7e158ed9b9 for some reason, injecting the session helper is not working here 2014-10-22 11:58:03 +02:00
Athou
2ec0d067f3 add logback config for tests 2014-10-22 10:55:42 +02:00
Athou
effc65b777 SecurityCheckProvider now depends on SessionHelper instead of the request 2014-10-22 10:52:01 +02:00
Athou
c48e248283 move session related classes to subpackage 2014-10-22 10:35:50 +02:00
Athou
f9e9a4547c remove unused variable 2014-10-22 10:34:55 +02:00
Athou
63e35aba6d remove unused generic type 2014-10-22 10:34:18 +02:00
Athou
8f852fb9ac performing post login activities for the custom css is not needed since the css is only retrieved on the website and api methods are going to get called right after this 2014-10-22 10:25:14 +02:00
Athou
bf6a13b43f Merge pull request #647 from rationalrevolt/userservice-tests
Remove dependency on HttpSession in UserService
2014-10-22 10:22:54 +02:00
Sankaranarayanan Viswanathan
12030f6ce9 Provide a SessionHelper to manage the session 2014-10-22 01:17:33 -04:00
Athou
07da878bba dependencies upgrade 2014-10-17 08:30:53 +02:00
Sankaranarayanan Viswanathan
8d5c3bdec8 Rename method 2014-10-11 13:37:11 -04:00
Sankaranarayanan Viswanathan
ce95772afa Delete method UserService.login(HttpSession) and copy body to callers 2014-10-11 13:29:29 -04:00
Sankaranarayanan Viswanathan
b9f27b2b00 Make cookieLogin handle HttpSession by itself 2014-10-11 13:24:12 -04:00
Sankaranarayanan Viswanathan
0059cabebe Cover SecurityCheckProvider.SecurityCheckInjectable.cookieLogin with tests 2014-10-11 13:18:09 -04:00
Sankaranarayanan Viswanathan
326ee79c8c Remove HttpSession dependency in UserService phase 1 complete 2014-10-09 20:53:38 -04:00
Sankaranarayanan Viswanathan
54cc265ee6 Refactored UserREST login to populate session itself 2014-10-09 08:38:50 -04:00
Sankaranarayanan Viswanathan
e38778b4d0 Added tests to UserREST.login 2014-10-09 08:31:34 -04:00
Athou
6152d3c14a Merge pull request #646 from rationalrevolt/userservice-tests
Additional tests on UserService.login and refactor
2014-10-09 05:05:23 +02:00
Sankaranarayanan Viswanathan
8a172170ea Test that PostLoginActivities are executed for user after auth success 2014-10-08 22:39:32 -04:00
Sankaranarayanan Viswanathan
64b5d64709 Inject PostLoginActivities and refactor 2014-10-08 22:18:16 -04:00
Sankaranarayanan Viswanathan
67d7315003 Extract afterLogin into a separate class 2014-10-08 21:39:39 -04:00
Sankaranarayanan Viswanathan
47da4a2a1a Change visibility to package private 2014-10-08 21:03:53 -04:00
Sankaranarayanan Viswanathan
174be9c2d1 Added additional tests for UserService login 2014-10-08 20:59:05 -04:00
Athou
9b68539322 fix wrong spacing 2014-10-08 06:56:01 +02:00
Athou
2a4660ffa6 Merge pull request #645 from rationalrevolt/userservice-tests
Added a couple of unit tests on login method of UserService
2014-10-08 06:50:03 +02:00
Sankaranarayanan Viswanathan
dce0cf7ee4 Added a couple of unit tests for UserService login 2014-10-08 00:31:49 -04:00
Sankaranarayanan Viswanathan
d6c39d4aba Add jcenter repository and mockito dependency to pom.xml 2014-10-07 23:14:03 -04:00
Athou
fd7e183f40 Merge pull request #642 from fabianofranz/master
Couple fixes
2014-10-07 20:38:29 +02:00
fabianofranz
bf78a80f29 Fixes OpenShift build 2014-10-07 15:07:54 -03:00
fabianofranz
0ff630b8bd Parenthesis in unread-counter is now on CSS 2014-10-07 13:52:31 -03:00
fabianofranz
49b9e3f278 Fixes OpenShift stop script which caused issues with git push 2014-10-07 13:52:31 -03:00
fabianofranz
a4cc65c6a4 Removed parenthesis from counter labels 2014-10-07 13:52:02 -03:00
Template builder
0b46187ac5 Creating template 2014-10-07 13:52:02 -03:00
Athou
14ef5af936 Merge pull request #641 from ebraminio/patch-1
Use split limit
2014-10-06 11:18:04 +02:00
ebraminio
539d9c6d0e Use split limit 2014-10-06 11:37:37 +03:30
Athou
56bcc5ef5e Merge pull request #623 from ebraminio/patch-1
Only checking 20 first words is usually enough
2014-10-06 09:58:11 +02:00
Athou
d6b0324e24 ubuntu ships maven3 now 2014-10-06 09:57:15 +02:00
Athou
ff044e2592 produce a debian/ubuntu package during build 2014-10-06 09:53:58 +02:00
Athou
3c7747ab97 Merge pull request #638 from rationalrevolt/refactorhttpgetter
Refactor content encoding interceptor out into a separate class
2014-10-03 06:42:15 +02:00
Sankaranarayanan Viswanathan
34d97221ed Rename internal method containsUnsupportedEncodings 2014-10-03 00:39:43 -04:00
Sankaranarayanan Viswanathan
84e78d34cd Refactor content encoding interceptor out into a separate class 2014-10-03 00:31:47 -04:00
Athou
ac73806aee dependency updates 2014-09-26 10:18:42 +02:00
Athou
2105e9a5c9 jedis upgrade 2014-09-23 15:21:34 +02:00
Athou
2a36cc4327 configurable redis pool (fix #629) 2014-09-22 09:51:55 +02:00
Athou
c3feaf9a15 lombok upgrade, project should compile faster 2014-09-19 16:41:49 +02:00
ebraminio
d8537a98aa Only checking 20 first words is usually enough 2014-09-13 22:56:17 +04:30
Athou
42a6001ba5 openjdk8 is not supported by travis 2014-09-13 10:30:11 +02:00
Athou
4d9eb35230 test on openjdk8 too 2014-09-13 10:26:35 +02:00
Athou
e4ac296a1f update badge to use travis 2014-09-13 10:25:50 +02:00
Athou
01b49e7864 should fix travis builds 2014-09-13 10:19:12 +02:00
Athou
bd0b85a8d2 typo fix 2014-09-13 10:11:10 +02:00
Athou
3d59a4c516 changelog update 2014-09-13 10:10:55 +02:00
Athou
08ceff0f03 travis support 2014-09-13 10:09:12 +02:00
Athou
d6ae88ac43 Merge pull request #624 from fabianofranz/master
Fixed Commafeed on OpenShift, added deployment instructions to README
2014-09-13 07:11:58 +02:00
fabianofranz
5c8f016dd6 Fixes OpenShift 2014-09-13 00:13:48 -03:00
Athou
17288017d8 Merge pull request #621 from ebraminio/master
Format test and remove volatile, probably not needed for here
2014-09-11 16:24:43 +02:00
Ebrahim Byagowi
1e2757b52f Use final instead volatile, probably needed for GWT but not here 2014-09-11 18:48:07 +04:30
Ebrahim Byagowi
0dce2f057e Format EstimateDirectionTest 2014-09-11 18:46:09 +04:30
Athou
e017c5c304 various dependency upgrades 2014-09-11 15:45:00 +02:00
Athou
a3e828f90a remove unused variable 2014-09-11 15:43:38 +02:00
Athou
74e5c24fdc fix import 2014-09-11 15:37:09 +02:00
Athou
76c0abaa22 Merge pull request #618 from ebraminio/master
Avoid GWT depedency by bringing simplified dir estimate logic
2014-09-11 15:33:53 +02:00
Ebrahim Byagowi
a52b5fd711 Avoid GWT depedency by bringing simplified dir estimate logic 2014-09-11 17:47:33 +04:30
Athou
ffa51406b6 fix error message display 2014-09-07 19:10:04 +02:00
Athou
0b3b267e63 categories are now deletable again 2014-08-30 16:36:11 +02:00
Athou
fcdb9d8257 dropwizard already has a filter for this 2014-08-22 20:23:31 +02:00
Athou
04943ca525 fix translations not loaded correctly 2014-08-22 20:17:13 +02:00
Athou
574d4a1223 changelog update 2014-08-22 18:19:58 +02:00
Athou
7349814cb2 ie ajax cache workaround 2014-08-22 18:01:06 +02:00
Athou
114c5eb356 [maven-release-plugin] prepare for next development iteration 2014-08-21 09:04:10 +02:00
Athou
191f861f6e [maven-release-plugin] prepare release commafeed-2.0.2 2014-08-21 09:04:01 +02:00
Athou
fac1fcc3a6 git over https 2014-08-21 09:01:54 +02:00
Athou
d0490c5eb5 scm properties 2014-08-21 08:54:18 +02:00
Athou
2673efa9fc fix scrolling of subscriptions list on mobile 2014-08-19 16:20:32 +02:00
Athou
d4bce7b0a1 plugin updates 2014-08-19 13:14:48 +02:00
Athou
ba4a7ce6ab add jar version to manifest, will be printed in stacktraces 2014-08-19 13:14:37 +02:00
Athou
58f10153ab override dropwizard's getname 2014-08-19 12:51:15 +02:00
Athou
e7b65e3f26 actually this works fine, the wrong constructor was injected 2014-08-19 12:49:31 +02:00
Athou
fe91473748 correctly handle error callback (fix #614) 2014-08-19 11:30:19 +02:00
Athou
0140402ad4 don't create a session if it does not exists 2014-08-19 07:34:07 +02:00
Athou
f56cba59ae correctly handle errors 2014-08-19 01:12:19 +02:00
Athou
fed74f05fc revert to using a static user agent, fixes issues with some sites refusing our http requests 2014-08-19 01:05:24 +02:00
Athou
0888f11257 less boilerplate 2014-08-19 01:04:07 +02:00
Athou
7205d5bb9c only queue if not already queued 2014-08-19 00:43:23 +02:00
Athou
17a5ef882f smaller session boundary 2014-08-18 15:30:25 +02:00
Athou
ea68dbc56f configurable session manager 2014-08-18 13:09:54 +02:00
Athou
0cec8af074 don't show jsessionid in url 2014-08-17 17:27:08 +02:00
Athou
f7d0fc5768 db pooling tweak 2014-08-17 16:56:38 +02:00
Athou
bcaab694c8 first try to login using api key 2014-08-17 16:56:23 +02:00
Athou
247a3d5ab3 enable @formatter:on and @formatter:off 2014-08-17 14:35:31 +02:00
Athou
8e262a1e10 guicing up 2014-08-17 14:16:30 +02:00
Athou
f63695bdc7 timeout is in millis but expected in seconds 2014-08-16 17:48:53 +02:00
Athou
b051613b62 merge properties service into configuration 2014-08-16 17:27:27 +02:00
Athou
b886379d34 configuration validation 2014-08-16 12:40:39 +02:00
Athou
2a780dd2bb changelog update 2014-08-16 12:32:06 +02:00
Athou
9cf7b80110 apiKey is in the query params not in the path (Athou/commafeed-newsplus#7) 2014-08-16 12:29:17 +02:00
Athou
8fee73f1d1 fetch i18n files using relative path (#613) 2014-08-16 12:21:25 +02:00
Athou
36edb9373b added config element for context path (fix #611) 2014-08-16 07:05:49 +02:00
Athou
374c4b265a query not used anymore 2014-08-15 19:21:39 +02:00
Athou
db0b685ae1 query rewritten with querydsl 2014-08-15 17:29:58 +02:00
Athou
23d33b8402 delete feed by feed as entries are now deleted in the same transaction as the feed 2014-08-15 15:30:24 +02:00
Athou
8a57be3e63 wrap calls in db session 2014-08-15 15:20:21 +02:00
Athou
823cb03f9b let hibernate clean entries 2014-08-15 15:09:48 +02:00
Athou
e96cbcb057 only reject response if status is 401 2014-08-15 15:04:16 +02:00
Athou
fa0e7bcb54 rome upgrade 2014-08-15 13:57:10 +02:00
Athou
20292a7742 user is logged in after registration 2014-08-15 12:49:10 +02:00
Athou
943bde7eed hide session management inside UserService 2014-08-15 12:46:52 +02:00
Athou
9701af0736 user update should proc with api key and cookie login too 2014-08-15 12:18:10 +02:00
Athou
1456cc40e1 link to metrics 2014-08-14 16:31:14 +02:00
Athou
dc1f88c44c remove old settings and save button as settings are read only now 2014-08-14 16:29:43 +02:00
Athou
55c916956f update changelog 2014-08-14 16:22:13 +02:00
Athou
51eda57618 dynamic user agent string 2014-08-14 16:19:06 +02:00
Athou
d6a55e1ec0 context not needed anymore 2014-08-14 16:10:07 +02:00
Athou
b78210421c ProxyPreserveHost no longer required 2014-08-14 12:45:00 +02:00
Athou
1324269f1d code cleanup 2014-08-14 11:40:30 +02:00
Athou
cda6cb5cc0 query not used anymore 2014-08-14 11:27:43 +02:00
Athou
c1b8619b26 toString() not needed 2014-08-14 10:21:36 +02:00
Athou
4203e25321 remove unused list 2014-08-14 10:21:21 +02:00
Athou
aa02c7b93a wrapping not needed 2014-08-14 09:40:40 +02:00
Athou
0ff477579b findbugs is now happy 2014-08-14 08:38:13 +02:00
Athou
62a8e8c119 prevent timing attacks by using a time-constant comparison algorithm 2014-08-13 17:08:42 +02:00
Athou
fa212e0911 readme update 2014-08-13 14:46:07 +02:00
Athou
c8ad902a60 initial changelog 2014-08-13 14:34:05 +02:00
Athou
f05515d7d6 finer transactions 2014-08-13 13:08:54 +02:00
Athou
95bbcce941 simplify queries 2014-08-13 12:59:06 +02:00
Athou
d6b98f1518 trigger inputs on login 2014-08-13 12:35:37 +02:00
Athou
bd9b1b11c5 don't use jersey dependency directly, keep jersey version in sync with dropwizard 2014-08-13 11:56:36 +02:00
Athou
e4c4960972 remove rest methods as those are scheduled now 2014-08-13 11:49:21 +02:00
Athou
2a26031261 not a good idea 2014-08-13 11:45:35 +02:00
Athou
1d6e212955 fix documentation link 2014-08-13 11:24:42 +02:00
Athou
9fa3743d21 disable autocomplete on profile page 2014-08-13 11:23:36 +02:00
Athou
7b373c79d9 fetch user from database instead of using the one in the session to avoid hibernate exception 2014-08-13 10:15:01 +02:00
Athou
4e9266e2d5 added comment 2014-08-13 10:12:18 +02:00
Athou
ea957e297c fix translations for zn and ms 2014-08-13 10:07:21 +02:00
Athou
9320b6beb8 units of work can now be chained 2014-08-13 10:01:38 +02:00
Athou
1319bf4a8c prepare next release version 2014-08-13 03:23:54 +02:00
Athou
78b1ec6e6a 2.0.1 2014-08-13 03:23:06 +02:00
Athou
6d4cbb889d increase connections in redis pool 2014-08-13 03:03:53 +02:00
Athou
27d16265d6 autofilled fields do not trigger model update, do it manually 2014-08-13 02:51:53 +02:00
Athou
9888e23cd9 next version 2014-08-12 20:49:00 +02:00
122 changed files with 2698 additions and 1542 deletions

3
.openshift/README.md Normal file
View File

@@ -0,0 +1,3 @@
For information about .openshift directory, consult the documentation:
http://openshift.github.io/documentation/oo_user_guide.html#the-openshift-directory

View File

@@ -0,0 +1,3 @@
For information about action hooks, consult the documentation:
http://openshift.github.io/documentation/oo_user_guide.html#action-hooks

22
.openshift/action_hooks/build Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/bash
cd $OPENSHIFT_REPO_DIR
rm -rf $OPENSHIFT_REPO_DIR/node
rm -rf $OPENSHIFT_REPO_DIR/node_modules
rm -rf $OPENSHIFT_TMP_DIR/npm
rm -rf $OPENSHIFT_TMP_DIR/npmrc
rm -rf $OPENSHIFT_TMP_DIR/m2
rm -rf $OPENSHIFT_TMP_DIR/local
export NPM_CONFIG_PREFIX="$OPENSHIFT_TMP_DIR/npm"
export NPM_CONFIG_USERCONFIG="$OPENSHIFT_TMP_DIR/npmrc"
export NPM_CONFIG_CACHE="$OPENSHIFT_TMP_DIR/npm/cache"
export MAVEN_OPTS="-Dmaven.repo.local=$OPENSHIFT_TMP_DIR/m2"
export HOME="$OPENSHIFT_TMP_DIR/local"
export NPM_CONFIG_ARCH="x64"
npm install npm
export PATH="$OPENSHIFT_REPO_DIR/node_modules/.bin:$PATH"
mvn clean package -DskipTests -Dos.arch=x64

9
.openshift/action_hooks/deploy Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
cd $OPENSHIFT_REPO_DIR
sed -i 's/@OPENSHIFT_DIY_IP@/'"$OPENSHIFT_DIY_IP"'/g' .openshift/config.mysql.yml
sed -i 's/@OPENSHIFT_DIY_PORT@/'"$OPENSHIFT_DIY_PORT"'/g' .openshift/config.mysql.yml
sed -i 's/@OPENSHIFT_APP_DNS@/'"$OPENSHIFT_APP_DNS"'/g' .openshift/config.mysql.yml
sed -i 's/@OPENSHIFT_APP_NAME@/'"$OPENSHIFT_APP_NAME"'/g' .openshift/config.mysql.yml
sed -i 's/@OPENSHIFT_MYSQL_DB_HOST@/'"$OPENSHIFT_MYSQL_DB_HOST"'/g' .openshift/config.mysql.yml
sed -i 's/@OPENSHIFT_MYSQL_DB_USERNAME@/'"$OPENSHIFT_MYSQL_DB_USERNAME"'/g' .openshift/config.mysql.yml
sed -i 's/@OPENSHIFT_MYSQL_DB_PASSWORD@/'"$OPENSHIFT_MYSQL_DB_PASSWORD"'/g' .openshift/config.mysql.yml

3
.openshift/action_hooks/start Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
cd $OPENSHIFT_REPO_DIR
nohup java -jar target/commafeed.jar server .openshift/config.mysql.yml > ${OPENSHIFT_DIY_LOG_DIR}/commafeed.log 2>&1 &

8
.openshift/action_hooks/stop Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
source $OPENSHIFT_CARTRIDGE_SDK_BASH
if [ -z "$(ps -ef | grep commafeed.jar | grep -v grep)" ]
then
client_result "Application is already stopped"
else
kill `ps -ef | grep commafeed.jar | grep -v grep | awk '{ print $2 }'` > /dev/null 2>&1
fi

107
.openshift/config.mysql.yml Normal file
View File

@@ -0,0 +1,107 @@
# CommaFeed settings
# ------------------
app:
# context path of the application
contextPath: /
# url used to access commafeed
publicUrl: https://@OPENSHIFT_APP_DNS@/
# wether to allow user registrations
allowRegistrations: false
# put your google analytics tracking code here
googleAnalyticsTrackingCode:
# number of http threads
backgroundThreads: 3
# number of database updating threads
databaseUpdateThreads: 1
# settings for sending emails (password recovery)
smtpHost:
smtpPort:
smtpTls: false
smtpUserName:
smtpPassword:
# wether this commafeed instance has a lot of feeds to refresh
# leave this to false in almost all cases
heavyLoad: false
# minimum amount of time commafeed will wait before refreshing the same feed
refreshIntervalMinutes: 15
# wether to enable pubsub
# probably not needed if refreshIntervalMinutes is low
pubsubhubbub: false
# if enabled, images in feed entries will be proxied through the server instead of accessed directly by the browser
# useful if commafeed is usually accessed through a restricting proxy
imageProxyEnabled: false
# database query timeout (in milliseconds), 0 to disable
queryTimeout: 0
# time to keep unread statuses (in days), 0 to disable
keepStatusDays: 0
# cache service to use, possible values are 'noop' and 'redis'
cache: noop
# announcement string displayed on the main page
announcement:
# Database connection
# -------------------
# for MySQL
# driverClass is com.mysql.jdbc.Driver
# url is jdbc:mysql://localhost/commafeed?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true
#
# for PostgreSQL
# driverClass is org.postgresql.Driver
# url is jdbc:postgresql://localhost:5432/commafeed
#
# for Microsoft SQL Server
# driverClass is net.sourceforge.jtds.jdbc.Driver
# url is jdbc:jtds:sqlserver://localhost:1433/commafeed;instance=<instanceName, remove if not needed>
database:
driverClass: com.mysql.jdbc.Driver
url: jdbc:mysql://@OPENSHIFT_MYSQL_DB_HOST@/@OPENSHIFT_APP_NAME@?autoReconnect=true&failOverReadOnly=false&maxReconnects=20&rewriteBatchedStatements=true
user: @OPENSHIFT_MYSQL_DB_USERNAME@
password: @OPENSHIFT_MYSQL_DB_PASSWORD@
properties:
charSet: UTF-8
maxWaitForConnection: 1s
validationQuery: "/* CommaFeed Health Check */ SELECT 1"
minSize: 1
maxSize: 50
checkConnectionWhileIdle: true
maxConnectionAge: 30m
server:
applicationConnectors:
- type: http
port: @OPENSHIFT_DIY_PORT@
bindHost: @OPENSHIFT_DIY_IP@
adminConnectors:
- type: http
port: 15000
bindHost: @OPENSHIFT_DIY_IP@
logging:
level: WARN
loggers:
com.commafeed: INFO
liquibase: INFO
io.dropwizard.server.ServerFactory: INFO
appenders:
- type: console
- type: file
currentLogFilename: log/commafeed.log
threshold: ALL
archive: true
archivedLogFilenamePattern: log/commafeed-%d.log
archivedFileCount: 5
timeZone: UTC

0
.openshift/cron/daily/.gitignore vendored Normal file
View File

0
.openshift/cron/hourly/.gitignore vendored Normal file
View File

0
.openshift/cron/minutely/.gitignore vendored Normal file
View File

0
.openshift/cron/monthly/.gitignore vendored Normal file
View File

View File

@@ -0,0 +1,16 @@
Run scripts or jobs on a weekly basis
=====================================
Any scripts or jobs added to this directory will be run on a scheduled basis
(weekly) using run-parts.
run-parts ignores any files that are hidden or dotfiles (.*) or backup
files (*~ or *,) or named *.{rpmsave,rpmorig,rpmnew,swp,cfsaved} and handles
the files named jobs.deny and jobs.allow specially.
In this specific example, the chronograph script is the only script or job file
executed on a weekly basis (due to white-listing it in jobs.allow). And the
README and chrono.dat file are ignored either as a result of being black-listed
in jobs.deny or because they are NOT white-listed in the jobs.allow file.
For more details, please see ../README.cron file.

View File

@@ -0,0 +1 @@
Time And Relative D...n In Execution (Open)Shift!

View File

@@ -0,0 +1,3 @@
#!/bin/bash
echo "`date`: `cat $(dirname \"$0\")/chrono.dat`"

View File

@@ -0,0 +1,12 @@
#
# Script or job files listed in here (one entry per line) will be
# executed on a weekly-basis.
#
# Example: The chronograph script will be executed weekly but the README
# and chrono.dat files in this directory will be ignored.
#
# The README file is actually ignored due to the entry in the
# jobs.deny which is checked before jobs.allow (this file).
#
chronograph

View File

@@ -0,0 +1,7 @@
#
# Any script or job files listed in here (one entry per line) will NOT be
# executed (read as ignored by run-parts).
#
README

View File

@@ -0,0 +1,3 @@
For information about markers, consult the documentation:
http://openshift.github.io/documentation/oo_user_guide.html#markers

3
.openshift/settings.xml Normal file
View File

@@ -0,0 +1,3 @@
<settings>
<localRepository>$OPENSHIFT_DATA_DIR</localRepository>
</settings>

5
.travis.yml Normal file
View File

@@ -0,0 +1,5 @@
language: java
jdk:
- openjdk7
- oraclejdk7
- oraclejdk8

26
CHANGELOG Normal file
View File

@@ -0,0 +1,26 @@
v 2.0.3
- internet explorer ajax cache workaround
- categories are now deletable again
- openshift support is back
- youtube feeds now show user favicon instead of youtube favicon
v 2.0.2
- api using the api key is now working again
- context path is now configurable in config.yml (see app.contextPath in config.yml.example)
- fix login on firefox when fields are autofilled by the browser
- fix scrolling of subscriptions list on mobile
- user is now logged in after registration
- fix link to documentation on home page and about page
- fields autocomplete is disabled on the profile page
- users are able to delete their account again
- chinese and malaysian translation files are now correctly loaded
- software version in user-agent when fetching feeds is no longer hardcoded
- admin settings page is now read only, settings are configured in config.yml
- added link to metrics on the admin settings page
- Rome (rss library) upgrade to 1.5.0
v 2.0.1
- the redis pool no longer throws an exception when it is unable to aquire a new connection
v2.0.0
- The backend has been completely rewritten using Dropwizard instead of TomEE, resulting in a lot less memory consumption and better overall performances.
See the README on how to build CommaFeed from now on.
- CommaFeed should no longer fetch the same feed multiple times in a row
- Users can use their username or email to log in

View File

@@ -1,4 +1,4 @@
CommaFeed [![Build Status](https://buildhive.cloudbees.com/job/Athou/job/commafeed/badge/icon)](https://buildhive.cloudbees.com/job/Athou/job/commafeed/) CommaFeed [![Build Status](https://travis-ci.org/Athou/commafeed.svg?branch=master)](https://travis-ci.org/Athou/commafeed)
========= =========
Sources for [CommaFeed.com](http://www.commafeed.com/). Sources for [CommaFeed.com](http://www.commafeed.com/).
@@ -21,12 +21,11 @@ You also need Maven 3.x (and a Java 1.7+ JDK) installed in order to build the ap
To install maven and openjdk on Ubuntu, issue the following commands To install maven and openjdk on Ubuntu, issue the following commands
sudo add-apt-repository ppa:natecarlson/maven3 sudo apt-get install build-essential openjdk-7-jdk maven
sudo apt-get update # Make sure java7 is the selected java version
sudo apt-get install openjdk-7-jdk maven3 sudo update-alternatives --config java
sudo update-alternatives --config javac
# Not required but if you don't, use 'mvn3' instead of 'mvn' for the rest of the instructions.
sudo ln -s /usr/bin/mvn3 /usr/bin/mvn
On Windows and other operating systems, just download maven 3.x from the [official site](http://maven.apache.org/), extract it somewhere and add the `bin` directory to your `PATH` environment variable. On Windows and other operating systems, just download maven 3.x from the [official site](http://maven.apache.org/), extract it somewhere and add the `bin` directory to your `PATH` environment variable.
@@ -40,18 +39,22 @@ Now build the application
mvn clean package mvn clean package
Copy `config.yml.example` to `config.yml` then edit the file to your liking. Copy `config.yml.example` to `config.yml` then edit the file to your liking.
Issue the following command to run the app, the server will listen by default on ``http://localhost:8082`. The default user is `admin` and the default password is `admin`. Issue the following command to run the app, the server will listen by default on `http://localhost:8082`. The default user is `admin` and the default password is `admin`.
java -jar target/commafeed.jar server config.yml java -jar target/commafeed.jar server config.yml
You can use nginx or apache as a proxy http server. Note that when using apache, the `ProxyPreserveHost on` option should be set in your config file. You can use a proxy http server such as nginx or apache.
Local development Deployment on OpenShift
----------------- -----------------------------
To start the dropwizard backend, use your IDE to run CommaFeedApplication as your main class, and pass `server config.dev.yml` as arguments to the program. [OpenShift](https://openshift.redhat.com) is Red Hat's Platform-as-a-Service (PaaS) that allows developers to quickly develop, host, and scale applications in a cloud environment. CommaFeed runs perfectly on OpenShift and can even be used in the free tier. Follow the [Getting Started](https://developers.openshift.com/en/getting-started-overview.html) guide and after you sign up and install the Command Line Tools (RHC), do:
To start the client-side webserver with watches on assets, run `gulp dev`. The server is now running on port 8082 and is proxying REST requests to dropwizard on port 8083.
rhc create-app commafeed diy-0.1 mysql-5.5
cd commafeed
git remote add upstream -m master https://github.com/Athou/commafeed.git
git pull -s recursive -X theirs upstream master
git push
Translate CommaFeed into your language Translate CommaFeed into your language
-------------------------------------- --------------------------------------
@@ -72,6 +75,31 @@ Don't forget to reference your theme in `src/main/webapp/sass/app.scss` and in `
See [_test.scss](https://github.com/Athou/commafeed/blob/master/src/main/webapp/sass/themes/_test.scss) for an example. See [_test.scss](https://github.com/Athou/commafeed/blob/master/src/main/webapp/sass/themes/_test.scss) for an example.
Local development
-----------------
Steps to configuring a development environment for CommaFeed may include, but may not be limited to:
1. `git clone https://github.com/Athou/CommaFeed` into some folder to get the project files.
2. Install Eclipse Luna (or latest) from http://www.eclipse.org/downloads/packages/eclipse-ide-java-developers/lunasr1 or your repo if available.
3. In Eclipse, Window → Preferences → Maven → Annotation Processing. Check "Automatically configure JDT APT"
* You may have to install the m2e-apt connector to have "Annotation Processing" as an option. Do so from Window → Preferences → Maven → Discovery → Open Catalog → type "m2e-apt" in the search box
* If you have installed Eclipse EE instead of Luna, you may have trouble installing m2e-apt
4. Install Lombok into Eclipse from http://projectlombok.org/download.html
* You may have to run `java -jar lombok.jar` as an administrator if your eclipse installation is not in your home folder
5. In Eclipse, File → Import → Maven → Existing Maven Projects. Navigate to where you cloned the CommaFeed files into, and select that as the root directory. Click Finish.
* You may notice some errors along the lines of "Plugin execution not covered by lifecycle configuration". These are inconsequential.
6. Find the file "CommaFeedApplication.java" under the navigation pane.
7. Right click it to bring up the context menu → Debug as... → Debug Configurations
8. Type `server config.dev.yml` under "Program arguments" in the "Arguments" tab for the Java Application setting "CommaFeedApplication"
9. Apply and hit "Debug"
10. The debugger is now working. To connect to it, open a terminal (or command prompt) and navigate to the directory where you cloned the CommaFeed files.
11. Issue the command `gulp dev` on Unix based systems or `gulp.cmd dev` in Windows.
12. The development server is now running at http://localhost:8082 and is proxying REST requests to dropwizard on port 8083.
13. Connect to the server from your browser; you should have functional breakpoints and watches on assets.
14. When you're done developing, create a fork at the top of https://github.com/Athou/CommaFeed page and commit your changes to it.
15. If you'd like to contribute to CommaFeed, create a pull request from your repository to https://github.com/Athou/CommaFeed when your changes are ready. There's a button to do so at the top of https://github.com/Athou/CommaFeed.
Copyright and license Copyright and license
--------------------- ---------------------

View File

@@ -3,7 +3,7 @@
"version": "2.0.0", "version": "2.0.0",
"dependencies": { "dependencies": {
"jquery": "1.11.0", "jquery": "1.11.0",
"jquery-ui": "1.11", "jquery-ui": "1.10.3",
"jquery-mousewheel": "3.1.12", "jquery-mousewheel": "3.1.12",
"lodash": "2.4.1", "lodash": "2.4.1",
"bootstrap": "3.1.1", "bootstrap": "3.1.1",

View File

@@ -1,6 +1,9 @@
# CommaFeed settings # CommaFeed settings
# ------------------ # ------------------
app: app:
# context path of the application
contextPath: /
# url used to access commafeed # url used to access commafeed
publicUrl: http://localhost:8082/ publicUrl: http://localhost:8082/
@@ -76,6 +79,7 @@ database:
minSize: 1 minSize: 1
maxSize: 50 maxSize: 50
checkConnectionWhileIdle: true checkConnectionWhileIdle: true
maxConnectionAge: 30m
server: server:
applicationConnectors: applicationConnectors:
@@ -84,6 +88,7 @@ server:
adminConnectors: adminConnectors:
- type: http - type: http
port: 8084 port: 8084
logging: logging:
level: INFO level: INFO
loggers: loggers:
@@ -100,3 +105,15 @@ logging:
archivedLogFilenamePattern: log/commafeed-%d.log archivedLogFilenamePattern: log/commafeed-%d.log
archivedFileCount: 5 archivedFileCount: 5
timeZone: UTC timeZone: UTC
# Redis pool configuration
# (only used if app.cache is 'redis')
# -----------------------------------
redis:
host: localhost
port: 6379
password:
timeout: 2000
database: 0
maxTotal: 500

View File

@@ -1,6 +1,9 @@
# CommaFeed settings # CommaFeed settings
# ------------------ # ------------------
app: app:
# context path of the application
contextPath: /
# url used to access commafeed # url used to access commafeed
publicUrl: http://localhost:8082/ publicUrl: http://localhost:8082/
@@ -66,7 +69,7 @@ app:
database: database:
driverClass: org.h2.Driver driverClass: org.h2.Driver
url: jdbc:h2:./target/example url: jdbc:h2:/home/commafeed/db
user: sa user: sa
password: sa password: sa
properties: properties:
@@ -76,6 +79,7 @@ database:
minSize: 1 minSize: 1
maxSize: 50 maxSize: 50
checkConnectionWhileIdle: true checkConnectionWhileIdle: true
maxConnectionAge: 30m
server: server:
applicationConnectors: applicationConnectors:
@@ -84,6 +88,7 @@ server:
adminConnectors: adminConnectors:
- type: http - type: http
port: 8084 port: 8084
logging: logging:
level: WARN level: WARN
loggers: loggers:
@@ -99,3 +104,15 @@ logging:
archivedLogFilenamePattern: log/commafeed-%d.log archivedLogFilenamePattern: log/commafeed-%d.log
archivedFileCount: 5 archivedFileCount: 5
timeZone: UTC timeZone: UTC
# Redis pool configuration
# (only used if app.cache is 'redis')
# -----------------------------------
redis:
host: localhost
port: 6379
password:
timeout: 2000
database: 0
maxTotal: 500

View File

@@ -1,283 +1,295 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="12"> <profiles version="12">
<profile kind="CodeFormatterProfile" name="Eclipse [built-in] 140 chars" 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.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.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_new_line_in_empty_annotation_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.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.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.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.comment.new_lines_at_block_boundaries" value="true"/>
<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_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="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.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_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.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.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.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/> <setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/> <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.disabling_tag" value="@formatter:off"/>
<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.continuation_indentation" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/> <setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="140"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="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.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.compiler.source" value="1.8"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="48"/>
<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_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.8"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.8"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="140"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
</profile> </profile>
</profiles> </profiles>

152
pom.xml
View File

@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId> <artifactId>commafeed</artifactId>
<version>2.0.0</version> <version>2.0.3</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>CommaFeed</name> <name>CommaFeed</name>
@@ -15,8 +15,17 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<dropwizard.version>0.7.1</dropwizard.version> <dropwizard.version>0.7.1</dropwizard.version>
<guice.version>3.0</guice.version>
<querydsl.version>3.5.0</querydsl.version>
<rome.version>1.5.0</rome.version>
</properties> </properties>
<scm>
<connection>scm:git:https://github.com/Athou/commafeed.git</connection>
<developerConnection>scm:git:https://github.com/Athou/commafeed.git</developerConnection>
<url>https://github.com/Athou/commafeed</url>
</scm>
<build> <build>
<finalName>commafeed</finalName> <finalName>commafeed</finalName>
<resources> <resources>
@@ -29,7 +38,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version> <version>3.2</version>
<configuration> <configuration>
<source>1.7</source> <source>1.7</source>
<target>1.7</target> <target>1.7</target>
@@ -38,7 +47,7 @@
<plugin> <plugin>
<groupId>pl.project13.maven</groupId> <groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId> <artifactId>git-commit-id-plugin</artifactId>
<version>2.1.7</version> <version>2.1.11</version>
<executions> <executions>
<execution> <execution>
<goals> <goals>
@@ -49,6 +58,7 @@
<configuration> <configuration>
<generateGitPropertiesFile>false</generateGitPropertiesFile> <generateGitPropertiesFile>false</generateGitPropertiesFile>
<failOnNoGitDirectory>false</failOnNoGitDirectory> <failOnNoGitDirectory>false</failOnNoGitDirectory>
<failOnUnableToExtractRepoInfo>false</failOnUnableToExtractRepoInfo>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
@@ -88,7 +98,7 @@
<plugin> <plugin>
<groupId>com.github.eirslett</groupId> <groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId> <artifactId>frontend-maven-plugin</artifactId>
<version>0.0.15</version> <version>0.0.16</version>
<executions> <executions>
<execution> <execution>
<id>install node and npm</id> <id>install node and npm</id>
@@ -117,6 +127,41 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>com.jamierf.dropwizard</groupId>
<artifactId>dropwizard-debpkg-maven-plugin</artifactId>
<version>0.7</version>
<configuration>
<configTemplate>${basedir}/config.yml.example</configTemplate>
<jvm>
<packageName>openjdk-7-jdk</packageName>
<server>true</server>
</jvm>
<unix>
<user>commafeed</user>
</unix>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>dwpackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins> </plugins>
</build> </build>
@@ -124,7 +169,7 @@
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.14.4</version> <version>1.14.8</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -133,6 +178,17 @@
<version>1.7.7</version> <version>1.7.7</version>
</dependency> </dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>${guice.version}</version>
</dependency>
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-multibindings</artifactId>
<version>${guice.version}</version>
</dependency>
<dependency> <dependency>
<groupId>io.dropwizard</groupId> <groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId> <artifactId>dropwizard-core</artifactId>
@@ -158,47 +214,47 @@
<artifactId>dropwizard-assets</artifactId> <artifactId>dropwizard-assets</artifactId>
<version>${dropwizard.version}</version> <version>${dropwizard.version}</version>
</dependency> </dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-forms</artifactId>
<version>${dropwizard.version}</version>
<type>pom</type>
</dependency>
<dependency> <dependency>
<groupId>com.wordnik</groupId> <groupId>com.wordnik</groupId>
<artifactId>swagger-jaxrs_2.10</artifactId> <artifactId>swagger-jaxrs_2.10</artifactId>
<version>1.3.7</version> <version>1.3.10</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<artifactId>jsr311-api</artifactId>
<groupId>javax.ws.rs</groupId> <groupId>javax.ws.rs</groupId>
</exclusion> <artifactId>jsr311-api</artifactId>
<exclusion>
<artifactId>javassist</artifactId>
<groupId>javassist</groupId>
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.mysema.querydsl</groupId> <groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-apt</artifactId> <artifactId>querydsl-apt</artifactId>
<version>3.4.2</version> <version>${querydsl.version}</version>
<scope>provided</scope> <scope>provided</scope>
<classifier>hibernate</classifier> <classifier>hibernate</classifier>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.mysema.querydsl</groupId> <groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId> <artifactId>querydsl-jpa</artifactId>
<version>3.4.2</version> <version>${querydsl.version}</version>
</dependency>
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-multipart</artifactId>
<version>1.18.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>commons-io</groupId> <groupId>commons-io</groupId>
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
<version>2.4</version> <version>2.4</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>commons-collections</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-collections</artifactId> <artifactId>commons-collections4</artifactId>
<version>3.2.1</version> <version>4.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>commons-codec</groupId> <groupId>commons-codec</groupId>
@@ -207,61 +263,41 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-math</artifactId> <artifactId>commons-math3</artifactId>
<version>2.2</version> <version>3.3</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>redis.clients</groupId> <groupId>redis.clients</groupId>
<artifactId>jedis</artifactId> <artifactId>jedis</artifactId>
<version>2.5.2</version> <version>2.6.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.sun.mail</groupId> <groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId> <artifactId>javax.mail</artifactId>
<version>1.5.2</version> <version>1.5.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.java.dev.rome</groupId> <groupId>com.rometools</groupId>
<artifactId>rome</artifactId> <artifactId>rome</artifactId>
<version>1.0.0</version> <version>${rome.version}</version>
<exclusions>
<exclusion>
<groupId>jdom</groupId>
<artifactId>jdom</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.rometools</groupId> <groupId>com.rometools</groupId>
<artifactId>rome-opml</artifactId> <artifactId>rome-opml</artifactId>
<version>1.0</version> <version>${rome.version}</version>
<exclusions>
<exclusion>
<groupId>rome</groupId>
<artifactId>rome</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom</artifactId>
<version>1.1.3</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jsoup</groupId> <groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId> <artifactId>jsoup</artifactId>
<version>1.7.3</version> <version>1.8.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.googlecode.juniversalchardet</groupId> <groupId>com.googlecode.juniversalchardet</groupId>
<artifactId>juniversalchardet</artifactId> <artifactId>juniversalchardet</artifactId>
<version>1.0.3</version> <version>1.0.3</version>
</dependency> </dependency>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-servlet</artifactId>
<version>2.6.1</version>
</dependency>
<dependency> <dependency>
<groupId>net.sourceforge.cssparser</groupId> <groupId>net.sourceforge.cssparser</groupId>
<artifactId>cssparser</artifactId> <artifactId>cssparser</artifactId>
@@ -271,17 +307,17 @@
<dependency> <dependency>
<groupId>com.h2database</groupId> <groupId>com.h2database</groupId>
<artifactId>h2</artifactId> <artifactId>h2</artifactId>
<version>1.4.181</version> <version>1.4.182</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version> <version>5.1.33</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>postgresql</groupId> <groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId> <artifactId>postgresql</artifactId>
<version>9.1-901-1.jdbc4</version> <version>9.3-1102-jdbc41</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.sourceforge.jtds</groupId> <groupId>net.sourceforge.jtds</groupId>
@@ -295,5 +331,11 @@
<version>4.11</version> <version>4.11</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.8</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -26,7 +26,7 @@
<link rel="stylesheet" href="lib/font-awesome/css/font-awesome.css" /> <link rel="stylesheet" href="lib/font-awesome/css/font-awesome.css" />
<link rel="stylesheet" href="lib/select2/select2.css" /> <link rel="stylesheet" href="lib/select2/select2.css" />
<link rel="stylesheet" href="lib/ng-grid/ng-grid.css" /> <link rel="stylesheet" href="lib/ng-grid/ng-grid.css" />
<link rel="stylesheet" href="lib/jquery-ui/themes/smoothness/jquery-ui.css" /> <link rel="stylesheet" href="lib/jquery-ui/themes/base/jquery-ui.css" />
<link rel="stylesheet" href="lib/angular-loading-bar/build/loading-bar.css" /> <link rel="stylesheet" href="lib/angular-loading-bar/build/loading-bar.css" />
<link rel="stylesheet" href="css/app.css" /> <link rel="stylesheet" href="css/app.css" />
@@ -41,7 +41,7 @@
<!-- build:js js/app.js --> <!-- build:js js/app.js -->
<script type="text/javascript" src="lib/lodash/dist/lodash.js"></script> <script type="text/javascript" src="lib/lodash/dist/lodash.js"></script>
<script type="text/javascript" src="lib/jquery/dist/jquery.js"></script> <script type="text/javascript" src="lib/jquery/dist/jquery.js"></script>
<script type="text/javascript" src="lib/jquery-ui/jquery-ui.js"></script> <script type="text/javascript" src="lib/jquery-ui/ui/jquery-ui.js"></script>
<script type="text/javascript" src="lib/jquery-mousewheel/jquery.mousewheel.js"></script> <script type="text/javascript" src="lib/jquery-mousewheel/jquery.mousewheel.js"></script>
<script type="text/javascript" src="lib/bootstrap/dist/js/bootstrap.js"></script> <script type="text/javascript" src="lib/bootstrap/dist/js/bootstrap.js"></script>
<script type="text/javascript" src="lib/angular/angular.js"></script> <script type="text/javascript" src="lib/angular/angular.js"></script>

View File

@@ -1432,6 +1432,9 @@ module.controller('ManageSettingsCtrl', ['$scope', '$location', '$state', 'Admin
$scope.toUsers = function() { $scope.toUsers = function() {
$state.transitionTo('admin.userlist'); $state.transitionTo('admin.userlist');
}; };
$scope.toMetrics = function() {
$state.transitionTo('admin.metrics');
};
}]); }]);
module.controller('HelpController', ['$scope', 'CategoryService', 'AnalyticsService', 'ServerService', module.controller('HelpController', ['$scope', 'CategoryService', 'AnalyticsService', 'ServerService',
@@ -1460,55 +1463,58 @@ module.controller('MetricsCtrl', ['$scope', 'AdminMetricsService', function($sco
$scope.metrics = AdminMetricsService.get(); $scope.metrics = AdminMetricsService.get();
}]); }]);
module.controller('LoginCtrl', ['$scope', '$location', 'SessionService', 'ServerService', function($scope, $location, SessionService, ServerService) { module.controller('LoginCtrl', ['$scope', '$location', '$timeout', 'SessionService', 'ServerService',
$scope.model = {}; function($scope, $location, $timeout, SessionService, ServerService) {
$scope.recovery_model = {}; $scope.model = {};
$scope.recovery = false; $scope.recovery_model = {};
$scope.recovery_enabled = false; $scope.recovery = false;
$scope.recovery_enabled = false;
ServerService.get(function(data) { ServerService.get(function(data) {
$scope.recovery_enabled = data.smtpEnabled; $scope.recovery_enabled = data.smtpEnabled;
}); });
var login = function(model) { var login = function(model) {
var success = function(data) { var success = function(data) {
window.location.href = window.location.href.substring(0, window.location.href.lastIndexOf('#')); window.location.href = window.location.href.substring(0, window.location.href.lastIndexOf('#'));
}; };
var error = function(data) { var error = function(data) {
$scope.message = data.data; $scope.message = data.data;
}; };
SessionService.login({ SessionService.login({
name : model.name, name : model.name,
password : model.password password : model.password
}, success, error); }, success, error);
} }
$scope.demoLogin = function() { $scope.demoLogin = function() {
login({ login({
name : 'demo', name : 'demo',
password : 'demo' password : 'demo'
}); });
}; };
$scope.login = function() { $scope.login = function() {
login($scope.model); // autofilled fields do not trigger model update, do it manually
}; $('input[ng-model]').trigger('input');
login($scope.model);
};
$scope.toggleRecovery = function() { $scope.toggleRecovery = function() {
$scope.recovery = !$scope.recovery; $scope.recovery = !$scope.recovery;
}; };
var recovery_success = function(data) { var recovery_success = function(data) {
$scope.recovery_message = "Email has ben sent. Check your inbox."; $scope.recovery_message = "Email has ben sent. Check your inbox.";
}; };
var recovery_error = function(data) { var recovery_error = function(data) {
$scope.recovery_message = data.data; $scope.recovery_message = data.data;
}; };
$scope.recover = function() { $scope.recover = function() {
SessionService.passwordReset({ SessionService.passwordReset({
email : $scope.recovery_model.email email : $scope.recovery_model.email
}, recovery_success, recovery_error); }, recovery_success, recovery_error);
} }
}]); }]);
module.controller('RegisterCtrl', ['$scope', '$location', 'SessionService', 'ServerService', module.controller('RegisterCtrl', ['$scope', '$location', 'SessionService', 'ServerService',
function($scope, $location, SessionService, ServerService) { function($scope, $location, SessionService, ServerService) {

View File

@@ -193,7 +193,7 @@ module.directive('category', [function() {
}); });
var label = ''; var label = '';
if (count > 0) { if (count > 0) {
label = '(' + count + ')'; label += count;
} }
return label; return label;
}; };
@@ -201,7 +201,7 @@ module.directive('category', [function() {
$scope.feedCountLabel = function(feed) { $scope.feedCountLabel = function(feed) {
var label = ''; var label = '';
if (feed.unread > 0) { if (feed.unread > 0) {
label = '(' + feed.unread + ')'; label += feed.unread;
} }
return label; return label;
}; };

View File

@@ -13,9 +13,8 @@ app.config([
function($routeProvider, $stateProvider, $urlRouterProvider, $httpProvider, $compileProvider, cfpLoadingBarProvider, function($routeProvider, $stateProvider, $urlRouterProvider, $httpProvider, $compileProvider, cfpLoadingBarProvider,
$translateProvider) { $translateProvider) {
// $translateProvider.useLocalStorage();
$translateProvider.useStaticFilesLoader({ $translateProvider.useStaticFilesLoader({
prefix : '/i18n/', prefix : 'i18n/',
suffix : '.js' suffix : '.js'
}); });
$translateProvider.preferredLanguage('en'); $translateProvider.preferredLanguage('en');

View File

@@ -56,13 +56,13 @@ module.factory('SettingsService', ['$resource', '$translate', function($resource
res.get(function(data) { res.get(function(data) {
s.settings = data; s.settings = data;
var lang = s.settings.language || 'en'; var lang = s.settings.language || 'en';
$translate.use(lang);
if (lang === 'zh') { if (lang === 'zh') {
lang = 'zh-cn'; lang = 'zh-cn';
} else if (lang === 'ms') { } else if (lang === 'ms') {
lang = 'ms-my'; lang = 'ms-my';
} }
moment.lang(lang, {}); moment.lang(lang, {});
$translate.use(lang);
if (callback) { if (callback) {
callback(data); callback(data);
} }
@@ -182,7 +182,7 @@ module.factory('CategoryService', ['$resource', '$http', function($resource, $ht
var actions = { var actions = {
get : { get : {
method : 'GET', method : 'GET',
ignoreLoadingBar: true, ignoreLoadingBar : true,
params : { params : {
_method : 'get' _method : 'get'
} }
@@ -243,11 +243,14 @@ module.factory('CategoryService', ['$resource', '$http', function($resource, $ht
callback(data); callback(data);
}); });
}; };
res.refresh = function(callback) { res.refresh = function(success, error) {
res.get(function(data) { res.get(function(data) {
_.merge(res.subscriptions, data); _.merge(res.subscriptions, data);
if (callback) if (success)
callback(data); success(data);
}, function(data) {
if (error)
error(data);
}); });
}; };
@@ -265,7 +268,7 @@ module.factory('EntryService', ['$resource', '$http', function($resource, $http)
}, },
mark : { mark : {
method : 'POST', method : 'POST',
ignoreLoadingBar: true, ignoreLoadingBar : true,
params : { params : {
_method : 'mark' _method : 'mark'
} }

View File

@@ -101,6 +101,14 @@
font-size: 11px; font-size: 11px;
} }
.css-treeview .unread ~ .unread-counter::before {
content: '(';
}
.css-treeview .unread ~ .unread-counter::after {
content: ')';
}
.css-treeview a { .css-treeview a {
cursor: pointer; cursor: pointer;
color: black; color: black;

View File

@@ -12,8 +12,8 @@
<i ng-class="{'icon-star' : node.id == 'starred', 'icon-inbox': node.id == 'all', 'icon-tag' : node.isTag}" ng-show="!showChildren"></i> <i ng-class="{'icon-star' : node.id == 'starred', 'icon-inbox': node.id == 'all', 'icon-tag' : node.isTag}" ng-show="!showChildren"></i>
</span> </span>
<span ng-class="{selected: (node.id == selectedId && (node.isTag ? selectedType == 'tag' : selectedType == 'category'))}"> <span ng-class="{selected: (node.id == selectedId && (node.isTag ? selectedType == 'tag' : selectedType == 'category'))}">
<span ng-class="{unread: unreadCount({category:node})}" class="bidi-embed"> {{categoryLabel(node)}} </span> <span ng-class="{unread: unreadCount({category:node})}" class="bidi-embed">{{categoryLabel(node)}}</span>
<span class="unread-counter"> {{categoryCountLabel(node)}} </span> <span class="unread-counter">{{categoryCountLabel(node)}}</span>
</span> </span>
</div> </div>
</div> </div>
@@ -30,8 +30,8 @@
<a ng-click="feedClicked(feed.id, $event)" ng-dblclick="showFeedDetails(feed)" class="feed-link" href="{{feed.feedLink}}" target="_blank" <a ng-click="feedClicked(feed.id, $event)" ng-dblclick="showFeedDetails(feed)" class="feed-link" href="{{feed.feedLink}}" target="_blank"
ng-class="{error: feed.message && feed.errorCount > 10, selected: (feed.id == selectedId && selectedType == 'feed') }"> ng-class="{error: feed.message && feed.errorCount > 10, selected: (feed.id == selectedId && selectedType == 'feed') }">
<favicon url="feed.iconUrl" /> <favicon url="feed.iconUrl" />
<span ng-class="{unread: feed.unread}" class="bidi-embed"> {{feed.name}} </span> <span ng-class="{unread: feed.unread}" class="bidi-embed">{{feed.name}}</span>
<span class="unread-counter"> {{feedCountLabel(feed)}} </span> <span class="unread-counter">{{feedCountLabel(feed)}}</span>
</a> </a>
</li> </li>
</ul> </ul>

View File

@@ -1,10 +1,14 @@
<div class="row"> <div class="row">
<div class="page-header"> <div class="page-header">
<h1> <h1>
Application settings Application settings -
<small> <small>
<a ng-click="toUsers()" class="pointer">Manage users</a> <a ng-click="toUsers()" class="pointer">Manage users</a>
</small> </small>
-
<small>
<a ng-click="toMetrics()" class="pointer">Metrics</a>
</small>
</h1> </h1>
</div> </div>
@@ -28,26 +32,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="form-group">
<label class="control-label col-sm-3" for="feedbackButton">Feedback button</label>
<div class="col-sm-9">
<div class="checkbox">
<input type="checkbox" id="feedbackButton" name="feedbackButton" ng-model="settings.feedbackButton" />
</div>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3" for="googleClientId">Google client ID</label>
<div class="col-sm-9">
<input type="text" name="googleClientId" class="form-control" ng-model="settings.googleClientId" />
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3" for="googleClientSecret">Google client secret</label>
<div class="col-sm-9">
<input type="text" name="googleClientSecret" class="form-control" ng-model="settings.googleClientSecret" />
</div>
</div>
<div class="form-group"> <div class="form-group">
<label class="control-label col-sm-3" for="googleAnalyticsTrackingCode">Google Analytics tracking code</label> <label class="control-label col-sm-3" for="googleAnalyticsTrackingCode">Google Analytics tracking code</label>
<div class="col-sm-9"> <div class="col-sm-9">
@@ -134,14 +118,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="form-group">
<label class="control-label col-sm-3" for="logLevel">Logging level</label>
<div class="col-sm-9">
<select name="logLevel" ng-model="settings.logLevel" class="form-control"
ng-options="level for level in ['DEBUG', 'INFO', 'WARN', 'ERROR']">
</select>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label class="control-label col-sm-3" for="logLevel">Database query timeout (ms)</label> <label class="control-label col-sm-3" for="logLevel">Database query timeout (ms)</label>
<div class="col-sm-9"> <div class="col-sm-9">
@@ -173,8 +149,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="text-center form-group"> <div class="text-center form-group">
<button type="submit" class="btn btn-primary">Save</button> <button type="button" class="btn btn-default" ng-click="cancel()">Back</button>
<button type="button" class="btn btn-default" ng-click="cancel()">Cancel</button>
</div> </div>
</div> </div>
</form> </form>

View File

@@ -40,7 +40,7 @@
<div class="col-sm-offset-2 col-sm-10"> <div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary" ng-if="!isMeta()">{{ 'global.save' | translate }}</button> <button type="submit" class="btn btn-primary" ng-if="!isMeta()">{{ 'global.save' | translate }}</button>
<button type="button" class="btn btn-danger" ng-click="deleteCategory()" ng-show="!isMeta()" <button type="button" class="btn btn-danger" ng-click="deleteCategory()" ng-show="!isMeta()"
confirm-click="{{ 'details.delete_category_confirmation}">{{ 'global.delete' | translate }}</button> confirm-click="'details.delete_category_confirmation' | translate">{{ 'global.delete' | translate }}</button>
<button type="button" class="btn btn-default" ng-click="back()">{{ 'global.cancel' | translate }}</button> <button type="button" class="btn btn-default" ng-click="back()">{{ 'global.cancel' | translate }}</button>
</div> </div>
</div> </div>

View File

@@ -107,7 +107,7 @@
</h4> </h4>
<p>{{ 'about.rest_api.line1' | translate }}</p> <p>{{ 'about.rest_api.line1' | translate }}</p>
<p> <p>
<a href="api" target="_blank">{{ 'about.rest_api.link_to_documentation' | translate }}</a> <a href="api/" target="_blank">{{ 'about.rest_api.link_to_documentation' | translate }}</a>
</p> </p>
</div> </div>
</div> </div>

View File

@@ -16,7 +16,7 @@
<div infinite-scroll="loadMoreEntries()" infinite-scroll-disabled="busy || !settingsService.settings.readingMode" <div infinite-scroll="loadMoreEntries()" infinite-scroll-disabled="busy || !settingsService.settings.readingMode"
infinite-scroll-distance="1" id="feed-accordion" ng-class="{'expanded' : settingsService.settings.viewMode == 'expanded' }"> infinite-scroll-distance="1" id="feed-accordion" ng-class="{'expanded' : settingsService.settings.viewMode == 'expanded' }">
<div ng-show="message && errorCount > 10">{{ 'view.error_while_loading_feed} : {{message}' | translate }}</div> <div ng-show="message && errorCount > 10">{{ 'view.error_while_loading_feed' | translate }} : {{ message }}</div>
<div ng-repeat="entry in entries" class="entry entry-font-size-{{font_size}}" id="entry_{{entry.id}}" <div ng-repeat="entry in entries" class="entry entry-font-size-{{font_size}}" id="entry_{{entry.id}}"
ng-class="{unread: entry.read == false, current: current==entry, open: isOpen, closed: !isOpen }"> ng-class="{unread: entry.read == false, current: current==entry, open: isOpen, closed: !isOpen }">
<div class="entry-heading" ng-swipe-right="mark(entry, !entry.read)"> <div class="entry-heading" ng-swipe-right="mark(entry, !entry.read)">

View File

@@ -12,13 +12,13 @@
<div class="form-group"> <div class="form-group">
<label class="col-sm-2 control-label" for="email">{{ 'profile.email' | translate }}</label> <label class="col-sm-2 control-label" for="email">{{ 'profile.email' | translate }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="email" id="email" ng-model="user.email" class="form-control" /> <input type="email" id="email" ng-model="user.email" class="form-control" autocomplete="off" />
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-2 control-label" for="password">{{ 'profile.change_password' | translate }}</label> <label class="col-sm-2 control-label" for="password">{{ 'profile.change_password' | translate }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="password" name="password" id="password" ng-model="user.password" class="form-control" ng-minlength="6" /> <input type="password" name="password" id="password" ng-model="user.password" class="form-control" ng-minlength="6" autocomplete="off" />
<span class="help-inline" ng-show="profileForm.password.$error.minlength">{{ 'profile.minimum_6_chars' | translate }}</span> <span class="help-inline" ng-show="profileForm.password.$error.minlength">{{ 'profile.minimum_6_chars' | translate }}</span>
</div> </div>
</div> </div>
@@ -26,7 +26,7 @@
<label class="col-sm-2 control-label" for="password">{{ 'profile.confirm_password' | translate }}</label> <label class="col-sm-2 control-label" for="password">{{ 'profile.confirm_password' | translate }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="password" class="form-control" name="password_c" id="password_c" ng-model="password_c" <input type="password" class="form-control" name="password_c" id="password_c" ng-model="password_c"
ui-validate="'$value==user.password'" ui-validate-watch="'user.password'"> ui-validate="'$value==user.password'" ui-validate-watch="'user.password'" autocomplete="off">
<span class="help-inline" ng-show="profileForm.password_c.$error.validator">{{ 'profile.passwords_do_not_match' | translate }}</span> <span class="help-inline" ng-show="profileForm.password_c.$error.validator">{{ 'profile.passwords_do_not_match' | translate }}</span>
</div> </div>
</div> </div>
@@ -57,7 +57,8 @@
<div class="form-group"> <div class="form-group">
<div class="col-sm-offset-2 col-sm-10"> <div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary">{{ 'global.save' | translate }}</button> <button type="submit" class="btn btn-primary">{{ 'global.save' | translate }}</button>
<button type="button" class="btn btn-danger" ng-click="deleteAccount()" confirm-click="'profile.delete_account_confirmation' | translate">{{ 'profile.delete_account' | translate }}</button> <button type="button" class="btn btn-danger" ng-click="deleteAccount()"
confirm-click="'profile.delete_account_confirmation' | translate">{{ 'profile.delete_account' | translate }}</button>
<button type="button" class="btn btn-default" ng-click="cancel()">{{ 'global.cancel' | translate }}</button> <button type="button" class="btn btn-default" ng-click="cancel()">{{ 'global.cancel' | translate }}</button>
</div> </div>
</div> </div>

View File

@@ -90,7 +90,7 @@
</span> </span>
<span> <span>
- REST API - REST API
<a href="api" target="_blank">documentation</a> <a href="api/" target="_blank">documentation</a>
</span> </span>
<span class="pull-right"> <span class="pull-right">
<a href="https://twitter.com/CommaFeed" class="twitter-follow-button" data-show-count="false" data-size="large">Follow @CommaFeed</a> <a href="https://twitter.com/CommaFeed" class="twitter-follow-button" data-show-count="false" data-size="large">Follow @CommaFeed</a>

View File

@@ -4,41 +4,25 @@ import io.dropwizard.Application;
import io.dropwizard.assets.AssetsBundle; import io.dropwizard.assets.AssetsBundle;
import io.dropwizard.db.DataSourceFactory; import io.dropwizard.db.DataSourceFactory;
import io.dropwizard.hibernate.HibernateBundle; import io.dropwizard.hibernate.HibernateBundle;
import io.dropwizard.jersey.sessions.HttpSessionProvider;
import io.dropwizard.migrations.MigrationsBundle; import io.dropwizard.migrations.MigrationsBundle;
import io.dropwizard.servlets.CacheBustingFilter;
import io.dropwizard.setup.Bootstrap; import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment; import io.dropwizard.setup.Environment;
import java.io.File; import java.io.IOException;
import java.util.Date; import java.util.Date;
import java.util.concurrent.TimeUnit; import java.util.EnumSet;
import java.util.concurrent.ScheduledExecutorService;
import lombok.extern.slf4j.Slf4j; import javax.servlet.DispatcherType;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.server.session.HashSessionManager;
import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.server.session.SessionHandler;
import org.hibernate.SessionFactory;
import com.codahale.metrics.MetricRegistry;
import com.commafeed.CommaFeedConfiguration.CacheType;
import com.commafeed.backend.HttpGetter;
import com.commafeed.backend.cache.CacheService;
import com.commafeed.backend.cache.NoopCacheService;
import com.commafeed.backend.cache.RedisCacheService;
import com.commafeed.backend.dao.FeedCategoryDAO;
import com.commafeed.backend.dao.FeedDAO;
import com.commafeed.backend.dao.FeedEntryContentDAO;
import com.commafeed.backend.dao.FeedEntryDAO;
import com.commafeed.backend.dao.FeedEntryStatusDAO;
import com.commafeed.backend.dao.FeedEntryTagDAO;
import com.commafeed.backend.dao.FeedSubscriptionDAO;
import com.commafeed.backend.dao.UserDAO;
import com.commafeed.backend.dao.UserRoleDAO;
import com.commafeed.backend.dao.UserSettingsDAO;
import com.commafeed.backend.feed.FaviconFetcher;
import com.commafeed.backend.feed.FeedFetcher;
import com.commafeed.backend.feed.FeedParser;
import com.commafeed.backend.feed.FeedQueues;
import com.commafeed.backend.feed.FeedRefreshTaskGiver; import com.commafeed.backend.feed.FeedRefreshTaskGiver;
import com.commafeed.backend.feed.FeedRefreshUpdater; import com.commafeed.backend.feed.FeedRefreshUpdater;
import com.commafeed.backend.feed.FeedRefreshWorker; import com.commafeed.backend.feed.FeedRefreshWorker;
@@ -53,24 +37,10 @@ import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserRole; import com.commafeed.backend.model.UserRole;
import com.commafeed.backend.model.UserSettings; import com.commafeed.backend.model.UserSettings;
import com.commafeed.backend.opml.OPMLExporter;
import com.commafeed.backend.opml.OPMLImporter;
import com.commafeed.backend.service.ApplicationPropertiesService;
import com.commafeed.backend.service.DatabaseCleaningService;
import com.commafeed.backend.service.FeedEntryContentService;
import com.commafeed.backend.service.FeedEntryService;
import com.commafeed.backend.service.FeedEntryTagService;
import com.commafeed.backend.service.FeedService;
import com.commafeed.backend.service.FeedSubscriptionService;
import com.commafeed.backend.service.FeedUpdateService;
import com.commafeed.backend.service.MailService;
import com.commafeed.backend.service.PasswordEncryptionService;
import com.commafeed.backend.service.PubSubService;
import com.commafeed.backend.service.StartupService; import com.commafeed.backend.service.StartupService;
import com.commafeed.backend.service.UserService; import com.commafeed.backend.service.UserService;
import com.commafeed.backend.task.OldStatusesCleanupTask; import com.commafeed.backend.task.OldStatusesCleanupTask;
import com.commafeed.backend.task.OrphansCleanupTask; import com.commafeed.backend.task.OrphansCleanupTask;
import com.commafeed.backend.task.SchedulingService;
import com.commafeed.frontend.auth.SecurityCheckProvider; import com.commafeed.frontend.auth.SecurityCheckProvider;
import com.commafeed.frontend.auth.SecurityCheckProvider.SecurityCheckUserServiceProvider; import com.commafeed.frontend.auth.SecurityCheckProvider.SecurityCheckUserServiceProvider;
import com.commafeed.frontend.resource.AdminREST; import com.commafeed.frontend.resource.AdminREST;
@@ -84,6 +54,10 @@ import com.commafeed.frontend.servlet.AnalyticsServlet;
import com.commafeed.frontend.servlet.CustomCssServlet; import com.commafeed.frontend.servlet.CustomCssServlet;
import com.commafeed.frontend.servlet.LogoutServlet; import com.commafeed.frontend.servlet.LogoutServlet;
import com.commafeed.frontend.servlet.NextUnreadServlet; import com.commafeed.frontend.servlet.NextUnreadServlet;
import com.commafeed.frontend.session.SessionHelperProvider;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.sun.jersey.api.core.ResourceConfig;
import com.wordnik.swagger.config.ConfigFactory; import com.wordnik.swagger.config.ConfigFactory;
import com.wordnik.swagger.config.ScannerFactory; import com.wordnik.swagger.config.ScannerFactory;
import com.wordnik.swagger.config.SwaggerConfig; import com.wordnik.swagger.config.SwaggerConfig;
@@ -94,154 +68,83 @@ import com.wordnik.swagger.jaxrs.listing.ResourceListingProvider;
import com.wordnik.swagger.jaxrs.reader.DefaultJaxrsApiReader; import com.wordnik.swagger.jaxrs.reader.DefaultJaxrsApiReader;
import com.wordnik.swagger.reader.ClassReaders; import com.wordnik.swagger.reader.ClassReaders;
@Slf4j
public class CommaFeedApplication extends Application<CommaFeedConfiguration> { public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
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";
public static final String SESSION_USER = "user";
public static final Date STARTUP_TIME = new Date(); public static final Date STARTUP_TIME = new Date();
private HibernateBundle<CommaFeedConfiguration> hibernateBundle; private HibernateBundle<CommaFeedConfiguration> hibernateBundle;
private MigrationsBundle<CommaFeedConfiguration> migrationsBundle;
@Override
public String getName() {
return "CommaFeed";
}
@Override @Override
public void initialize(Bootstrap<CommaFeedConfiguration> bootstrap) { public void initialize(Bootstrap<CommaFeedConfiguration> bootstrap) {
hibernateBundle = new HibernateBundle<CommaFeedConfiguration>(AbstractModel.class, Feed.class, FeedCategory.class, FeedEntry.class, bootstrap.addBundle(hibernateBundle = new HibernateBundle<CommaFeedConfiguration>(AbstractModel.class, Feed.class,
FeedEntryContent.class, FeedEntryStatus.class, FeedEntryTag.class, FeedSubscription.class, User.class, UserRole.class, FeedCategory.class, FeedEntry.class, FeedEntryContent.class, FeedEntryStatus.class, FeedEntryTag.class,
UserSettings.class) { FeedSubscription.class, User.class, UserRole.class, UserSettings.class) {
@Override @Override
public DataSourceFactory getDataSourceFactory(CommaFeedConfiguration configuration) { public DataSourceFactory getDataSourceFactory(CommaFeedConfiguration configuration) {
return configuration.getDatabase(); return configuration.getDatabase();
} }
}; });
bootstrap.addBundle(hibernateBundle);
migrationsBundle = new MigrationsBundle<CommaFeedConfiguration>() { bootstrap.addBundle(new MigrationsBundle<CommaFeedConfiguration>() {
@Override @Override
public DataSourceFactory getDataSourceFactory(CommaFeedConfiguration configuration) { public DataSourceFactory getDataSourceFactory(CommaFeedConfiguration configuration) {
return configuration.getDatabase(); return configuration.getDatabase();
} }
}; });
bootstrap.addBundle(migrationsBundle);
bootstrap.addBundle(new AssetsBundle("/assets/", "/", "index.html")); bootstrap.addBundle(new AssetsBundle("/assets/", "/", "index.html"));
} }
@Override @Override
public void run(CommaFeedConfiguration config, Environment environment) throws Exception { public void run(CommaFeedConfiguration config, Environment environment) throws Exception {
MetricRegistry metrics = environment.metrics(); // configure context path
SessionFactory sessionFactory = hibernateBundle.getSessionFactory(); environment.getApplicationContext().setContextPath(config.getApplicationSettings().getContextPath());
CacheService cacheService = config.getApplicationSettings().getCache() == CacheType.NOOP ? new NoopCacheService() // guice init
: new RedisCacheService(); Injector injector = Guice.createInjector(new CommaFeedModule(hibernateBundle.getSessionFactory(), config, environment.metrics()));
log.info("using cache {}", cacheService.getClass());
// DAOs
FeedCategoryDAO feedCategoryDAO = new FeedCategoryDAO(sessionFactory);
FeedDAO feedDAO = new FeedDAO(sessionFactory);
FeedEntryContentDAO feedEntryContentDAO = new FeedEntryContentDAO(sessionFactory);
FeedEntryDAO feedEntryDAO = new FeedEntryDAO(sessionFactory);
FeedEntryTagDAO feedEntryTagDAO = new FeedEntryTagDAO(sessionFactory);
FeedSubscriptionDAO feedSubscriptionDAO = new FeedSubscriptionDAO(sessionFactory);
UserDAO userDAO = new UserDAO(sessionFactory);
UserRoleDAO userRoleDAO = new UserRoleDAO(sessionFactory);
UserSettingsDAO userSettingsDAO = new UserSettingsDAO(sessionFactory);
FeedEntryStatusDAO feedEntryStatusDAO = new FeedEntryStatusDAO(sessionFactory, feedEntryDAO, feedEntryTagDAO, config);
// Queuing system
FeedQueues queues = new FeedQueues(feedDAO, config, metrics);
// Services
ApplicationPropertiesService applicationPropertiesService = new ApplicationPropertiesService();
DatabaseCleaningService cleaningService = new DatabaseCleaningService(sessionFactory, feedDAO, feedEntryDAO, feedEntryContentDAO,
feedEntryStatusDAO);
FeedEntryContentService feedEntryContentService = new FeedEntryContentService(feedEntryContentDAO);
FeedEntryService feedEntryService = new FeedEntryService(feedSubscriptionDAO, feedEntryDAO, feedEntryStatusDAO, cacheService);
FeedEntryTagService feedEntryTagService = new FeedEntryTagService(feedEntryDAO, feedEntryTagDAO);
FeedService feedService = new FeedService(feedDAO);
FeedSubscriptionService feedSubscriptionService = new FeedSubscriptionService(feedEntryStatusDAO, feedSubscriptionDAO, feedService,
queues, cacheService, config);
FeedUpdateService feedUpdateService = new FeedUpdateService(feedEntryDAO, feedEntryContentService);
MailService mailService = new MailService(config);
PasswordEncryptionService encryptionService = new PasswordEncryptionService();
PubSubService pubSubService = new PubSubService(config, queues);
UserService userService = new UserService(feedCategoryDAO, userDAO, userSettingsDAO, feedSubscriptionService, encryptionService,
config);
StartupService startupService = new StartupService(sessionFactory, userDAO, userService);
OPMLImporter opmlImporter = new OPMLImporter(feedCategoryDAO, feedSubscriptionService, cacheService);
OPMLExporter opmlExporter = new OPMLExporter(feedCategoryDAO, feedSubscriptionDAO);
// Feed fetching/parsing
HttpGetter httpGetter = new HttpGetter();
FeedParser feedParser = new FeedParser();
FaviconFetcher faviconFetcher = new FaviconFetcher(httpGetter);
FeedFetcher feedFetcher = new FeedFetcher(feedParser, httpGetter);
FeedRefreshUpdater feedUpdater = new FeedRefreshUpdater(sessionFactory, feedUpdateService, pubSubService, queues, config, metrics,
feedSubscriptionDAO, cacheService);
FeedRefreshWorker feedWorker = new FeedRefreshWorker(feedUpdater, feedFetcher, queues, config, metrics);
FeedRefreshTaskGiver taskGiver = new FeedRefreshTaskGiver(sessionFactory, queues, feedDAO, feedWorker, config, metrics);
// Auth/session management // Auth/session management
HashSessionManager sessionManager = new HashSessionManager(); environment.servlets().setSessionHandler(new SessionHandler(config.getSessionManagerFactory().build()));
sessionManager.setHttpOnly(true); environment.jersey().register(new SecurityCheckUserServiceProvider(injector.getInstance(UserService.class)));
sessionManager.getSessionCookieConfig().setHttpOnly(true);
sessionManager.setStoreDirectory(new File("sessions"));
sessionManager.getSessionCookieConfig().setMaxAge((int) TimeUnit.DAYS.toSeconds(30));
sessionManager.setMaxInactiveInterval((int) TimeUnit.DAYS.toSeconds(30));
sessionManager.setDeleteUnrestorableSessions(true);
sessionManager.setIdleSavePeriod((int) TimeUnit.HOURS.toSeconds(2));
sessionManager.setRefreshCookieAge((int) TimeUnit.DAYS.toSeconds(1));
sessionManager.setSavePeriod((int) TimeUnit.MINUTES.toSeconds(5));
sessionManager.setScavengePeriod((int) TimeUnit.MINUTES.toSeconds(5));
environment.servlets().setSessionHandler(new SessionHandler(sessionManager));
environment.jersey().register(new SecurityCheckUserServiceProvider(userService));
environment.jersey().register(SecurityCheckProvider.class); environment.jersey().register(SecurityCheckProvider.class);
environment.jersey().register(HttpSessionProvider.class); environment.jersey().register(SessionHelperProvider.class);
// REST resources // REST resources
environment.jersey().setUrlPattern("/rest/*"); environment.jersey().setUrlPattern("/rest/*");
environment.jersey() environment.jersey().register(injector.getInstance(AdminREST.class));
.register(new AdminREST(userDAO, userRoleDAO, userService, encryptionService, cleaningService, config, metrics)); environment.jersey().register(injector.getInstance(CategoryREST.class));
environment.jersey().register( environment.jersey().register(injector.getInstance(EntryREST.class));
new CategoryREST(feedCategoryDAO, feedEntryStatusDAO, feedSubscriptionDAO, feedEntryService, feedSubscriptionService, environment.jersey().register(injector.getInstance(FeedREST.class));
cacheService, config)); environment.jersey().register(injector.getInstance(PubSubHubbubCallbackREST.class));
environment.jersey().register(new EntryREST(feedEntryTagDAO, feedEntryService, feedEntryTagService)); environment.jersey().register(injector.getInstance(ServerREST.class));
environment.jersey().register( environment.jersey().register(injector.getInstance(UserREST.class));
new FeedREST(feedSubscriptionDAO, feedCategoryDAO, feedEntryStatusDAO, faviconFetcher, feedFetcher, feedEntryService,
feedSubscriptionService, queues, opmlImporter, opmlExporter, cacheService, config));
environment.jersey().register(new PubSubHubbubCallbackREST(feedDAO, feedParser, queues, config, metrics));
environment.jersey().register(new ServerREST(httpGetter, config, applicationPropertiesService));
environment.jersey().register(
new UserREST(userDAO, userRoleDAO, userSettingsDAO, userService, encryptionService, mailService, config));
// Servlets // Servlets
NextUnreadServlet nextUnreadServlet = new NextUnreadServlet(sessionFactory, feedSubscriptionDAO, feedEntryStatusDAO, environment.servlets().addServlet("next", injector.getInstance(NextUnreadServlet.class)).addMapping("/next");
feedCategoryDAO, config); environment.servlets().addServlet("logout", injector.getInstance(LogoutServlet.class)).addMapping("/logout");
LogoutServlet logoutServlet = new LogoutServlet(config); environment.servlets().addServlet("customCss", injector.getInstance(CustomCssServlet.class)).addMapping("/custom_css.css");
CustomCssServlet customCssServlet = new CustomCssServlet(sessionFactory, userSettingsDAO); environment.servlets().addServlet("analytics.js", injector.getInstance(AnalyticsServlet.class)).addMapping("/analytics.js");
AnalyticsServlet analyticsServlet = new AnalyticsServlet(config);
environment.servlets().addServlet("next", nextUnreadServlet).addMapping("/next");
environment.servlets().addServlet("logout", logoutServlet).addMapping("/logout");
environment.servlets().addServlet("customCss", customCssServlet).addMapping("/custom_css.css");
environment.servlets().addServlet("analytics.js", analyticsServlet).addMapping("/analytics.js");
// Tasks // Scheduled tasks
SchedulingService schedulingService = new SchedulingService(); ScheduledExecutorService executor = environment.lifecycle().scheduledExecutorService("task-scheduler").build();
schedulingService.register(new OldStatusesCleanupTask(config, cleaningService)); injector.getInstance(OldStatusesCleanupTask.class).register(executor);
schedulingService.register(new OrphansCleanupTask(cleaningService)); injector.getInstance(OrphansCleanupTask.class).register(executor);
// Managed objects // database init/changelogs
environment.lifecycle().manage(startupService); environment.lifecycle().manage(injector.getInstance(StartupService.class));
environment.lifecycle().manage(taskGiver);
environment.lifecycle().manage(feedWorker); // background feed fetching
environment.lifecycle().manage(feedUpdater); environment.lifecycle().manage(injector.getInstance(FeedRefreshTaskGiver.class));
environment.lifecycle().manage(schedulingService); environment.lifecycle().manage(injector.getInstance(FeedRefreshWorker.class));
environment.lifecycle().manage(injector.getInstance(FeedRefreshUpdater.class));
// Swagger // Swagger
environment.jersey().register(new ApiListingResourceJSON()); environment.jersey().register(new ApiListingResourceJSON());
@@ -252,6 +155,23 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
SwaggerConfig swaggerConfig = ConfigFactory.config(); SwaggerConfig swaggerConfig = ConfigFactory.config();
swaggerConfig.setApiVersion("1"); swaggerConfig.setApiVersion("1");
swaggerConfig.setBasePath("/rest"); swaggerConfig.setBasePath("/rest");
// cache configuration
// prevent caching on REST resources, except for favicons
environment.servlets().addFilter("cache-filter", new CacheBustingFilter() {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String path = ((HttpServletRequest) request).getRequestURI();
if (path.contains("/feed/favicon")) {
chain.doFilter(request, response);
} else {
super.doFilter(request, response, chain);
}
}
}).addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/rest/*");
// enable wadl
environment.jersey().disable(ResourceConfig.FEATURE_DISABLE_WADL);
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {

View File

@@ -4,14 +4,19 @@ import io.dropwizard.Configuration;
import io.dropwizard.db.DataSourceFactory; import io.dropwizard.db.DataSourceFactory;
import java.util.Date; import java.util.Date;
import java.util.ResourceBundle;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import lombok.Getter; import lombok.Getter;
import org.apache.commons.lang.time.DateUtils; import org.apache.commons.lang.time.DateUtils;
import org.hibernate.validator.constraints.NotBlank;
import com.commafeed.backend.cache.RedisPoolFactory;
import com.commafeed.frontend.session.SessionManagerFactory;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
@Getter @Getter
@@ -21,70 +26,98 @@ public class CommaFeedConfiguration extends Configuration {
NOOP, REDIS NOOP, REDIS
} }
private ResourceBundle bundle;
public CommaFeedConfiguration() {
bundle = ResourceBundle.getBundle("application");
}
@Valid @Valid
@NotNull @NotNull
@JsonProperty("database") @JsonProperty("database")
private DataSourceFactory database = new DataSourceFactory(); private DataSourceFactory database = new DataSourceFactory();
@Valid
@NotNull
@JsonProperty("redis")
private RedisPoolFactory redisPoolFactory = new RedisPoolFactory();
@Valid
@NotNull
@JsonProperty("session")
private SessionManagerFactory sessionManagerFactory = new SessionManagerFactory();
@Valid @Valid
@NotNull @NotNull
@JsonProperty("app") @JsonProperty("app")
private ApplicationSettings applicationSettings; private ApplicationSettings applicationSettings;
public String getVersion() {
return bundle.getString("version");
}
public String getGitCommit() {
return bundle.getString("git.commit");
}
@Getter @Getter
public static class ApplicationSettings { public static class ApplicationSettings {
@JsonProperty @NotNull
@NotBlank
private String contextPath;
@NotNull
@NotBlank
private String publicUrl; private String publicUrl;
@JsonProperty @NotNull
private boolean allowRegistrations; private boolean allowRegistrations;
@JsonProperty
private String googleAnalyticsTrackingCode; private String googleAnalyticsTrackingCode;
@JsonProperty @NotNull
@Min(1)
private int backgroundThreads; private int backgroundThreads;
@JsonProperty @NotNull
@Min(1)
private int databaseUpdateThreads; private int databaseUpdateThreads;
@JsonProperty
private String smtpHost; private String smtpHost;
@JsonProperty
private int smtpPort; private int smtpPort;
@JsonProperty
private boolean smtpTls; private boolean smtpTls;
@JsonProperty
private String smtpUserName; private String smtpUserName;
@JsonProperty
private String smtpPassword; private String smtpPassword;
@JsonProperty @NotNull
private boolean heavyLoad; private boolean heavyLoad;
@JsonProperty @NotNull
private boolean pubsubhubbub; private boolean pubsubhubbub;
@JsonProperty @NotNull
private boolean imageProxyEnabled; private boolean imageProxyEnabled;
@JsonProperty @NotNull
@Min(0)
private int queryTimeout; private int queryTimeout;
@JsonProperty @NotNull
@Min(0)
private int keepStatusDays; private int keepStatusDays;
@JsonProperty @NotNull
@Min(0)
private int refreshIntervalMinutes; private int refreshIntervalMinutes;
@JsonProperty @NotNull
private CacheType cache; private CacheType cache;
@JsonProperty @NotNull
private String announcement; private String announcement;
public Date getUnreadThreshold() { public Date getUnreadThreshold() {

View File

@@ -0,0 +1,45 @@
package com.commafeed;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.SessionFactory;
import com.codahale.metrics.MetricRegistry;
import com.commafeed.CommaFeedConfiguration.CacheType;
import com.commafeed.backend.cache.CacheService;
import com.commafeed.backend.cache.NoopCacheService;
import com.commafeed.backend.cache.RedisCacheService;
import com.commafeed.backend.favicon.DefaultFaviconFetcher;
import com.commafeed.backend.favicon.AbstractFaviconFetcher;
import com.commafeed.backend.favicon.YoutubeFaviconFetcher;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.multibindings.Multibinder;
@RequiredArgsConstructor
@Slf4j
public class CommaFeedModule extends AbstractModule {
@Getter(onMethod = @__({ @Provides }))
private final SessionFactory sessionFactory;
@Getter(onMethod = @__({ @Provides }))
private final CommaFeedConfiguration config;
@Getter(onMethod = @__({ @Provides }))
private final MetricRegistry metrics;
@Override
protected void configure() {
CacheService cacheService = config.getApplicationSettings().getCache() == CacheType.NOOP ? new NoopCacheService()
: new RedisCacheService(config.getRedisPoolFactory().build());
log.info("using cache {}", cacheService.getClass());
bind(CacheService.class).toInstance(cacheService);
Multibinder<AbstractFaviconFetcher> multibinder = Multibinder.newSetBinder(binder(), AbstractFaviconFetcher.class);
multibinder.addBinding().to(YoutubeFaviconFetcher.class);
multibinder.addBinding().to(DefaultFaviconFetcher.class);
}
}

View File

@@ -0,0 +1,60 @@
package com.commafeed.backend;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.entity.HttpEntityWrapper;
import org.apache.http.protocol.HttpContext;
class ContentEncodingInterceptor implements HttpResponseInterceptor {
private static final Set<String> ALLOWED_CONTENT_ENCODINGS = new HashSet<>(Arrays.asList("gzip", "x-gzip", "deflate", "identity"));
@Override
public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
if (hasContent(response)) {
Header contentEncodingHeader = response.getEntity().getContentEncoding();
if (contentEncodingHeader != null && containsUnsupportedEncodings(contentEncodingHeader)) {
overrideContentEncoding(response);
}
}
}
private boolean containsUnsupportedEncodings(Header contentEncodingHeader) {
HeaderElement[] codecs = contentEncodingHeader.getElements();
for (final HeaderElement codec : codecs) {
String codecName = codec.getName().toLowerCase(Locale.US);
if (!ALLOWED_CONTENT_ENCODINGS.contains(codecName)) {
return true;
}
}
return false;
}
private void overrideContentEncoding(HttpResponse response) {
HttpEntity wrapped = new HttpEntityWrapper(response.getEntity()) {
@Override
public Header getContentEncoding() {
return null;
};
};
response.setEntity(wrapped);
}
private boolean hasContent(HttpResponse response) {
return response.getEntity() != null && response.getEntity().getContentLength() != 0;
}
}

View File

@@ -4,27 +4,25 @@ import java.io.IOException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.http.Consts; import org.apache.http.Consts;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHeaders; import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost; import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor; import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException; import org.apache.http.client.ClientProtocolException;
@@ -37,50 +35,26 @@ import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.ConnectionConfig; import org.apache.http.config.ConnectionConfig;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.HttpEntityWrapper;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import com.commafeed.CommaFeedConfiguration;
/** /**
* Smart HTTP getter: handles gzip, ssl, last modified and etag headers * Smart HTTP getter: handles gzip, ssl, last modified and etag headers
* *
*/ */
@Slf4j @Slf4j
@Singleton
public class HttpGetter { public class HttpGetter {
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";
private static final String CACHE_CONTROL_NO_CACHE = "no-cache"; private static final String CACHE_CONTROL_NO_CACHE = "no-cache";
private static final List<String> ALLOWED_CONTENT_ENCODINGS = Arrays.asList("gzip", "x-gzip", "deflate", "identity"); private static final HttpResponseInterceptor REMOVE_INCORRECT_CONTENT_ENCODING = new ContentEncodingInterceptor();
private static final HttpResponseInterceptor REMOVE_INCORRECT_CONTENT_ENCODING = new HttpResponseInterceptor() {
@Override
public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
HttpEntity entity = response.getEntity();
if (entity != null && entity.getContentLength() != 0) {
Header header = entity.getContentEncoding();
if (header != null) {
HeaderElement[] codecs = header.getElements();
for (final HeaderElement codec : codecs) {
String codecName = codec.getName().toLowerCase(Locale.US);
if (!ALLOWED_CONTENT_ENCODINGS.contains(codecName)) {
response.setEntity(new HttpEntityWrapper(entity) {
@Override
public Header getContentEncoding() {
return null;
};
});
}
}
}
}
}
};
private static SSLContext SSL_CONTEXT = null; private static SSLContext SSL_CONTEXT = null;
static { static {
@@ -92,6 +66,13 @@ public class HttpGetter {
} }
} }
private String userAgent;
@Inject
public HttpGetter(CommaFeedConfiguration config) {
this.userAgent = String.format("CommaFeed/%s (https://www.commafeed.com)", config.getVersion());
}
public HttpResult getBinary(String url, int timeout) throws ClientProtocolException, IOException, NotModifiedException { public HttpResult getBinary(String url, int timeout) throws ClientProtocolException, IOException, NotModifiedException {
return getBinary(url, null, null, timeout); return getBinary(url, null, null, timeout);
} }
@@ -124,7 +105,7 @@ public class HttpGetter {
httpget.addHeader(HttpHeaders.ACCEPT_LANGUAGE, ACCEPT_LANGUAGE); httpget.addHeader(HttpHeaders.ACCEPT_LANGUAGE, ACCEPT_LANGUAGE);
httpget.addHeader(HttpHeaders.PRAGMA, PRAGMA_NO_CACHE); httpget.addHeader(HttpHeaders.PRAGMA, PRAGMA_NO_CACHE);
httpget.addHeader(HttpHeaders.CACHE_CONTROL, CACHE_CONTROL_NO_CACHE); httpget.addHeader(HttpHeaders.CACHE_CONTROL, CACHE_CONTROL_NO_CACHE);
httpget.addHeader(HttpHeaders.USER_AGENT, USER_AGENT); httpget.addHeader(HttpHeaders.USER_AGENT, userAgent);
if (lastModified != null) { if (lastModified != null) {
httpget.addHeader(HttpHeaders.IF_MODIFIED_SINCE, lastModified); httpget.addHeader(HttpHeaders.IF_MODIFIED_SINCE, lastModified);
@@ -183,47 +164,15 @@ public class HttpGetter {
return result; return result;
} }
@Getter
@RequiredArgsConstructor
public static class HttpResult { public static class HttpResult {
private final byte[] content;
private byte[] content; private final String contentType;
private String contentType; private final String lastModifiedSince;
private String lastModifiedSince; private final String eTag;
private String eTag; private final long duration;
private long duration; private final String urlAfterRedirect;
private String urlAfterRedirect;
public HttpResult(byte[] content, String contentType, String lastModifiedSince, String eTag, long duration, String urlAfterRedirect) {
this.content = content;
this.contentType = contentType;
this.lastModifiedSince = lastModifiedSince;
this.eTag = eTag;
this.duration = duration;
this.urlAfterRedirect = urlAfterRedirect;
}
public byte[] getContent() {
return content;
}
public String getContentType() {
return contentType;
}
public String getLastModifiedSince() {
return lastModifiedSince;
}
public String geteTag() {
return eTag;
}
public long getDuration() {
return duration;
}
public String getUrlAfterRedirect() {
return urlAfterRedirect;
}
} }
public static CloseableHttpClient newClient(int timeout) { public static CloseableHttpClient newClient(int timeout) {

View File

@@ -4,10 +4,10 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
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.Pipeline; import redis.clients.jedis.Pipeline;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
@@ -21,38 +21,29 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@Slf4j @Slf4j
@RequiredArgsConstructor
public class RedisCacheService extends CacheService { public class RedisCacheService extends CacheService {
private static ObjectMapper mapper = new ObjectMapper(); private static ObjectMapper MAPPER = new ObjectMapper();
private JedisPool pool; private final JedisPool pool;
public RedisCacheService() {
JedisPoolConfig config = new JedisPoolConfig();
config.setBlockWhenExhausted(false);
pool = new JedisPool(config, "localhost");
}
@Override @Override
public List<String> getLastEntries(Feed feed) { public List<String> getLastEntries(Feed feed) {
List<String> list = Lists.newArrayList(); List<String> list = Lists.newArrayList();
Jedis jedis = pool.getResource(); try (Jedis jedis = pool.getResource()) {
try {
String key = buildRedisEntryKey(feed); String key = buildRedisEntryKey(feed);
Set<String> members = jedis.smembers(key); Set<String> members = jedis.smembers(key);
for (String member : members) { for (String member : members) {
list.add(member); list.add(member);
} }
} finally {
pool.returnResource(jedis);
} }
return list; return list;
} }
@Override @Override
public void setLastEntries(Feed feed, List<String> entries) { public void setLastEntries(Feed feed, List<String> entries) {
Jedis jedis = pool.getResource(); try (Jedis jedis = pool.getResource()) {
try {
String key = buildRedisEntryKey(feed); String key = buildRedisEntryKey(feed);
Pipeline pipe = jedis.pipelined(); Pipeline pipe = jedis.pipelined();
@@ -62,87 +53,72 @@ public class RedisCacheService extends CacheService {
} }
pipe.expire(key, (int) TimeUnit.DAYS.toSeconds(7)); pipe.expire(key, (int) TimeUnit.DAYS.toSeconds(7));
pipe.sync(); pipe.sync();
} finally {
pool.returnResource(jedis);
} }
} }
@Override @Override
public Category getUserRootCategory(User user) { public Category getUserRootCategory(User user) {
Category cat = null; Category cat = null;
Jedis jedis = pool.getResource(); try (Jedis jedis = pool.getResource()) {
try {
String key = buildRedisUserRootCategoryKey(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);
} }
} catch (Exception e) { } catch (Exception e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
} finally {
pool.returnResource(jedis);
} }
return cat; return cat;
} }
@Override @Override
public void setUserRootCategory(User user, Category category) { public void setUserRootCategory(User user, Category category) {
Jedis jedis = pool.getResource(); try (Jedis jedis = pool.getResource()) {
try {
String key = buildRedisUserRootCategoryKey(user); String key = buildRedisUserRootCategoryKey(user);
Pipeline pipe = jedis.pipelined(); Pipeline pipe = jedis.pipelined();
pipe.del(key); pipe.del(key);
pipe.set(key, mapper.writeValueAsString(category)); pipe.set(key, MAPPER.writeValueAsString(category));
pipe.expire(key, (int) TimeUnit.MINUTES.toSeconds(30)); pipe.expire(key, (int) TimeUnit.MINUTES.toSeconds(30));
pipe.sync(); pipe.sync();
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
} finally {
pool.returnResource(jedis);
} }
} }
@Override @Override
public UnreadCount getUnreadCount(FeedSubscription sub) { public UnreadCount getUnreadCount(FeedSubscription sub) {
UnreadCount count = null; UnreadCount count = null;
Jedis jedis = pool.getResource(); try (Jedis jedis = pool.getResource()) {
try {
String key = buildRedisUnreadCountKey(sub); String key = buildRedisUnreadCountKey(sub);
String json = jedis.get(key); String json = jedis.get(key);
if (json != null) { if (json != null) {
count = mapper.readValue(json, UnreadCount.class); count = MAPPER.readValue(json, UnreadCount.class);
} }
} catch (Exception e) { } catch (Exception e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
} finally {
pool.returnResource(jedis);
} }
return count; return count;
} }
@Override @Override
public void setUnreadCount(FeedSubscription sub, UnreadCount count) { public void setUnreadCount(FeedSubscription sub, UnreadCount count) {
Jedis jedis = pool.getResource(); try (Jedis jedis = pool.getResource()) {
try {
String key = buildRedisUnreadCountKey(sub); String key = buildRedisUnreadCountKey(sub);
Pipeline pipe = jedis.pipelined(); Pipeline pipe = jedis.pipelined();
pipe.del(key); pipe.del(key);
pipe.set(key, mapper.writeValueAsString(count)); 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 (Exception e) { } catch (Exception e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
} finally {
pool.returnResource(jedis);
} }
} }
@Override @Override
public void invalidateUserRootCategory(User... users) { public void invalidateUserRootCategory(User... users) {
Jedis jedis = pool.getResource(); try (Jedis jedis = pool.getResource()) {
try {
Pipeline pipe = jedis.pipelined(); Pipeline pipe = jedis.pipelined();
if (users != null) { if (users != null) {
for (User user : users) { for (User user : users) {
@@ -151,15 +127,12 @@ public class RedisCacheService extends CacheService {
} }
} }
pipe.sync(); pipe.sync();
} finally {
pool.returnResource(jedis);
} }
} }
@Override @Override
public void invalidateUnreadCount(FeedSubscription... subs) { public void invalidateUnreadCount(FeedSubscription... subs) {
Jedis jedis = pool.getResource(); try (Jedis jedis = pool.getResource()) {
try {
Pipeline pipe = jedis.pipelined(); Pipeline pipe = jedis.pipelined();
if (subs != null) { if (subs != null) {
for (FeedSubscription sub : subs) { for (FeedSubscription sub : subs) {
@@ -168,8 +141,6 @@ public class RedisCacheService extends CacheService {
} }
} }
pipe.sync(); pipe.sync();
} finally {
pool.returnResource(jedis);
} }
} }

View File

@@ -0,0 +1,28 @@
package com.commafeed.backend.cache;
import lombok.Getter;
import org.apache.commons.lang.StringUtils;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol;
@Getter
public class RedisPoolFactory {
private String host = "localhost";
private int port = Protocol.DEFAULT_PORT;
private String password = null;
private int timeout = Protocol.DEFAULT_TIMEOUT;
private int database = Protocol.DEFAULT_DATABASE;
private int maxTotal = 500;
public JedisPool build() {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(maxTotal);
return new JedisPool(config, host, port, timeout, StringUtils.trimToNull(password), database);
}
}

View File

@@ -2,6 +2,9 @@ package com.commafeed.backend.dao;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.ObjectUtils;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
@@ -12,10 +15,12 @@ import com.commafeed.backend.model.User;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.mysema.query.types.Predicate; import com.mysema.query.types.Predicate;
@Singleton
public class FeedCategoryDAO extends GenericDAO<FeedCategory> { public class FeedCategoryDAO extends GenericDAO<FeedCategory> {
private QFeedCategory category = QFeedCategory.feedCategory; private QFeedCategory category = QFeedCategory.feedCategory;
@Inject
public FeedCategoryDAO(SessionFactory sessionFactory) { public FeedCategoryDAO(SessionFactory sessionFactory) {
super(sessionFactory); super(sessionFactory);
} }
@@ -59,7 +64,7 @@ public class FeedCategoryDAO extends GenericDAO<FeedCategory> {
return list; return list;
} }
public boolean isChild(FeedCategory child, FeedCategory parent) { private boolean isChild(FeedCategory child, FeedCategory parent) {
if (parent == null) { if (parent == null) {
return true; return true;
} }

View File

@@ -3,6 +3,9 @@ package com.commafeed.backend.dao;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
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;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
@@ -14,32 +17,17 @@ import com.commafeed.backend.model.QUser;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.mysema.query.BooleanBuilder; import com.mysema.query.BooleanBuilder;
import com.mysema.query.jpa.hibernate.HibernateQuery; import com.mysema.query.jpa.hibernate.HibernateQuery;
import com.mysema.query.jpa.hibernate.HibernateSubQuery;
@Singleton
public class FeedDAO extends GenericDAO<Feed> { public class FeedDAO extends GenericDAO<Feed> {
private QFeed feed = QFeed.feed; private QFeed feed = QFeed.feed;
@Inject
public FeedDAO(SessionFactory sessionFactory) { public FeedDAO(SessionFactory sessionFactory) {
super(sessionFactory); super(sessionFactory);
} }
public Long getUpdatableCount(Date lastLoginThreshold) {
BooleanBuilder disabledDatePredicate = new BooleanBuilder();
disabledDatePredicate.or(feed.disabledUntil.isNull());
disabledDatePredicate.or(feed.disabledUntil.lt(new Date()));
HibernateQuery query = newQuery().from(feed).where(disabledDatePredicate);
if (lastLoginThreshold != null) {
QFeedSubscription sub = QFeedSubscription.feedSubscription;
QUser user = QUser.user;
HibernateSubQuery subquery = new HibernateSubQuery();
subquery.from(sub).join(sub.user, user).where(sub.feed.eq(feed), user.lastLogin.gt(lastLoginThreshold));
query.where(subquery.exists());
}
return query.orderBy(feed.disabledUntil.asc()).count();
}
public List<Feed> findNextUpdatable(int count, Date lastLoginThreshold) { public List<Feed> findNextUpdatable(int count, Date lastLoginThreshold) {
BooleanBuilder disabledDatePredicate = new BooleanBuilder(); BooleanBuilder disabledDatePredicate = new BooleanBuilder();
disabledDatePredicate.or(feed.disabledUntil.isNull()); disabledDatePredicate.or(feed.disabledUntil.isNull());

View File

@@ -2,25 +2,29 @@ package com.commafeed.backend.dao;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import com.commafeed.backend.model.FeedEntryContent; import com.commafeed.backend.model.FeedEntryContent;
import com.commafeed.backend.model.QFeedEntry; import com.commafeed.backend.model.QFeedEntry;
import com.commafeed.backend.model.QFeedEntryContent; import com.commafeed.backend.model.QFeedEntryContent;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.mysema.query.types.ConstructorExpression;
@Singleton
public class FeedEntryContentDAO extends GenericDAO<FeedEntryContent> { public class FeedEntryContentDAO extends GenericDAO<FeedEntryContent> {
private QFeedEntryContent content = QFeedEntryContent.feedEntryContent; private QFeedEntryContent content = QFeedEntryContent.feedEntryContent;
@Inject
public FeedEntryContentDAO(SessionFactory sessionFactory) { public FeedEntryContentDAO(SessionFactory sessionFactory) {
super(sessionFactory); super(sessionFactory);
} }
public Long findExisting(String contentHash, String titleHash) { public Long findExisting(String contentHash, String titleHash) {
List<Long> list = newQuery().from(content).where(content.contentHash.eq(contentHash), content.titleHash.eq(titleHash)).limit(1) List<Long> list = newQuery().from(content).where(content.contentHash.eq(contentHash), content.titleHash.eq(titleHash)).limit(1)
.list(ConstructorExpression.create(Long.class, content.id)); .list(content.id);
return Iterables.getFirst(list, null); return Iterables.getFirst(list, null);
} }

View File

@@ -3,6 +3,9 @@ package com.commafeed.backend.dao;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
@@ -12,19 +15,20 @@ import com.commafeed.backend.model.QFeed;
import com.commafeed.backend.model.QFeedEntry; import com.commafeed.backend.model.QFeedEntry;
import com.commafeed.backend.model.QFeedSubscription; import com.commafeed.backend.model.QFeedSubscription;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.mysema.query.types.ConstructorExpression;
@Singleton
public class FeedEntryDAO extends GenericDAO<FeedEntry> { public class FeedEntryDAO extends GenericDAO<FeedEntry> {
private QFeedEntry entry = QFeedEntry.feedEntry; private QFeedEntry entry = QFeedEntry.feedEntry;
@Inject
public FeedEntryDAO(SessionFactory sessionFactory) { public FeedEntryDAO(SessionFactory sessionFactory) {
super(sessionFactory); super(sessionFactory);
} }
public Long findExisting(String guid, Feed feed) { public Long findExisting(String guid, Feed feed) {
List<Long> list = newQuery().from(entry).where(entry.guidHash.eq(DigestUtils.sha1Hex(guid)), entry.feed.eq(feed)).limit(1) List<Long> list = newQuery().from(entry).where(entry.guidHash.eq(DigestUtils.sha1Hex(guid)), entry.feed.eq(feed)).limit(1)
.list(ConstructorExpression.create(Long.class, entry.id)); .list(entry.id);
return Iterables.getFirst(list, null); return Iterables.getFirst(list, null);
} }
@@ -35,13 +39,6 @@ public class FeedEntryDAO extends GenericDAO<FeedEntry> {
.list(entry); .list(entry);
} }
public int delete(Feed feed, int max) {
List<FeedEntry> list = newQuery().from(entry).where(entry.feed.eq(feed)).limit(max).list(entry);
int deleted = list.size();
delete(list);
return deleted;
}
public int delete(Date olderThan, int max) { public int delete(Date olderThan, int max) {
List<FeedEntry> list = newQuery().from(entry).where(entry.inserted.lt(olderThan)).limit(max).list(entry); List<FeedEntry> list = newQuery().from(entry).where(entry.inserted.lt(olderThan)).limit(max).list(entry);
int deleted = list.size(); int deleted = list.size();

View File

@@ -3,21 +3,13 @@ package com.commafeed.backend.dao;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.CompareToBuilder; import org.apache.commons.lang.builder.CompareToBuilder;
import org.hibernate.Criteria;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import org.hibernate.criterion.Conjunction;
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.CommaFeedConfiguration; import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.FixedSizeSortedSet; import com.commafeed.backend.FixedSizeSortedSet;
@@ -36,20 +28,23 @@ 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.Ordering; import com.google.common.collect.Ordering;
import com.mysema.query.BooleanBuilder;
import com.mysema.query.Tuple;
import com.mysema.query.jpa.hibernate.HibernateQuery; import com.mysema.query.jpa.hibernate.HibernateQuery;
@Singleton
public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> { public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
private static final String ALIAS_STATUS = "status";
private static final String ALIAS_ENTRY = "entry";
private static final String ALIAS_TAG = "tag";
private FeedEntryDAO feedEntryDAO; private FeedEntryDAO feedEntryDAO;
private FeedEntryTagDAO feedEntryTagDAO; private FeedEntryTagDAO feedEntryTagDAO;
private CommaFeedConfiguration config; private CommaFeedConfiguration config;
private QFeedEntryStatus status = QFeedEntryStatus.feedEntryStatus; private QFeedEntryStatus status = QFeedEntryStatus.feedEntryStatus;
private QFeedEntry entry = QFeedEntry.feedEntry;
private QFeedEntryContent content = QFeedEntryContent.feedEntryContent;
private QFeedEntryTag entryTag = QFeedEntryTag.feedEntryTag;
@Inject
public FeedEntryStatusDAO(SessionFactory sessionFactory, FeedEntryDAO feedEntryDAO, FeedEntryTagDAO feedEntryTagDAO, public FeedEntryStatusDAO(SessionFactory sessionFactory, FeedEntryDAO feedEntryDAO, FeedEntryTagDAO feedEntryTagDAO,
CommaFeedConfiguration config) { CommaFeedConfiguration config) {
super(sessionFactory); super(sessionFactory);
@@ -112,88 +107,79 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
List<FeedEntryStatus> statuses = query.list(status); List<FeedEntryStatus> statuses = query.list(status);
for (FeedEntryStatus status : statuses) { for (FeedEntryStatus status : statuses) {
status = handleStatus(user, status, status.getSubscription(), status.getEntry()); status = handleStatus(user, status, status.getSubscription(), status.getEntry());
status = fetchTags(user, status); fetchTags(user, status);
} }
return lazyLoadContent(includeContent, statuses); return lazyLoadContent(includeContent, statuses);
} }
private Criteria buildSearchCriteria(User user, FeedSubscription sub, boolean unreadOnly, String keywords, Date newerThan, int offset, private HibernateQuery buildQuery(User user, FeedSubscription sub, boolean unreadOnly, String keywords, Date newerThan, int offset,
int limit, ReadingOrder order, Date last, String tag) { int limit, ReadingOrder order, Date last, String tag) {
QFeedEntry entry = QFeedEntry.feedEntry;
QFeedEntryContent content = QFeedEntryContent.feedEntryContent;
QFeedEntryStatus status = QFeedEntryStatus.feedEntryStatus;
QFeedEntryTag entryTag = QFeedEntryTag.feedEntryTag;
Criteria criteria = currentSession().createCriteria(FeedEntry.class, ALIAS_ENTRY); HibernateQuery query = newQuery().from(entry).where(entry.feed.eq(sub.getFeed()));
criteria.add(Restrictions.eq(entry.feed.getMetadata().getName(), sub.getFeed()));
if (keywords != null) { if (keywords != null) {
Criteria contentJoin = criteria.createCriteria(entry.content.getMetadata().getName(), "content", JoinType.INNER_JOIN); query.join(entry.content, content);
for (String keyword : StringUtils.split(keywords)) { for (String keyword : StringUtils.split(keywords)) {
Disjunction or = Restrictions.disjunction(); BooleanBuilder or = new BooleanBuilder();
or.add(Restrictions.ilike(content.content.getMetadata().getName(), keyword, MatchMode.ANYWHERE)); or.or(content.content.containsIgnoreCase(keyword));
or.add(Restrictions.ilike(content.title.getMetadata().getName(), keyword, MatchMode.ANYWHERE)); or.or(content.title.containsIgnoreCase(keyword));
contentJoin.add(or); query.where(or);
} }
} }
Criteria statusJoin = criteria.createCriteria(entry.statuses.getMetadata().getName(), ALIAS_STATUS, JoinType.LEFT_OUTER_JOIN, query.leftJoin(entry.statuses, status).on(status.subscription.id.eq(sub.getId()));
Restrictions.eq(status.subscription.getMetadata().getName(), sub));
if (unreadOnly && tag == null) { if (unreadOnly && tag == null) {
BooleanBuilder or = new BooleanBuilder();
Disjunction or = Restrictions.disjunction(); or.or(status.read.isNull());
or.add(Restrictions.isNull(status.read.getMetadata().getName())); or.or(status.read.isFalse());
or.add(Restrictions.eq(status.read.getMetadata().getName(), false)); query.where(or);
statusJoin.add(or);
Date unreadThreshold = config.getApplicationSettings().getUnreadThreshold(); Date unreadThreshold = config.getApplicationSettings().getUnreadThreshold();
if (unreadThreshold != null) { if (unreadThreshold != null) {
criteria.add(Restrictions.ge(entry.updated.getMetadata().getName(), unreadThreshold)); query.where(entry.updated.goe(unreadThreshold));
} }
} }
if (tag != null) { if (tag != null) {
Conjunction and = Restrictions.conjunction(); BooleanBuilder and = new BooleanBuilder();
and.add(Restrictions.eq(entryTag.user.getMetadata().getName(), user)); and.and(entryTag.user.id.eq(user.getId()));
and.add(Restrictions.eq(entryTag.name.getMetadata().getName(), tag)); and.and(entryTag.name.eq(tag));
criteria.createCriteria(entry.tags.getMetadata().getName(), ALIAS_TAG, JoinType.INNER_JOIN, and); query.join(entry.tags, entryTag).on(and);
} }
if (newerThan != null) { if (newerThan != null) {
criteria.add(Restrictions.ge(entry.inserted.getMetadata().getName(), newerThan)); query.where(entry.inserted.goe(newerThan));
} }
if (last != null) { if (last != null) {
if (order == ReadingOrder.desc) { if (order == ReadingOrder.desc) {
criteria.add(Restrictions.gt(entry.updated.getMetadata().getName(), last)); query.where(entry.updated.gt(last));
} else { } else {
criteria.add(Restrictions.lt(entry.updated.getMetadata().getName(), last)); query.where(entry.updated.lt(last));
} }
} }
if (order != null) { if (order != null) {
if (order == ReadingOrder.asc) { if (order == ReadingOrder.asc) {
criteria.addOrder(Order.asc(entry.updated.getMetadata().getName())).addOrder(Order.asc(entry.id.getMetadata().getName())); query.orderBy(entry.updated.asc(), entry.id.asc());
} else { } else {
criteria.addOrder(Order.desc(entry.updated.getMetadata().getName())).addOrder(Order.desc(entry.id.getMetadata().getName())); query.orderBy(entry.updated.desc(), entry.id.desc());
} }
} }
if (offset > -1) { if (offset > -1) {
criteria.setFirstResult(offset); query.offset(offset);
} }
if (limit > -1) { if (limit > -1) {
criteria.setMaxResults(limit); query.limit(limit);
} }
int timeout = config.getApplicationSettings().getQueryTimeout(); int timeout = config.getApplicationSettings().getQueryTimeout();
if (timeout > 0) { if (timeout > 0) {
// hibernate timeout is in seconds, jpa timeout is in millis query.setTimeout(timeout / 1000);
criteria.setTimeout(timeout / 1000);
} }
return criteria; return query;
} }
@SuppressWarnings("unchecked")
public List<FeedEntryStatus> findBySubscriptions(User user, List<FeedSubscription> subs, boolean unreadOnly, String keywords, public List<FeedEntryStatus> findBySubscriptions(User user, List<FeedSubscription> subs, boolean unreadOnly, String keywords,
Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent, boolean onlyIds, String tag) { Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent, boolean onlyIds, String tag) {
int capacity = offset + limit; int capacity = offset + limit;
@@ -201,18 +187,12 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
FixedSizeSortedSet<FeedEntryStatus> set = new FixedSizeSortedSet<FeedEntryStatus>(capacity, comparator); FixedSizeSortedSet<FeedEntryStatus> set = new FixedSizeSortedSet<FeedEntryStatus>(capacity, comparator);
for (FeedSubscription sub : subs) { for (FeedSubscription sub : subs) {
Date last = (order != null && set.isFull()) ? set.last().getEntryUpdated() : null; Date last = (order != null && set.isFull()) ? set.last().getEntryUpdated() : null;
Criteria criteria = buildSearchCriteria(user, sub, unreadOnly, keywords, newerThan, -1, capacity, order, last, tag); HibernateQuery query = buildQuery(user, sub, unreadOnly, keywords, newerThan, -1, capacity, order, last, tag);
ProjectionList projection = Projections.projectionList(); List<Tuple> tuples = query.list(entry.id, entry.updated, status.id);
projection.add(Projections.property("id"), "id"); for (Tuple tuple : tuples) {
projection.add(Projections.property("updated"), "updated"); Long id = tuple.get(entry.id);
projection.add(Projections.property(ALIAS_STATUS + ".id"), "status_id"); Date updated = tuple.get(entry.updated);
criteria.setProjection(projection); Long statusId = tuple.get(status.id);
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(); FeedEntry entry = new FeedEntry();
entry.setId(id); entry.setId(id);
@@ -253,19 +233,13 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
return statuses; return statuses;
} }
@SuppressWarnings("unchecked")
public UnreadCount getUnreadCount(User user, FeedSubscription subscription) { public UnreadCount getUnreadCount(User user, FeedSubscription subscription) {
UnreadCount uc = null; UnreadCount uc = null;
Criteria criteria = buildSearchCriteria(user, subscription, true, null, null, -1, -1, null, null, null); HibernateQuery query = buildQuery(user, subscription, true, null, null, -1, -1, null, null, null);
ProjectionList projection = Projections.projectionList(); List<Tuple> tuples = query.list(entry.count(), entry.updated.max());
projection.add(Projections.rowCount(), "count"); for (Tuple tuple : tuples) {
projection.add(Projections.max(QFeedEntry.feedEntry.updated.getMetadata().getName()), "updated"); Long count = tuple.get(entry.count());
criteria.setProjection(projection); Date updated = tuple.get(entry.updated.max());
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); uc = new UnreadCount(subscription.getId(), count, updated);
} }
return uc; return uc;

View File

@@ -2,24 +2,28 @@ package com.commafeed.backend.dao;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryTag; import com.commafeed.backend.model.FeedEntryTag;
import com.commafeed.backend.model.QFeedEntryTag; import com.commafeed.backend.model.QFeedEntryTag;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
import com.mysema.query.types.ConstructorExpression;
@Singleton
public class FeedEntryTagDAO extends GenericDAO<FeedEntryTag> { public class FeedEntryTagDAO extends GenericDAO<FeedEntryTag> {
private QFeedEntryTag tag = QFeedEntryTag.feedEntryTag; private QFeedEntryTag tag = QFeedEntryTag.feedEntryTag;
@Inject
public FeedEntryTagDAO(SessionFactory sessionFactory) { public FeedEntryTagDAO(SessionFactory sessionFactory) {
super(sessionFactory); super(sessionFactory);
} }
public List<String> findByUser(User user) { public List<String> findByUser(User user) {
return newQuery().from(tag).where(tag.user.eq(user)).distinct().list(ConstructorExpression.create(String.class, tag.name)); return newQuery().from(tag).where(tag.user.eq(user)).distinct().list(tag.name);
} }
public List<FeedEntryTag> findByEntry(User user, FeedEntry entry) { public List<FeedEntryTag> findByEntry(User user, FeedEntry entry) {

View File

@@ -2,6 +2,9 @@ package com.commafeed.backend.dao;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
@@ -15,10 +18,12 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.mysema.query.jpa.hibernate.HibernateQuery; import com.mysema.query.jpa.hibernate.HibernateQuery;
@Singleton
public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> { public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
private QFeedSubscription sub = QFeedSubscription.feedSubscription; private QFeedSubscription sub = QFeedSubscription.feedSubscription;
@Inject
public FeedSubscriptionDAO(SessionFactory sessionFactory) { public FeedSubscriptionDAO(SessionFactory sessionFactory) {
super(sessionFactory); super(sessionFactory);
} }

View File

@@ -11,7 +11,7 @@ import com.mysema.query.jpa.hibernate.HibernateQuery;
public abstract class GenericDAO<T extends AbstractModel> extends AbstractDAO<T> { public abstract class GenericDAO<T extends AbstractModel> extends AbstractDAO<T> {
public GenericDAO(SessionFactory sessionFactory) { protected GenericDAO(SessionFactory sessionFactory) {
super(sessionFactory); super(sessionFactory);
} }
@@ -56,9 +56,4 @@ public abstract class GenericDAO<T extends AbstractModel> extends AbstractDAO<T>
return objects.size(); return objects.size();
} }
protected void setTimeout(javax.persistence.Query query, int queryTimeout) {
if (queryTimeout > 0) {
query.setHint("javax.persistence.query.timeout", queryTimeout);
}
}
} }

View File

@@ -1,15 +1,20 @@
package com.commafeed.backend.dao; package com.commafeed.backend.dao;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import com.commafeed.backend.model.QUser; import com.commafeed.backend.model.QUser;
import com.commafeed.backend.model.QUserRole; import com.commafeed.backend.model.QUserRole;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
@Singleton
public class UserDAO extends GenericDAO<User> { public class UserDAO extends GenericDAO<User> {
private QUser user = QUser.user; private QUser user = QUser.user;
@Inject
public UserDAO(SessionFactory sessionFactory) { public UserDAO(SessionFactory sessionFactory) {
super(sessionFactory); super(sessionFactory);
} }

View File

@@ -3,6 +3,9 @@ package com.commafeed.backend.dao;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import com.commafeed.backend.model.QUserRole; import com.commafeed.backend.model.QUserRole;
@@ -11,10 +14,12 @@ import com.commafeed.backend.model.UserRole;
import com.commafeed.backend.model.UserRole.Role; import com.commafeed.backend.model.UserRole.Role;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@Singleton
public class UserRoleDAO extends GenericDAO<UserRole> { public class UserRoleDAO extends GenericDAO<UserRole> {
private QUserRole role = QUserRole.userRole; private QUserRole role = QUserRole.userRole;
@Inject
public UserRoleDAO(SessionFactory sessionFactory) { public UserRoleDAO(SessionFactory sessionFactory) {
super(sessionFactory); super(sessionFactory);
} }

View File

@@ -1,15 +1,20 @@
package com.commafeed.backend.dao; package com.commafeed.backend.dao;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import com.commafeed.backend.model.QUserSettings; import com.commafeed.backend.model.QUserSettings;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserSettings; import com.commafeed.backend.model.UserSettings;
@Singleton
public class UserSettingsDAO extends GenericDAO<UserSettings> { public class UserSettingsDAO extends GenericDAO<UserSettings> {
private QUserSettings settings = QUserSettings.userSettings; private QUserSettings settings = QUserSettings.userSettings;
@Inject
public UserSettingsDAO(SessionFactory sessionFactory) { public UserSettingsDAO(SessionFactory sessionFactory) {
super(sessionFactory); super(sessionFactory);
} }

View File

@@ -0,0 +1,51 @@
package com.commafeed.backend.favicon;
import java.util.Arrays;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import com.commafeed.backend.model.Feed;
@Slf4j
public abstract class AbstractFaviconFetcher {
private static List<String> ICON_MIMETYPE_BLACKLIST = Arrays.asList("application/xml", "text/html");
private static long MIN_ICON_LENGTH = 100;
private static long MAX_ICON_LENGTH = 100000;
protected static int TIMEOUT = 4000;
public abstract byte[] fetch(Feed feed);
protected boolean isValidIconResponse(byte[] content, String contentType) {
if (content == null) {
return false;
}
long length = content.length;
if (StringUtils.isNotBlank(contentType)) {
contentType = contentType.split(";")[0];
}
if (ICON_MIMETYPE_BLACKLIST.contains(contentType)) {
log.debug("Content-Type {} is blacklisted", contentType);
return false;
}
if (length < MIN_ICON_LENGTH) {
log.debug("Length {} below MIN_ICON_LENGTH {}", length, MIN_ICON_LENGTH);
return false;
}
if (length > MAX_ICON_LENGTH) {
log.debug("Length {} greater than MAX_ICON_LENGTH {}", length, MAX_ICON_LENGTH);
return false;
}
return true;
}
}

View File

@@ -1,9 +1,9 @@
package com.commafeed.backend.feed; package com.commafeed.backend.favicon;
import java.util.Arrays; import javax.inject.Inject;
import java.util.List; import javax.inject.Singleton;
import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@@ -13,26 +13,23 @@ import org.jsoup.select.Elements;
import com.commafeed.backend.HttpGetter; import com.commafeed.backend.HttpGetter;
import com.commafeed.backend.HttpGetter.HttpResult; import com.commafeed.backend.HttpGetter.HttpResult;
import com.commafeed.backend.feed.FeedUtils;
import com.commafeed.backend.model.Feed;
/** /**
* Inspired/Ported from https://github.com/potatolondon/getfavicon * Inspired/Ported from https://github.com/potatolondon/getfavicon
* *
*/ */
@Slf4j @Slf4j
@AllArgsConstructor @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
public class FaviconFetcher { @Singleton
public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
private static long MIN_ICON_LENGTH = 100;
private static long MAX_ICON_LENGTH = 100000;
private static int TIMEOUT = 4000;
protected static List<String> ICON_MIMETYPES = Arrays.asList("image/x-icon", "image/vnd.microsoft.icon", "image/ico", "image/icon",
"text/ico", "application/ico", "image/x-ms-bmp", "image/x-bmp", "image/gif", "image/png", "image/jpeg");
private static List<String> ICON_MIMETYPE_BLACKLIST = Arrays.asList("application/xml", "text/html");
private final HttpGetter getter; private final HttpGetter getter;
public byte[] fetch(String url) { @Override
public byte[] fetch(Feed feed) {
String url = feed.getLink() != null ? feed.getLink() : feed.getUrl();
if (url == null) { if (url == null) {
log.debug("url is null"); log.debug("url is null");
@@ -70,7 +67,7 @@ public class FaviconFetcher {
bytes = result.getContent(); bytes = result.getContent();
contentType = result.getContentType(); contentType = result.getContentType();
} catch (Exception e) { } catch (Exception e) {
log.debug("Failed to retrieve iconAtRoot: " + e.getMessage(), e); log.debug("Failed to retrieve iconAtRoot for url {}: ", url, e);
} }
if (!isValidIconResponse(bytes, contentType)) { if (!isValidIconResponse(bytes, contentType)) {
@@ -79,35 +76,6 @@ public class FaviconFetcher {
return bytes; return bytes;
} }
private boolean isValidIconResponse(byte[] content, String contentType) {
if (content == null) {
return false;
}
long length = content.length;
if (StringUtils.isNotBlank(contentType)) {
contentType = contentType.split(";")[0];
}
if (ICON_MIMETYPE_BLACKLIST.contains(contentType)) {
log.debug("Content-Type {} is blacklisted", contentType);
return false;
}
if (length < MIN_ICON_LENGTH) {
log.debug("Length {} below MIN_ICON_LENGTH {}", length, MIN_ICON_LENGTH);
return false;
}
if (length > MAX_ICON_LENGTH) {
log.debug("Length {} greater than MAX_ICON_LENGTH {}", length, MAX_ICON_LENGTH);
return false;
}
return true;
}
private byte[] getIconInPage(String url) { private byte[] getIconInPage(String url) {
Document doc = null; Document doc = null;
@@ -115,7 +83,7 @@ public class FaviconFetcher {
HttpResult result = getter.getBinary(url, TIMEOUT); HttpResult result = getter.getBinary(url, TIMEOUT);
doc = Jsoup.parse(new String(result.getContent()), url); doc = Jsoup.parse(new String(result.getContent()), url);
} catch (Exception e) { } catch (Exception e) {
log.debug("Failed to retrieve page to find icon"); log.debug("Failed to retrieve page to find icon", e);
return null; return null;
} }
@@ -141,7 +109,7 @@ public class FaviconFetcher {
bytes = result.getContent(); bytes = result.getContent();
contentType = result.getContentType(); contentType = result.getContentType();
} catch (Exception e) { } catch (Exception e) {
log.debug("Failed to retrieve icon found in page {}", href); log.debug("Failed to retrieve icon found in page {}", href, e);
return null; return null;
} }
@@ -152,5 +120,4 @@ public class FaviconFetcher {
return bytes; return bytes;
} }
} }

View File

@@ -0,0 +1,90 @@
package com.commafeed.backend.favicon;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import com.commafeed.backend.HttpGetter;
import com.commafeed.backend.HttpGetter.HttpResult;
import com.commafeed.backend.model.Feed;
@Slf4j
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class YoutubeFaviconFetcher extends AbstractFaviconFetcher {
private final HttpGetter getter;
@Override
public byte[] fetch(Feed feed) {
String url = feed.getUrl();
if (!url.toLowerCase().contains("://gdata.youtube.com/")) {
return null;
}
String userName = extractUserName(url);
if (userName == null) {
return null;
}
String profileUrl = "https://gdata.youtube.com/feeds/users/" + userName;
byte[] bytes = null;
String contentType = null;
try {
log.debug("Getting YouTube user's icon, {}", url);
// initial get to translate username to obscure user thumbnail URL
HttpResult profileResult = getter.getBinary(profileUrl, TIMEOUT);
Document doc = Jsoup.parse(new String(profileResult.getContent()), profileUrl);
Elements thumbnails = doc.select("media|thumbnail");
if (thumbnails.isEmpty()) {
return null;
}
String thumbnailUrl = thumbnails.get(0).attr("abs:url");
int thumbnailStart = thumbnailUrl.indexOf("<media:thumbnail url='");
int thumbnailEnd = thumbnailUrl.indexOf("'/>", thumbnailStart);
if (thumbnailStart != -1) {
thumbnailUrl = thumbnailUrl.substring(thumbnailStart + "<media:thumbnail url='".length(), thumbnailEnd);
}
// final get to actually retrieve the thumbnail
HttpResult iconResult = getter.getBinary(thumbnailUrl, TIMEOUT);
bytes = iconResult.getContent();
contentType = iconResult.getContentType();
} catch (Exception e) {
log.debug("Failed to retrieve YouTube icon", e);
}
if (!isValidIconResponse(bytes, contentType)) {
bytes = null;
}
return bytes;
}
private String extractUserName(String url) {
int apiOrBase = url.indexOf("/users/");
if (apiOrBase == -1) {
return null;
}
int userEndSlash = url.indexOf('/', apiOrBase + "/users/".length());
if (userEndSlash == -1) {
return null;
}
return url.substring(apiOrBase + "/users/".length(), userEndSlash);
}
}

View File

@@ -0,0 +1,48 @@
package com.commafeed.backend.feed;
import java.util.regex.Pattern;
/**
* This code is copied and simplified from GWT
* https://github.com/google-web-toolkit/gwt/blob/master/user/src/com/google/gwt/i18n/shared/BidiUtils.java Released under Apache 2.0
* license, credit of it goes to Google and please use GWT wherever possible instead of this
*/
class EstimateDirection {
private static final float RTL_DETECTION_THRESHOLD = 0.40f;
private static final String LTR_CHARS = "A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF"
+ "\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF";
private static final String RTL_CHARS = "\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC";
private static final Pattern WORD_SEPARATOR_RE = Pattern.compile("\\s+");
private static final Pattern FIRST_STRONG_IS_RTL_RE = Pattern.compile("^[^" + LTR_CHARS + "]*[" + RTL_CHARS + ']');
private static final Pattern IS_REQUIRED_LTR_RE = Pattern.compile("^http://.*");
private static final Pattern HAS_ANY_LTR_RE = Pattern.compile("[" + LTR_CHARS + ']');
private static boolean startsWithRtl(String str) {
return FIRST_STRONG_IS_RTL_RE.matcher(str).matches();
}
private static boolean hasAnyLtr(String str) {
return HAS_ANY_LTR_RE.matcher(str).matches();
}
static boolean isRTL(String str) {
int rtlCount = 0;
int total = 0;
String[] tokens = WORD_SEPARATOR_RE.split(str, 20); // limit splits to 20, usually enough
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i];
if (startsWithRtl(token)) {
rtlCount++;
total++;
} else if (IS_REQUIRED_LTR_RE.matcher(token).matches()) {
// do nothing
} else if (hasAnyLtr(token)) {
total++;
}
}
return total == 0 ? false : ((float) rtlCount / total > RTL_DETECTION_THRESHOLD ? true : false);
}
}

View File

@@ -3,7 +3,10 @@ package com.commafeed.backend.feed;
import java.io.IOException; import java.io.IOException;
import java.util.Date; import java.util.Date;
import lombok.AllArgsConstructor; import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.StringUtils; import org.apache.commons.codec.binary.StringUtils;
@@ -17,10 +20,11 @@ import com.commafeed.backend.HttpGetter;
import com.commafeed.backend.HttpGetter.HttpResult; import com.commafeed.backend.HttpGetter.HttpResult;
import com.commafeed.backend.HttpGetter.NotModifiedException; 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.rometools.rome.io.FeedException;
@Slf4j @Slf4j
@AllArgsConstructor @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class FeedFetcher { public class FeedFetcher {
private final FeedParser parser; private final FeedParser parser;
@@ -73,7 +77,7 @@ public class FeedFetcher {
Feed feed = fetchedFeed.getFeed(); Feed feed = fetchedFeed.getFeed();
feed.setLastModifiedHeader(result.getLastModifiedSince()); feed.setLastModifiedHeader(result.getLastModifiedSince());
feed.setEtagHeader(FeedUtils.truncate(result.geteTag(), 255)); feed.setEtagHeader(FeedUtils.truncate(result.getETag(), 255));
feed.setLastContentHash(hash); feed.setLastContentHash(hash);
fetchedFeed.setFetchDuration(result.getDuration()); fetchedFeed.setFetchDuration(result.getDuration());
fetchedFeed.setUrlAfterRedirect(result.getUrlAfterRedirect()); fetchedFeed.setUrlAfterRedirect(result.getUrlAfterRedirect());
@@ -89,9 +93,9 @@ public class FeedFetcher {
Elements atom = doc.select("link[type=application/atom+xml]"); Elements atom = doc.select("link[type=application/atom+xml]");
Elements rss = doc.select("link[type=application/rss+xml]"); Elements rss = doc.select("link[type=application/rss+xml]");
if (!atom.isEmpty()) { if (!atom.isEmpty()) {
foundUrl = atom.get(0).attr("abs:href").toString(); foundUrl = atom.get(0).attr("abs:href");
} else if (!rss.isEmpty()) { } else if (!rss.isEmpty()) {
foundUrl = rss.get(0).attr("abs:href").toString(); foundUrl = rss.get(0).attr("abs:href");
} }
} }
return foundUrl; return foundUrl;

View File

@@ -5,12 +5,16 @@ import java.text.DateFormat;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; 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.jdom2.Element;
import org.jdom.Namespace; import org.jdom2.Namespace;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
@@ -19,16 +23,18 @@ import com.commafeed.backend.model.FeedEntryContent;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.collect.Collections2; import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.sun.syndication.feed.synd.SyndContent; import com.rometools.rome.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndEnclosure; import com.rometools.rome.feed.synd.SyndEnclosure;
import com.sun.syndication.feed.synd.SyndEntry; import com.rometools.rome.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed; import com.rometools.rome.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndLink; import com.rometools.rome.feed.synd.SyndLink;
import com.sun.syndication.feed.synd.SyndLinkImpl; import com.rometools.rome.feed.synd.SyndLinkImpl;
import com.sun.syndication.io.FeedException; import com.rometools.rome.io.FeedException;
import com.sun.syndication.io.SyndFeedInput; import com.rometools.rome.io.SyndFeedInput;
@Slf4j @Slf4j
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class FeedParser { public class FeedParser {
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";
@@ -38,12 +44,12 @@ public class FeedParser {
private static final Date END = new Date(1000l * Integer.MAX_VALUE - 86400000); private static final Date END = new Date(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>() {
@Override
public String apply(SyndContent content) { public String apply(SyndContent content) {
return content.getValue(); return content.getValue();
} }
}; };
@SuppressWarnings("unchecked")
public FetchedFeed parse(String feedUrl, byte[] xml) throws FeedException { public FetchedFeed parse(String feedUrl, byte[] xml) throws FeedException {
FetchedFeed fetchedFeed = new FetchedFeed(); FetchedFeed fetchedFeed = new FetchedFeed();
Feed feed = fetchedFeed.getFeed(); Feed feed = fetchedFeed.getFeed();
@@ -91,7 +97,7 @@ public class FeedParser {
content.setContent(getContent(item)); content.setContent(getContent(item));
content.setTitle(getTitle(item)); content.setTitle(getTitle(item));
content.setAuthor(StringUtils.trimToNull(item.getAuthor())); content.setAuthor(StringUtils.trimToNull(item.getAuthor()));
SyndEnclosure enclosure = (SyndEnclosure) Iterables.getFirst(item.getEnclosures(), null); SyndEnclosure enclosure = Iterables.getFirst(item.getEnclosures(), null);
if (enclosure != null) { if (enclosure != null) {
content.setEnclosureUrl(FeedUtils.truncate(enclosure.getUrl(), 2048)); content.setEnclosureUrl(FeedUtils.truncate(enclosure.getUrl(), 2048));
content.setEnclosureType(enclosure.getType()); content.setEnclosureType(enclosure.getType());
@@ -121,24 +127,17 @@ public class FeedParser {
/** /**
* Adds atom links for rss feeds * Adds atom links for rss feeds
*/ */
@SuppressWarnings({ "unchecked", "rawtypes" })
private void handleForeignMarkup(SyndFeed feed) { private void handleForeignMarkup(SyndFeed feed) {
Object foreignMarkup = feed.getForeignMarkup(); List<Element> foreignMarkup = feed.getForeignMarkup();
if (foreignMarkup == null) { if (foreignMarkup == null) {
return; return;
} }
if (foreignMarkup instanceof List) { for (Element element : foreignMarkup) {
List elements = (List) foreignMarkup; if ("link".equals(element.getName()) && ATOM_10_NS.equals(element.getNamespace())) {
for (Object object : elements) { SyndLink link = new SyndLinkImpl();
if (object instanceof Element) { link.setRel(element.getAttributeValue("rel"));
Element element = (Element) object; link.setHref(element.getAttributeValue("href"));
if ("link".equals(element.getName()) && ATOM_10_NS.equals(element.getNamespace())) { feed.getLinks().add(link);
SyndLink link = new SyndLinkImpl();
link.setRel(element.getAttributeValue("rel"));
link.setHref(element.getAttributeValue("href"));
feed.getLinks().add(link);
}
}
} }
} }
} }
@@ -169,7 +168,6 @@ public class FeedParser {
return date; return date;
} }
@SuppressWarnings("unchecked")
private String getContent(SyndEntry item) { private String getContent(SyndEntry item) {
String content = null; String content = null;
if (item.getContents().isEmpty()) { if (item.getContents().isEmpty()) {
@@ -193,9 +191,8 @@ public class FeedParser {
return StringUtils.trimToNull(title); return StringUtils.trimToNull(title);
} }
@SuppressWarnings("unchecked")
private String findHub(SyndFeed feed) { private String findHub(SyndFeed feed) {
for (SyndLink l : (List<SyndLink>) feed.getLinks()) { for (SyndLink l : feed.getLinks()) {
if ("hub".equalsIgnoreCase(l.getRel())) { if ("hub".equalsIgnoreCase(l.getRel())) {
log.debug("found hub {} for feed {}", l.getHref(), feed.getLink()); log.debug("found hub {} for feed {}", l.getHref(), feed.getLink());
return l.getHref(); return l.getHref();
@@ -204,9 +201,8 @@ public class FeedParser {
return null; return null;
} }
@SuppressWarnings("unchecked")
private String findSelf(SyndFeed feed) { private String findSelf(SyndFeed feed) {
for (SyndLink l : (List<SyndLink>) feed.getLinks()) { for (SyndLink l : feed.getLinks()) {
if ("self".equalsIgnoreCase(l.getRel())) { if ("self".equalsIgnoreCase(l.getRel())) {
log.debug("found self {} for feed {}", l.getHref(), feed.getLink()); log.debug("found self {} for feed {}", l.getHref(), feed.getLink());
return l.getHref(); return l.getHref();

View File

@@ -6,6 +6,7 @@ import java.util.Map;
import java.util.Queue; import java.util.Queue;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton;
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;
@@ -20,6 +21,7 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Queues; import com.google.common.collect.Queues;
@Singleton
public class FeedQueues { public class FeedQueues {
private final FeedDAO feedDAO; private final FeedDAO feedDAO;
@@ -76,7 +78,16 @@ public class FeedQueues {
public void add(Feed feed, boolean urgent) { public void add(Feed feed, boolean urgent) {
int refreshInterval = config.getApplicationSettings().getRefreshIntervalMinutes(); int refreshInterval = config.getApplicationSettings().getRefreshIntervalMinutes();
if (feed.getLastUpdated() == null || feed.getLastUpdated().before(DateUtils.addMinutes(new Date(), -1 * refreshInterval))) { if (feed.getLastUpdated() == null || feed.getLastUpdated().before(DateUtils.addMinutes(new Date(), -1 * refreshInterval))) {
addQueue.add(new FeedRefreshContext(feed, urgent)); boolean alreadyQueued = false;
for (FeedRefreshContext context : addQueue) {
if (context.getFeed().getId().equals(feed.getId())) {
alreadyQueued = true;
break;
}
}
if (!alreadyQueued) {
addQueue.add(new FeedRefreshContext(feed, urgent));
}
} }
} }

View File

@@ -6,6 +6,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -22,6 +23,7 @@ import com.commafeed.backend.dao.UnitOfWork;
* *
*/ */
@Slf4j @Slf4j
@Singleton
public class FeedRefreshTaskGiver implements Managed { public class FeedRefreshTaskGiver implements Managed {
private final SessionFactory sessionFactory; private final SessionFactory sessionFactory;
@@ -69,15 +71,13 @@ public class FeedRefreshTaskGiver implements Managed {
FeedRefreshContext context = new UnitOfWork<FeedRefreshContext>(sessionFactory) { FeedRefreshContext context = new UnitOfWork<FeedRefreshContext>(sessionFactory) {
@Override @Override
protected FeedRefreshContext runInSession() throws Exception { protected FeedRefreshContext runInSession() throws Exception {
FeedRefreshContext context = queues.take(); return queues.take();
if (context != null) {
feedRefreshed.mark();
worker.updateFeed(context);
}
return context;
} }
}.run(); }.run();
if (context == null) { if (context != null) {
feedRefreshed.mark();
worker.updateFeed(context);
} else {
log.debug("nothing to do, sleeping for 15s"); log.debug("nothing to do, sleeping for 15s");
threadWaited.mark(); threadWaited.mark();
try { try {

View File

@@ -9,10 +9,13 @@ 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;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils; import org.apache.commons.lang.time.DateUtils;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
@@ -36,6 +39,7 @@ import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Striped; import com.google.common.util.concurrent.Striped;
@Slf4j @Slf4j
@Singleton
public class FeedRefreshUpdater implements Managed { public class FeedRefreshUpdater implements Managed {
private final SessionFactory sessionFactory; private final SessionFactory sessionFactory;
@@ -54,6 +58,7 @@ public class FeedRefreshUpdater implements Managed {
private Meter feedUpdated; private Meter feedUpdated;
private Meter entryInserted; private Meter entryInserted;
@Inject
public FeedRefreshUpdater(SessionFactory sessionFactory, FeedUpdateService feedUpdateService, PubSubService pubSubService, public FeedRefreshUpdater(SessionFactory sessionFactory, FeedUpdateService feedUpdateService, PubSubService pubSubService,
FeedQueues queues, CommaFeedConfiguration config, MetricRegistry metrics, FeedSubscriptionDAO feedSubscriptionDAO, FeedQueues queues, CommaFeedConfiguration config, MetricRegistry metrics, FeedSubscriptionDAO feedSubscriptionDAO,
CacheService cache) { CacheService cache) {
@@ -100,18 +105,8 @@ public class FeedRefreshUpdater implements Managed {
@Override @Override
public void run() { public void run() {
new UnitOfWork<Void>(sessionFactory) {
@Override
protected Void runInSession() throws Exception {
internalRun();
return null;
}
}.run();
}
public void internalRun() {
boolean ok = true; boolean ok = true;
Feed feed = context.getFeed(); final Feed feed = context.getFeed();
List<FeedEntry> entries = context.getEntries(); List<FeedEntry> entries = context.getEntries();
if (entries.isEmpty()) { if (entries.isEmpty()) {
feed.setMessage("Feed has no entries"); feed.setMessage("Feed has no entries");
@@ -125,7 +120,12 @@ public class FeedRefreshUpdater implements Managed {
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.findByFeed(feed); subscriptions = new UnitOfWork<List<FeedSubscription>>(sessionFactory) {
@Override
protected List<FeedSubscription> runInSession() throws Exception {
return feedSubscriptionDAO.findByFeed(feed);
}
}.run();
} }
ok &= addEntry(feed, entry, subscriptions); ok &= addEntry(feed, entry, subscriptions);
entryCacheMiss.mark(); entryCacheMiss.mark();
@@ -190,7 +190,12 @@ public class FeedRefreshUpdater implements Managed {
locked1 = lock1.tryLock(1, TimeUnit.MINUTES); locked1 = lock1.tryLock(1, TimeUnit.MINUTES);
locked2 = lock2.tryLock(1, TimeUnit.MINUTES); locked2 = lock2.tryLock(1, TimeUnit.MINUTES);
if (locked1 && locked2) { if (locked1 && locked2) {
boolean inserted = feedUpdateService.addEntry(feed, entry); boolean inserted = new UnitOfWork<Boolean>(sessionFactory) {
@Override
protected Boolean runInSession() throws Exception {
return feedUpdateService.addEntry(feed, entry);
}
}.run();
if (inserted) { if (inserted) {
entryInserted.mark(); entryInserted.mark();
} }

View File

@@ -5,6 +5,9 @@ import io.dropwizard.lifecycle.Managed;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
@@ -24,6 +27,7 @@ import com.google.common.base.Optional;
* *
*/ */
@Slf4j @Slf4j
@Singleton
public class FeedRefreshWorker implements Managed { public class FeedRefreshWorker implements Managed {
private final FeedRefreshUpdater feedRefreshUpdater; private final FeedRefreshUpdater feedRefreshUpdater;
@@ -32,6 +36,7 @@ public class FeedRefreshWorker implements Managed {
private final CommaFeedConfiguration config; private final CommaFeedConfiguration config;
private final FeedRefreshExecutor pool; private final FeedRefreshExecutor pool;
@Inject
public FeedRefreshWorker(FeedRefreshUpdater feedRefreshUpdater, FeedFetcher fetcher, FeedQueues queues, CommaFeedConfiguration config, public FeedRefreshWorker(FeedRefreshUpdater feedRefreshUpdater, FeedFetcher fetcher, FeedQueues queues, CommaFeedConfiguration config,
MetricRegistry metrics) { MetricRegistry metrics) {
this.feedRefreshUpdater = feedRefreshUpdater; this.feedRefreshUpdater = feedRefreshUpdater;

View File

@@ -16,7 +16,7 @@ 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.lang.time.DateUtils; import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.math.stat.descriptive.SummaryStatistics; import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Document.OutputSettings; import org.jsoup.nodes.Document.OutputSettings;
@@ -33,8 +33,6 @@ import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.frontend.model.Entry; import com.commafeed.frontend.model.Entry;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.gwt.i18n.client.HasDirection.Direction;
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;
@@ -132,7 +130,7 @@ public class FeedUtils {
public static String replaceHtmlEntitiesWithNumericEntities(String source) { public static String replaceHtmlEntitiesWithNumericEntities(String source) {
String result = source; String result = source;
for (String entity : HtmlEntities.NUMERIC_MAPPING.keySet()) { for (String entity : HtmlEntities.NUMERIC_MAPPING.keySet()) {
result = result.replace(entity, HtmlEntities.NUMERIC_MAPPING.get(entity)); result = StringUtils.replace(result, entity, HtmlEntities.NUMERIC_MAPPING.get(entity));
} }
return result; return result;
} }
@@ -291,8 +289,7 @@ public class FeedUtils {
return false; return false;
} }
Direction direction = BidiUtils.get().estimateDirection(text); return EstimateDirection.isRTL(text);
return direction == Direction.RTL;
} }
public static String trimInvalidXmlCharacters(String xml) { public static String trimInvalidXmlCharacters(String xml) {
@@ -520,5 +517,4 @@ public class FeedUtils {
} }
} }
} }
} }

View File

@@ -3,7 +3,7 @@ package com.commafeed.backend.feed;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import com.google.gwt.thirdparty.guava.common.collect.Maps; import com.google.common.collect.Maps;
public class HtmlEntities { public class HtmlEntities {
public static final Map<String, String> NUMERIC_MAPPING = Collections.unmodifiableMap(loadMap()); public static final Map<String, String> NUMERIC_MAPPING = Collections.unmodifiableMap(loadMap());

View File

@@ -8,8 +8,6 @@ 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.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
@@ -25,9 +23,6 @@ import com.google.common.collect.Lists;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@Getter @Getter
@Setter @Setter
@NamedQueries(@NamedQuery(
name = "Statuses.deleteOld",
query = "delete from FeedEntryStatus s where s.entryInserted < :date and s.starred = false"))
public class FeedEntryStatus extends AbstractModel { public class FeedEntryStatus extends AbstractModel {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)

View File

@@ -15,6 +15,7 @@ import javax.persistence.TemporalType;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.apache.commons.lang.time.DateUtils;
import org.hibernate.annotations.Cascade; import org.hibernate.annotations.Cascade;
import com.commafeed.backend.model.UserRole.Role; import com.commafeed.backend.model.UserRole.Role;
@@ -78,4 +79,12 @@ public class User extends AbstractModel {
return false; return false;
} }
public boolean shouldRefreshFeedsAt(Date when) {
return (lastFullRefresh == null || lastFullRefreshMoreThan30MinutesBefore(when));
}
private boolean lastFullRefreshMoreThan30MinutesBefore(Date when) {
return lastFullRefresh.before(DateUtils.addMinutes(when, -30));
}
} }

View File

@@ -3,24 +3,27 @@ package com.commafeed.backend.opml;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor; import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
import com.commafeed.backend.dao.FeedCategoryDAO; import com.commafeed.backend.dao.FeedCategoryDAO;
import com.commafeed.backend.dao.FeedSubscriptionDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO;
import com.commafeed.backend.model.FeedCategory; import com.commafeed.backend.model.FeedCategory;
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.sun.syndication.feed.opml.Attribute; import com.rometools.opml.feed.opml.Attribute;
import com.sun.syndication.feed.opml.Opml; import com.rometools.opml.feed.opml.Opml;
import com.sun.syndication.feed.opml.Outline; import com.rometools.opml.feed.opml.Outline;
@AllArgsConstructor @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class OPMLExporter { public class OPMLExporter {
private final FeedCategoryDAO feedCategoryDAO; private final FeedCategoryDAO feedCategoryDAO;
private final FeedSubscriptionDAO feedSubscriptionDAO; private final FeedSubscriptionDAO feedSubscriptionDAO;
@SuppressWarnings("unchecked")
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");
@@ -48,7 +51,6 @@ public class OPMLExporter {
} }
@SuppressWarnings("unchecked")
private Outline buildCategoryOutline(FeedCategory cat, List<FeedSubscription> subscriptions) { private Outline buildCategoryOutline(FeedCategory cat, List<FeedSubscription> subscriptions) {
Outline outline = new Outline(); Outline outline = new Outline();
outline.setText(cat.getName()); outline.setText(cat.getName());
@@ -66,7 +68,6 @@ public class OPMLExporter {
return outline; return outline;
} }
@SuppressWarnings("unchecked")
private Outline buildSubscriptionOutline(FeedSubscription sub) { private Outline buildSubscriptionOutline(FeedSubscription sub) {
Outline outline = new Outline(); Outline outline = new Outline();
outline.setText(sub.getTitle()); outline.setText(sub.getTitle());

View File

@@ -3,9 +3,13 @@ package com.commafeed.backend.opml;
import java.io.StringReader; import java.io.StringReader;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.cache.CacheService;
@@ -15,25 +19,19 @@ import com.commafeed.backend.model.FeedCategory;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
import com.commafeed.backend.service.FeedSubscriptionService; import com.commafeed.backend.service.FeedSubscriptionService;
import com.commafeed.backend.service.FeedSubscriptionService.FeedSubscriptionException; import com.commafeed.backend.service.FeedSubscriptionService.FeedSubscriptionException;
import com.sun.syndication.feed.opml.Opml; import com.rometools.opml.feed.opml.Opml;
import com.sun.syndication.feed.opml.Outline; import com.rometools.opml.feed.opml.Outline;
import com.sun.syndication.io.WireFeedInput; import com.rometools.rome.io.WireFeedInput;
@Slf4j @Slf4j
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class OPMLImporter { public class OPMLImporter {
private FeedCategoryDAO feedCategoryDAO; private final FeedCategoryDAO feedCategoryDAO;
private FeedSubscriptionService feedSubscriptionService; private final FeedSubscriptionService feedSubscriptionService;
private CacheService cache; private final CacheService cache;
public OPMLImporter(FeedCategoryDAO feedCategoryDAO, FeedSubscriptionService feedSubscriptionService, CacheService cache) {
super();
this.feedCategoryDAO = feedCategoryDAO;
this.feedSubscriptionService = feedSubscriptionService;
this.cache = cache;
}
@SuppressWarnings("unchecked")
public void importOpml(User user, String xml) { public void importOpml(User user, String xml) {
xml = xml.substring(xml.indexOf('<')); xml = xml.substring(xml.indexOf('<'));
WireFeedInput input = new WireFeedInput(); WireFeedInput input = new WireFeedInput();
@@ -49,7 +47,6 @@ public class OPMLImporter {
} }
@SuppressWarnings("unchecked")
private void handleOutline(User user, Outline outline, FeedCategory parent) { private void handleOutline(User user, Outline outline, FeedCategory parent) {
List<Outline> children = outline.getChildren(); List<Outline> children = outline.getChildren();
if (CollectionUtils.isNotEmpty(children)) { if (CollectionUtils.isNotEmpty(children)) {

View File

@@ -1,19 +1,20 @@
package com.commafeed.backend.rome; package com.commafeed.backend.rome;
import org.jdom.Element; import org.jdom2.Element;
import com.sun.syndication.feed.opml.Opml; import com.rometools.opml.feed.opml.Opml;
/** /**
* Add missing title to the generated OPML * Add missing title to the generated OPML
* *
*/ */
public class OPML11Generator extends com.sun.syndication.io.impl.OPML10Generator { public class OPML11Generator extends com.rometools.opml.io.impl.OPML10Generator {
public OPML11Generator() { public OPML11Generator() {
super("opml_1.1"); super("opml_1.1");
} }
@Override
protected Element generateHead(Opml opml) { protected Element generateHead(Opml opml) {
Element head = new Element("head"); Element head = new Element("head");
addNotNullSimpleElement(head, "title", opml.getTitle()); addNotNullSimpleElement(head, "title", opml.getTitle());

View File

@@ -1,9 +1,9 @@
package com.commafeed.backend.rome; package com.commafeed.backend.rome;
import org.jdom.Document; import org.jdom2.Document;
import org.jdom.Element; import org.jdom2.Element;
import com.sun.syndication.io.impl.OPML10Parser; import com.rometools.opml.io.impl.OPML10Parser;
/** /**
* Support for OPML 1.1 parsing * Support for OPML 1.1 parsing

View File

@@ -1,10 +1,10 @@
package com.commafeed.backend.rome; package com.commafeed.backend.rome;
import com.sun.syndication.feed.rss.Description; import com.rometools.rome.feed.rss.Description;
import com.sun.syndication.feed.rss.Item; import com.rometools.rome.feed.rss.Item;
import com.sun.syndication.feed.synd.SyndContentImpl; import com.rometools.rome.feed.synd.SyndContentImpl;
import com.sun.syndication.feed.synd.SyndEntry; import com.rometools.rome.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.impl.ConverterForRSS090; import com.rometools.rome.feed.synd.impl.ConverterForRSS090;
/** /**
* Support description tag for RSS09 * Support description tag for RSS09

View File

@@ -1,10 +1,12 @@
package com.commafeed.backend.rome; package com.commafeed.backend.rome;
import org.jdom.Element; import java.util.Locale;
import com.sun.syndication.feed.rss.Description; import org.jdom2.Element;
import com.sun.syndication.feed.rss.Item;
import com.sun.syndication.io.impl.RSS090Parser; import com.rometools.rome.feed.rss.Description;
import com.rometools.rome.feed.rss.Item;
import com.rometools.rome.io.impl.RSS090Parser;
/** /**
* Support description tag for RSS09 * Support description tag for RSS09
@@ -13,9 +15,8 @@ import com.sun.syndication.io.impl.RSS090Parser;
public class RSS090DescriptionParser extends RSS090Parser { public class RSS090DescriptionParser extends RSS090Parser {
@Override @Override
protected Item parseItem(Element rssRoot, Element eItem) { protected Item parseItem(Element rssRoot, Element eItem, Locale locale) {
Item item = super.parseItem(rssRoot, eItem); Item item = super.parseItem(rssRoot, eItem, locale);
Element e = eItem.getChild("description", getRSSNamespace()); Element e = eItem.getChild("description", getRSSNamespace());
if (e != null) { if (e != null) {
Description desc = new Description(); Description desc = new Description();
@@ -25,5 +26,4 @@ public class RSS090DescriptionParser extends RSS090Parser {
return item; return item;
} }
} }

View File

@@ -2,13 +2,13 @@ package com.commafeed.backend.rome;
import java.util.List; import java.util.List;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.jdom.Document; import org.jdom2.Document;
import org.jdom.Element; import org.jdom2.Element;
import org.jdom.Namespace; import org.jdom2.Namespace;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.sun.syndication.io.impl.RSS10Parser; import com.rometools.rome.io.impl.RSS10Parser;
public class RSSRDF10Parser extends RSS10Parser { public class RSSRDF10Parser extends RSS10Parser {
@@ -19,14 +19,13 @@ public class RSSRDF10Parser extends RSS10Parser {
super("rss_1.0", RSS_NS); super("rss_1.0", RSS_NS);
} }
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override @Override
public boolean isMyType(Document document) { public boolean isMyType(Document document) {
boolean ok = false; boolean ok = false;
Element rssRoot = document.getRootElement(); Element rssRoot = document.getRootElement();
Namespace defaultNS = rssRoot.getNamespace(); Namespace defaultNS = rssRoot.getNamespace();
List additionalNSs = Lists.newArrayList(rssRoot.getAdditionalNamespaces()); List<Namespace> additionalNSs = Lists.newArrayList(rssRoot.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,20 +0,0 @@
package com.commafeed.backend.service;
import java.util.ResourceBundle;
public class ApplicationPropertiesService {
private ResourceBundle bundle;
public ApplicationPropertiesService() {
bundle = ResourceBundle.getBundle("application");
}
public String getVersion() {
return bundle.getString("version");
}
public String getGitCommit() {
return bundle.getString("git.commit");
}
}

View File

@@ -5,6 +5,9 @@ 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.Singleton;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -16,7 +19,6 @@ import com.commafeed.backend.dao.FeedEntryDAO;
import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO;
import com.commafeed.backend.dao.UnitOfWork; import com.commafeed.backend.dao.UnitOfWork;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryStatus; import com.commafeed.backend.model.FeedEntryStatus;
/** /**
@@ -24,7 +26,8 @@ import com.commafeed.backend.model.FeedEntryStatus;
* *
*/ */
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class DatabaseCleaningService { public class DatabaseCleaningService {
private static final int BATCH_SIZE = 100; private static final int BATCH_SIZE = 100;
@@ -35,25 +38,6 @@ public class DatabaseCleaningService {
private final FeedEntryContentDAO feedEntryContentDAO; private final FeedEntryContentDAO feedEntryContentDAO;
private final FeedEntryStatusDAO feedEntryStatusDAO; private final FeedEntryStatusDAO feedEntryStatusDAO;
public long cleanEntriesWithoutSubscriptions() {
log.info("cleaning entries without subscriptions");
long total = 0;
int deleted = 0;
do {
deleted = new UnitOfWork<Integer>(sessionFactory) {
@Override
protected Integer runInSession() throws Exception {
List<FeedEntry> entries = feedEntryDAO.findWithoutSubscriptions(BATCH_SIZE);
return feedEntryDAO.delete(entries);
}
}.run();
total += deleted;
log.info("removed {} entries without subscriptions", total);
} while (deleted != 0);
log.info("cleanup done: {} entries without subscriptions deleted", total);
return total;
}
public long cleanFeedsWithoutSubscriptions() { public long cleanFeedsWithoutSubscriptions() {
log.info("cleaning feeds without subscriptions"); log.info("cleaning feeds without subscriptions");
long total = 0; long total = 0;
@@ -62,7 +46,7 @@ public class DatabaseCleaningService {
deleted = new UnitOfWork<Integer>(sessionFactory) { deleted = new UnitOfWork<Integer>(sessionFactory) {
@Override @Override
protected Integer runInSession() throws Exception { protected Integer runInSession() throws Exception {
List<Feed> feeds = feedDAO.findWithoutSubscriptions(BATCH_SIZE); List<Feed> feeds = feedDAO.findWithoutSubscriptions(1);
return feedDAO.delete(feeds); return feedDAO.delete(feeds);
}; };
}.run(); }.run();

View File

@@ -1,5 +1,8 @@
package com.commafeed.backend.service; package com.commafeed.backend.service;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
@@ -9,7 +12,8 @@ import com.commafeed.backend.dao.FeedEntryContentDAO;
import com.commafeed.backend.feed.FeedUtils; import com.commafeed.backend.feed.FeedUtils;
import com.commafeed.backend.model.FeedEntryContent; import com.commafeed.backend.model.FeedEntryContent;
@RequiredArgsConstructor @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class FeedEntryContentService { public class FeedEntryContentService {
private final FeedEntryContentDAO feedEntryContentDAO; private final FeedEntryContentDAO feedEntryContentDAO;

View File

@@ -3,6 +3,9 @@ package com.commafeed.backend.service;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.cache.CacheService;
@@ -15,7 +18,8 @@ import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@RequiredArgsConstructor @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class FeedEntryService { public class FeedEntryService {
private final FeedSubscriptionDAO feedSubscriptionDAO; private final FeedSubscriptionDAO feedSubscriptionDAO;

View File

@@ -3,6 +3,9 @@ package com.commafeed.backend.service;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import com.commafeed.backend.dao.FeedEntryDAO; import com.commafeed.backend.dao.FeedEntryDAO;
@@ -14,7 +17,8 @@ import com.google.common.base.Function;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@RequiredArgsConstructor @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class FeedEntryTagService { public class FeedEntryTagService {
private final FeedEntryDAO feedEntryDAO; private final FeedEntryDAO feedEntryDAO;

View File

@@ -1,19 +1,39 @@
package com.commafeed.backend.service; package com.commafeed.backend.service;
import java.io.IOException;
import java.util.Date; import java.util.Date;
import java.util.Set;
import lombok.RequiredArgsConstructor; import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import com.commafeed.backend.dao.FeedDAO; import com.commafeed.backend.dao.FeedDAO;
import com.commafeed.backend.favicon.AbstractFaviconFetcher;
import com.commafeed.backend.feed.FeedUtils; import com.commafeed.backend.feed.FeedUtils;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
@RequiredArgsConstructor @Singleton
public class FeedService { public class FeedService {
private final FeedDAO feedDAO; private final FeedDAO feedDAO;
private final Set<AbstractFaviconFetcher> faviconFetchers;
private byte[] defaultFavicon;
@Inject
public FeedService(FeedDAO feedDAO, Set<AbstractFaviconFetcher> faviconFetchers) {
this.feedDAO = feedDAO;
this.faviconFetchers = faviconFetchers;
try {
defaultFavicon = IOUtils.toByteArray(getClass().getResource("/images/default_favicon.gif"));
} catch (IOException e) {
throw new RuntimeException("could not load default favicon", e);
}
}
public synchronized Feed findOrCreate(String url) { public synchronized Feed findOrCreate(String url) {
String normalized = FeedUtils.normalizeURL(url); String normalized = FeedUtils.normalizeURL(url);
@@ -29,4 +49,19 @@ public class FeedService {
return feed; return feed;
} }
public byte[] fetchFavicon(Feed feed) {
byte[] icon = null;
for (AbstractFaviconFetcher faviconFetcher : faviconFetchers) {
icon = faviconFetcher.fetch(feed);
if (icon != null) {
break;
}
}
if (icon == null) {
icon = defaultFavicon;
}
return icon;
}
} }

View File

@@ -3,6 +3,9 @@ package com.commafeed.backend.service;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -23,12 +26,13 @@ import com.commafeed.frontend.model.UnreadCount;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class FeedSubscriptionService { public class FeedSubscriptionService {
@SuppressWarnings("serial") @SuppressWarnings("serial")
public static class FeedSubscriptionException extends RuntimeException { public static class FeedSubscriptionException extends RuntimeException {
public FeedSubscriptionException(String msg) { private FeedSubscriptionException(String msg) {
super(msg); super(msg);
} }
} }
@@ -87,16 +91,6 @@ public class FeedSubscriptionService {
} }
} }
public UnreadCount getUnreadCount(User user, FeedSubscription sub) {
UnreadCount count = cache.getUnreadCount(sub);
if (count == null) {
log.debug("unread count cache miss for {}", Models.getId(sub));
count = feedEntryStatusDAO.getUnreadCount(user, sub);
cache.setUnreadCount(sub, count);
}
return count;
}
public Map<Long, UnreadCount> getUnreadCount(User user) { public Map<Long, UnreadCount> getUnreadCount(User user) {
Map<Long, UnreadCount> map = Maps.newHashMap(); Map<Long, UnreadCount> map = Maps.newHashMap();
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user); List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
@@ -106,4 +100,14 @@ public class FeedSubscriptionService {
return map; return map;
} }
private UnreadCount getUnreadCount(User user, FeedSubscription sub) {
UnreadCount count = cache.getUnreadCount(sub);
if (count == null) {
log.debug("unread count cache miss for {}", Models.getId(sub));
count = feedEntryStatusDAO.getUnreadCount(user, sub);
cache.setUnreadCount(sub, count);
}
return count;
}
} }

View File

@@ -2,7 +2,10 @@ package com.commafeed.backend.service;
import java.util.Date; import java.util.Date;
import lombok.AllArgsConstructor; import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
@@ -11,7 +14,8 @@ 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;
@AllArgsConstructor @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class FeedUpdateService { public class FeedUpdateService {
private final FeedEntryDAO feedEntryDAO; private final FeedEntryDAO feedEntryDAO;

View File

@@ -1,8 +1,9 @@
package com.commafeed.backend.service; package com.commafeed.backend.service;
import java.io.Serializable;
import java.util.Properties; import java.util.Properties;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.mail.Authenticator; import javax.mail.Authenticator;
import javax.mail.Message; import javax.mail.Message;
import javax.mail.PasswordAuthentication; import javax.mail.PasswordAuthentication;
@@ -21,9 +22,9 @@ import com.commafeed.backend.model.User;
* Mailing service * Mailing service
* *
*/ */
@SuppressWarnings("serial") @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@RequiredArgsConstructor @Singleton
public class MailService implements Serializable { public class MailService {
private final CommaFeedConfiguration config; private final CommaFeedConfiguration config;

View File

@@ -1,23 +1,33 @@
package com.commafeed.backend.service; package com.commafeed.backend.service;
import java.io.Serializable; import java.io.Serializable;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.spec.KeySpec; import java.security.spec.KeySpec;
import java.util.Arrays;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory; import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEKeySpec;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
// taken from http://www.javacodegeeks.com/2012/05/secure-password-storage-donts-dos-and.html // taken from http://www.javacodegeeks.com/2012/05/secure-password-storage-donts-dos-and.html
@SuppressWarnings("serial") @SuppressWarnings("serial")
@Slf4j @Slf4j
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class PasswordEncryptionService implements Serializable { public class PasswordEncryptionService implements Serializable {
public boolean authenticate(String attemptedPassword, byte[] encryptedPassword, byte[] salt) { public boolean authenticate(String attemptedPassword, byte[] encryptedPassword, byte[] salt) {
if (StringUtils.isBlank(attemptedPassword)) {
return false;
}
// 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;
@@ -28,9 +38,13 @@ public class PasswordEncryptionService implements Serializable {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
} }
if (encryptedAttemptedPassword == null) {
return false;
}
// Authentication succeeds if encrypted password that the user entered // Authentication succeeds if encrypted password that the user entered
// is equal to the stored hash // is equal to the stored hash
return Arrays.equals(encryptedPassword, encryptedAttemptedPassword); return MessageDigest.isEqual(encryptedPassword, encryptedAttemptedPassword);
} }
public byte[] getEncryptedPassword(String password, byte[] salt) { public byte[] getEncryptedPassword(String password, byte[] salt) {

View File

@@ -2,6 +2,8 @@ package com.commafeed.backend.service;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -31,7 +33,8 @@ import com.google.common.collect.Lists;
* *
*/ */
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class PubSubService { public class PubSubService {
private final CommaFeedConfiguration config; private final CommaFeedConfiguration config;

View File

@@ -5,8 +5,8 @@ import io.dropwizard.lifecycle.Managed;
import java.sql.Connection; import java.sql.Connection;
import java.util.Arrays; import java.util.Arrays;
import javax.naming.Context; import javax.inject.Inject;
import javax.naming.InitialContext; import javax.inject.Singleton;
import javax.sql.DataSource; import javax.sql.DataSource;
import liquibase.Liquibase; import liquibase.Liquibase;
@@ -17,6 +17,7 @@ import liquibase.database.jvm.JdbcConnection;
import liquibase.resource.ClassLoaderResourceAccessor; import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.resource.ResourceAccessor; import liquibase.resource.ResourceAccessor;
import liquibase.structure.DatabaseObject; import liquibase.structure.DatabaseObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
@@ -30,17 +31,13 @@ import com.commafeed.backend.dao.UserDAO;
import com.commafeed.backend.model.UserRole.Role; import com.commafeed.backend.model.UserRole.Role;
@Slf4j @Slf4j
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class StartupService implements Managed { public class StartupService implements Managed {
private SessionFactory sessionFactory; private final SessionFactory sessionFactory;
private UserDAO userDAO; private final UserDAO userDAO;
private UserService userService; private final UserService userService;
public StartupService(SessionFactory sessionFactory, UserDAO userDAO, UserService userService) {
this.sessionFactory = sessionFactory;
this.userDAO = userDAO;
this.userService = userService;
}
@Override @Override
public void start() throws Exception { public void start() throws Exception {
@@ -58,14 +55,12 @@ public class StartupService implements Managed {
private void updateSchema() { private void updateSchema() {
try { try {
Context context = null;
Connection connection = null; Connection connection = null;
try { try {
Thread currentThread = Thread.currentThread(); Thread currentThread = Thread.currentThread();
ClassLoader classLoader = currentThread.getContextClassLoader(); ClassLoader classLoader = currentThread.getContextClassLoader();
ResourceAccessor accessor = new ClassLoaderResourceAccessor(classLoader); ResourceAccessor accessor = new ClassLoaderResourceAccessor(classLoader);
context = new InitialContext();
DataSource dataSource = getDataSource(sessionFactory); DataSource dataSource = getDataSource(sessionFactory);
connection = dataSource.getConnection(); connection = dataSource.getConnection();
JdbcConnection jdbcConnection = new JdbcConnection(connection); JdbcConnection jdbcConnection = new JdbcConnection(connection);
@@ -85,9 +80,6 @@ public class StartupService implements Managed {
Liquibase liq = new Liquibase("migrations.xml", accessor, database); Liquibase liq = new Liquibase("migrations.xml", accessor, database);
liq.update("prod"); liq.update("prod");
} finally { } finally {
if (context != null) {
context.close();
}
if (connection != null) { if (connection != null) {
connection.close(); connection.close();
} }

View File

@@ -4,11 +4,13 @@ import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.UUID; import java.util.UUID;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
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;
import org.apache.commons.lang.time.DateUtils;
import com.commafeed.CommaFeedConfiguration; import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.dao.FeedCategoryDAO; import com.commafeed.backend.dao.FeedCategoryDAO;
@@ -17,20 +19,26 @@ import com.commafeed.backend.dao.UserSettingsDAO;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserRole; import com.commafeed.backend.model.UserRole;
import com.commafeed.backend.model.UserRole.Role; import com.commafeed.backend.model.UserRole.Role;
import com.commafeed.backend.service.internal.PostLoginActivities;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
@RequiredArgsConstructor @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class UserService { public class UserService {
private final FeedCategoryDAO feedCategoryDAO; private final FeedCategoryDAO feedCategoryDAO;
private final UserDAO userDAO; private final UserDAO userDAO;
private final UserSettingsDAO userSettingsDAO; private final UserSettingsDAO userSettingsDAO;
private final FeedSubscriptionService feedSubscriptionService;
private final PasswordEncryptionService encryptionService; private final PasswordEncryptionService encryptionService;
private final CommaFeedConfiguration config; private final CommaFeedConfiguration config;
private final PostLoginActivities postLoginActivities;
/**
* try to log in with given credentials
*/
public Optional<User> login(String nameOrEmail, String password) { public Optional<User> login(String nameOrEmail, String password) {
if (nameOrEmail == null || password == null) { if (nameOrEmail == null || password == null) {
return Optional.absent(); return Optional.absent();
@@ -43,31 +51,16 @@ public class UserService {
if (user != null && !user.isDisabled()) { if (user != null && !user.isDisabled()) {
boolean authenticated = encryptionService.authenticate(password, user.getPassword(), user.getSalt()); boolean authenticated = encryptionService.authenticate(password, user.getPassword(), user.getSalt());
if (authenticated) { if (authenticated) {
Date lastLogin = user.getLastLogin(); performPostLoginActivities(user);
Date now = new Date(); return Optional.of(user);
boolean saveUser = false;
// only update lastLogin field every hour in order to not
// invalidate the cache everytime someone logs in
if (lastLogin == null || lastLogin.before(DateUtils.addHours(now, -1))) {
user.setLastLogin(now);
saveUser = true;
}
if (config.getApplicationSettings().isHeavyLoad()
&& (user.getLastFullRefresh() == null || user.getLastFullRefresh().before(DateUtils.addMinutes(now, -30)))) {
user.setLastFullRefresh(now);
saveUser = true;
feedSubscriptionService.refreshAll(user);
}
if (saveUser) {
userDAO.saveOrUpdate(user);
}
return Optional.fromNullable(user);
} }
} }
return Optional.absent(); return Optional.absent();
} }
/**
* try to log in with given api key
*/
public Optional<User> login(String apiKey) { public Optional<User> login(String apiKey) {
if (apiKey == null) { if (apiKey == null) {
return Optional.absent(); return Optional.absent();
@@ -75,11 +68,19 @@ public class UserService {
User user = userDAO.findByApiKey(apiKey); User user = userDAO.findByApiKey(apiKey);
if (user != null && !user.isDisabled()) { if (user != null && !user.isDisabled()) {
return Optional.fromNullable(user); performPostLoginActivities(user);
return Optional.of(user);
} }
return Optional.absent(); return Optional.absent();
} }
/**
* should triggers after successful login
*/
public void performPostLoginActivities(User user) {
postLoginActivities.executeFor(user);
}
public User register(String name, String password, String email, Collection<Role> roles) { public User register(String name, String password, String email, Collection<Role> roles) {
return register(name, password, email, roles, false); return register(name, password, email, roles, false);
} }

View File

@@ -0,0 +1,46 @@
package com.commafeed.backend.service.internal;
import java.util.Date;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang.time.DateUtils;
import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.dao.UserDAO;
import com.commafeed.backend.model.User;
import com.commafeed.backend.service.FeedSubscriptionService;
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class PostLoginActivities {
private final UserDAO userDAO;
private final FeedSubscriptionService feedSubscriptionService;
private final CommaFeedConfiguration config;
public void executeFor(User user) {
Date lastLogin = user.getLastLogin();
Date now = new Date();
boolean saveUser = false;
// only update lastLogin field every hour in order to not
// invalidate the cache every time someone logs in
if (lastLogin == null || lastLogin.before(DateUtils.addHours(now, -1))) {
user.setLastLogin(now);
saveUser = true;
}
if (config.getApplicationSettings().isHeavyLoad() && user.shouldRefreshFeedsAt(now)) {
feedSubscriptionService.refreshAll(user);
user.setLastFullRefresh(now);
saveUser = true;
}
if (saveUser) {
userDAO.merge(user);
}
}
}

View File

@@ -3,14 +3,17 @@ package com.commafeed.backend.task;
import java.util.Date; import java.util.Date;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import com.commafeed.CommaFeedConfiguration; import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.service.DatabaseCleaningService; import com.commafeed.backend.service.DatabaseCleaningService;
import com.commafeed.backend.task.SchedulingService.ScheduledTask;
@RequiredArgsConstructor @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
public class OldStatusesCleanupTask implements ScheduledTask { @Singleton
public class OldStatusesCleanupTask extends ScheduledTask {
private final CommaFeedConfiguration config; private final CommaFeedConfiguration config;
private final DatabaseCleaningService cleaner; private final DatabaseCleaningService cleaner;
@@ -25,7 +28,7 @@ public class OldStatusesCleanupTask implements ScheduledTask {
@Override @Override
public long getInitialDelay() { public long getInitialDelay() {
return 5; return 10;
} }
@Override @Override

View File

@@ -2,26 +2,28 @@ package com.commafeed.backend.task;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import com.commafeed.backend.service.DatabaseCleaningService; import com.commafeed.backend.service.DatabaseCleaningService;
import com.commafeed.backend.task.SchedulingService.ScheduledTask;
@RequiredArgsConstructor @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
public class OrphansCleanupTask implements ScheduledTask { @Singleton
public class OrphansCleanupTask extends ScheduledTask {
private final DatabaseCleaningService cleaner; private final DatabaseCleaningService cleaner;
@Override @Override
public void run() { public void run() {
cleaner.cleanEntriesWithoutSubscriptions();
cleaner.cleanFeedsWithoutSubscriptions(); cleaner.cleanFeedsWithoutSubscriptions();
cleaner.cleanContentsWithoutEntries(); cleaner.cleanContentsWithoutEntries();
} }
@Override @Override
public long getInitialDelay() { public long getInitialDelay() {
return 30; return 5;
} }
@Override @Override

View File

@@ -0,0 +1,32 @@
package com.commafeed.backend.task;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class ScheduledTask {
protected abstract void run();
protected abstract long getInitialDelay();
protected abstract long getPeriod();
protected abstract TimeUnit getTimeUnit();
public void register(ScheduledExecutorService executor) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
ScheduledTask.this.run();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
};
executor.scheduleWithFixedDelay(runnable, getInitialDelay(), getPeriod(), getTimeUnit());
}
}

View File

@@ -1,56 +0,0 @@
package com.commafeed.backend.task;
import io.dropwizard.lifecycle.Managed;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import com.google.common.collect.Lists;
@Slf4j
public class SchedulingService implements Managed {
public static interface ScheduledTask {
void run();
long getInitialDelay();
long getPeriod();
TimeUnit getTimeUnit();
}
private List<ScheduledTask> tasks = Lists.newArrayList();
private ScheduledExecutorService executor;
@Override
public void start() throws Exception {
executor = Executors.newScheduledThreadPool(tasks.size());
for (final ScheduledTask task : tasks) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
task.run();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
};
executor.scheduleWithFixedDelay(runnable, task.getInitialDelay(), task.getPeriod(), task.getTimeUnit());
}
}
@Override
public void stop() throws Exception {
executor.shutdown();
}
public void register(ScheduledTask task) {
tasks.add(task);
}
}

View File

@@ -1,7 +1,6 @@
package com.commafeed.frontend.auth; package com.commafeed.frontend.auth;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.ws.rs.WebApplicationException; import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
@@ -13,10 +12,10 @@ import lombok.RequiredArgsConstructor;
import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import com.commafeed.CommaFeedApplication;
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.service.UserService; import com.commafeed.backend.service.UserService;
import com.commafeed.frontend.session.SessionHelper;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.sun.jersey.api.core.HttpContext; import com.sun.jersey.api.core.HttpContext;
import com.sun.jersey.api.model.Parameter; import com.sun.jersey.api.model.Parameter;
@@ -37,39 +36,43 @@ public class SecurityCheckProvider implements InjectableProvider<SecurityCheck,
} }
@RequiredArgsConstructor @RequiredArgsConstructor
private static class SecurityCheckInjectable<T> extends AbstractHttpContextInjectable<User> { static class SecurityCheckInjectable extends AbstractHttpContextInjectable<User> {
private static final String PREFIX = "Basic"; private static final String PREFIX = "Basic";
private final HttpServletRequest request; private final SessionHelper sessionHelper;
private final UserService userService; private final UserService userService;
private final Role role; private final Role role;
private final boolean apiKeyAllowed; private final boolean apiKeyAllowed;
@Override @Override
public User getValue(HttpContext c) { public User getValue(HttpContext c) {
Optional<User> user = cookieSessionLogin(); Optional<User> user = apiKeyLogin(c);
if (!user.isPresent()) { if (!user.isPresent()) {
user = basicAuthenticationLogin(c); user = basicAuthenticationLogin(c);
} }
if (!user.isPresent()) { if (!user.isPresent()) {
user = apiKeyLogin(c); user = cookieSessionLogin();
} }
if (user.isPresent()) { if (user.isPresent()) {
return user.get(); if (user.get().hasRole(role)) {
return user.get();
} else {
throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN)
.entity("You don't have the required role to access this resource.").type(MediaType.TEXT_PLAIN_TYPE).build());
}
} else { } else {
throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED) throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED)
.entity("Credentials are required to access this resource.").type(MediaType.TEXT_PLAIN_TYPE).build()); .entity("Credentials are required to access this resource.").type(MediaType.TEXT_PLAIN_TYPE).build());
} }
} }
private Optional<User> cookieSessionLogin() { Optional<User> cookieSessionLogin() {
HttpSession session = request.getSession(false); Optional<User> loggedInUser = sessionHelper.getLoggedInUser();
if (session != null) { if (loggedInUser.isPresent()) {
User user = (User) session.getAttribute(CommaFeedApplication.SESSION_USER); userService.performPostLoginActivities(loggedInUser.get());
return Optional.fromNullable(user);
} }
return Optional.absent(); return loggedInUser;
} }
private Optional<User> basicAuthenticationLogin(HttpContext c) { private Optional<User> basicAuthenticationLogin(HttpContext c) {
@@ -84,10 +87,7 @@ public class SecurityCheckProvider implements InjectableProvider<SecurityCheck,
if (i > 0) { if (i > 0) {
String username = decoded.substring(0, i); String username = decoded.substring(0, i);
String password = decoded.substring(i + 1); String password = decoded.substring(i + 1);
Optional<User> user = userService.login(username, password); return userService.login(username, password);
if (user.isPresent() && user.get().hasRole(role)) {
return user;
}
} }
} }
} }
@@ -96,22 +96,19 @@ public class SecurityCheckProvider implements InjectableProvider<SecurityCheck,
} }
private Optional<User> apiKeyLogin(HttpContext c) { private Optional<User> apiKeyLogin(HttpContext c) {
String apiKey = c.getUriInfo().getPathParameters().getFirst("apiKey"); String apiKey = c.getUriInfo().getQueryParameters().getFirst("apiKey");
if (apiKey != null && apiKeyAllowed) { if (apiKey != null && apiKeyAllowed) {
Optional<User> user = userService.login(apiKey); return userService.login(apiKey);
if (user.isPresent() && user.get().hasRole(role)) {
return user;
}
} }
return Optional.absent(); return Optional.absent();
} }
} }
private HttpServletRequest request; private SessionHelper sessionHelper;
private UserService userService; private UserService userService;
public SecurityCheckProvider(@Context HttpServletRequest request, @Context UserService userService) { public SecurityCheckProvider(@Context HttpServletRequest req, @Context UserService userService) {
this.request = request; this.sessionHelper = new SessionHelper(req);
this.userService = userService; this.userService = userService;
} }
@@ -122,6 +119,6 @@ public class SecurityCheckProvider implements InjectableProvider<SecurityCheck,
@Override @Override
public Injectable<?> getInjectable(ComponentContext ic, SecurityCheck sc, Parameter c) { public Injectable<?> getInjectable(ComponentContext ic, SecurityCheck sc, Parameter c) {
return new SecurityCheckInjectable<>(request, userService, sc.value(), sc.apiKeyAllowed()); return new SecurityCheckInjectable(sessionHelper, userService, sc.value(), sc.apiKeyAllowed());
} }
} }

View File

@@ -14,9 +14,10 @@ import com.commafeed.backend.model.FeedEntryStatus;
import com.commafeed.backend.model.FeedEntryTag; import com.commafeed.backend.model.FeedEntryTag;
import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.FeedSubscription;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.sun.syndication.feed.synd.SyndContentImpl; import com.rometools.rome.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndEntry; import com.rometools.rome.feed.synd.SyndContentImpl;
import com.sun.syndication.feed.synd.SyndEntryImpl; import com.rometools.rome.feed.synd.SyndEntry;
import com.rometools.rome.feed.synd.SyndEntryImpl;
import com.wordnik.swagger.annotations.ApiModel; import com.wordnik.swagger.annotations.ApiModel;
import com.wordnik.swagger.annotations.ApiModelProperty; import com.wordnik.swagger.annotations.ApiModelProperty;
@@ -72,7 +73,7 @@ public class Entry implements Serializable {
SyndContentImpl content = new SyndContentImpl(); SyndContentImpl content = new SyndContentImpl();
content.setValue(getContent()); content.setValue(getContent());
entry.setContents(Arrays.asList(content)); entry.setContents(Arrays.<SyndContent> asList(content));
entry.setLink(getUrl()); entry.setLink(getUrl());
entry.setPublishedDate(getDate()); entry.setPublishedDate(getDate());
return entry; return entry;

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