commit 2a4db4eb31d3de959b3b9131338587e3362b35b5
parent 2787d8245f7d591820da3dd7434f5efe46385a11
Author: Kebigon <git@kebigon.xyz>
Date: Fri, 28 Dec 2018 22:03:05 +0900
Clean code
Diffstat:
8 files changed, 227 insertions(+), 52 deletions(-)
diff --git a/src/main/java/fr/lrgn/yuzurss/FeedClient.java b/src/main/java/fr/lrgn/yuzurss/FeedClient.java
@@ -1,12 +1,7 @@
package fr.lrgn.yuzurss;
import java.net.URI;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import org.json.JSONException;
import org.json.JSONObject;
import org.json.XML;
import org.slf4j.Logger;
@@ -17,19 +12,19 @@ import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec;
import org.springframework.web.reactive.function.client.WebClient.ResponseSpec;
+import fr.lrgn.yuzurss.exception.NoParserFoundException;
+import fr.lrgn.yuzurss.parser.AtomFeedParser;
+import fr.lrgn.yuzurss.parser.FeedParser;
+import fr.lrgn.yuzurss.parser.RSSFeedParser;
import reactor.core.publisher.Flux;
@Component
public class FeedClient
{
- private static final String ATOM_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssXXX"; // 2018-11-03T18:12:15+00:00
- private static final String RSS_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss Z"; // Sun, 09 Dec 2018 09:22:00 +0000
-
- private final SimpleDateFormat atomDateFormat = new SimpleDateFormat(ATOM_DATE_FORMAT);
- private final SimpleDateFormat rssDateFormat = new SimpleDateFormat(RSS_DATE_FORMAT, Locale.ROOT);
-
private final Logger log = LoggerFactory.getLogger(getClass());
+ private final FeedParser[] parsers = new FeedParser[] { new AtomFeedParser(), new RSSFeedParser() };
+
@Cacheable("feeds")
public Flux<FeedEntry> getFeed(URI uri)
{
@@ -37,6 +32,8 @@ public class FeedClient
final RequestHeadersSpec<?> request = client.get().uri(uri);
final ResponseSpec response = request.retrieve();
+ log.info("Downloading {}", uri);
+
return response.bodyToMono(String.class).flatMapMany(xml -> {
log.info("Downloaded {}", uri);
@@ -44,9 +41,9 @@ public class FeedClient
try
{
- return isAtom(root) ? parseAtomFeed(root) : parseRSSFeed(root);
+ return getFeedParser(uri, root).parseFeed(root);
}
- catch (final JSONException | ParseException e)
+ catch (final Throwable e)
{
log.info("Exception while parsing {}", uri, e);
return Flux.empty();
@@ -54,44 +51,12 @@ public class FeedClient
}).cache();
}
- private static boolean isAtom(JSONObject root)
- {
- return root.has("feed");
- }
-
- private Flux<FeedEntry> parseAtomFeed(JSONObject root) throws JSONException, ParseException
- {
- Flux<FeedEntry> entries = Flux.empty();
-
- for (final Object entry : root.getJSONObject("feed").getJSONArray("entry"))
- {
- final String author = ((JSONObject) entry).getJSONObject("author").getString("name");
- final String link = ((JSONObject) entry).getJSONObject("link").getString("href");
- final String title = ((JSONObject) entry).getString("title");
- final Date published = atomDateFormat.parse(((JSONObject) entry).getString("published"));
-
- entries = entries.mergeWith(Flux.just(new FeedEntry(title, link, published, author)));
- }
-
- return entries;
- }
-
- private Flux<FeedEntry> parseRSSFeed(JSONObject root) throws JSONException, ParseException
+ private FeedParser getFeedParser(URI uri, JSONObject root)
{
- Flux<FeedEntry> entries = Flux.empty();
-
- final JSONObject channel = root.getJSONObject("rss").getJSONObject("channel");
- final String author = channel.getString("title");
-
- for (final Object entry : channel.getJSONArray("item"))
- {
- final String link = ((JSONObject) entry).getString("link");
- final String title = ((JSONObject) entry).getString("title");
- final Date published = rssDateFormat.parse(((JSONObject) entry).getString("pubDate"));
-
- entries = entries.mergeWith(Flux.just(new FeedEntry(title, link, published, author)));
- }
+ for (final FeedParser parser : parsers)
+ if (parser.acceptFeed(root))
+ return parser;
- return entries;
+ throw new NoParserFoundException(uri);
}
-}
-\ No newline at end of file
+}
diff --git a/src/main/java/fr/lrgn/yuzurss/YuzuRSSErrorAttributes.java b/src/main/java/fr/lrgn/yuzurss/YuzuRSSErrorAttributes.java
@@ -0,0 +1,23 @@
+package fr.lrgn.yuzurss;
+
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.web.reactive.error.DefaultErrorAttributes;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.server.ServerRequest;
+
+@Component
+public class YuzuRSSErrorAttributes extends DefaultErrorAttributes
+{
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Override
+ public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace)
+ {
+ log.error("Error while processsing request {}", request.uri(), getError(request));
+
+ return super.getErrorAttributes(request, includeStackTrace);
+ }
+}
diff --git a/src/main/java/fr/lrgn/yuzurss/exception/DateParseException.java b/src/main/java/fr/lrgn/yuzurss/exception/DateParseException.java
@@ -0,0 +1,13 @@
+package fr.lrgn.yuzurss.exception;
+
+import java.text.ParseException;
+
+public class DateParseException extends YuzuRSSException
+{
+ private static final long serialVersionUID = 1L;
+
+ public DateParseException(String date, String format, ParseException cause)
+ {
+ super("Unable to parse date " + date + " with format " + format, cause);
+ }
+}
diff --git a/src/main/java/fr/lrgn/yuzurss/exception/NoParserFoundException.java b/src/main/java/fr/lrgn/yuzurss/exception/NoParserFoundException.java
@@ -0,0 +1,13 @@
+package fr.lrgn.yuzurss.exception;
+
+import java.net.URI;
+
+public class NoParserFoundException extends YuzuRSSException
+{
+ private static final long serialVersionUID = 1L;
+
+ public NoParserFoundException(URI uri)
+ {
+ super("No parser found for feed " + uri);
+ }
+}
diff --git a/src/main/java/fr/lrgn/yuzurss/exception/YuzuRSSException.java b/src/main/java/fr/lrgn/yuzurss/exception/YuzuRSSException.java
@@ -0,0 +1,16 @@
+package fr.lrgn.yuzurss.exception;
+
+abstract class YuzuRSSException extends RuntimeException
+{
+ private static final long serialVersionUID = 1L;
+
+ protected YuzuRSSException(String message)
+ {
+ super(message);
+ }
+
+ protected YuzuRSSException(String message, Throwable cause)
+ {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/fr/lrgn/yuzurss/parser/AtomFeedParser.java b/src/main/java/fr/lrgn/yuzurss/parser/AtomFeedParser.java
@@ -0,0 +1,63 @@
+package fr.lrgn.yuzurss.parser;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.json.JSONObject;
+
+import fr.lrgn.yuzurss.FeedEntry;
+import fr.lrgn.yuzurss.exception.DateParseException;
+import reactor.core.publisher.Flux;
+
+public class AtomFeedParser extends FeedParser
+{
+ private static final String ATOM_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssXXX"; // 2018-11-03T18:12:15+00:00
+
+ private static final ThreadLocal<SimpleDateFormat> atomDateFormat = new ThreadLocal<SimpleDateFormat>()
+ {
+ @Override
+ protected SimpleDateFormat initialValue()
+ {
+ return new SimpleDateFormat(ATOM_DATE_FORMAT);
+ };
+ };
+
+ @Override
+ public boolean acceptFeed(JSONObject root)
+ {
+ return root.has("feed");
+ }
+
+ @Override
+ public Flux<FeedEntry> parseFeed(JSONObject root)
+ {
+ Flux<FeedEntry> entries = Flux.empty();
+
+ for (final Object entry : root.getJSONObject("feed").getJSONArray("entry"))
+ {
+ log.debug("Parsing entry {}", entry);
+
+ final String author = ((JSONObject) entry).getJSONObject("author").getString("name");
+ final String link = ((JSONObject) entry).getJSONObject("link").getString("href");
+ final String title = ((JSONObject) entry).getString("title");
+ final Date published = parseDate(((JSONObject) entry).getString("published"));
+
+ entries = entries.mergeWith(Flux.just(new FeedEntry(title, link, published, author)));
+ }
+
+ return entries;
+ }
+
+ public Date parseDate(String date)
+ {
+ try
+ {
+ return atomDateFormat.get().parse(date);
+ }
+ catch (final ParseException ex)
+ {
+ throw new DateParseException(date, ATOM_DATE_FORMAT, ex);
+ }
+ }
+}
diff --git a/src/main/java/fr/lrgn/yuzurss/parser/FeedParser.java b/src/main/java/fr/lrgn/yuzurss/parser/FeedParser.java
@@ -0,0 +1,17 @@
+package fr.lrgn.yuzurss.parser;
+
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import fr.lrgn.yuzurss.FeedEntry;
+import reactor.core.publisher.Flux;
+
+public abstract class FeedParser
+{
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+
+ public abstract boolean acceptFeed(JSONObject root);
+
+ public abstract Flux<FeedEntry> parseFeed(JSONObject root);
+}
diff --git a/src/main/java/fr/lrgn/yuzurss/parser/RSSFeedParser.java b/src/main/java/fr/lrgn/yuzurss/parser/RSSFeedParser.java
@@ -0,0 +1,66 @@
+package fr.lrgn.yuzurss.parser;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import org.json.JSONObject;
+
+import fr.lrgn.yuzurss.FeedEntry;
+import fr.lrgn.yuzurss.exception.DateParseException;
+import reactor.core.publisher.Flux;
+
+public class RSSFeedParser extends FeedParser
+{
+ private static final String RSS_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss Z"; // Sun, 09 Dec 2018 09:22:00 +0000
+
+ private static final ThreadLocal<SimpleDateFormat> rssDateFormat = new ThreadLocal<SimpleDateFormat>()
+ {
+ @Override
+ protected SimpleDateFormat initialValue()
+ {
+ return new SimpleDateFormat(RSS_DATE_FORMAT, Locale.ROOT);
+ };
+ };
+
+ @Override
+ public boolean acceptFeed(JSONObject root)
+ {
+ return root.has("rss");
+ }
+
+ @Override
+ public Flux<FeedEntry> parseFeed(JSONObject root)
+ {
+ Flux<FeedEntry> entries = Flux.empty();
+
+ final JSONObject channel = root.getJSONObject("rss").getJSONObject("channel");
+ final String author = channel.getString("title");
+
+ for (final Object entry : channel.getJSONArray("item"))
+ {
+ log.debug("Parsing entry {}", entry);
+
+ final String link = ((JSONObject) entry).getString("link");
+ final String title = ((JSONObject) entry).getString("title");
+ final Date published = parseDate(((JSONObject) entry).getString("pubDate"));
+
+ entries = entries.mergeWith(Flux.just(new FeedEntry(title, link, published, author)));
+ }
+
+ return entries;
+ }
+
+ public Date parseDate(String date)
+ {
+ try
+ {
+ return rssDateFormat.get().parse(date);
+ }
+ catch (final ParseException ex)
+ {
+ throw new DateParseException(date, RSS_DATE_FORMAT, ex);
+ }
+ }
+}