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

208
README.md
View File

@@ -1,90 +1,118 @@
CommaFeed [![Build Status](https://buildhive.cloudbees.com/job/Athou/job/commafeed/badge/icon)](https://buildhive.cloudbees.com/job/Athou/job/commafeed/)
=========
Sources for [CommaFeed.com](http://www.commafeed.com/).
Google Reader inspired self-hosted RSS reader, based on Dropwizard and AngularJS.
Related open-source projects
----------------------------
Android apps: [News+ extension](https://github.com/Athou/commafeed-newsplus) - [Android app](https://github.com/doomrobo/CommaFeed-Android-Reader)
Browser extensions: [Chrome](https://github.com/Athou/commafeed-chrome) - [Firefox](https://github.com/Athou/commafeed-firefox) - [Opera](https://github.com/Athou/commafeed-opera) - [Safari](https://github.com/Athou/commafeed-safari)
Deployment on your own server
-----------------------------
CommaFeed 2.0 has been rewritten to use Dropwizard and gulp instead of using tomee and wro4j. The latest version of the 1.x branch is available [here](https://github.com/Athou/commafeed/tree/1.x).
For storage, you can either use an embedded H2 database or an external MySQL, PostgreSQL or SQLServer database.
You also need Maven 3.x (and a Java 1.7+ JDK) installed in order to build the application.
To install maven and openjdk on Ubuntu, issue the following commands
sudo add-apt-repository ppa:natecarlson/maven3
sudo apt-get update
sudo apt-get install openjdk-7-jdk maven3
# 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.
Clone this repository. If you don't have git you can download the sources as a zip file from [here](https://github.com/Athou/commafeed/archive/master.zip)
git clone https://github.com/Athou/commafeed.git
cd commafeed
Now build the application
mvn clean package
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`.
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.
Local development
-----------------
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.
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.
Translate CommaFeed into your language
--------------------------------------
Files for internationalization are located [here](https://github.com/Athou/commafeed/tree/master/src/main/app/i18n).
To add a new language, create a new file in that directory.
The name of the file should be the two-letters [ISO-639-1 language code](http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes).
The language has to be referenced in the `src/main/app/js/i18n.js` file to be picked up.
Themes
---------------------
To create a theme, create a new file `src/main/webapp/sass/themes/_<theme>.scss`. Your styles should be wrapped in a `#theme-<theme>` element and use the [SCSS format](http://sass-lang.com/) which is a superset of CSS.
Don't forget to reference your theme in `src/main/webapp/sass/app.scss` and in `src/main/webapp/js/controllers.js` (look for `$scope.themes`).
See [_test.scss](https://github.com/Athou/commafeed/blob/master/src/main/webapp/sass/themes/_test.scss) for an example.
Copyright and license
---------------------
Copyright 2013-2014 CommaFeed.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this work except in compliance with the License.
You may obtain a copy of the License in the LICENSE file, or at:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
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/).
Google Reader inspired self-hosted RSS reader, based on Dropwizard and AngularJS.
Related open-source projects
----------------------------
Android apps: [News+ extension](https://github.com/Athou/commafeed-newsplus) - [Android app](https://github.com/doomrobo/CommaFeed-Android-Reader)
Browser extensions: [Chrome](https://github.com/Athou/commafeed-chrome) - [Firefox](https://github.com/Athou/commafeed-firefox) - [Opera](https://github.com/Athou/commafeed-opera) - [Safari](https://github.com/Athou/commafeed-safari)
Deployment on your own server
-----------------------------
CommaFeed 2.0 has been rewritten to use Dropwizard and gulp instead of using tomee and wro4j. The latest version of the 1.x branch is available [here](https://github.com/Athou/commafeed/tree/1.x).
For storage, you can either use an embedded H2 database or an external MySQL, PostgreSQL or SQLServer database.
You also need Maven 3.x (and a Java 1.7+ JDK) installed in order to build the application.
To install maven and openjdk on Ubuntu, issue the following commands
sudo apt-get install build-essential openjdk-7-jdk maven
# Make sure java7 is the selected java version
sudo update-alternatives --config java
sudo update-alternatives --config javac
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.
Clone this repository. If you don't have git you can download the sources as a zip file from [here](https://github.com/Athou/commafeed/archive/master.zip)
git clone https://github.com/Athou/commafeed.git
cd commafeed
Now build the application
mvn clean package
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`.
java -jar target/commafeed.jar server config.yml
You can use a proxy http server such as nginx or apache.
Deployment on OpenShift
-----------------------------
[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:
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
--------------------------------------
Files for internationalization are located [here](https://github.com/Athou/commafeed/tree/master/src/main/app/i18n).
To add a new language, create a new file in that directory.
The name of the file should be the two-letters [ISO-639-1 language code](http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes).
The language has to be referenced in the `src/main/app/js/i18n.js` file to be picked up.
Themes
---------------------
To create a theme, create a new file `src/main/webapp/sass/themes/_<theme>.scss`. Your styles should be wrapped in a `#theme-<theme>` element and use the [SCSS format](http://sass-lang.com/) which is a superset of CSS.
Don't forget to reference your theme in `src/main/webapp/sass/app.scss` and in `src/main/webapp/js/controllers.js` (look for `$scope.themes`).
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 2013-2014 CommaFeed.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this work except in compliance with the License.
You may obtain a copy of the License in the LICENSE file, or at:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

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

View File

@@ -1,6 +1,9 @@
# CommaFeed settings
# ------------------
app:
# context path of the application
contextPath: /
# url used to access commafeed
publicUrl: http://localhost:8082/
@@ -76,6 +79,7 @@ database:
minSize: 1
maxSize: 50
checkConnectionWhileIdle: true
maxConnectionAge: 30m
server:
applicationConnectors:
@@ -84,6 +88,7 @@ server:
adminConnectors:
- type: http
port: 8084
logging:
level: INFO
loggers:
@@ -99,4 +104,16 @@ logging:
archive: true
archivedLogFilenamePattern: log/commafeed-%d.log
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
# ------------------
app:
# context path of the application
contextPath: /
# url used to access commafeed
publicUrl: http://localhost:8082/
@@ -66,7 +69,7 @@ app:
database:
driverClass: org.h2.Driver
url: jdbc:h2:./target/example
url: jdbc:h2:/home/commafeed/db
user: sa
password: sa
properties:
@@ -76,6 +79,7 @@ database:
minSize: 1
maxSize: 50
checkConnectionWhileIdle: true
maxConnectionAge: 30m
server:
applicationConnectors:
@@ -84,6 +88,7 @@ server:
adminConnectors:
- type: http
port: 8084
logging:
level: WARN
loggers:
@@ -98,4 +103,16 @@ logging:
archive: true
archivedLogFilenamePattern: log/commafeed-%d.log
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"?>
<profiles version="12">
<profile kind="CodeFormatterProfile" name="Eclipse [built-in] 140 chars" version="12">
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.jdt.core.compiler.source" value="1.5"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="140"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.5"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="140"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="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.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.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.never_indent_line_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
<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.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>
</profiles>

154
pom.xml
View File

@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId>
<version>2.0.0</version>
<version>2.0.3</version>
<packaging>jar</packaging>
<name>CommaFeed</name>
@@ -15,8 +15,17 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<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>
<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>
<finalName>commafeed</finalName>
<resources>
@@ -29,7 +38,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<version>3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
@@ -38,7 +47,7 @@
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<version>2.1.7</version>
<version>2.1.11</version>
<executions>
<execution>
<goals>
@@ -49,6 +58,7 @@
<configuration>
<generateGitPropertiesFile>false</generateGitPropertiesFile>
<failOnNoGitDirectory>false</failOnNoGitDirectory>
<failOnUnableToExtractRepoInfo>false</failOnUnableToExtractRepoInfo>
</configuration>
</plugin>
<plugin>
@@ -88,7 +98,7 @@
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>0.0.15</version>
<version>0.0.16</version>
<executions>
<execution>
<id>install node and npm</id>
@@ -117,6 +127,41 @@
</execution>
</executions>
</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>
</build>
@@ -124,7 +169,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.14.4</version>
<version>1.14.8</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -133,6 +178,17 @@
<version>1.7.7</version>
</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>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId>
@@ -158,47 +214,47 @@
<artifactId>dropwizard-assets</artifactId>
<version>${dropwizard.version}</version>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-forms</artifactId>
<version>${dropwizard.version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>com.wordnik</groupId>
<artifactId>swagger-jaxrs_2.10</artifactId>
<version>1.3.7</version>
<version>1.3.10</version>
<exclusions>
<exclusion>
<artifactId>jsr311-api</artifactId>
<groupId>javax.ws.rs</groupId>
</exclusion>
<exclusion>
<artifactId>javassist</artifactId>
<groupId>javassist</groupId>
<artifactId>jsr311-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>3.4.2</version>
<version>${querydsl.version}</version>
<scope>provided</scope>
<classifier>hibernate</classifier>
</dependency>
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-multipart</artifactId>
<version>1.18.1</version>
<version>${querydsl.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
@@ -207,61 +263,41 @@
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math</artifactId>
<version>2.2</version>
<artifactId>commons-math3</artifactId>
<version>3.3</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.5.2</version>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>net.java.dev.rome</groupId>
<groupId>com.rometools</groupId>
<artifactId>rome</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>jdom</groupId>
<artifactId>jdom</artifactId>
</exclusion>
</exclusions>
<version>${rome.version}</version>
</dependency>
<dependency>
<groupId>org.rometools</groupId>
<groupId>com.rometools</groupId>
<artifactId>rome-opml</artifactId>
<version>1.0</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>
<version>${rome.version}</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.7.3</version>
<version>1.8.1</version>
</dependency>
<dependency>
<groupId>com.googlecode.juniversalchardet</groupId>
<artifactId>juniversalchardet</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-servlet</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>net.sourceforge.cssparser</groupId>
<artifactId>cssparser</artifactId>
@@ -271,17 +307,17 @@
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.181</version>
<version>1.4.182</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
<version>5.1.33</version>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.1-901-1.jdbc4</version>
<version>9.3-1102-jdbc41</version>
</dependency>
<dependency>
<groupId>net.sourceforge.jtds</groupId>
@@ -295,5 +331,11 @@
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.8</version>
<scope>test</scope>
</dependency>
</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/select2/select2.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="css/app.css" />
@@ -41,7 +41,7 @@
<!-- build:js js/app.js -->
<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-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/bootstrap/dist/js/bootstrap.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() {
$state.transitionTo('admin.userlist');
};
$scope.toMetrics = function() {
$state.transitionTo('admin.metrics');
};
}]);
module.controller('HelpController', ['$scope', 'CategoryService', 'AnalyticsService', 'ServerService',
@@ -1460,55 +1463,58 @@ module.controller('MetricsCtrl', ['$scope', 'AdminMetricsService', function($sco
$scope.metrics = AdminMetricsService.get();
}]);
module.controller('LoginCtrl', ['$scope', '$location', 'SessionService', 'ServerService', function($scope, $location, SessionService, ServerService) {
$scope.model = {};
$scope.recovery_model = {};
$scope.recovery = false;
$scope.recovery_enabled = false;
ServerService.get(function(data) {
$scope.recovery_enabled = data.smtpEnabled;
});
var login = function(model) {
var success = function(data) {
window.location.href = window.location.href.substring(0, window.location.href.lastIndexOf('#'));
};
var error = function(data) {
$scope.message = data.data;
};
SessionService.login({
name : model.name,
password : model.password
}, success, error);
}
$scope.demoLogin = function() {
login({
name : 'demo',
password : 'demo'
});
};
module.controller('LoginCtrl', ['$scope', '$location', '$timeout', 'SessionService', 'ServerService',
function($scope, $location, $timeout, SessionService, ServerService) {
$scope.model = {};
$scope.recovery_model = {};
$scope.recovery = false;
$scope.recovery_enabled = false;
$scope.login = function() {
login($scope.model);
};
$scope.toggleRecovery = function() {
$scope.recovery = !$scope.recovery;
};
var recovery_success = function(data) {
$scope.recovery_message = "Email has ben sent. Check your inbox.";
};
var recovery_error = function(data) {
$scope.recovery_message = data.data;
};
$scope.recover = function() {
SessionService.passwordReset({
email : $scope.recovery_model.email
}, recovery_success, recovery_error);
}
}]);
ServerService.get(function(data) {
$scope.recovery_enabled = data.smtpEnabled;
});
var login = function(model) {
var success = function(data) {
window.location.href = window.location.href.substring(0, window.location.href.lastIndexOf('#'));
};
var error = function(data) {
$scope.message = data.data;
};
SessionService.login({
name : model.name,
password : model.password
}, success, error);
}
$scope.demoLogin = function() {
login({
name : 'demo',
password : 'demo'
});
};
$scope.login = function() {
// autofilled fields do not trigger model update, do it manually
$('input[ng-model]').trigger('input');
login($scope.model);
};
$scope.toggleRecovery = function() {
$scope.recovery = !$scope.recovery;
};
var recovery_success = function(data) {
$scope.recovery_message = "Email has ben sent. Check your inbox.";
};
var recovery_error = function(data) {
$scope.recovery_message = data.data;
};
$scope.recover = function() {
SessionService.passwordReset({
email : $scope.recovery_model.email
}, recovery_success, recovery_error);
}
}]);
module.controller('RegisterCtrl', ['$scope', '$location', 'SessionService', 'ServerService',
function($scope, $location, SessionService, ServerService) {

View File

@@ -193,7 +193,7 @@ module.directive('category', [function() {
});
var label = '';
if (count > 0) {
label = '(' + count + ')';
label += count;
}
return label;
};
@@ -201,7 +201,7 @@ module.directive('category', [function() {
$scope.feedCountLabel = function(feed) {
var label = '';
if (feed.unread > 0) {
label = '(' + feed.unread + ')';
label += feed.unread;
}
return label;
};
@@ -358,4 +358,4 @@ module.directive('metricGauge', function() {
restrict : 'E',
templateUrl : 'templates/_metrics.gauge.html'
};
});
});

View File

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

View File

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

View File

@@ -101,6 +101,14 @@
font-size: 11px;
}
.css-treeview .unread ~ .unread-counter::before {
content: '(';
}
.css-treeview .unread ~ .unread-counter::after {
content: ')';
}
.css-treeview a {
cursor: pointer;
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>
</span>
<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 class="unread-counter"> {{categoryCountLabel(node)}} </span>
<span ng-class="{unread: unreadCount({category:node})}" class="bidi-embed">{{categoryLabel(node)}}</span>
<span class="unread-counter">{{categoryCountLabel(node)}}</span>
</span>
</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"
ng-class="{error: feed.message && feed.errorCount > 10, selected: (feed.id == selectedId && selectedType == 'feed') }">
<favicon url="feed.iconUrl" />
<span ng-class="{unread: feed.unread}" class="bidi-embed"> {{feed.name}} </span>
<span class="unread-counter"> {{feedCountLabel(feed)}} </span>
<span ng-class="{unread: feed.unread}" class="bidi-embed">{{feed.name}}</span>
<span class="unread-counter">{{feedCountLabel(feed)}}</span>
</a>
</li>
</ul>

View File

@@ -1,10 +1,14 @@
<div class="row">
<div class="page-header">
<h1>
Application settings
Application settings -
<small>
<a ng-click="toUsers()" class="pointer">Manage users</a>
</small>
-
<small>
<a ng-click="toMetrics()" class="pointer">Metrics</a>
</small>
</h1>
</div>
@@ -28,26 +32,6 @@
</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">
<label class="control-label col-sm-3" for="googleAnalyticsTrackingCode">Google Analytics tracking code</label>
<div class="col-sm-9">
@@ -134,14 +118,6 @@
</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">
<label class="control-label col-sm-3" for="logLevel">Database query timeout (ms)</label>
<div class="col-sm-9">
@@ -173,8 +149,7 @@
</div>
<div class="row">
<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()">Cancel</button>
<button type="button" class="btn btn-default" ng-click="cancel()">Back</button>
</div>
</div>
</form>

View File

@@ -40,7 +40,7 @@
<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="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>
</div>
</div>

View File

@@ -107,7 +107,7 @@
</h4>
<p>{{ 'about.rest_api.line1' | translate }}</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>
</div>
</div>

View File

@@ -16,7 +16,7 @@
<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' }">
<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}}"
ng-class="{unread: entry.read == false, current: current==entry, open: isOpen, closed: !isOpen }">
<div class="entry-heading" ng-swipe-right="mark(entry, !entry.read)">

View File

@@ -12,13 +12,13 @@
<div class="form-group">
<label class="col-sm-2 control-label" for="email">{{ 'profile.email' | translate }}</label>
<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 class="form-group">
<label class="col-sm-2 control-label" for="password">{{ 'profile.change_password' | translate }}</label>
<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>
</div>
</div>
@@ -26,7 +26,7 @@
<label class="col-sm-2 control-label" for="password">{{ 'profile.confirm_password' | translate }}</label>
<div class="col-sm-10">
<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>
</div>
</div>
@@ -57,7 +57,8 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<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>
</div>
</div>

View File

@@ -90,7 +90,7 @@
</span>
<span>
- REST API
<a href="api" target="_blank">documentation</a>
<a href="api/" target="_blank">documentation</a>
</span>
<span class="pull-right">
<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.db.DataSourceFactory;
import io.dropwizard.hibernate.HibernateBundle;
import io.dropwizard.jersey.sessions.HttpSessionProvider;
import io.dropwizard.migrations.MigrationsBundle;
import io.dropwizard.servlets.CacheBustingFilter;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import java.io.File;
import java.io.IOException;
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.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.FeedRefreshUpdater;
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.UserRole;
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.UserService;
import com.commafeed.backend.task.OldStatusesCleanupTask;
import com.commafeed.backend.task.OrphansCleanupTask;
import com.commafeed.backend.task.SchedulingService;
import com.commafeed.frontend.auth.SecurityCheckProvider;
import com.commafeed.frontend.auth.SecurityCheckProvider.SecurityCheckUserServiceProvider;
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.LogoutServlet;
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.ScannerFactory;
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.reader.ClassReaders;
@Slf4j
public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
public static final String USERNAME_ADMIN = "admin";
public static final String USERNAME_DEMO = "demo";
public static final String SESSION_USER = "user";
public static final Date STARTUP_TIME = new Date();
private HibernateBundle<CommaFeedConfiguration> hibernateBundle;
private MigrationsBundle<CommaFeedConfiguration> migrationsBundle;
@Override
public String getName() {
return "CommaFeed";
}
@Override
public void initialize(Bootstrap<CommaFeedConfiguration> bootstrap) {
hibernateBundle = new HibernateBundle<CommaFeedConfiguration>(AbstractModel.class, Feed.class, FeedCategory.class, FeedEntry.class,
FeedEntryContent.class, FeedEntryStatus.class, FeedEntryTag.class, FeedSubscription.class, User.class, UserRole.class,
UserSettings.class) {
bootstrap.addBundle(hibernateBundle = new HibernateBundle<CommaFeedConfiguration>(AbstractModel.class, Feed.class,
FeedCategory.class, FeedEntry.class, FeedEntryContent.class, FeedEntryStatus.class, FeedEntryTag.class,
FeedSubscription.class, User.class, UserRole.class, UserSettings.class) {
@Override
public DataSourceFactory getDataSourceFactory(CommaFeedConfiguration configuration) {
return configuration.getDatabase();
}
};
bootstrap.addBundle(hibernateBundle);
});
migrationsBundle = new MigrationsBundle<CommaFeedConfiguration>() {
bootstrap.addBundle(new MigrationsBundle<CommaFeedConfiguration>() {
@Override
public DataSourceFactory getDataSourceFactory(CommaFeedConfiguration configuration) {
return configuration.getDatabase();
}
};
bootstrap.addBundle(migrationsBundle);
});
bootstrap.addBundle(new AssetsBundle("/assets/", "/", "index.html"));
}
@Override
public void run(CommaFeedConfiguration config, Environment environment) throws Exception {
MetricRegistry metrics = environment.metrics();
SessionFactory sessionFactory = hibernateBundle.getSessionFactory();
// configure context path
environment.getApplicationContext().setContextPath(config.getApplicationSettings().getContextPath());
CacheService cacheService = config.getApplicationSettings().getCache() == CacheType.NOOP ? new NoopCacheService()
: new RedisCacheService();
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);
// guice init
Injector injector = Guice.createInjector(new CommaFeedModule(hibernateBundle.getSessionFactory(), config, environment.metrics()));
// Auth/session management
HashSessionManager sessionManager = new HashSessionManager();
sessionManager.setHttpOnly(true);
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.servlets().setSessionHandler(new SessionHandler(config.getSessionManagerFactory().build()));
environment.jersey().register(new SecurityCheckUserServiceProvider(injector.getInstance(UserService.class)));
environment.jersey().register(SecurityCheckProvider.class);
environment.jersey().register(HttpSessionProvider.class);
environment.jersey().register(SessionHelperProvider.class);
// REST resources
environment.jersey().setUrlPattern("/rest/*");
environment.jersey()
.register(new AdminREST(userDAO, userRoleDAO, userService, encryptionService, cleaningService, config, metrics));
environment.jersey().register(
new CategoryREST(feedCategoryDAO, feedEntryStatusDAO, feedSubscriptionDAO, feedEntryService, feedSubscriptionService,
cacheService, config));
environment.jersey().register(new EntryREST(feedEntryTagDAO, feedEntryService, feedEntryTagService));
environment.jersey().register(
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));
environment.jersey().register(injector.getInstance(AdminREST.class));
environment.jersey().register(injector.getInstance(CategoryREST.class));
environment.jersey().register(injector.getInstance(EntryREST.class));
environment.jersey().register(injector.getInstance(FeedREST.class));
environment.jersey().register(injector.getInstance(PubSubHubbubCallbackREST.class));
environment.jersey().register(injector.getInstance(ServerREST.class));
environment.jersey().register(injector.getInstance(UserREST.class));
// Servlets
NextUnreadServlet nextUnreadServlet = new NextUnreadServlet(sessionFactory, feedSubscriptionDAO, feedEntryStatusDAO,
feedCategoryDAO, config);
LogoutServlet logoutServlet = new LogoutServlet(config);
CustomCssServlet customCssServlet = new CustomCssServlet(sessionFactory, userSettingsDAO);
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");
environment.servlets().addServlet("next", injector.getInstance(NextUnreadServlet.class)).addMapping("/next");
environment.servlets().addServlet("logout", injector.getInstance(LogoutServlet.class)).addMapping("/logout");
environment.servlets().addServlet("customCss", injector.getInstance(CustomCssServlet.class)).addMapping("/custom_css.css");
environment.servlets().addServlet("analytics.js", injector.getInstance(AnalyticsServlet.class)).addMapping("/analytics.js");
// Tasks
SchedulingService schedulingService = new SchedulingService();
schedulingService.register(new OldStatusesCleanupTask(config, cleaningService));
schedulingService.register(new OrphansCleanupTask(cleaningService));
// Scheduled tasks
ScheduledExecutorService executor = environment.lifecycle().scheduledExecutorService("task-scheduler").build();
injector.getInstance(OldStatusesCleanupTask.class).register(executor);
injector.getInstance(OrphansCleanupTask.class).register(executor);
// Managed objects
environment.lifecycle().manage(startupService);
environment.lifecycle().manage(taskGiver);
environment.lifecycle().manage(feedWorker);
environment.lifecycle().manage(feedUpdater);
environment.lifecycle().manage(schedulingService);
// database init/changelogs
environment.lifecycle().manage(injector.getInstance(StartupService.class));
// background feed fetching
environment.lifecycle().manage(injector.getInstance(FeedRefreshTaskGiver.class));
environment.lifecycle().manage(injector.getInstance(FeedRefreshWorker.class));
environment.lifecycle().manage(injector.getInstance(FeedRefreshUpdater.class));
// Swagger
environment.jersey().register(new ApiListingResourceJSON());
@@ -252,6 +155,23 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
SwaggerConfig swaggerConfig = ConfigFactory.config();
swaggerConfig.setApiVersion("1");
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 {

View File

@@ -4,14 +4,19 @@ import io.dropwizard.Configuration;
import io.dropwizard.db.DataSourceFactory;
import java.util.Date;
import java.util.ResourceBundle;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import lombok.Getter;
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;
@Getter
@@ -21,70 +26,98 @@ public class CommaFeedConfiguration extends Configuration {
NOOP, REDIS
}
private ResourceBundle bundle;
public CommaFeedConfiguration() {
bundle = ResourceBundle.getBundle("application");
}
@Valid
@NotNull
@JsonProperty("database")
private DataSourceFactory database = new DataSourceFactory();
@Valid
@NotNull
@JsonProperty("redis")
private RedisPoolFactory redisPoolFactory = new RedisPoolFactory();
@Valid
@NotNull
@JsonProperty("session")
private SessionManagerFactory sessionManagerFactory = new SessionManagerFactory();
@Valid
@NotNull
@JsonProperty("app")
private ApplicationSettings applicationSettings;
public String getVersion() {
return bundle.getString("version");
}
public String getGitCommit() {
return bundle.getString("git.commit");
}
@Getter
public static class ApplicationSettings {
@JsonProperty
@NotNull
@NotBlank
private String contextPath;
@NotNull
@NotBlank
private String publicUrl;
@JsonProperty
@NotNull
private boolean allowRegistrations;
@JsonProperty
private String googleAnalyticsTrackingCode;
@JsonProperty
@NotNull
@Min(1)
private int backgroundThreads;
@JsonProperty
@NotNull
@Min(1)
private int databaseUpdateThreads;
@JsonProperty
private String smtpHost;
@JsonProperty
private int smtpPort;
@JsonProperty
private boolean smtpTls;
@JsonProperty
private String smtpUserName;
@JsonProperty
private String smtpPassword;
@JsonProperty
@NotNull
private boolean heavyLoad;
@JsonProperty
@NotNull
private boolean pubsubhubbub;
@JsonProperty
@NotNull
private boolean imageProxyEnabled;
@JsonProperty
@NotNull
@Min(0)
private int queryTimeout;
@JsonProperty
@NotNull
@Min(0)
private int keepStatusDays;
@JsonProperty
@NotNull
@Min(0)
private int refreshIntervalMinutes;
@JsonProperty
@NotNull
private CacheType cache;
@JsonProperty
@NotNull
private String announcement;
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.cert.CertificateException;
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.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpStatus;
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.config.ConnectionConfig;
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.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import com.commafeed.CommaFeedConfiguration;
/**
* Smart HTTP getter: handles gzip, ssl, last modified and etag headers
*
*/
@Slf4j
@Singleton
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 PRAGMA_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 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 final HttpResponseInterceptor REMOVE_INCORRECT_CONTENT_ENCODING = new ContentEncodingInterceptor();
private static SSLContext SSL_CONTEXT = null;
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 {
return getBinary(url, null, null, timeout);
}
@@ -124,7 +105,7 @@ public class HttpGetter {
httpget.addHeader(HttpHeaders.ACCEPT_LANGUAGE, ACCEPT_LANGUAGE);
httpget.addHeader(HttpHeaders.PRAGMA, PRAGMA_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) {
httpget.addHeader(HttpHeaders.IF_MODIFIED_SINCE, lastModified);
@@ -183,47 +164,15 @@ public class HttpGetter {
return result;
}
@Getter
@RequiredArgsConstructor
public static class HttpResult {
private byte[] content;
private String contentType;
private String lastModifiedSince;
private String eTag;
private long duration;
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;
}
private final byte[] content;
private final String contentType;
private final String lastModifiedSince;
private final String eTag;
private final long duration;
private final String urlAfterRedirect;
}
public static CloseableHttpClient newClient(int timeout) {

View File

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

View File

@@ -3,6 +3,9 @@ package com.commafeed.backend.dao;
import java.util.Date;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.hibernate.SessionFactory;
@@ -14,32 +17,17 @@ import com.commafeed.backend.model.QUser;
import com.google.common.collect.Iterables;
import com.mysema.query.BooleanBuilder;
import com.mysema.query.jpa.hibernate.HibernateQuery;
import com.mysema.query.jpa.hibernate.HibernateSubQuery;
@Singleton
public class FeedDAO extends GenericDAO<Feed> {
private QFeed feed = QFeed.feed;
@Inject
public FeedDAO(SessionFactory 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) {
BooleanBuilder disabledDatePredicate = new BooleanBuilder();
disabledDatePredicate.or(feed.disabledUntil.isNull());

View File

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

View File

@@ -3,6 +3,9 @@ package com.commafeed.backend.dao;
import java.util.Date;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.codec.digest.DigestUtils;
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.QFeedSubscription;
import com.google.common.collect.Iterables;
import com.mysema.query.types.ConstructorExpression;
@Singleton
public class FeedEntryDAO extends GenericDAO<FeedEntry> {
private QFeedEntry entry = QFeedEntry.feedEntry;
@Inject
public FeedEntryDAO(SessionFactory sessionFactory) {
super(sessionFactory);
}
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(ConstructorExpression.create(Long.class, entry.id));
.list(entry.id);
return Iterables.getFirst(list, null);
}
@@ -35,13 +39,6 @@ public class FeedEntryDAO extends GenericDAO<FeedEntry> {
.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) {
List<FeedEntry> list = newQuery().from(entry).where(entry.inserted.lt(olderThan)).limit(max).list(entry);
int deleted = list.size();

View File

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

View File

@@ -2,24 +2,28 @@ package com.commafeed.backend.dao;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.hibernate.SessionFactory;
import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryTag;
import com.commafeed.backend.model.QFeedEntryTag;
import com.commafeed.backend.model.User;
import com.mysema.query.types.ConstructorExpression;
@Singleton
public class FeedEntryTagDAO extends GenericDAO<FeedEntryTag> {
private QFeedEntryTag tag = QFeedEntryTag.feedEntryTag;
@Inject
public FeedEntryTagDAO(SessionFactory sessionFactory) {
super(sessionFactory);
}
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) {

View File

@@ -2,6 +2,9 @@ package com.commafeed.backend.dao;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.hibernate.SessionFactory;
import com.commafeed.backend.model.Feed;
@@ -15,10 +18,12 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.mysema.query.jpa.hibernate.HibernateQuery;
@Singleton
public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
private QFeedSubscription sub = QFeedSubscription.feedSubscription;
@Inject
public FeedSubscriptionDAO(SessionFactory 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 GenericDAO(SessionFactory sessionFactory) {
protected GenericDAO(SessionFactory sessionFactory) {
super(sessionFactory);
}
@@ -56,9 +56,4 @@ public abstract class GenericDAO<T extends AbstractModel> extends AbstractDAO<T>
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;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.hibernate.SessionFactory;
import com.commafeed.backend.model.QUser;
import com.commafeed.backend.model.QUserRole;
import com.commafeed.backend.model.User;
@Singleton
public class UserDAO extends GenericDAO<User> {
private QUser user = QUser.user;
@Inject
public UserDAO(SessionFactory sessionFactory) {
super(sessionFactory);
}

View File

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

View File

@@ -1,15 +1,20 @@
package com.commafeed.backend.dao;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.hibernate.SessionFactory;
import com.commafeed.backend.model.QUserSettings;
import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserSettings;
@Singleton
public class UserSettingsDAO extends GenericDAO<UserSettings> {
private QUserSettings settings = QUserSettings.userSettings;
@Inject
public UserSettingsDAO(SessionFactory 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,156 +1,123 @@
package com.commafeed.backend.feed;
import java.util.Arrays;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
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;
/**
* Inspired/Ported from https://github.com/potatolondon/getfavicon
*
*/
@Slf4j
@AllArgsConstructor
public class FaviconFetcher {
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;
public byte[] fetch(String url) {
if (url == null) {
log.debug("url is null");
return null;
}
int doubleSlash = url.indexOf("//");
if (doubleSlash == -1) {
doubleSlash = 0;
} else {
doubleSlash += 2;
}
int firstSlash = url.indexOf('/', doubleSlash);
if (firstSlash != -1) {
url = url.substring(0, firstSlash);
}
byte[] icon = getIconAtRoot(url);
if (icon == null) {
icon = getIconInPage(url);
}
return icon;
}
private byte[] getIconAtRoot(String url) {
byte[] bytes = null;
String contentType = null;
try {
url = FeedUtils.removeTrailingSlash(url) + "/favicon.ico";
log.debug("getting root icon at {}", url);
HttpResult result = getter.getBinary(url, TIMEOUT);
bytes = result.getContent();
contentType = result.getContentType();
} catch (Exception e) {
log.debug("Failed to retrieve iconAtRoot: " + e.getMessage(), e);
}
if (!isValidIconResponse(bytes, contentType)) {
bytes = null;
}
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) {
Document doc = null;
try {
HttpResult result = getter.getBinary(url, TIMEOUT);
doc = Jsoup.parse(new String(result.getContent()), url);
} catch (Exception e) {
log.debug("Failed to retrieve page to find icon");
return null;
}
Elements icons = doc.select("link[rel~=(?i)^(shortcut|icon|shortcut icon)$]");
if (icons.isEmpty()) {
log.debug("No icon found in page {}", url);
return null;
}
String href = icons.get(0).attr("abs:href");
if (StringUtils.isBlank(href)) {
log.debug("No icon found in page");
return null;
}
log.debug("Found unconfirmed iconInPage at {}", href);
byte[] bytes = null;
String contentType = null;
try {
HttpResult result = getter.getBinary(href, TIMEOUT);
bytes = result.getContent();
contentType = result.getContentType();
} catch (Exception e) {
log.debug("Failed to retrieve icon found in page {}", href);
return null;
}
if (!isValidIconResponse(bytes, contentType)) {
log.debug("Invalid icon found for {}", href);
return null;
}
return bytes;
}
}
package com.commafeed.backend.favicon;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
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.feed.FeedUtils;
import com.commafeed.backend.model.Feed;
/**
* Inspired/Ported from https://github.com/potatolondon/getfavicon
*
*/
@Slf4j
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
private final HttpGetter getter;
@Override
public byte[] fetch(Feed feed) {
String url = feed.getLink() != null ? feed.getLink() : feed.getUrl();
if (url == null) {
log.debug("url is null");
return null;
}
int doubleSlash = url.indexOf("//");
if (doubleSlash == -1) {
doubleSlash = 0;
} else {
doubleSlash += 2;
}
int firstSlash = url.indexOf('/', doubleSlash);
if (firstSlash != -1) {
url = url.substring(0, firstSlash);
}
byte[] icon = getIconAtRoot(url);
if (icon == null) {
icon = getIconInPage(url);
}
return icon;
}
private byte[] getIconAtRoot(String url) {
byte[] bytes = null;
String contentType = null;
try {
url = FeedUtils.removeTrailingSlash(url) + "/favicon.ico";
log.debug("getting root icon at {}", url);
HttpResult result = getter.getBinary(url, TIMEOUT);
bytes = result.getContent();
contentType = result.getContentType();
} catch (Exception e) {
log.debug("Failed to retrieve iconAtRoot for url {}: ", url, e);
}
if (!isValidIconResponse(bytes, contentType)) {
bytes = null;
}
return bytes;
}
private byte[] getIconInPage(String url) {
Document doc = null;
try {
HttpResult result = getter.getBinary(url, TIMEOUT);
doc = Jsoup.parse(new String(result.getContent()), url);
} catch (Exception e) {
log.debug("Failed to retrieve page to find icon", e);
return null;
}
Elements icons = doc.select("link[rel~=(?i)^(shortcut|icon|shortcut icon)$]");
if (icons.isEmpty()) {
log.debug("No icon found in page {}", url);
return null;
}
String href = icons.get(0).attr("abs:href");
if (StringUtils.isBlank(href)) {
log.debug("No icon found in page");
return null;
}
log.debug("Found unconfirmed iconInPage at {}", href);
byte[] bytes = null;
String contentType = null;
try {
HttpResult result = getter.getBinary(href, TIMEOUT);
bytes = result.getContent();
contentType = result.getContentType();
} catch (Exception e) {
log.debug("Failed to retrieve icon found in page {}", href, e);
return null;
}
if (!isValidIconResponse(bytes, contentType)) {
log.debug("Invalid icon found for {}", href);
return null;
}
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.util.Date;
import lombok.AllArgsConstructor;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.NotModifiedException;
import com.commafeed.backend.model.Feed;
import com.sun.syndication.io.FeedException;
import com.rometools.rome.io.FeedException;
@Slf4j
@AllArgsConstructor
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class FeedFetcher {
private final FeedParser parser;
@@ -73,7 +77,7 @@ public class FeedFetcher {
Feed feed = fetchedFeed.getFeed();
feed.setLastModifiedHeader(result.getLastModifiedSince());
feed.setEtagHeader(FeedUtils.truncate(result.geteTag(), 255));
feed.setEtagHeader(FeedUtils.truncate(result.getETag(), 255));
feed.setLastContentHash(hash);
fetchedFeed.setFetchDuration(result.getDuration());
fetchedFeed.setUrlAfterRedirect(result.getUrlAfterRedirect());
@@ -89,9 +93,9 @@ public class FeedFetcher {
Elements atom = doc.select("link[type=application/atom+xml]");
Elements rss = doc.select("link[type=application/rss+xml]");
if (!atom.isEmpty()) {
foundUrl = atom.get(0).attr("abs:href").toString();
foundUrl = atom.get(0).attr("abs:href");
} else if (!rss.isEmpty()) {
foundUrl = rss.get(0).attr("abs:href").toString();
foundUrl = rss.get(0).attr("abs:href");
}
}
return foundUrl;

View File

@@ -5,12 +5,16 @@ import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.xml.sax.InputSource;
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.collect.Collections2;
import com.google.common.collect.Iterables;
import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndEnclosure;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndLink;
import com.sun.syndication.feed.synd.SyndLinkImpl;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedInput;
import com.rometools.rome.feed.synd.SyndContent;
import com.rometools.rome.feed.synd.SyndEnclosure;
import com.rometools.rome.feed.synd.SyndEntry;
import com.rometools.rome.feed.synd.SyndFeed;
import com.rometools.rome.feed.synd.SyndLink;
import com.rometools.rome.feed.synd.SyndLinkImpl;
import com.rometools.rome.io.FeedException;
import com.rometools.rome.io.SyndFeedInput;
@Slf4j
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class FeedParser {
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 Function<SyndContent, String> CONTENT_TO_STRING = new Function<SyndContent, String>() {
@Override
public String apply(SyndContent content) {
return content.getValue();
}
};
@SuppressWarnings("unchecked")
public FetchedFeed parse(String feedUrl, byte[] xml) throws FeedException {
FetchedFeed fetchedFeed = new FetchedFeed();
Feed feed = fetchedFeed.getFeed();
@@ -81,7 +87,7 @@ public class FeedParser {
entry.setGuid(FeedUtils.truncate(guid, 2048));
entry.setUpdated(validateDate(getEntryUpdateDate(item), true));
entry.setUrl(FeedUtils.truncate(FeedUtils.toAbsoluteUrl(item.getLink(), feed.getLink(), feed.getUrlAfterRedirect()), 2048));
// if link is empty but guid is used as url
if (StringUtils.isBlank(entry.getUrl()) && StringUtils.startsWith(entry.getGuid(), "http")) {
entry.setUrl(entry.getGuid());
@@ -91,7 +97,7 @@ public class FeedParser {
content.setContent(getContent(item));
content.setTitle(getTitle(item));
content.setAuthor(StringUtils.trimToNull(item.getAuthor()));
SyndEnclosure enclosure = (SyndEnclosure) Iterables.getFirst(item.getEnclosures(), null);
SyndEnclosure enclosure = Iterables.getFirst(item.getEnclosures(), null);
if (enclosure != null) {
content.setEnclosureUrl(FeedUtils.truncate(enclosure.getUrl(), 2048));
content.setEnclosureType(enclosure.getType());
@@ -121,24 +127,17 @@ public class FeedParser {
/**
* Adds atom links for rss feeds
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private void handleForeignMarkup(SyndFeed feed) {
Object foreignMarkup = feed.getForeignMarkup();
List<Element> foreignMarkup = feed.getForeignMarkup();
if (foreignMarkup == null) {
return;
}
if (foreignMarkup instanceof List) {
List elements = (List) foreignMarkup;
for (Object object : elements) {
if (object instanceof Element) {
Element element = (Element) object;
if ("link".equals(element.getName()) && ATOM_10_NS.equals(element.getNamespace())) {
SyndLink link = new SyndLinkImpl();
link.setRel(element.getAttributeValue("rel"));
link.setHref(element.getAttributeValue("href"));
feed.getLinks().add(link);
}
}
for (Element element : foreignMarkup) {
if ("link".equals(element.getName()) && ATOM_10_NS.equals(element.getNamespace())) {
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;
}
@SuppressWarnings("unchecked")
private String getContent(SyndEntry item) {
String content = null;
if (item.getContents().isEmpty()) {
@@ -193,9 +191,8 @@ public class FeedParser {
return StringUtils.trimToNull(title);
}
@SuppressWarnings("unchecked")
private String findHub(SyndFeed feed) {
for (SyndLink l : (List<SyndLink>) feed.getLinks()) {
for (SyndLink l : feed.getLinks()) {
if ("hub".equalsIgnoreCase(l.getRel())) {
log.debug("found hub {} for feed {}", l.getHref(), feed.getLink());
return l.getHref();
@@ -204,9 +201,8 @@ public class FeedParser {
return null;
}
@SuppressWarnings("unchecked")
private String findSelf(SyndFeed feed) {
for (SyndLink l : (List<SyndLink>) feed.getLinks()) {
for (SyndLink l : feed.getLinks()) {
if ("self".equalsIgnoreCase(l.getRel())) {
log.debug("found self {} for feed {}", l.getHref(), feed.getLink());
return l.getHref();

View File

@@ -6,6 +6,7 @@ import java.util.Map;
import java.util.Queue;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.codec.digest.DigestUtils;
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.Queues;
@Singleton
public class FeedQueues {
private final FeedDAO feedDAO;
@@ -76,7 +78,16 @@ public class FeedQueues {
public void add(Feed feed, boolean urgent) {
int refreshInterval = config.getApplicationSettings().getRefreshIntervalMinutes();
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 javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
@@ -22,6 +23,7 @@ import com.commafeed.backend.dao.UnitOfWork;
*
*/
@Slf4j
@Singleton
public class FeedRefreshTaskGiver implements Managed {
private final SessionFactory sessionFactory;
@@ -69,15 +71,13 @@ public class FeedRefreshTaskGiver implements Managed {
FeedRefreshContext context = new UnitOfWork<FeedRefreshContext>(sessionFactory) {
@Override
protected FeedRefreshContext runInSession() throws Exception {
FeedRefreshContext context = queues.take();
if (context != null) {
feedRefreshed.mark();
worker.updateFeed(context);
}
return context;
return queues.take();
}
}.run();
if (context == null) {
if (context != null) {
feedRefreshed.mark();
worker.updateFeed(context);
} else {
log.debug("nothing to do, sleeping for 15s");
threadWaited.mark();
try {

View File

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

View File

@@ -5,6 +5,9 @@ import io.dropwizard.lifecycle.Managed;
import java.util.Date;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
@@ -24,6 +27,7 @@ import com.google.common.base.Optional;
*
*/
@Slf4j
@Singleton
public class FeedRefreshWorker implements Managed {
private final FeedRefreshUpdater feedRefreshUpdater;
@@ -32,6 +36,7 @@ public class FeedRefreshWorker implements Managed {
private final CommaFeedConfiguration config;
private final FeedRefreshExecutor pool;
@Inject
public FeedRefreshWorker(FeedRefreshUpdater feedRefreshUpdater, FeedFetcher fetcher, FeedQueues queues, CommaFeedConfiguration config,
MetricRegistry metrics) {
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.StringUtils;
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.nodes.Document;
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.frontend.model.Entry;
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 edu.uci.ics.crawler4j.url.URLCanonicalizer;
@@ -132,7 +130,7 @@ public class FeedUtils {
public static String replaceHtmlEntitiesWithNumericEntities(String source) {
String result = source;
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;
}
@@ -291,8 +289,7 @@ public class FeedUtils {
return false;
}
Direction direction = BidiUtils.get().estimateDirection(text);
return direction == Direction.RTL;
return EstimateDirection.isRTL(text);
}
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.Map;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.common.collect.Maps;
public class HtmlEntities {
public static final Map<String, String> NUMERIC_MAPPING = Collections.unmodifiableMap(loadMap());
@@ -260,7 +260,7 @@ public class HtmlEntities {
map.put("&zeta;", "&#950;");
map.put("&zwj;", "&#8205;");
map.put("&zwnj;", "&#8204;");
return map;
}
}

View File

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

View File

@@ -15,6 +15,7 @@ import javax.persistence.TemporalType;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang.time.DateUtils;
import org.hibernate.annotations.Cascade;
import com.commafeed.backend.model.UserRole.Role;
@@ -77,5 +78,13 @@ public class User extends AbstractModel {
}
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.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.FeedSubscriptionDAO;
import com.commafeed.backend.model.FeedCategory;
import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.User;
import com.sun.syndication.feed.opml.Attribute;
import com.sun.syndication.feed.opml.Opml;
import com.sun.syndication.feed.opml.Outline;
import com.rometools.opml.feed.opml.Attribute;
import com.rometools.opml.feed.opml.Opml;
import com.rometools.opml.feed.opml.Outline;
@AllArgsConstructor
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class OPMLExporter {
private final FeedCategoryDAO feedCategoryDAO;
private final FeedSubscriptionDAO feedSubscriptionDAO;
@SuppressWarnings("unchecked")
public Opml export(User user) {
Opml opml = new Opml();
opml.setFeedType("opml_1.1");
@@ -48,7 +51,6 @@ public class OPMLExporter {
}
@SuppressWarnings("unchecked")
private Outline buildCategoryOutline(FeedCategory cat, List<FeedSubscription> subscriptions) {
Outline outline = new Outline();
outline.setText(cat.getName());
@@ -66,7 +68,6 @@ public class OPMLExporter {
return outline;
}
@SuppressWarnings("unchecked")
private Outline buildSubscriptionOutline(FeedSubscription sub) {
Outline outline = new Outline();
outline.setText(sub.getTitle());

View File

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

View File

@@ -1,19 +1,20 @@
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
*
*/
public class OPML11Generator extends com.sun.syndication.io.impl.OPML10Generator {
public class OPML11Generator extends com.rometools.opml.io.impl.OPML10Generator {
public OPML11Generator() {
super("opml_1.1");
}
@Override
protected Element generateHead(Opml opml) {
Element head = new Element("head");
addNotNullSimpleElement(head, "title", opml.getTitle());

View File

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

View File

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

View File

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

View File

@@ -2,13 +2,13 @@ package com.commafeed.backend.rome;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.apache.commons.collections4.CollectionUtils;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
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 {
@@ -19,14 +19,13 @@ public class RSSRDF10Parser extends RSS10Parser {
super("rss_1.0", RSS_NS);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public boolean isMyType(Document document) {
boolean ok = false;
Element rssRoot = document.getRootElement();
Namespace defaultNS = rssRoot.getNamespace();
List additionalNSs = Lists.newArrayList(rssRoot.getAdditionalNamespaces());
List<Namespace> additionalNSs = Lists.newArrayList(rssRoot.getAdditionalNamespaces());
List<Element> children = rssRoot.getChildren();
if (CollectionUtils.isNotEmpty(children)) {
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.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
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.UnitOfWork;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryStatus;
/**
@@ -24,7 +26,8 @@ import com.commafeed.backend.model.FeedEntryStatus;
*
*/
@Slf4j
@RequiredArgsConstructor
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class DatabaseCleaningService {
private static final int BATCH_SIZE = 100;
@@ -35,25 +38,6 @@ public class DatabaseCleaningService {
private final FeedEntryContentDAO feedEntryContentDAO;
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() {
log.info("cleaning feeds without subscriptions");
long total = 0;
@@ -62,7 +46,7 @@ public class DatabaseCleaningService {
deleted = new UnitOfWork<Integer>(sessionFactory) {
@Override
protected Integer runInSession() throws Exception {
List<Feed> feeds = feedDAO.findWithoutSubscriptions(BATCH_SIZE);
List<Feed> feeds = feedDAO.findWithoutSubscriptions(1);
return feedDAO.delete(feeds);
};
}.run();

View File

@@ -1,5 +1,8 @@
package com.commafeed.backend.service;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
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.model.FeedEntryContent;
@RequiredArgsConstructor
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class FeedEntryContentService {
private final FeedEntryContentDAO feedEntryContentDAO;

View File

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

View File

@@ -3,6 +3,9 @@ package com.commafeed.backend.service;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
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.Maps;
@RequiredArgsConstructor
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class FeedEntryTagService {
private final FeedEntryDAO feedEntryDAO;

View File

@@ -1,19 +1,39 @@
package com.commafeed.backend.service;
import java.io.IOException;
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.io.IOUtils;
import com.commafeed.backend.dao.FeedDAO;
import com.commafeed.backend.favicon.AbstractFaviconFetcher;
import com.commafeed.backend.feed.FeedUtils;
import com.commafeed.backend.model.Feed;
@RequiredArgsConstructor
@Singleton
public class FeedService {
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) {
String normalized = FeedUtils.normalizeURL(url);
@@ -29,4 +49,19 @@ public class FeedService {
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.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -23,12 +26,13 @@ import com.commafeed.frontend.model.UnreadCount;
import com.google.common.collect.Maps;
@Slf4j
@RequiredArgsConstructor
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class FeedSubscriptionService {
@SuppressWarnings("serial")
public static class FeedSubscriptionException extends RuntimeException {
public FeedSubscriptionException(String msg) {
private FeedSubscriptionException(String 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) {
Map<Long, UnreadCount> map = Maps.newHashMap();
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
@@ -106,4 +100,14 @@ public class FeedSubscriptionService {
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 lombok.AllArgsConstructor;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
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.FeedEntryContent;
@AllArgsConstructor
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class FeedUpdateService {
private final FeedEntryDAO feedEntryDAO;

View File

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

View File

@@ -1,23 +1,33 @@
package com.commafeed.backend.service;
import java.io.Serializable;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Arrays;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
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
@SuppressWarnings("serial")
@Slf4j
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class PasswordEncryptionService implements Serializable {
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 original password
byte[] encryptedAttemptedPassword = null;
@@ -28,9 +38,13 @@ public class PasswordEncryptionService implements Serializable {
log.error(e.getMessage(), e);
}
if (encryptedAttemptedPassword == null) {
return false;
}
// Authentication succeeds if encrypted password that the user entered
// is equal to the stored hash
return Arrays.equals(encryptedPassword, encryptedAttemptedPassword);
return MessageDigest.isEqual(encryptedPassword, encryptedAttemptedPassword);
}
public byte[] getEncryptedPassword(String password, byte[] salt) {

View File

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

View File

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

View File

@@ -4,11 +4,13 @@ import java.util.Collection;
import java.util.Date;
import java.util.UUID;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import com.commafeed.CommaFeedConfiguration;
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.UserRole;
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.Preconditions;
@RequiredArgsConstructor
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class UserService {
private final FeedCategoryDAO feedCategoryDAO;
private final UserDAO userDAO;
private final UserSettingsDAO userSettingsDAO;
private final FeedSubscriptionService feedSubscriptionService;
private final PasswordEncryptionService encryptionService;
private final CommaFeedConfiguration config;
private final PostLoginActivities postLoginActivities;
/**
* try to log in with given credentials
*/
public Optional<User> login(String nameOrEmail, String password) {
if (nameOrEmail == null || password == null) {
return Optional.absent();
@@ -43,31 +51,16 @@ public class UserService {
if (user != null && !user.isDisabled()) {
boolean authenticated = encryptionService.authenticate(password, user.getPassword(), user.getSalt());
if (authenticated) {
Date lastLogin = user.getLastLogin();
Date now = new Date();
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);
performPostLoginActivities(user);
return Optional.of(user);
}
}
return Optional.absent();
}
}
/**
* try to log in with given api key
*/
public Optional<User> login(String apiKey) {
if (apiKey == null) {
return Optional.absent();
@@ -75,11 +68,19 @@ public class UserService {
User user = userDAO.findByApiKey(apiKey);
if (user != null && !user.isDisabled()) {
return Optional.fromNullable(user);
performPostLoginActivities(user);
return Optional.of(user);
}
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) {
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.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.service.DatabaseCleaningService;
import com.commafeed.backend.task.SchedulingService.ScheduledTask;
@RequiredArgsConstructor
public class OldStatusesCleanupTask implements ScheduledTask {
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class OldStatusesCleanupTask extends ScheduledTask {
private final CommaFeedConfiguration config;
private final DatabaseCleaningService cleaner;
@@ -25,7 +28,7 @@ public class OldStatusesCleanupTask implements ScheduledTask {
@Override
public long getInitialDelay() {
return 5;
return 10;
}
@Override

View File

@@ -2,26 +2,28 @@ package com.commafeed.backend.task;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
import com.commafeed.backend.service.DatabaseCleaningService;
import com.commafeed.backend.task.SchedulingService.ScheduledTask;
@RequiredArgsConstructor
public class OrphansCleanupTask implements ScheduledTask {
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class OrphansCleanupTask extends ScheduledTask {
private final DatabaseCleaningService cleaner;
@Override
public void run() {
cleaner.cleanEntriesWithoutSubscriptions();
cleaner.cleanFeedsWithoutSubscriptions();
cleaner.cleanContentsWithoutEntries();
}
@Override
public long getInitialDelay() {
return 30;
return 5;
}
@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;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
@@ -13,10 +12,10 @@ import lombok.RequiredArgsConstructor;
import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.StringUtil;
import com.commafeed.CommaFeedApplication;
import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserRole.Role;
import com.commafeed.backend.service.UserService;
import com.commafeed.frontend.session.SessionHelper;
import com.google.common.base.Optional;
import com.sun.jersey.api.core.HttpContext;
import com.sun.jersey.api.model.Parameter;
@@ -37,39 +36,43 @@ public class SecurityCheckProvider implements InjectableProvider<SecurityCheck,
}
@RequiredArgsConstructor
private static class SecurityCheckInjectable<T> extends AbstractHttpContextInjectable<User> {
static class SecurityCheckInjectable extends AbstractHttpContextInjectable<User> {
private static final String PREFIX = "Basic";
private final HttpServletRequest request;
private final SessionHelper sessionHelper;
private final UserService userService;
private final Role role;
private final boolean apiKeyAllowed;
@Override
public User getValue(HttpContext c) {
Optional<User> user = cookieSessionLogin();
Optional<User> user = apiKeyLogin(c);
if (!user.isPresent()) {
user = basicAuthenticationLogin(c);
}
if (!user.isPresent()) {
user = apiKeyLogin(c);
user = cookieSessionLogin();
}
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 {
throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED)
.entity("Credentials are required to access this resource.").type(MediaType.TEXT_PLAIN_TYPE).build());
}
}
private Optional<User> cookieSessionLogin() {
HttpSession session = request.getSession(false);
if (session != null) {
User user = (User) session.getAttribute(CommaFeedApplication.SESSION_USER);
return Optional.fromNullable(user);
Optional<User> cookieSessionLogin() {
Optional<User> loggedInUser = sessionHelper.getLoggedInUser();
if (loggedInUser.isPresent()) {
userService.performPostLoginActivities(loggedInUser.get());
}
return Optional.absent();
return loggedInUser;
}
private Optional<User> basicAuthenticationLogin(HttpContext c) {
@@ -84,10 +87,7 @@ public class SecurityCheckProvider implements InjectableProvider<SecurityCheck,
if (i > 0) {
String username = decoded.substring(0, i);
String password = decoded.substring(i + 1);
Optional<User> user = userService.login(username, password);
if (user.isPresent() && user.get().hasRole(role)) {
return user;
}
return userService.login(username, password);
}
}
}
@@ -96,22 +96,19 @@ public class SecurityCheckProvider implements InjectableProvider<SecurityCheck,
}
private Optional<User> apiKeyLogin(HttpContext c) {
String apiKey = c.getUriInfo().getPathParameters().getFirst("apiKey");
String apiKey = c.getUriInfo().getQueryParameters().getFirst("apiKey");
if (apiKey != null && apiKeyAllowed) {
Optional<User> user = userService.login(apiKey);
if (user.isPresent() && user.get().hasRole(role)) {
return user;
}
return userService.login(apiKey);
}
return Optional.absent();
}
}
private HttpServletRequest request;
private SessionHelper sessionHelper;
private UserService userService;
public SecurityCheckProvider(@Context HttpServletRequest request, @Context UserService userService) {
this.request = request;
public SecurityCheckProvider(@Context HttpServletRequest req, @Context UserService userService) {
this.sessionHelper = new SessionHelper(req);
this.userService = userService;
}
@@ -122,6 +119,6 @@ public class SecurityCheckProvider implements InjectableProvider<SecurityCheck,
@Override
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.FeedSubscription;
import com.google.common.collect.Lists;
import com.sun.syndication.feed.synd.SyndContentImpl;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndEntryImpl;
import com.rometools.rome.feed.synd.SyndContent;
import com.rometools.rome.feed.synd.SyndContentImpl;
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.ApiModelProperty;
@@ -72,7 +73,7 @@ public class Entry implements Serializable {
SyndContentImpl content = new SyndContentImpl();
content.setValue(getContent());
entry.setContents(Arrays.asList(content));
entry.setContents(Arrays.<SyndContent> asList(content));
entry.setLink(getUrl());
entry.setPublishedDate(getDate());
return entry;

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