Added feed fetcher code from previous Wicket project, updated dependencies and made it compile. Web app runs and shows an empty page.
This commit is contained in:
27
pom.xml
27
pom.xml
@@ -76,6 +76,33 @@
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rometools</groupId>
|
||||
<artifactId>rome</artifactId>
|
||||
<version>1.19.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rometools</groupId>
|
||||
<artifactId>rome-fetcher</artifactId>
|
||||
<version>1.19.0</version>
|
||||
</dependency>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.jdom</groupId>-->
|
||||
<!-- <artifactId>jdom</artifactId>-->
|
||||
<!-- <version>1.1.3</version>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- https://mvnrepository.com/artifact/org.jdom/jdom2 -->
|
||||
<dependency>
|
||||
<groupId>org.jdom</groupId>
|
||||
<artifactId>jdom2</artifactId>
|
||||
<version>2.0.6.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.17.2</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
217
src/main/java/dev/rsems/feedreader/FeedReader.java
Normal file
217
src/main/java/dev/rsems/feedreader/FeedReader.java
Normal file
@@ -0,0 +1,217 @@
|
||||
package dev.rsems.feedreader;
|
||||
|
||||
//import com.j256.ormlite.dao.CloseableIterator;
|
||||
//import com.j256.ormlite.dao.Dao;
|
||||
//import com.j256.ormlite.dao.DaoManager;
|
||||
//import com.j256.ormlite.jdbc.JdbcConnectionSource;
|
||||
//import com.j256.ormlite.support.ConnectionSource;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
/**
|
||||
* Homepage
|
||||
*/
|
||||
@Slf4j @SuppressWarnings("all")
|
||||
public class FeedReader /* extends WebPage */ {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final org.slf4j.Logger logger = log; // Logger.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
// private static final CssResourceReference feedReaderCss = new CssResourceReference(FeedReader.class, "resources/stylesheet.css");
|
||||
// private static final FaviconResourceReference feedReaderFavicon = new FaviconResourceReference(FeedReader.class, "resources/favicon.ico");
|
||||
|
||||
private static final int MAX_ITEMS_PER_FEED = 20;
|
||||
private static final DateFormat TIME_FMT = new SimpleDateFormat("HH:mm");
|
||||
private static final DateFormat DAY_FMT = new SimpleDateFormat("EEEE',' dd. M. yyyy");
|
||||
|
||||
// @Override
|
||||
// public void renderHead(IHeaderResponse response) {
|
||||
// super.renderHead(response);
|
||||
// response.render(CssReferenceHeaderItem.forReference(feedReaderCss));
|
||||
// response.render(FaviconReferenceHeaderItem.forReference(feedReaderFavicon));
|
||||
// }
|
||||
|
||||
/**
|
||||
* Constructor that is invoked when page is invoked without a session.
|
||||
* // @param parameters Page parameters
|
||||
*/
|
||||
public FeedReader(/* final PageParameters parameters */) {
|
||||
|
||||
// HttpServletRequest httpReq = (HttpServletRequest) getRequest().getContainerRequest();
|
||||
// String remoteHost = httpReq.getRemoteHost();
|
||||
//
|
||||
// Stopwatch stw = new Stopwatch(true);
|
||||
//
|
||||
// Set<FeedFetcherTask> feedFetcherTasks = new TreeSet<FeedFetcherTask>();
|
||||
//
|
||||
// logger.info("[" + remoteHost + "] Reading database and building the feed fetcher tasks list");
|
||||
//
|
||||
// ConnectionSource connectionSource = null;
|
||||
// try {
|
||||
// connectionSource = new JdbcConnectionSource("jdbc:mysql://db.rsems.de/feeds?user=feeds&password=fems1211");
|
||||
// Dao<UserFeed, Integer> userFeedDao = DaoManager.createDao(connectionSource, UserFeed.class);
|
||||
// CloseableIterator<UserFeed> ituf = userFeedDao.closeableIterator();
|
||||
// try {
|
||||
// int i = 0;
|
||||
// while (ituf.hasNext()) {
|
||||
// UserFeed userFeed = ituf.next();
|
||||
// if (userFeed.getVisible()) {
|
||||
// i++;
|
||||
//
|
||||
// feedFetcherTasks.add(new FeedFetcherTask(i, userFeed.getFeed().getName(), userFeed.getFeed().getUrl(), "", userFeed.getFilter()));
|
||||
// // logger.info("[" + remoteHost + "] #" + new DecimalFormat("00").format(i) + " " + userFeed.getFeed().getName() + " added");
|
||||
// }
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// logger.error(ExceptionUtils.getMessages(e));
|
||||
// logger.error("\n" + ExceptionUtils.getStackTrace(e));
|
||||
// throw new FeedException(e);
|
||||
// } finally {
|
||||
// ituf.close();
|
||||
// }
|
||||
// } catch (SQLException e) {
|
||||
// logger.error(ExceptionUtils.getMessages(e));
|
||||
// logger.error("\n" + ExceptionUtils.getStackTrace(e));
|
||||
// throw new FeedException("Failed to access database", e);
|
||||
// } finally {
|
||||
// if (connectionSource != null) {
|
||||
// try {
|
||||
// connectionSource.close();
|
||||
// } catch (SQLException unhandled) {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// logger.info("[" + remoteHost + "] About to invoke " + feedFetcherTasks.size() + " feed retrieval tasks after "
|
||||
// + stw.getTimeElapsed() + " ms");
|
||||
//
|
||||
// List<Future<IFeed>> feedFutures = null;
|
||||
// ExecutorService executorService = Executors.newCachedThreadPool();
|
||||
// try {
|
||||
// feedFutures = executorService.invokeAll(feedFetcherTasks);
|
||||
// } catch (InterruptedException e) {
|
||||
// logger.error(ExceptionUtils.getMessages(e));
|
||||
// logger.error("\n" + ExceptionUtils.getStackTrace(e));
|
||||
// throw new FeedException(e);
|
||||
// }
|
||||
//
|
||||
// logger.info("[" + remoteHost + "] About to extract retrieved data from task results after " + stw.getTimeElapsed() + " ms");
|
||||
//
|
||||
// ArrayList<IFeed> feeds = new ArrayList<IFeed>();
|
||||
// try {
|
||||
// for (Future<IFeed> ff : feedFutures) {
|
||||
// feeds.add(ff.get());
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// logger.error(ExceptionUtils.getMessages(e));
|
||||
// logger.error("\n" + ExceptionUtils.getStackTrace(e));
|
||||
// throw new FeedException(e);
|
||||
// } finally {
|
||||
// executorService.shutdown();
|
||||
// }
|
||||
//
|
||||
// logger.info("[" + remoteHost + "] About to render retrieved data after " + stw.getTimeElapsed() + " ms");
|
||||
//
|
||||
// add(new Label("head.title", Globals.TITLE));
|
||||
// RepeatingView feedRv = new RepeatingView("feed");
|
||||
// add(feedRv);
|
||||
//
|
||||
// int fc = 0;
|
||||
// for (IFeed feed : feeds) {
|
||||
// fc++;
|
||||
//
|
||||
// long lap = stw.getTimeElapsed();
|
||||
//
|
||||
// WebMarkupContainer feedMarkup = new WebMarkupContainer(feedRv.newChildId());
|
||||
// feedRv.add(feedMarkup);
|
||||
// String errorStatus = feed.getErrorStatus();
|
||||
//
|
||||
// feedMarkup.add(new ExternalLink("feedtitle", (feed.getLink() == null ? "" : feed.getLink()), feed.getName()));
|
||||
//
|
||||
// if (errorStatus.equals("")) {
|
||||
// feedMarkup.add(new ExternalLink("feedurl", feed.getUrl().toExternalForm(), feed.getTitle()));
|
||||
// feedMarkup
|
||||
// .add(new Label("feeddate", feed.getDate() != null ? Globals.DEFAULT_DISPLAY_DATE_FORMAT.format(feed.getDate()) : " ")); // this is ANSI 160, not space
|
||||
// } else {
|
||||
// feedMarkup.add(new ExternalLink("feedurl", feed.getUrl().toExternalForm(), "Error retrieving feed").add(new Behavior() {
|
||||
// @Override
|
||||
// public void beforeRender(Component component) {
|
||||
// component.getResponse().write("<strong>");
|
||||
// super.beforeRender(component);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void afterRender(Component component) {
|
||||
// super.afterRender(component);
|
||||
// component.getResponse().write("</strong>");
|
||||
// }
|
||||
// }));
|
||||
// feedMarkup.add(new Label("feeddate", errorStatus));
|
||||
// }
|
||||
//
|
||||
// RepeatingView itemRv = new RepeatingView("item");
|
||||
// feedMarkup.add(itemRv);
|
||||
// String lastDay = null;
|
||||
// ArrayList<FeedItem> items = feed.getFeedItems();
|
||||
//
|
||||
// if (feed.getErrorStatus() != "") {
|
||||
// logger.info("[Client " + remoteHost + "] " + feed.getErrorStatus() + "\n" + feed.getStackTrace());
|
||||
// }
|
||||
//
|
||||
// Collections.sort(items, FeedItem.LATEST_FIRST);
|
||||
// int count = 0;
|
||||
// for (Iterator<FeedItem> it = items.iterator(); it.hasNext();) {
|
||||
// FeedItem item = it.next();
|
||||
//
|
||||
// String itemlabel = item.getTitle() == null ? "—" : StringUtils.cleanHtml(item.getTitle(), feed.getUrl().getPath());
|
||||
//
|
||||
// if (!item.getTitle().isEmpty() && !feed.getFilter().matches(itemlabel)) {
|
||||
//
|
||||
// WebMarkupContainer itemMarkup = new WebMarkupContainer(itemRv.newChildId());
|
||||
// itemRv.add(itemMarkup);
|
||||
//
|
||||
// Date itemDate = item.getDate();
|
||||
// String day = itemDate == null ? " " : DAY_FMT.format(itemDate); //   not space
|
||||
// final boolean dayEqualsLastDay = day.equals(lastDay);
|
||||
// itemMarkup.add(new Label("date", day) {
|
||||
// @Override
|
||||
// public boolean isVisible() {
|
||||
// return !dayEqualsLastDay;
|
||||
// }
|
||||
// });
|
||||
// if (!dayEqualsLastDay) {
|
||||
// lastDay = day;
|
||||
// }
|
||||
// itemMarkup.add(new Label("itemdate", itemDate == null ? "—" : TIME_FMT.format(itemDate)));
|
||||
//
|
||||
// String itemlink = item.getLink() == null ? "" : item.getLink();
|
||||
//
|
||||
// String itemsummary = item.getSummary() == null ? "" : StringUtils.cleanHtml(item.getSummary(), feed.getUrl().getPath());
|
||||
// itemMarkup.add(new ExternalLink("itemlink", itemlink, itemlabel).add(new AttributeModifier("title", itemsummary)));
|
||||
//
|
||||
// String itemAuthor = item.getAuthor() == null || item.getAuthor().equals("") ? ""
|
||||
// : " (" + item.getAuthor().replaceFirst("(.*?)<(.*?)@(.*?)>", "$1").trim() + ")";
|
||||
// itemMarkup.add(new Label("itemauthor", StringUtils.unquote(itemAuthor)));
|
||||
//
|
||||
// count++;
|
||||
// if (count >= MAX_ITEMS_PER_FEED) {
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// logger.info("[" + remoteHost + "] #" + fc + ": " + count + " feed items processed (" + (stw.getTimeElapsed() - lap) + " ms)");
|
||||
//
|
||||
// }
|
||||
//
|
||||
// add(new Label("foot", Globals.TITLE + " " + Version.VERSION + " " + Globals.DEFAULT_DISPLAY_DATE_FORMAT.format(new Date())));
|
||||
//
|
||||
// logger.info("[" + remoteHost + "] Finished processing feeds after " + stw.getTimeElapsed() + " ms");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,13 +6,13 @@ import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class SimpleController {
|
||||
public class FeedReaderController {
|
||||
@Value("${spring.application.name}")
|
||||
String appName;
|
||||
|
||||
@GetMapping("/")
|
||||
public String homePage(Model model) {
|
||||
model.addAttribute("appName", appName);
|
||||
return "home";
|
||||
return "index";
|
||||
}
|
||||
}
|
||||
68
src/main/java/dev/rsems/feedreader/FeedReaderWebApp.java
Normal file
68
src/main/java/dev/rsems/feedreader/FeedReaderWebApp.java
Normal file
@@ -0,0 +1,68 @@
|
||||
package dev.rsems.feedreader;
|
||||
|
||||
import com.rometools.fetcher.FeedFetcher;
|
||||
import com.rometools.fetcher.impl.FeedFetcherCache;
|
||||
import com.rometools.fetcher.impl.HttpURLFeedFetcher;
|
||||
import com.rometools.fetcher.impl.LinkedHashMapFeedInfoCache;
|
||||
import dev.rsems.feedreader.fetcher.FetcherEventListenerImpl;
|
||||
|
||||
/**
|
||||
* Application object for your web application. If you want to run this
|
||||
* application without deploying, run the Start class.
|
||||
* // @see dev.rsems.feedreader.Start#main(String[])
|
||||
*/
|
||||
@SuppressWarnings("all")
|
||||
public class FeedReaderWebApp /* extends WebApplication */ {
|
||||
// private static final Logger logger = Logger.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
private static final FeedFetcherCache feedInfoCache = LinkedHashMapFeedInfoCache.getInstance();
|
||||
public static final FeedFetcher fetcher = new HttpURLFeedFetcher(feedInfoCache);
|
||||
private static final FetcherEventListenerImpl listener = new FetcherEventListenerImpl();
|
||||
|
||||
static {
|
||||
fetcher.addFetcherEventListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public FeedReaderWebApp() {}
|
||||
|
||||
/**
|
||||
* // @see org.apache.wicket.Application#getHomePage()
|
||||
*/
|
||||
// @Override
|
||||
public Class<FeedReader> getHomePage() {
|
||||
return FeedReader.class;
|
||||
}
|
||||
|
||||
// @Override
|
||||
// protected void init() {
|
||||
// super.init();
|
||||
// getMarkupSettings().setStripWicketTags(true);
|
||||
//
|
||||
// getRequestCycleListeners().add(new AbstractRequestCycleListener() {
|
||||
// @Override
|
||||
// public IRequestHandler onException(RequestCycle cycle, Exception e) {
|
||||
// return new RenderPageRequestHandler(new PageProvider(new Error(e)));
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// IMarkupSettings ms = getMarkupSettings();
|
||||
// logger.info(getMarkupSettingsInfo(ms, "DefaultMarkupEncoding"));
|
||||
// logger.info(getMarkupSettingsInfo(ms, "CompressWhitespace"));
|
||||
// logger.info(getMarkupSettingsInfo(ms, "AutomaticLinking"));
|
||||
// logger.info(getMarkupSettingsInfo(ms, "DefaultBeforeDisabledLink"));
|
||||
// logger.info(getMarkupSettingsInfo(ms, "DefaultAfterDisabledLink"));
|
||||
// logger.info(getMarkupSettingsInfo(ms, "StripComments"));
|
||||
// logger.info(getMarkupSettingsInfo(ms, "StripWicketTags"));
|
||||
// logger.info(getMarkupSettingsInfo(ms, "ThrowExceptionOnMissingXmlDeclaration"));
|
||||
//
|
||||
// }
|
||||
|
||||
// private static String getMarkupSettingsInfo(IMarkupSettings ms, String info) {
|
||||
// Object result = ReflectionUtils.invokeMethod(ms, ms.getClass().getName(), "get" + info, (Class<?>[]) null, (Object[]) null);
|
||||
// return "get" + info + " is " + StringUtils.quote(result);
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
@EntityScan("dev.rsems.feedreader.model")
|
||||
@SpringBootApplication
|
||||
public class FeedreaderWebApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(FeedreaderWebApplication.class, args);
|
||||
}
|
||||
|
||||
20
src/main/java/dev/rsems/feedreader/Globals.java
Normal file
20
src/main/java/dev/rsems/feedreader/Globals.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package dev.rsems.feedreader;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class Globals {
|
||||
private Globals() {}
|
||||
|
||||
public static final boolean DEBUG = true;
|
||||
|
||||
public static final String TITLE = "Feed Reader";
|
||||
|
||||
public static final int SO_CONNECT_TIMEOUT_MILLIS = 200;
|
||||
public static final int SO_READ_TIMEOUT_MILLIS = 3000;
|
||||
|
||||
public static final String DEFAULT_DISPLAY_DATE_FORMAT_PATTERN = "dd.MM.yy HH:mm";
|
||||
public static final DateFormat DEFAULT_DISPLAY_DATE_FORMAT = new SimpleDateFormat(DEFAULT_DISPLAY_DATE_FORMAT_PATTERN);
|
||||
|
||||
}
|
||||
24
src/main/java/dev/rsems/feedreader/feeds/FeedException.java
Normal file
24
src/main/java/dev/rsems/feedreader/feeds/FeedException.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package dev.rsems.feedreader.feeds;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class FeedException extends RuntimeException {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1;
|
||||
|
||||
public FeedException() {}
|
||||
|
||||
public FeedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public FeedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public FeedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
46
src/main/java/dev/rsems/feedreader/feeds/FeedItem.java
Normal file
46
src/main/java/dev/rsems/feedreader/feeds/FeedItem.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package dev.rsems.feedreader.feeds;
|
||||
|
||||
import dev.rsems.feedreader.Globals;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
|
||||
@Data @NoArgsConstructor @AllArgsConstructor @Slf4j
|
||||
public class FeedItem implements Comparable<FeedItem> {
|
||||
private String title = null;
|
||||
private Date date = null;
|
||||
private String link = null;
|
||||
private String summary = null;
|
||||
private String author = null;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
(date == null ? "" : new SimpleDateFormat(Globals.DEFAULT_DISPLAY_DATE_FORMAT_PATTERN).format(date)) + "; "
|
||||
+ title + "; "
|
||||
+ summary + "; "
|
||||
+ author + "; "
|
||||
+ link;
|
||||
}
|
||||
|
||||
public int compareTo(FeedItem o) {
|
||||
return getDate().compareTo(o.getDate());
|
||||
}
|
||||
|
||||
// public static final Comparator<FeedItem> LATEST_FIRST_ = new Comparator<>() {
|
||||
// public int compare(FeedItem o1, FeedItem o2) {
|
||||
// Date d1 = o1.getDate() == null ? new Date(0L) : o1.getDate();
|
||||
// Date d2 = o2.getDate() == null ? new Date(0L) : o2.getDate();
|
||||
// return d2.compareTo(d1);
|
||||
// }
|
||||
// };
|
||||
|
||||
public static final Comparator<FeedItem> LATEST_FIRST = (FeedItem o1, FeedItem o2) ->
|
||||
(o2.getDate() == null ? new Date(0L) : o2.getDate()).compareTo(o1.getDate() == null ? new Date(0L) : o1.getDate());
|
||||
|
||||
}
|
||||
33
src/main/java/dev/rsems/feedreader/feeds/Filter.java
Normal file
33
src/main/java/dev/rsems/feedreader/feeds/Filter.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package dev.rsems.feedreader.feeds;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
public class Filter {
|
||||
private static final Logger logger = log; // Logger.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
private String filter = null;
|
||||
private String[] items = {};
|
||||
|
||||
public Filter(String filter) {
|
||||
if (Objects.nonNull(filter)) {
|
||||
this.filter = filter;
|
||||
this.items = filter.split("\\|");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public boolean matches(String string) {
|
||||
if (Objects.nonNull(filter)) {
|
||||
logger.info("string = \"" + string + "\", filter = \"" + filter + "\", items = [" + String.join( ", ", items) + "], match = " + Arrays.stream(items).anyMatch(string::contains));
|
||||
return Arrays.stream(items).anyMatch(string::contains);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
32
src/main/java/dev/rsems/feedreader/feeds/IFeed.java
Normal file
32
src/main/java/dev/rsems/feedreader/feeds/IFeed.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package dev.rsems.feedreader.feeds;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public interface IFeed extends Comparable<IFeed> {
|
||||
|
||||
int getId();
|
||||
|
||||
String getName();
|
||||
|
||||
URL getUrl();
|
||||
|
||||
String getTitle();
|
||||
|
||||
String getLink();
|
||||
|
||||
Date getDate();
|
||||
|
||||
Filter getFilter();
|
||||
|
||||
ArrayList<FeedItem> getFeedItems();
|
||||
|
||||
String getErrorStatus();
|
||||
|
||||
String getStackTrace();
|
||||
|
||||
void refresh();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package dev.rsems.feedreader.fetcher;
|
||||
|
||||
import dev.rsems.feedreader.feeds.IFeed;
|
||||
import dev.rsems.feedreader.rome.Feed;
|
||||
import dev.rsems.util.stopwatch.Stopwatch;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@AllArgsConstructor @Slf4j
|
||||
public class FeedFetcherTask implements Callable<IFeed>, Comparable<FeedFetcherTask> {
|
||||
private static final Logger logger = log; // Logger.getLogger(MethodHandles.lookup().lookupClass());
|
||||
private int id;
|
||||
private String name;
|
||||
private String url;
|
||||
private String info;
|
||||
private String filter;
|
||||
|
||||
@Override
|
||||
public IFeed call() {
|
||||
Stopwatch stw = new Stopwatch(true);
|
||||
IFeed feed = new Feed(id, name, url, filter);
|
||||
logger.info((info != null && !info.isEmpty() ? "[" + info + "]" : "") + " #" + id + " \"" + name + "\" retrieved (" + stw.getTimeElapsed() + " ms)");
|
||||
return feed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(FeedFetcherTask o) {
|
||||
return Integer.compare(id, o.id);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package dev.rsems.feedreader.fetcher;
|
||||
|
||||
import com.rometools.fetcher.FetcherEvent;
|
||||
import com.rometools.fetcher.FetcherListener;
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public class FetcherEventListenerImpl implements FetcherListener {
|
||||
// private static Logger logger = Logger.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
public void fetcherEvent(FetcherEvent event) {
|
||||
String eventType = event.getEventType();
|
||||
if (FetcherEvent.EVENT_TYPE_FEED_POLLED.equals(eventType)) {
|
||||
; // logger.info("\tEVENT: Feed Polled. URL = " + event.getUrlString());
|
||||
} else if (FetcherEvent.EVENT_TYPE_FEED_RETRIEVED.equals(eventType)) {
|
||||
; // logger.info("Retrieved " + event.getUrlString());
|
||||
} else if (FetcherEvent.EVENT_TYPE_FEED_UNCHANGED.equals(eventType)) {
|
||||
; // logger.info("Unchanged: " + event.getUrlString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
308
src/main/java/dev/rsems/feedreader/myownfeedcode/MyOwnFeed.java
Normal file
308
src/main/java/dev/rsems/feedreader/myownfeedcode/MyOwnFeed.java
Normal file
@@ -0,0 +1,308 @@
|
||||
package dev.rsems.feedreader.myownfeedcode;
|
||||
|
||||
import dev.rsems.feedreader.Globals;
|
||||
import dev.rsems.feedreader.feeds.FeedException;
|
||||
import dev.rsems.feedreader.feeds.FeedItem;
|
||||
import dev.rsems.feedreader.feeds.Filter;
|
||||
import dev.rsems.feedreader.feeds.IFeed;
|
||||
import dev.rsems.feedreader.myownfeedcode.specification.Atom10Specification;
|
||||
import dev.rsems.feedreader.myownfeedcode.specification.FeedDescription;
|
||||
import dev.rsems.feedreader.myownfeedcode.specification.field.FieldSpec;
|
||||
import dev.rsems.feedreader.myownfeedcode.specification.field.FieldSpecPathNode;
|
||||
import dev.rsems.feedreader.myownfeedcode.specification.field.FieldType;
|
||||
import dev.rsems.util.exceptions.ExceptionUtils;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jdom2.Document;
|
||||
import org.jdom2.Element;
|
||||
import org.jdom2.JDOMException;
|
||||
import org.jdom2.input.SAXBuilder;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.safety.Safelist;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Vector;
|
||||
|
||||
@Slf4j
|
||||
public class MyOwnFeed implements IFeed {
|
||||
private static final Logger logger = log; // Logger.getLogger(MethodHandles.lookup().lookupClass());
|
||||
private static final DateFormat FALLBACK_DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
|
||||
@Getter
|
||||
private int id;
|
||||
|
||||
/**
|
||||
* The feed name given manually at instance creation
|
||||
*/
|
||||
@Getter
|
||||
private final String name;
|
||||
/**
|
||||
* The URL the link was received from after being specified manually at instance creation
|
||||
*/
|
||||
@Getter
|
||||
private URL url;
|
||||
/**
|
||||
* This feed's feed specification
|
||||
*/
|
||||
@Getter
|
||||
private final FeedDescription feedSpec;
|
||||
|
||||
/**
|
||||
* The filter read from the database for the feed
|
||||
*/
|
||||
@Getter
|
||||
private Filter filter;
|
||||
|
||||
/**
|
||||
* The feed title as specified in the feed
|
||||
*/
|
||||
@Getter
|
||||
private String title;
|
||||
/**
|
||||
* Link to the feed source as specified in the feed
|
||||
*/
|
||||
@Getter
|
||||
private String link;
|
||||
/**
|
||||
* The feed's date
|
||||
*/
|
||||
@Getter
|
||||
private Date date = null;
|
||||
|
||||
@Getter
|
||||
private Document document;
|
||||
@Getter
|
||||
private ArrayList<FeedItem> feedItems = new ArrayList<>();
|
||||
|
||||
@Getter
|
||||
private String errorStatus = "";
|
||||
@Getter
|
||||
private String stackTrace = null;
|
||||
|
||||
private FeedItem item;
|
||||
|
||||
public MyOwnFeed(int id, String name, String urlString, FeedDescription feedSpec) {
|
||||
this(id, name, urlString, feedSpec, null);
|
||||
}
|
||||
|
||||
public MyOwnFeed(int id, String name, String urlString, FeedDescription feedSpec, String filterString) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.filter = new Filter(filterString);
|
||||
this.feedSpec = feedSpec;
|
||||
try {
|
||||
url = new URL(urlString);
|
||||
build();
|
||||
} catch (Exception e) {
|
||||
errorStatus = ExceptionUtils.getMessages(e);
|
||||
stackTrace = ExceptionUtils.getStackTrace(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public MyOwnFeed(String name, URL url, FeedDescription feedSpec) {
|
||||
this.name = name;
|
||||
this.feedSpec = feedSpec;
|
||||
this.url = url;
|
||||
try {
|
||||
build();
|
||||
} catch (Exception e) {
|
||||
errorStatus = ExceptionUtils.getMessages(e);
|
||||
stackTrace = ExceptionUtils.getStackTrace(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public MyOwnFeed(String name, File f, FeedDescription feedSpec) {
|
||||
this.name = name;
|
||||
this.feedSpec = feedSpec;
|
||||
try {
|
||||
this.url = f.toURI().toURL();
|
||||
build();
|
||||
} catch (Exception e) {
|
||||
errorStatus = ExceptionUtils.getMessages(e);
|
||||
stackTrace = ExceptionUtils.getStackTrace(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void build() throws JDOMException, IOException {
|
||||
SAXBuilder sb = new SAXBuilder();
|
||||
document = sb.build(url.openStream());
|
||||
// logger.info("Namespace: " + document.getRootElement().getNamespace());
|
||||
// logger.info("NamespacePrefix: " + document.getRootElement().getNamespacePrefix());
|
||||
// logger.info("NamespaceURI: " + document.getRootElement().getNamespaceURI());
|
||||
// logger.info("AdditionalNamespaces: " + document.getRootElement().getAdditionalNamespaces());
|
||||
// logger.info("NamespacesInScope: " + document.getRootElement().getNamespacesInScope());
|
||||
feedItems = new ArrayList<>();
|
||||
item = null;
|
||||
transferElementsToItems(document.getRootElement());
|
||||
if (item != null) {
|
||||
feedItems.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void transferElementsToItems(Element element) {
|
||||
try {
|
||||
if (getElementPath(element).equals(feedSpec.getEntryRootElement().getPath())) {
|
||||
if (item != null) {
|
||||
feedItems.add(item);
|
||||
}
|
||||
item = new FeedItem();
|
||||
} else {
|
||||
FieldSpecPathNode fieldSpecPathNode = feedSpec.find(getElementPath(element));
|
||||
if (fieldSpecPathNode != null) {
|
||||
addElementToItem(this, item, element, fieldSpecPathNode.getElement());
|
||||
}
|
||||
}
|
||||
ArrayList<Element> children = new ArrayList<>(element.getChildren());
|
||||
for (Element child : children) {
|
||||
transferElementsToItems(child);
|
||||
}
|
||||
errorStatus = "";
|
||||
stackTrace = null;
|
||||
} catch (Exception e) {
|
||||
errorStatus = ExceptionUtils.getMessages(e);
|
||||
stackTrace = ExceptionUtils.getStackTrace(e);
|
||||
logger.info("\n" + stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addElementToItem(MyOwnFeed myOwnFeed, FeedItem item, Element element, FieldSpec fieldSpec) {
|
||||
String text = fieldSpec.hasAttribute() ? element.getAttributeValue(fieldSpec.getAttribute()) : element.getText();
|
||||
FieldType type = fieldSpec.getType();
|
||||
try {
|
||||
switch (type) {
|
||||
case FEEDTITLE:
|
||||
myOwnFeed.title = text;
|
||||
break;
|
||||
case FEEDLINK:
|
||||
myOwnFeed.link = text;
|
||||
break;
|
||||
case FEEDDATE:
|
||||
myOwnFeed.date = parseDateString(myOwnFeed.feedSpec, text);
|
||||
break;
|
||||
case ENTRYTITLE:
|
||||
item.setTitle(Jsoup.clean(text, Safelist.none()));
|
||||
break;
|
||||
case ENTRYLINK:
|
||||
item.setLink(text);
|
||||
break;
|
||||
case ENTRYDATE:
|
||||
item.setDate(parseDateString(myOwnFeed.feedSpec, text));
|
||||
break;
|
||||
case ENTRYSUMMARY:
|
||||
item.setSummary(Jsoup.clean(text, Safelist.none()));
|
||||
break;
|
||||
case ENTRYAUTHOR:
|
||||
item.setAuthor(text);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.info("\n" + ExceptionUtils.getStackTrace(e));
|
||||
}
|
||||
}
|
||||
|
||||
public static String getElementPath(Element element) {
|
||||
String path = element.getQualifiedName();
|
||||
Element parent = element.getParentElement();
|
||||
while (parent != null) {
|
||||
path = parent.getQualifiedName() + FieldSpec.PATH_SEP + path;
|
||||
parent = parent.getParentElement();
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
try {
|
||||
SAXBuilder sb = new SAXBuilder();
|
||||
document = sb.build(url.openStream());
|
||||
} catch (Exception e) {
|
||||
throw new FeedException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(IFeed o) {
|
||||
return Integer.compare(id, o.getId());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public Vector<Object> getColumnNames() {
|
||||
return new Vector<>(Arrays.asList(new Object[]{"Date", "Title", "Summary"}));
|
||||
}
|
||||
|
||||
public Vector<Vector<Object>> getData() {
|
||||
Vector<Vector<Object>> data = new Vector<>();
|
||||
for (FeedItem item : feedItems) {
|
||||
Vector<Object> row = new Vector<>(Arrays.asList(new Object[]{
|
||||
Globals.DEFAULT_DISPLAY_DATE_FORMAT.format(item.getDate()), item.getTitle(), item.getSummary()}));
|
||||
data.add(row);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private static Date parseDateString(FeedDescription feedSpec, String text) {
|
||||
try {
|
||||
if (feedSpec.getEntryDateFormatString().matches("(.*)Z") && text.matches("(.*):00")) {
|
||||
int colonpos = text.lastIndexOf(':');
|
||||
text = text.substring(0, colonpos) + text.substring(colonpos + 1);
|
||||
}
|
||||
return feedSpec.getEntryDateFormat().parse(text);
|
||||
} catch (ParseException pe) {
|
||||
try {
|
||||
return FALLBACK_DATEFORMAT.parse(text);
|
||||
} catch (ParseException unhandled) {
|
||||
throw new FeedException("Date could not be parsed by either feed-specification-supplied or fallback date format", pe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Test ----------
|
||||
|
||||
// 2012-10-24T13:28:21+02:00
|
||||
// yyyy-MM-dd'T'HH:mm:ssZ
|
||||
|
||||
public static void main(String[] args) {
|
||||
// System.setProperty("java.net.useSystemProxies", "true");
|
||||
// Feed feed = new Feed("Heise",
|
||||
// "http://www.heise.de/newsticker/heise-atom.xml", new Atom10FeedSpec());
|
||||
// Feed feed = new Feed("Golem", "http://rss.golem.de/rss.php?feed=ATOM1.0",
|
||||
// new Atom10FeedSpec());
|
||||
// Feed feed = new Feed("Slashdot",
|
||||
// "http://rss.slashdot.org/Slashdot/slashdot", new Rdf09FeedSpec());
|
||||
// Feed feed = new Feed("Telepolis", "http://www.heise.de/tp/news-atom.xml",
|
||||
// new Atom10FeedSpec());
|
||||
// Feed feed = new Feed("Wordpress Blog",
|
||||
// "http://en.blog.wordpress.com/feed/", new Rss20FeedSpec());
|
||||
|
||||
// Feed feed = new Feed("dpreview",
|
||||
// "http://www.dpreview.com/feeds/news.xml", new
|
||||
// Rss20FeedSpec("EEE',' d MMM yyyy HH:mm:ss' Z'"));
|
||||
// Feed feed = new Feed("dpreview",
|
||||
// "http://www.dpreview.com/feeds/news.xml", new
|
||||
// FeedSpecificationByName("Rss20", "EEE',' d MMM yyyy HH:mm:ss' Z'"));
|
||||
|
||||
MyOwnFeed myOwnFeed = new MyOwnFeed(0, "WU ML", "http://mail-archives.apache.org/mod_mbox/wicket-users/?format=atom", new Atom10Specification());
|
||||
|
||||
ArrayList<FeedItem> items = myOwnFeed.getFeedItems();
|
||||
for (FeedItem item : items) {
|
||||
System.out.println(item.getDate() == null ? "" : Globals.DEFAULT_DISPLAY_DATE_FORMAT.format(item.getDate()) + ";" + "\""
|
||||
+ item.getTitle() + "\";" + "\"" + item.getSummary() + "\";" + "\"" + item.getAuthor() + "\";" + "<" + item.getLink()
|
||||
+ ">");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package dev.rsems.feedreader.myownfeedcode.specification;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class Atom10DSpecification extends FeedSpecification {
|
||||
|
||||
public Atom10DSpecification() {
|
||||
|
||||
super(
|
||||
"feed/title",
|
||||
"feed/link href",
|
||||
"feed/updated",
|
||||
"feed/entry",
|
||||
"feed/entry/title",
|
||||
"feed/entry/link href",
|
||||
"feed/entry/content/div/pre",
|
||||
"feed/entry/author/name",
|
||||
"feed/entry/updated",
|
||||
ISO8610_DATE_FORMAT);
|
||||
|
||||
formatName = "Atom 1.0";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package dev.rsems.feedreader.myownfeedcode.specification;
|
||||
|
||||
public class Atom10Specification extends FeedSpecification {
|
||||
|
||||
public Atom10Specification() {
|
||||
|
||||
super(
|
||||
"feed/title",
|
||||
"feed/link href",
|
||||
"feed/updated",
|
||||
"feed/entry",
|
||||
"feed/entry/title",
|
||||
"feed/entry/link href",
|
||||
"feed/entry/summary",
|
||||
"feed/entry/author/name",
|
||||
"feed/entry/updated",
|
||||
ISO8610_DATE_FORMAT);
|
||||
|
||||
formatName = "Atom 1.0";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package dev.rsems.feedreader.myownfeedcode.specification;
|
||||
|
||||
import dev.rsems.feedreader.myownfeedcode.specification.field.FieldSpec;
|
||||
import dev.rsems.feedreader.myownfeedcode.specification.field.FieldSpecPathNode;
|
||||
|
||||
import java.text.DateFormat;
|
||||
|
||||
public interface FeedDescription {
|
||||
String[] AVAILABLE_FEED_SPECS = new String[] { "Atom10", "Atom10D", "Rss09", "Rss10", "Rss20" };
|
||||
|
||||
String getFormatName();
|
||||
|
||||
FieldSpec getFeedTitleElement();
|
||||
|
||||
FieldSpec getFeedLinkElement();
|
||||
|
||||
FieldSpec getFeedDateElement();
|
||||
|
||||
FieldSpec getEntryRootElement();
|
||||
|
||||
FieldSpec getEntryTitleElement();
|
||||
|
||||
FieldSpec getEntryLinkElement();
|
||||
|
||||
FieldSpec getEntrySummaryElement();
|
||||
|
||||
FieldSpec getEntryAuthorElement();
|
||||
|
||||
FieldSpec getEntryDateElement();
|
||||
|
||||
String getEntryDateFormatString();
|
||||
|
||||
DateFormat getEntryDateFormat();
|
||||
|
||||
void setDateFormat(String dateFormatString);
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
FieldSpecPathNode getFieldSpecPathTree();
|
||||
|
||||
FieldSpecPathNode find(String path);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package dev.rsems.feedreader.myownfeedcode.specification;
|
||||
|
||||
import dev.rsems.feedreader.myownfeedcode.specification.field.FieldSpec;
|
||||
import dev.rsems.feedreader.myownfeedcode.specification.field.FieldSpecPathNode;
|
||||
import dev.rsems.feedreader.util.FeedReaderUtils;
|
||||
import lombok.Data;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static dev.rsems.feedreader.myownfeedcode.specification.field.FieldType.*;
|
||||
|
||||
@Data
|
||||
public class FeedSpecification implements FeedDescription {
|
||||
public static final String ISO8610_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
|
||||
protected static final String genericXmlFormatName = "Generic XML";
|
||||
|
||||
protected String formatName = genericXmlFormatName;
|
||||
|
||||
private FieldSpec feedTitleElement;
|
||||
private FieldSpec feedLinkElement;
|
||||
private FieldSpec feedDateElement;
|
||||
private FieldSpec entryRootElement;
|
||||
private FieldSpec entryTitleElement;
|
||||
private FieldSpec entryLinkElement;
|
||||
private FieldSpec entrySummaryElement;
|
||||
private FieldSpec entryAuthorElement;
|
||||
private FieldSpec entryDateElement;
|
||||
|
||||
private DateFormat entryDateFormat;
|
||||
private String entryDateFormatString;
|
||||
|
||||
private FieldSpecPathNode pathRootNode;
|
||||
|
||||
public FeedSpecification(
|
||||
String feedTitleElementPath,
|
||||
String feedLinkElementPath,
|
||||
String feedDateElementPath,
|
||||
String entryRootElementPath,
|
||||
String entryTitleElementPath,
|
||||
String entryLinkElementPath,
|
||||
String entrySummaryElementPath,
|
||||
String entryAuthorElementPath,
|
||||
String entryDateElementPath,
|
||||
String dateFormatString) {
|
||||
|
||||
this(
|
||||
new FieldSpec(FEEDTITLE, feedTitleElementPath),
|
||||
new FieldSpec(FEEDLINK, feedLinkElementPath),
|
||||
new FieldSpec(FEEDDATE, feedDateElementPath),
|
||||
new FieldSpec(ENTRYROOT, entryRootElementPath),
|
||||
new FieldSpec(ENTRYTITLE, entryTitleElementPath),
|
||||
new FieldSpec(ENTRYLINK, entryLinkElementPath),
|
||||
new FieldSpec(ENTRYSUMMARY, entrySummaryElementPath),
|
||||
new FieldSpec(ENTRYAUTHOR, entryAuthorElementPath),
|
||||
new FieldSpec(ENTRYDATE, entryDateElementPath),
|
||||
dateFormatString);
|
||||
}
|
||||
|
||||
public FeedSpecification(
|
||||
FieldSpec feedTitleElement,
|
||||
FieldSpec feedLinkElement,
|
||||
FieldSpec feedDateElement,
|
||||
FieldSpec entryRootElement,
|
||||
FieldSpec entryTitleElement,
|
||||
FieldSpec entryLinkElement,
|
||||
FieldSpec entrySummaryElement,
|
||||
FieldSpec entryAuthorElement,
|
||||
FieldSpec entryDateElement,
|
||||
String dateFormatString) {
|
||||
|
||||
this.feedTitleElement = feedTitleElement;
|
||||
this.feedLinkElement = feedLinkElement;
|
||||
this.feedDateElement = feedDateElement;
|
||||
this.entryRootElement = entryRootElement;
|
||||
this.entryTitleElement = entryTitleElement;
|
||||
this.entryLinkElement = entryLinkElement;
|
||||
this.entrySummaryElement = entrySummaryElement;
|
||||
this.entryAuthorElement = entryAuthorElement;
|
||||
this.entryDateFormatString = dateFormatString;
|
||||
this.entryDateElement = entryDateElement;
|
||||
this.entryDateFormat = new SimpleDateFormat(dateFormatString, Locale.US);
|
||||
|
||||
FieldSpec[] fieldSpecs = new FieldSpec[] {
|
||||
feedTitleElement,
|
||||
feedLinkElement,
|
||||
feedDateElement,
|
||||
entryRootElement,
|
||||
entryTitleElement,
|
||||
entryLinkElement,
|
||||
entrySummaryElement,
|
||||
entryAuthorElement,
|
||||
entryDateElement };
|
||||
|
||||
pathRootNode = new FieldSpecPathNode(null, feedTitleElement.getPathParts()[0], null, new TreeSet<>());
|
||||
|
||||
for (FieldSpec fieldSpec : fieldSpecs) {
|
||||
String[] pathParts = fieldSpec.getPathParts();
|
||||
FieldSpecPathNode node = pathRootNode;
|
||||
if (pathParts[0].equals(pathRootNode.getName())) {
|
||||
for (int j = 1; j < pathParts.length; j++) {
|
||||
FieldSpec element = j == pathParts.length - 1 ? fieldSpec : null;
|
||||
FieldSpecPathNode newNode = new FieldSpecPathNode(node, pathParts[j], element, new TreeSet<>());
|
||||
Collection<FieldSpecPathNode> children = node.getChildren();
|
||||
boolean found = false;
|
||||
for (FieldSpecPathNode child : children) {
|
||||
if (child.getName().equals(newNode.getName())) {
|
||||
newNode = child;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
children.add(newNode);
|
||||
node = newNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setDateFormat(String dateFormatString) {
|
||||
this.entryDateFormatString = dateFormatString;
|
||||
this.entryDateFormat = new SimpleDateFormat(dateFormatString, Locale.US);
|
||||
}
|
||||
|
||||
public FieldSpecPathNode getFieldSpecPathTree() {
|
||||
return pathRootNode;
|
||||
}
|
||||
|
||||
public FieldSpecPathNode find(String path) {
|
||||
return FeedReaderUtils.find(pathRootNode, path);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package dev.rsems.feedreader.myownfeedcode.specification;
|
||||
|
||||
import dev.rsems.feedreader.feeds.FeedException;
|
||||
import dev.rsems.feedreader.myownfeedcode.specification.field.FieldSpec;
|
||||
import dev.rsems.feedreader.myownfeedcode.specification.field.FieldSpecPathNode;
|
||||
import dev.rsems.util.strings.StringUtils;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Arrays;
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public class FeedSpecificationByName implements FeedDescription {
|
||||
private static final String FEED_SPECIFICATION_CLASS_NAME_SUFFIX = "Specification";
|
||||
private FeedSpecification feedSpec = null;
|
||||
|
||||
public FeedSpecificationByName(String name) {
|
||||
Class<?> clazz;
|
||||
if (Arrays.asList(FeedDescription.AVAILABLE_FEED_SPECS).indexOf(name) > -1) {
|
||||
try {
|
||||
clazz = Class.forName(FeedSpecification.class.getPackage().getName() + "." + name + FEED_SPECIFICATION_CLASS_NAME_SUFFIX);
|
||||
Constructor<?> constructor;
|
||||
constructor = clazz.getConstructor();
|
||||
feedSpec = FeedSpecification.class.cast(constructor.newInstance());
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new FeedException(e);
|
||||
} catch (SecurityException e) {
|
||||
throw new FeedException(e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new FeedException(e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new FeedException(e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new FeedException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FeedException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new FeedException(e);
|
||||
}
|
||||
} else {
|
||||
// Class<?>[] classes =
|
||||
// Util.getClasses(FeedSpec.class.getPackage().getName());
|
||||
// ArrayList<String> classNames = new ArrayList<String>();
|
||||
// for (int i = 0; i < classes.length; i++) {
|
||||
// String className = classes[i].getSimpleName();
|
||||
// if (!className.equals(FeedSpec.class.getSimpleName()) &&
|
||||
// className.endsWith(FeedSpec.class.getSimpleName())) {
|
||||
// classNames.add(className.substring(0,
|
||||
// className.indexOf(FeedSpec.class.getSimpleName())));
|
||||
// }
|
||||
// }
|
||||
throw new FeedException("Feed spec \"" + name + "\" not found. Available: " + StringUtils.concatenateQuoted(AVAILABLE_FEED_SPECS));
|
||||
}
|
||||
}
|
||||
|
||||
public FeedSpecificationByName(String name, String dateFormatString) {
|
||||
this(name);
|
||||
setDateFormat(dateFormatString);
|
||||
}
|
||||
|
||||
public String getFormatName() {
|
||||
return feedSpec.getFormatName();
|
||||
}
|
||||
|
||||
public FieldSpec getFeedTitleElement() {
|
||||
return feedSpec.getFeedTitleElement();
|
||||
}
|
||||
|
||||
public FieldSpec getFeedLinkElement() {
|
||||
return feedSpec.getFeedLinkElement();
|
||||
}
|
||||
|
||||
public FieldSpec getFeedDateElement() {
|
||||
return feedSpec.getFeedDateElement();
|
||||
}
|
||||
|
||||
public FieldSpec getEntryRootElement() {
|
||||
return feedSpec.getEntryRootElement();
|
||||
}
|
||||
|
||||
public FieldSpec getEntryTitleElement() {
|
||||
return feedSpec.getEntryTitleElement();
|
||||
}
|
||||
|
||||
public FieldSpec getEntryLinkElement() {
|
||||
return feedSpec.getEntryLinkElement();
|
||||
}
|
||||
|
||||
public FieldSpec getEntrySummaryElement() {
|
||||
return feedSpec.getEntrySummaryElement();
|
||||
}
|
||||
|
||||
public FieldSpec getEntryAuthorElement() {
|
||||
return feedSpec.getEntryAuthorElement();
|
||||
}
|
||||
|
||||
public FieldSpec getEntryDateElement() {
|
||||
return feedSpec.getEntryDateElement();
|
||||
}
|
||||
|
||||
public String getEntryDateFormatString() {
|
||||
return feedSpec.getEntryDateFormatString();
|
||||
}
|
||||
|
||||
public DateFormat getEntryDateFormat() {
|
||||
return feedSpec.getEntryDateFormat();
|
||||
}
|
||||
|
||||
public void setDateFormat(String dateFormatString) {
|
||||
feedSpec.setDateFormat(dateFormatString);
|
||||
}
|
||||
|
||||
public FieldSpecPathNode getFieldSpecPathTree() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public FieldSpecPathNode find(String path) {
|
||||
return feedSpec.find(path);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package dev.rsems.feedreader.myownfeedcode.specification;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class Rss09Specification extends FeedSpecification {
|
||||
|
||||
public Rss09Specification() {
|
||||
|
||||
super(
|
||||
"rdf:RDF/channel/title",
|
||||
"rdf:RDF/channel/link",
|
||||
"rdf:RDF/channel/dc:date",
|
||||
"rdf:RDF/item",
|
||||
"rdf:RDF/item/title",
|
||||
"rdf:RDF/item/link",
|
||||
"rdf:RDF/item/description",
|
||||
"rdf:RDF/item/dc:creator",
|
||||
"rdf:RDF/item/dc:date",
|
||||
"yyyy-MM-dd'T'HH:mm:ssZ");
|
||||
|
||||
formatName = "RSS 0.9";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package dev.rsems.feedreader.myownfeedcode.specification;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class Rss10Specification extends FeedSpecification {
|
||||
|
||||
public Rss10Specification() {
|
||||
|
||||
super(
|
||||
"rdf:RDF/channel/title",
|
||||
"rdf:RDF/channel/link",
|
||||
"rdf:RDF/channel/dc:date",
|
||||
"rdf:RDF/item",
|
||||
"rdf:RDF/item/title",
|
||||
"rdf:RDF/item/link",
|
||||
"rdf:RDF/item/description",
|
||||
"rdf:RDF/item/dc:creator",
|
||||
"rdf:RDF/item/dc:date",
|
||||
"yyyy-MM-dd'T'HH:mm:ssZ");
|
||||
|
||||
formatName = "RSS 1.0";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package dev.rsems.feedreader.myownfeedcode.specification;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class Rss20Specification extends FeedSpecification {
|
||||
|
||||
public Rss20Specification(String dateFormatString) {
|
||||
|
||||
super(
|
||||
"rss/channel/title",
|
||||
"rss/channel/link",
|
||||
"rss/channel/lastBuildDate",
|
||||
"rss/channel/item",
|
||||
"rss/channel/item/title",
|
||||
"rss/channel/item/link",
|
||||
"rss/channel/item/description",
|
||||
"rss/channel/item/dc:creator",
|
||||
"rss/channel/item/pubDate",
|
||||
dateFormatString);
|
||||
|
||||
formatName = "RSS 2.0";
|
||||
|
||||
}
|
||||
|
||||
public Rss20Specification() {
|
||||
this("EEE',' d MMM yyyy HH:mm:ss Z");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package dev.rsems.feedreader.myownfeedcode.specification.field;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import static dev.rsems.util.strings.StringUtils.quote;
|
||||
|
||||
@Getter
|
||||
public class FieldSpec {
|
||||
public static final char PATH_SEP = '/';
|
||||
public static final char ATTR_SEP = ' ';
|
||||
|
||||
private final FieldType type;
|
||||
@Setter private String name;
|
||||
@Setter private String attribute;
|
||||
@Setter private String path;
|
||||
private final String[] pathParts;
|
||||
|
||||
public FieldSpec(FieldType type, String path) {
|
||||
this.type = type;
|
||||
this.path = path;
|
||||
pathParts = path.split(String.valueOf(PATH_SEP));
|
||||
String[] element = pathParts[pathParts.length - 1].split(String.valueOf(ATTR_SEP));
|
||||
name = element[0];
|
||||
attribute = null;
|
||||
if (element.length > 1) {
|
||||
attribute = element[1];
|
||||
name = path.substring(0, path.lastIndexOf(ATTR_SEP)); //TODO Änderung path -> name ok?
|
||||
pathParts[pathParts.length - 1] = name;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasAttribute() {
|
||||
return attribute != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public boolean isFeedField() {
|
||||
return (type.name().startsWith("FEED"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public boolean isEntryField() {
|
||||
return (type.name().startsWith("ENTRY") && !type.equals(FieldType.ENTRYROOT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + type + "] hasAttribute=" + hasAttribute() + ", name=" + quote(name) + "; attribute=" + quote(attribute) + "; path=" + quote(path);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package dev.rsems.feedreader.myownfeedcode.specification.field;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@Data @AllArgsConstructor
|
||||
public class FieldSpecPathNode implements Comparable<FieldSpecPathNode> {
|
||||
private FieldSpecPathNode parent;
|
||||
private String part;
|
||||
private FieldSpec element;
|
||||
private Collection<FieldSpecPathNode> children;
|
||||
|
||||
public String getName() {
|
||||
return part;
|
||||
}
|
||||
|
||||
public void setName(String part) {
|
||||
this.part = part;
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public String path() {
|
||||
String path = part;
|
||||
FieldSpecPathNode ancestor = parent;
|
||||
while (ancestor != null) {
|
||||
path = ancestor.part + FieldSpec.PATH_SEP + path;
|
||||
ancestor = ancestor.parent;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public int depth() {
|
||||
int depth = 0;
|
||||
FieldSpecPathNode ancestor = parent;
|
||||
while (ancestor != null) {
|
||||
depth++;
|
||||
ancestor = ancestor.parent;
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
|
||||
public int compareTo(FieldSpecPathNode o) {
|
||||
int depth = depth();
|
||||
int otherDepth = o.depth();
|
||||
if (depth == otherDepth) {
|
||||
return part.compareTo(o.part);
|
||||
} else {
|
||||
return depth > otherDepth ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return path();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package dev.rsems.feedreader.myownfeedcode.specification.field;
|
||||
|
||||
public enum FieldType {
|
||||
FEEDTITLE("feed title"),
|
||||
FEEDLINK("feed link"),
|
||||
FEEDDATE("feed date"),
|
||||
ENTRYROOT("entry root"),
|
||||
ENTRYTITLE("entry title"),
|
||||
ENTRYLINK("entry link"),
|
||||
ENTRYSUMMARY("entry summary"),
|
||||
ENTRYAUTHOR("entry author"),
|
||||
ENTRYDATE("entry date");
|
||||
|
||||
private final String description;
|
||||
|
||||
FieldType(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return description;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package rest;
|
||||
package dev.rsems.feedreader.rest;
|
||||
|
||||
import dev.rsems.feedreader.exception.FeedIdMismatchException;
|
||||
import dev.rsems.feedreader.exception.FeedNotFoundException;
|
||||
@@ -18,7 +18,7 @@ public class FeedController {
|
||||
private FeedRepository feedRepository;
|
||||
|
||||
@GetMapping
|
||||
public Iterable findAll() {
|
||||
public Iterable<?> findAll() {
|
||||
return feedRepository.findAll();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package rest;
|
||||
package dev.rsems.feedreader.rest;
|
||||
|
||||
import dev.rsems.feedreader.exception.FeedIdMismatchException;
|
||||
import dev.rsems.feedreader.exception.FeedNotFoundException;
|
||||
124
src/main/java/dev/rsems/feedreader/rome/Feed.java
Normal file
124
src/main/java/dev/rsems/feedreader/rome/Feed.java
Normal file
@@ -0,0 +1,124 @@
|
||||
package dev.rsems.feedreader.rome;
|
||||
|
||||
import com.rometools.rome.feed.synd.SyndEntry;
|
||||
import com.rometools.rome.feed.synd.SyndFeed;
|
||||
import com.rometools.rome.io.SyndFeedInput;
|
||||
import com.rometools.rome.io.XmlReader;
|
||||
import dev.rsems.feedreader.FeedReaderWebApp;
|
||||
import dev.rsems.feedreader.feeds.FeedException;
|
||||
import dev.rsems.feedreader.feeds.FeedItem;
|
||||
import dev.rsems.feedreader.feeds.Filter;
|
||||
import dev.rsems.feedreader.feeds.IFeed;
|
||||
import dev.rsems.util.exceptions.ExceptionUtils;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
@Slf4j
|
||||
public class Feed implements IFeed {
|
||||
private static final Logger logger = log; // Logger.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
@Getter private final int id;
|
||||
@Getter private final String name;
|
||||
@Getter private URL url;
|
||||
@Getter private final Filter filter;
|
||||
@Getter private XmlReader reader;
|
||||
@Getter private SyndFeed syndFeed;
|
||||
|
||||
private ArrayList<FeedItem> items;
|
||||
|
||||
@Getter private String errorStatus = "";
|
||||
@Getter private String stackTrace = "";
|
||||
|
||||
public Feed(int id, String name, String url) {
|
||||
this(id, name, url, null);
|
||||
}
|
||||
|
||||
public Feed(int id, String name, String url, String filterString) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.filter = new Filter(filterString);
|
||||
items = new ArrayList<>();
|
||||
try {
|
||||
this.url = new URL(url);
|
||||
// reader = new XmlReader(this.url);
|
||||
// syndFeed = new SyndFeedInput().build(reader);
|
||||
syndFeed = FeedReaderWebApp.fetcher.retrieveFeed(this.url);
|
||||
|
||||
} catch (Exception e) {
|
||||
errorStatus = ExceptionUtils.getMessages(e);
|
||||
stackTrace = ExceptionUtils.getStackTrace(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return syndFeed == null ? null : syndFeed.getTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLink() {
|
||||
return syndFeed == null ? null : syndFeed.getLink();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getDate() {
|
||||
return syndFeed == null ? null : syndFeed.getPublishedDate();
|
||||
}
|
||||
|
||||
@Override @SuppressWarnings("UnnecessarySemicolon")
|
||||
public ArrayList<FeedItem> getFeedItems() {
|
||||
items = new ArrayList<>();
|
||||
try {
|
||||
reader = new XmlReader(url);
|
||||
SyndFeedInput input = new SyndFeedInput();
|
||||
input.setPreserveWireFeed(true);
|
||||
input.setPreserveWireFeed(true);
|
||||
SyndFeed feed = input.build(reader);
|
||||
ArrayList<SyndEntry> entries = new ArrayList<>(feed.getEntries());
|
||||
for (SyndEntry entry : entries) {
|
||||
Date date = entry.getUpdatedDate();
|
||||
if (date == null) {
|
||||
date = entry.getPublishedDate();
|
||||
}
|
||||
items.add(
|
||||
new FeedItem(
|
||||
entry.getTitle(),
|
||||
date,
|
||||
entry.getLink(),
|
||||
entry.getDescription() == null ? null : entry.getDescription().getValue(),
|
||||
entry.getAuthor()));
|
||||
}
|
||||
errorStatus = "";
|
||||
stackTrace = null;
|
||||
} catch (Exception e) {
|
||||
errorStatus = ExceptionUtils.getMessages(e);
|
||||
stackTrace = ExceptionUtils.getStackTrace(e);
|
||||
logger.info("\n" + stackTrace);
|
||||
} finally {
|
||||
if (reader != null)
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException ignored) {
|
||||
;
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
throw new FeedException("Unimplemented method");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(IFeed o) {
|
||||
return Integer.compare(id, o.getId());
|
||||
}
|
||||
|
||||
}
|
||||
130
src/main/java/dev/rsems/feedreader/util/FeedReaderUtils.java
Normal file
130
src/main/java/dev/rsems/feedreader/util/FeedReaderUtils.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package dev.rsems.feedreader.util;
|
||||
|
||||
import dev.rsems.feedreader.Globals;
|
||||
import dev.rsems.feedreader.myownfeedcode.specification.field.FieldSpecPathNode;
|
||||
import dev.rsems.util.exceptions.ExceptionUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
@Slf4j @SuppressWarnings("unused")
|
||||
public class FeedReaderUtils {
|
||||
private static final Logger logger = log; // Logger.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
public static final String URLINFO_PROTOCOL = "Protocol";
|
||||
public static final String URLINFO_AUTHORITY = "Authority";
|
||||
public static final String URLINFO_HOST = "Host";
|
||||
public static final String URLINFO_PORT = "Port";
|
||||
public static final String URLINFO_PATH = "Path";
|
||||
public static final String URLINFO_FILE = "File";
|
||||
public static final String URLINFO_QUERY = "Query";
|
||||
public static final String URLINFO_REF = "Ref";
|
||||
public static final String URLINFO_USERINFO = "UserInfo";
|
||||
|
||||
private FeedReaderUtils() {}
|
||||
|
||||
public static void testPrint(Object... objects) {
|
||||
if (Globals.DEBUG) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Object object : objects) {
|
||||
sb.append(object == null ? "null" : object);
|
||||
}
|
||||
logger.info(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static FieldSpecPathNode find(FieldSpecPathNode node, String what) {
|
||||
if (node.path().equals(what) && node.getElement() != null) {
|
||||
return node;
|
||||
} else {
|
||||
Collection<FieldSpecPathNode> children = node.getChildren();
|
||||
for (FieldSpecPathNode child : children) {
|
||||
FieldSpecPathNode result = find(child, what);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans all classes accessible from the context class loader which belong to the given package and subpackages.
|
||||
* Adapted from <a href="http://snippets.dzone.com/posts/show/4831">dzone</a> and extended to support use of JAR files
|
||||
* @param packageName The base package
|
||||
* @return The classes
|
||||
* // @throws ClassNotFoundException when class not founf
|
||||
* // @throws IOException when io error
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static Class<?>[] getClasses(String packageName) {
|
||||
try {
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
assert classLoader != null;
|
||||
String path = packageName.replace('.', '/');
|
||||
Enumeration<URL> resources = classLoader.getResources(path);
|
||||
List<String> dirs = new ArrayList<>();
|
||||
while (resources.hasMoreElements()) {
|
||||
URL resource = resources.nextElement();
|
||||
dirs.add(resource.getFile());
|
||||
}
|
||||
TreeSet<String> classes = new TreeSet<>();
|
||||
for (String directory : dirs) {
|
||||
classes.addAll(findClasses(directory, packageName));
|
||||
}
|
||||
ArrayList<Class<?>> classList = new ArrayList<>();
|
||||
for (String clazz : classes) {
|
||||
classList.add(Class.forName(clazz));
|
||||
}
|
||||
return classList.toArray(new Class[classes.size()]);
|
||||
} catch (Exception e) {
|
||||
ExceptionUtils.printStackTrace(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive method used to find all classes in a given directory and subdirs.
|
||||
* Adapted from <a href="http://snippets.dzone.com/posts/show/4831">dzone</a> and extended to support use of JAR files
|
||||
* @param directory The base directory
|
||||
* @param packageName The package name for classes found inside the base directory
|
||||
* @return The classes
|
||||
* @throws ClassNotFoundException if class not found
|
||||
*/
|
||||
private static TreeSet<String> findClasses(String directory, String packageName) throws Exception {
|
||||
TreeSet<String> classes = new TreeSet<>();
|
||||
if (directory.startsWith("file:") && directory.contains("!")) {
|
||||
String[] split = directory.split("!");
|
||||
URL jar = new URL(split[0]);
|
||||
ZipInputStream zip = new ZipInputStream(jar.openStream());
|
||||
ZipEntry entry = null;
|
||||
while ((entry = zip.getNextEntry()) != null) {
|
||||
if (entry.getName().endsWith(".class")) {
|
||||
String className = entry.getName().replaceAll("[$].*", "").replaceAll("[.]class", "").replace('/', '.');
|
||||
classes.add(className);
|
||||
}
|
||||
}
|
||||
}
|
||||
File dir = new File(directory);
|
||||
if (!dir.exists()) {
|
||||
return classes;
|
||||
}
|
||||
File[] files = dir.listFiles();
|
||||
assert files != null;
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
assert !file.getName().contains(".");
|
||||
classes.addAll(findClasses(file.getAbsolutePath(), packageName + "." + file.getName()));
|
||||
} else if (file.getName().endsWith(".class")) {
|
||||
classes.add(packageName + '.' + file.getName().substring(0, file.getName().length() - 6));
|
||||
}
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
|
||||
}
|
||||
291
src/main/java/dev/rsems/syndication/rome/io/impl/DateParser.java
Normal file
291
src/main/java/dev/rsems/syndication/rome/io/impl/DateParser.java
Normal file
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Copyright 2004 Sun Microsystems, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License 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.
|
||||
*
|
||||
*/
|
||||
package dev.rsems.syndication.rome.io.impl;
|
||||
|
||||
import com.rometools.rome.io.impl.PropertiesLoader;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParsePosition;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* A helper class that parses Dates out of Strings with date time in RFC822 and W3CDateTime
|
||||
* formats plus the variants Atom (0.3) and RSS (0.9, 0.91, 0.92, 0.93, 0.94, 1.0 and 2.0)
|
||||
* specificators added to those formats.
|
||||
* <p/>
|
||||
* It uses the JDK java.text.SimpleDateFormat class attemtping the parse using a mask for
|
||||
* each one of the possible formats.
|
||||
* <p/>
|
||||
* Date parsing enhanced (RS)
|
||||
* </p>
|
||||
*
|
||||
* @author Alejandro Abdelnur
|
||||
* @author Robert Schroeder
|
||||
*
|
||||
*/
|
||||
public class DateParser {
|
||||
|
||||
private static final String[] ADDITIONAL_MASKS;
|
||||
|
||||
static {
|
||||
ADDITIONAL_MASKS = PropertiesLoader.getPropertiesLoader().getTokenizedProperty("datetime.extra.masks", "|");
|
||||
}
|
||||
|
||||
// order is like this because the SimpleDateFormat.parse does not fail with exception
|
||||
// if it can parse a valid date out of a substring of the full string given the mask
|
||||
// so we have to check the most complete format first, then it fails with exception
|
||||
private static final String[] RFC822_MASKS = {
|
||||
"EEE, dd MMM yy HH:mm:ss z",
|
||||
"EEE, dd MMM yy HH:mm z",
|
||||
"dd MMM yy HH:mm:ss z",
|
||||
"dd MMM yy HH:mm z" };
|
||||
|
||||
// order is like this because the SimpleDateFormat.parse does not fail with exception
|
||||
// if it can parse a valid date out of a substring of the full string given the mask
|
||||
// so we have to check the most complete format first, then it fails with exception
|
||||
private static final String[] W3CDATETIME_MASKS = {
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSSz",
|
||||
"yyyy-MM-dd't'HH:mm:ss.SSSz",
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
|
||||
"yyyy-MM-dd't'HH:mm:ss.SSS'z'",
|
||||
"yyyy-MM-dd'T'HH:mm:ssz",
|
||||
"yyyy-MM-dd't'HH:mm:ssz",
|
||||
"yyyy-MM-dd'T'HH:mm:ssZ",
|
||||
"yyyy-MM-dd't'HH:mm:ssZ",
|
||||
"yyyy-MM-dd'T'HH:mm:ss'Z'",
|
||||
"yyyy-MM-dd't'HH:mm:ss'z'",
|
||||
"yyyy-MM-dd'T'HH:mmz", // together with logic in the parseW3CDateTime they
|
||||
"yyyy-MM'T'HH:mmz", // handle W3C dates without time forcing them to be GMT
|
||||
"yyyy'T'HH:mmz",
|
||||
"yyyy-MM-dd't'HH:mmz",
|
||||
"yyyy-MM-dd'T'HH:mm'Z'",
|
||||
"yyyy-MM-dd't'HH:mm'z'",
|
||||
"yyyy-MM-dd", "yyyy-MM",
|
||||
"yyyy" };
|
||||
|
||||
/**
|
||||
* The masks used to validate and parse the input to this Atom date.
|
||||
* These are a lot more forgiving than what the Atom spec allows.
|
||||
* The forms that are invalid according to the spec are indicated.
|
||||
*/
|
||||
private static final String[] masks = {
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSSz",
|
||||
"yyyy-MM-dd't'HH:mm:ss.SSSz", // invalid
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
|
||||
"yyyy-MM-dd't'HH:mm:ss.SSS'z'", // invalid
|
||||
"yyyy-MM-dd'T'HH:mm:ssz",
|
||||
"yyyy-MM-dd't'HH:mm:ssz", // invalid
|
||||
"yyyy-MM-dd'T'HH:mm:ss'Z'",
|
||||
"yyyy-MM-dd't'HH:mm:ss'z'", // invalid
|
||||
"yyyy-MM-dd'T'HH:mmz", // invalid
|
||||
"yyyy-MM-dd't'HH:mmz", // invalid
|
||||
"yyyy-MM-dd'T'HH:mm'Z'", // invalid
|
||||
"yyyy-MM-dd't'HH:mm'z'", // invalid
|
||||
"yyyy-MM-dd", "yyyy-MM", "yyyy" };
|
||||
|
||||
/**
|
||||
* Private constructor to avoid DateParser instances creation.
|
||||
*/
|
||||
private DateParser() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a Date out of a string using an array of masks.
|
||||
* <p/>
|
||||
* It uses the masks in order until one of them succedes or all fail.
|
||||
* <p/>
|
||||
*
|
||||
* @param masks array of masks to use for parsing the string
|
||||
* @param sDate string to parse for a date.
|
||||
* @return the Date represented by the given string using one of the given masks.
|
||||
* It returns <b>null</b> if it was not possible to parse the the string with any of the masks.
|
||||
*
|
||||
*/
|
||||
private static Date parseUsingMask(String[] masks, String sDate) {
|
||||
sDate = (sDate != null) ? sDate.trim() : null;
|
||||
ParsePosition pp = null;
|
||||
Date d = null;
|
||||
for (int i = 0; d == null && i < masks.length; i++) {
|
||||
DateFormat df = new SimpleDateFormat(masks[i], Locale.US);
|
||||
//df.setLenient(false);
|
||||
df.setLenient(true);
|
||||
try {
|
||||
pp = new ParsePosition(0);
|
||||
d = df.parse(sDate, pp);
|
||||
assert sDate != null;
|
||||
if (pp.getIndex() != sDate.length()) {
|
||||
d = null;
|
||||
}
|
||||
//System.out.println("pp["+pp.getIndex()+"] s["+sDate+" m["+masks[i]+"] d["+d+"]");
|
||||
} catch (Exception ex1) {
|
||||
//System.out.println("s: "+sDate+" m: "+masks[i]+" d: "+null);
|
||||
}
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a Date out of a String with a date in RFC822 format.
|
||||
* <p/>
|
||||
* It parsers the following formats:
|
||||
* <ul>
|
||||
* <li>"EEE, dd MMM yyyy HH:mm:ss z"</li>
|
||||
* <li>"EEE, dd MMM yyyy HH:mm z"</li>
|
||||
* <li>"EEE, dd MMM yy HH:mm:ss z"</li>
|
||||
* <li>"EEE, dd MMM yy HH:mm z"</li>
|
||||
* <li>"dd MMM yyyy HH:mm:ss z"</li>
|
||||
* <li>"dd MMM yyyy HH:mm z"</li>
|
||||
* <li>"dd MMM yy HH:mm:ss z"</li>
|
||||
* <li>"dd MMM yy HH:mm z"</li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* Refer to the java.text.SimpleDateFormat javadocs for details on the format of each element.
|
||||
* <p/>
|
||||
* @param sDate string to parse for a date.
|
||||
* @return the Date represented by the given RFC822 string.
|
||||
* It returns <b>null</b> if it was not possible to parse the given string into a Date.
|
||||
*
|
||||
*/
|
||||
public static Date parseRFC822(String sDate) {
|
||||
int utIndex = sDate.indexOf(" UT");
|
||||
if (utIndex > -1) {
|
||||
String pre = sDate.substring(0, utIndex);
|
||||
String post = sDate.substring(utIndex + 3);
|
||||
sDate = pre + " GMT" + post;
|
||||
} else { // Schroeder 11-2012
|
||||
int zIndex = sDate.indexOf(" Z");
|
||||
if (zIndex > -1) {
|
||||
String pre = sDate.substring(0, zIndex);
|
||||
String post = sDate.substring(zIndex + 2);
|
||||
sDate = pre + " GMT" + post;
|
||||
}
|
||||
}
|
||||
return parseUsingMask(RFC822_MASKS, sDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a Date out of a String with a date in W3C date-time format.
|
||||
* <p/>
|
||||
* It parsers the following formats:
|
||||
* <ul>
|
||||
* <li>"yyyy-MM-dd'T'HH:mm:ssz"</li>
|
||||
* <li>"yyyy-MM-dd'T'HH:mmz"</li>
|
||||
* <li>"yyyy-MM-dd"</li>
|
||||
* <li>"yyyy-MM"</li>
|
||||
* <li>"yyyy"</li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* Refer to the java.text.SimpleDateFormat javadocs for details on the format of each element.
|
||||
* <p/>
|
||||
* @param sDate string to parse for a date.
|
||||
* @return the Date represented by the given W3C date-time string.
|
||||
* It returns <b>null</b> if it was not possible to parse the given string into a Date.
|
||||
*
|
||||
*/
|
||||
public static Date parseW3CDateTime(String sDate) {
|
||||
// if sDate has time on it, it injects 'GTM' before de TZ displacement to
|
||||
// allow the SimpleDateFormat parser to parse it properly
|
||||
int tIndex = sDate.indexOf("T");
|
||||
if (tIndex > -1) {
|
||||
if (sDate.endsWith("Z")) {
|
||||
sDate = sDate.substring(0, sDate.length() - 1) + "+00:00";
|
||||
}
|
||||
int tzdIndex = sDate.indexOf("+", tIndex);
|
||||
if (tzdIndex == -1) {
|
||||
tzdIndex = sDate.indexOf("-", tIndex);
|
||||
}
|
||||
if (tzdIndex > -1) {
|
||||
String pre = sDate.substring(0, tzdIndex);
|
||||
int secFraction = pre.indexOf(",");
|
||||
if (secFraction > -1) {
|
||||
pre = pre.substring(0, secFraction);
|
||||
}
|
||||
String post = sDate.substring(tzdIndex);
|
||||
sDate = pre + "GMT" + post;
|
||||
}
|
||||
} else {
|
||||
sDate += "T00:00GMT";
|
||||
}
|
||||
return parseUsingMask(W3CDATETIME_MASKS, sDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a Date out of a String with a date in W3C date-time format or
|
||||
* in a RFC822 format.
|
||||
* <p>
|
||||
* @param sDate string to parse for a date.
|
||||
* @return the Date represented by the given W3C date-time string.
|
||||
* It returns <b>null</b> if it was not possible to parse the given string into a Date.
|
||||
*
|
||||
* */
|
||||
public static Date parseDate(String sDate) {
|
||||
Date d = parseW3CDateTime(sDate);
|
||||
if (d == null) {
|
||||
d = parseRFC822(sDate);
|
||||
if (d == null && ADDITIONAL_MASKS.length > 0) {
|
||||
d = parseUsingMask(ADDITIONAL_MASKS, sDate);
|
||||
}
|
||||
if (d == null) { // Schroeder 11-2012
|
||||
d = parseUsingMask(masks, sDate);
|
||||
}
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
* create a RFC822 representation of a date.
|
||||
* <p/>
|
||||
* Refer to the java.text.SimpleDateFormat javadocs for details on the format of each element.
|
||||
* <p/>
|
||||
* @param date Date to parse
|
||||
* @return the RFC822 represented by the given Date
|
||||
* It returns <b>null</b> if it was not possible to parse the date.
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static String formatRFC822(Date date) {
|
||||
SimpleDateFormat dateFormater = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
|
||||
dateFormater.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
return dateFormater.format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* create a W3C Date Time representation of a date.
|
||||
* <p/>
|
||||
* Refer to the java.text.SimpleDateFormat javadocs for details on the format of each element.
|
||||
* <p/>
|
||||
* @param date Date to parse
|
||||
* @return the W3C Date Time represented by the given Date
|
||||
* It returns <b>null</b> if it was not possible to parse the date.
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static String formatW3CDateTime(Date date) {
|
||||
SimpleDateFormat dateFormater = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
||||
dateFormater.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
return dateFormater.format(date);
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(parseDate("Mon, 19 Nov 2012 23:22:39 Z"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
/*
|
||||
* Copyright 2004 Sun Microsystems, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License 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.
|
||||
*
|
||||
*/
|
||||
package dev.rsems.syndication.rome.io.impl;
|
||||
|
||||
import com.rometools.rome.feed.WireFeed;
|
||||
import com.rometools.rome.feed.rss.Channel;
|
||||
import com.rometools.rome.feed.rss.Image;
|
||||
import com.rometools.rome.feed.rss.Item;
|
||||
import com.rometools.rome.feed.rss.TextInput;
|
||||
import com.rometools.rome.io.FeedException;
|
||||
import com.rometools.rome.io.impl.BaseWireFeedParser;
|
||||
import org.jdom2.Document;
|
||||
import org.jdom2.Element;
|
||||
import org.jdom2.Namespace;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public class RSS090Parser extends BaseWireFeedParser {
|
||||
private static final String RDF_URI = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
|
||||
private static final String RSS_URI = "http://my.netscape.com/rdf/simple/0.9/";
|
||||
private static final String CONTENT_URI = "http://purl.org/rss/1.0/modules/content/";
|
||||
|
||||
private static final Namespace RDF_NS = Namespace.getNamespace(RDF_URI);
|
||||
private static final Namespace RSS_NS = Namespace.getNamespace(RSS_URI);
|
||||
private static final Namespace CONTENT_NS = Namespace.getNamespace(CONTENT_URI);
|
||||
|
||||
public RSS090Parser() {
|
||||
this("rss_0.9", RSS_NS);
|
||||
}
|
||||
|
||||
protected RSS090Parser(String type, Namespace ns) {
|
||||
super(type, ns);
|
||||
}
|
||||
|
||||
public boolean isMyType(Document document) {
|
||||
boolean ok = false;
|
||||
|
||||
Element rssRoot = document.getRootElement();
|
||||
Namespace defaultNS = rssRoot.getNamespace();
|
||||
List additionalNSs = rssRoot.getAdditionalNamespaces();
|
||||
|
||||
ok = defaultNS != null && defaultNS.equals(getRDFNamespace());
|
||||
if (ok) {
|
||||
if (additionalNSs == null) {
|
||||
ok = false;
|
||||
} else {
|
||||
ok = false;
|
||||
for (int i = 0; !ok && i < additionalNSs.size(); i++) {
|
||||
ok = getRSSNamespace().equals(additionalNSs.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public WireFeed parse(Document document, boolean validate) throws IllegalArgumentException, FeedException {
|
||||
if (validate) {
|
||||
validateFeed(document);
|
||||
}
|
||||
Element rssRoot = document.getRootElement();
|
||||
return parseChannel(rssRoot);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
protected void validateFeed(@SuppressWarnings("unused") Document document) throws FeedException {
|
||||
// TBD
|
||||
// here we have to validate the Feed against a schema or whatever
|
||||
// not sure how to do it
|
||||
// one posibility would be to inject our own schema for the feed (they don't exist out there)
|
||||
// to the document, produce an ouput and attempt to parse it again with validation turned on.
|
||||
// otherwise will have to check the document elements by hand.
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the namespace used by RSS elements in document of the RSS version the parser supports.
|
||||
* <P>
|
||||
* This implementation returns the EMTPY namespace.
|
||||
* <p>
|
||||
*
|
||||
* @return returns the EMPTY namespace.
|
||||
*/
|
||||
protected Namespace getRSSNamespace() {
|
||||
return RSS_NS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the namespace used by RDF elements in document of the RSS version the parser supports.
|
||||
* <P>
|
||||
* This implementation returns the EMTPY namespace.
|
||||
* <p>
|
||||
*
|
||||
* @return returns the EMPTY namespace.
|
||||
*/
|
||||
protected Namespace getRDFNamespace() {
|
||||
return RDF_NS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the namespace used by Content Module elements in document.
|
||||
* <P>
|
||||
* This implementation returns the EMTPY namespace.
|
||||
* <p>
|
||||
*
|
||||
* @return returns the EMPTY namespace.
|
||||
*/
|
||||
protected Namespace getContentNamespace() {
|
||||
return CONTENT_NS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the root element of an RSS document into a Channel bean.
|
||||
* <p/>
|
||||
* It reads title, link and description and delegates to parseImage, parseItems
|
||||
* and parseTextInput. This delegation always passes the root element of the RSS
|
||||
* document as different RSS version may have this information in different parts
|
||||
* of the XML tree (no assumptions made thanks to the specs variaty)
|
||||
* <p/>
|
||||
*
|
||||
* @param rssRoot the root element of the RSS document to parse.
|
||||
* @return the parsed Channel bean.
|
||||
*/
|
||||
protected WireFeed parseChannel(Element rssRoot) {
|
||||
Element eChannel = rssRoot.getChild("channel", getRSSNamespace());
|
||||
|
||||
Channel channel = new Channel(getType());
|
||||
|
||||
Element e = eChannel.getChild("title", getRSSNamespace());
|
||||
if (e != null) {
|
||||
channel.setTitle(e.getText());
|
||||
}
|
||||
e = eChannel.getChild("link", getRSSNamespace());
|
||||
if (e != null) {
|
||||
channel.setLink(e.getText());
|
||||
}
|
||||
e = eChannel.getChild("description", getRSSNamespace());
|
||||
if (e != null) {
|
||||
channel.setDescription(e.getText());
|
||||
}
|
||||
|
||||
channel.setImage(parseImage(rssRoot));
|
||||
|
||||
channel.setTextInput(parseTextInput(rssRoot));
|
||||
|
||||
// Unfortunately Microsoft's SSE extension has a special case of
|
||||
// effectively putting the sharing channel module inside the RSS tag
|
||||
// and not inside the channel itself. So we also need to look for
|
||||
// channel modules from the root RSS element.
|
||||
List allFeedModules = new ArrayList();
|
||||
List rootModules = parseFeedModules(rssRoot, Locale.getDefault());
|
||||
List channelModules = parseFeedModules(eChannel, Locale.getDefault());
|
||||
if (rootModules != null) {
|
||||
allFeedModules.addAll(rootModules);
|
||||
}
|
||||
if (channelModules != null) {
|
||||
allFeedModules.addAll(channelModules);
|
||||
}
|
||||
channel.setModules(allFeedModules);
|
||||
channel.setItems(parseItems(rssRoot));
|
||||
|
||||
List foreignMarkup = extractForeignMarkup(eChannel, channel, getRSSNamespace());
|
||||
if (!foreignMarkup.isEmpty()) {
|
||||
channel.setForeignMarkup(foreignMarkup);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method exists because RSS0.90 and RSS1.0 have the 'item' elements under the root elemment.
|
||||
* And RSS0.91, RSS0.02, RSS0.93, RSS0.94 and RSS2.0 have the item elements under the 'channel' element.
|
||||
* <p/>
|
||||
*/
|
||||
protected List getItems(Element rssRoot) {
|
||||
return rssRoot.getChildren("item", getRSSNamespace());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method exists because RSS0.90 and RSS1.0 have the 'image' element under the root elemment.
|
||||
* And RSS0.91, RSS0.02, RSS0.93, RSS0.94 and RSS2.0 have it under the 'channel' element.
|
||||
* <p/>
|
||||
*/
|
||||
protected Element getImage(Element rssRoot) {
|
||||
return rssRoot.getChild("image", getRSSNamespace());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method exists because RSS0.90 and RSS1.0 have the 'textinput' element under the root elemment.
|
||||
* And RSS0.91, RSS0.02, RSS0.93, RSS0.94 and RSS2.0 have it under the 'channel' element.
|
||||
* <p/>
|
||||
*/
|
||||
protected Element getTextInput(Element rssRoot) {
|
||||
return rssRoot.getChild("textinput", getRSSNamespace());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the root element of an RSS document looking for image information.
|
||||
* <p/>
|
||||
* It reads title and url out of the 'image' element.
|
||||
* <p/>
|
||||
*
|
||||
* @param rssRoot the root element of the RSS document to parse for image information.
|
||||
* @return the parsed image bean.
|
||||
*/
|
||||
protected Image parseImage(Element rssRoot) {
|
||||
Image image = null;
|
||||
Element eImage = getImage(rssRoot);
|
||||
if (eImage != null) {
|
||||
image = new Image();
|
||||
|
||||
Element e = eImage.getChild("title", getRSSNamespace());
|
||||
if (e != null) {
|
||||
image.setTitle(e.getText());
|
||||
}
|
||||
e = eImage.getChild("url", getRSSNamespace());
|
||||
if (e != null) {
|
||||
image.setUrl(e.getText());
|
||||
}
|
||||
e = eImage.getChild("link", getRSSNamespace());
|
||||
if (e != null) {
|
||||
image.setLink(e.getText());
|
||||
}
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the root element of an RSS document looking for all items information.
|
||||
* <p/>
|
||||
* It iterates through the item elements list, obtained from the getItems() method, and invoke parseItem()
|
||||
* for each item element. The resulting RSSItem of each item element is stored in a list.
|
||||
* <p/>
|
||||
*
|
||||
* @param rssRoot the root element of the RSS document to parse for all items information.
|
||||
* @return a list with all the parsed RSSItem beans.
|
||||
*/
|
||||
protected List parseItems(Element rssRoot) {
|
||||
Collection eItems = getItems(rssRoot);
|
||||
|
||||
List items = new ArrayList();
|
||||
for (Object item : eItems) {
|
||||
Element eItem = (Element) item;
|
||||
items.add(parseItem(rssRoot, eItem));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an item element of an RSS document looking for item information.
|
||||
* <p/>
|
||||
* It reads title and link out of the 'item' element.
|
||||
* <p/>
|
||||
*
|
||||
* @param rssRoot the root element of the RSS document in case it's needed for context.
|
||||
* @param eItem the item element to parse.
|
||||
* @return the parsed RSSItem bean.
|
||||
*/
|
||||
protected Item parseItem(Element rssRoot, Element eItem) {
|
||||
Item item = new Item();
|
||||
Element e = eItem.getChild("title", getRSSNamespace());
|
||||
if (e != null) {
|
||||
item.setTitle(e.getText());
|
||||
}
|
||||
e = eItem.getChild("link", getRSSNamespace());
|
||||
if (e != null) {
|
||||
item.setLink(e.getText());
|
||||
item.setUri(e.getText());
|
||||
}
|
||||
|
||||
item.setModules(parseItemModules(eItem, Locale.getDefault()));
|
||||
|
||||
List foreignMarkup = extractForeignMarkup(eItem, item, getRSSNamespace());
|
||||
//content:encoded elements are treated special, without a module, they have to be removed from the foreign
|
||||
//markup to avoid duplication in case of read/write. Note that this fix will break if a content module is
|
||||
//used
|
||||
Iterator iterator = foreignMarkup.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Element ie = (Element) iterator.next();
|
||||
if (getContentNamespace().equals(ie.getNamespace()) && ie.getName().equals("encoded")) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
if (!foreignMarkup.isEmpty()) {
|
||||
item.setForeignMarkup(foreignMarkup);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the root element of an RSS document looking for text-input information.
|
||||
* <p/>
|
||||
* It reads title, description, name and link out of the 'textinput' or 'textInput' element.
|
||||
* <p/>
|
||||
*
|
||||
* @param rssRoot the root element of the RSS document to parse for text-input information.
|
||||
* @return the parsed RSSTextInput bean.
|
||||
*/
|
||||
protected TextInput parseTextInput(Element rssRoot) {
|
||||
TextInput textInput = null;
|
||||
Element eTextInput = getTextInput(rssRoot);
|
||||
if (eTextInput != null) {
|
||||
textInput = new TextInput();
|
||||
Element e = eTextInput.getChild("title", getRSSNamespace());
|
||||
if (e != null) {
|
||||
textInput.setTitle(e.getText());
|
||||
}
|
||||
e = eTextInput.getChild("description", getRSSNamespace());
|
||||
if (e != null) {
|
||||
textInput.setDescription(e.getText());
|
||||
}
|
||||
e = eTextInput.getChild("name", getRSSNamespace());
|
||||
if (e != null) {
|
||||
textInput.setName(e.getText());
|
||||
}
|
||||
e = eTextInput.getChild("link", getRSSNamespace());
|
||||
if (e != null) {
|
||||
textInput.setLink(e.getText());
|
||||
}
|
||||
}
|
||||
return textInput;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("all")
|
||||
public WireFeed parse(org.jdom2.Document document, boolean b, Locale locale) throws IllegalArgumentException, FeedException {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright 2004 Sun Microsystems, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License 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.
|
||||
*
|
||||
*/
|
||||
package dev.rsems.syndication.rome.io.impl;
|
||||
|
||||
import com.rometools.rome.feed.WireFeed;
|
||||
import com.rometools.rome.feed.rss.*;
|
||||
import com.rometools.rome.io.impl.NumberParser;
|
||||
import org.jdom2.Attribute;
|
||||
import org.jdom2.Document;
|
||||
import org.jdom2.Element;
|
||||
import org.jdom2.Namespace;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public class RSS091UserlandParser extends RSS090Parser {
|
||||
|
||||
public RSS091UserlandParser() {
|
||||
this("rss_0.91U");
|
||||
}
|
||||
|
||||
protected RSS091UserlandParser(String type) {
|
||||
super(type, null);
|
||||
}
|
||||
|
||||
public boolean isMyType(Document document) {
|
||||
boolean ok;
|
||||
Element rssRoot = document.getRootElement();
|
||||
ok = rssRoot.getName().equals("rss");
|
||||
if (ok) {
|
||||
ok = false;
|
||||
Attribute version = rssRoot.getAttribute("version");
|
||||
if (version != null) {
|
||||
ok = version.getValue().equals(getRSSVersion());
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
protected String getRSSVersion() {
|
||||
return "0.91";
|
||||
}
|
||||
|
||||
protected Namespace getRSSNamespace() {
|
||||
return Namespace.getNamespace("");
|
||||
}
|
||||
|
||||
/**
|
||||
* To be overriden by RSS 0.91 Netscape and RSS 0.94
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
protected boolean isHourFormat24(Element rssRoot) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the root element of an RSS document into a Channel bean.
|
||||
* <p/>
|
||||
* It first invokes super.parseChannel and then parses and injects the following
|
||||
* properties if present: language, pubDate, rating and copyright.
|
||||
* <p/>
|
||||
*
|
||||
* @param rssRoot the root element of the RSS document to parse.
|
||||
* @return the parsed Channel bean.
|
||||
*/
|
||||
protected WireFeed parseChannel(Element rssRoot) {
|
||||
Channel channel = (Channel) super.parseChannel(rssRoot);
|
||||
|
||||
Element eChannel = rssRoot.getChild("channel", getRSSNamespace());
|
||||
|
||||
Element e = eChannel.getChild("language", getRSSNamespace());
|
||||
if (e != null) {
|
||||
channel.setLanguage(e.getText());
|
||||
}
|
||||
e = eChannel.getChild("rating", getRSSNamespace());
|
||||
if (e != null) {
|
||||
channel.setRating(e.getText());
|
||||
}
|
||||
e = eChannel.getChild("copyright", getRSSNamespace());
|
||||
if (e != null) {
|
||||
channel.setCopyright(e.getText());
|
||||
}
|
||||
e = eChannel.getChild("pubDate", getRSSNamespace());
|
||||
if (e != null) {
|
||||
channel.setPubDate(DateParser.parseDate(e.getText()));
|
||||
}
|
||||
e = eChannel.getChild("lastBuildDate", getRSSNamespace());
|
||||
if (e != null) {
|
||||
channel.setLastBuildDate(DateParser.parseDate(e.getText()));
|
||||
}
|
||||
e = eChannel.getChild("docs", getRSSNamespace());
|
||||
if (e != null) {
|
||||
channel.setDocs(e.getText());
|
||||
}
|
||||
e = eChannel.getChild("docs", getRSSNamespace());
|
||||
if (e != null) {
|
||||
channel.setDocs(e.getText());
|
||||
}
|
||||
e = eChannel.getChild("managingEditor", getRSSNamespace());
|
||||
if (e != null) {
|
||||
channel.setManagingEditor(e.getText());
|
||||
}
|
||||
e = eChannel.getChild("webMaster", getRSSNamespace());
|
||||
if (e != null) {
|
||||
channel.setWebMaster(e.getText());
|
||||
}
|
||||
e = eChannel.getChild("skipHours");
|
||||
if (e != null) {
|
||||
List skipHours = new ArrayList();
|
||||
List eHours = e.getChildren("hour", getRSSNamespace());
|
||||
for (Object hour : eHours) {
|
||||
Element eHour = (Element) hour;
|
||||
skipHours.add(eHour.getText().trim());
|
||||
}
|
||||
channel.setSkipHours(skipHours);
|
||||
}
|
||||
|
||||
e = eChannel.getChild("skipDays");
|
||||
if (e != null) {
|
||||
List skipDays = new ArrayList();
|
||||
List eDays = e.getChildren("day", getRSSNamespace());
|
||||
for (Object day : eDays) {
|
||||
Element eDay = (Element) day;
|
||||
skipDays.add(eDay.getText().trim());
|
||||
}
|
||||
channel.setSkipDays(skipDays);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the root element of an RSS document looking for image information.
|
||||
* <p/>
|
||||
* It first invokes super.parseImage and then parses and injects the following
|
||||
* properties if present: url, link, width, height and description.
|
||||
* <p/>
|
||||
*
|
||||
* @param rssRoot the root element of the RSS document to parse for image information.
|
||||
* @return the parsed RSSImage bean.
|
||||
*/
|
||||
protected Image parseImage(Element rssRoot) {
|
||||
Image image = super.parseImage(rssRoot);
|
||||
if (image != null) {
|
||||
Element eImage = getImage(rssRoot);
|
||||
Element e = eImage.getChild("width", getRSSNamespace());
|
||||
if (e != null) {
|
||||
Integer val = NumberParser.parseInt(e.getText());
|
||||
if (val != null) {
|
||||
image.setWidth(val);
|
||||
}
|
||||
}
|
||||
e = eImage.getChild("height", getRSSNamespace());
|
||||
if (e != null) {
|
||||
Integer val = NumberParser.parseInt(e.getText());
|
||||
if (val != null) {
|
||||
image.setHeight(val);
|
||||
}
|
||||
}
|
||||
e = eImage.getChild("description", getRSSNamespace());
|
||||
if (e != null) {
|
||||
image.setDescription(e.getText());
|
||||
}
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* It looks for the 'item' elements under the 'channel' elemment.
|
||||
*/
|
||||
protected List getItems(Element rssRoot) {
|
||||
Element eChannel = rssRoot.getChild("channel", getRSSNamespace());
|
||||
return (eChannel != null) ? eChannel.getChildren("item", getRSSNamespace()) : Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
/**
|
||||
* It looks for the 'image' elements under the 'channel' elemment.
|
||||
*/
|
||||
protected Element getImage(Element rssRoot) {
|
||||
Element eChannel = rssRoot.getChild("channel", getRSSNamespace());
|
||||
return (eChannel != null) ? eChannel.getChild("image", getRSSNamespace()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be overriden by RSS 0.91 Netscape parser
|
||||
*/
|
||||
protected String getTextInputLabel() {
|
||||
return "textInput";
|
||||
}
|
||||
|
||||
/**
|
||||
* It looks for the 'textinput' elements under the 'channel' elemment.
|
||||
*/
|
||||
protected Element getTextInput(Element rssRoot) {
|
||||
String elementName = getTextInputLabel();
|
||||
Element eChannel = rssRoot.getChild("channel", getRSSNamespace());
|
||||
return (eChannel != null) ? eChannel.getChild(elementName, getRSSNamespace()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an item element of an RSS document looking for item information.
|
||||
* <p/>
|
||||
* It first invokes super.parseItem and then parses and injects the description property if present.
|
||||
* <p/>
|
||||
*
|
||||
* @param rssRoot the root element of the RSS document in case it's needed for context.
|
||||
* @param eItem the item element to parse.
|
||||
* @return the parsed RSSItem bean.
|
||||
*/
|
||||
protected Item parseItem(Element rssRoot, Element eItem) {
|
||||
Item item = super.parseItem(rssRoot, eItem);
|
||||
Element e = eItem.getChild("description", getRSSNamespace());
|
||||
if (e != null) {
|
||||
item.setDescription(parseItemDescription(rssRoot, e));
|
||||
}
|
||||
Element ce = eItem.getChild("encoded", getContentNamespace());
|
||||
if (ce != null) {
|
||||
Content content = new Content();
|
||||
content.setType(Content.HTML);
|
||||
content.setValue(ce.getText());
|
||||
item.setContent(content);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
protected Description parseItemDescription(Element rssRoot, Element eDesc) {
|
||||
Description desc = new Description();
|
||||
desc.setType("text/plain");
|
||||
desc.setValue(eDesc.getText());
|
||||
return desc;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2004 Sun Microsystems, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License 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.
|
||||
*
|
||||
*/
|
||||
package dev.rsems.syndication.rome.io.impl;
|
||||
|
||||
import com.rometools.rome.feed.WireFeed;
|
||||
import com.rometools.rome.feed.rss.*;
|
||||
import com.rometools.rome.io.impl.NumberParser;
|
||||
import org.jdom2.Element;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public class RSS092Parser extends RSS091UserlandParser {
|
||||
|
||||
public RSS092Parser() {
|
||||
this("rss_0.92");
|
||||
}
|
||||
|
||||
protected RSS092Parser(String type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
protected String getRSSVersion() {
|
||||
return "0.92";
|
||||
}
|
||||
|
||||
protected WireFeed parseChannel(Element rssRoot) {
|
||||
Channel channel = (Channel) super.parseChannel(rssRoot);
|
||||
|
||||
Element eChannel = rssRoot.getChild("channel", getRSSNamespace());
|
||||
Element eCloud = eChannel.getChild("cloud", getRSSNamespace());
|
||||
if (eCloud != null) {
|
||||
Cloud cloud = new Cloud();
|
||||
String att = eCloud.getAttributeValue("domain");//getRSSNamespace()); DONT KNOW WHY DOESN'T WORK
|
||||
if (att != null) {
|
||||
cloud.setDomain(att);
|
||||
}
|
||||
att = eCloud.getAttributeValue("port");//getRSSNamespace()); DONT KNOW WHY DOESN'T WORK
|
||||
if (att != null) {
|
||||
cloud.setPort(Integer.parseInt(att.trim()));
|
||||
}
|
||||
att = eCloud.getAttributeValue("path");//getRSSNamespace()); DONT KNOW WHY DOESN'T WORK
|
||||
if (att != null) {
|
||||
cloud.setPath(att);
|
||||
}
|
||||
att = eCloud.getAttributeValue("registerProcedure");//getRSSNamespace()); DONT KNOW WHY DOESN'T WORK
|
||||
if (att != null) {
|
||||
cloud.setRegisterProcedure(att);
|
||||
}
|
||||
att = eCloud.getAttributeValue("protocol");//getRSSNamespace()); DONT KNOW WHY DOESN'T WORK
|
||||
if (att != null) {
|
||||
cloud.setProtocol(att);
|
||||
}
|
||||
channel.setCloud(cloud);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
protected Item parseItem(Element rssRoot, Element eItem) {
|
||||
Item item = super.parseItem(rssRoot, eItem);
|
||||
|
||||
Element e = eItem.getChild("source", getRSSNamespace());
|
||||
if (e != null) {
|
||||
Source source = new Source();
|
||||
String url = e.getAttributeValue("url");//getRSSNamespace()); DONT KNOW WHY DOESN'T WORK
|
||||
source.setUrl(url);
|
||||
source.setValue(e.getText());
|
||||
item.setSource(source);
|
||||
}
|
||||
|
||||
// 0.92 allows one enclosure occurrence, 0.93 multiple
|
||||
// just saving to write some code.
|
||||
List eEnclosures = eItem.getChildren("enclosure");//getRSSNamespace()); DONT KNOW WHY DOESN'T WORK
|
||||
if (!eEnclosures.isEmpty()) {
|
||||
List enclosures = new ArrayList();
|
||||
for (Object eEnclosure : eEnclosures) {
|
||||
e = (Element) eEnclosure;
|
||||
|
||||
Enclosure enclosure = new Enclosure();
|
||||
String att = e.getAttributeValue("url");//getRSSNamespace()); DONT KNOW WHY DOESN'T WORK
|
||||
if (att != null) {
|
||||
enclosure.setUrl(att);
|
||||
}
|
||||
att = e.getAttributeValue("length");//getRSSNamespace()); DONT KNOW WHY DOESN'T WORK
|
||||
enclosure.setLength(NumberParser.parseLong(att, 0L));
|
||||
|
||||
att = e.getAttributeValue("type");//getRSSNamespace()); DONT KNOW WHY DOESN'T WORK
|
||||
if (att != null) {
|
||||
enclosure.setType(att);
|
||||
}
|
||||
enclosures.add(enclosure);
|
||||
}
|
||||
item.setEnclosures(enclosures);
|
||||
}
|
||||
|
||||
List eCats = eItem.getChildren("category");//getRSSNamespace()); DONT KNOW WHY DOESN'T WORK
|
||||
item.setCategories(parseCategories(eCats));
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
protected List parseCategories(List eCats) {
|
||||
List cats = null;
|
||||
if (!eCats.isEmpty()) {
|
||||
cats = new ArrayList();
|
||||
for (Object eCat : eCats) {
|
||||
Category cat = new Category();
|
||||
Element e = (Element) eCat;
|
||||
String att = e.getAttributeValue("domain");//getRSSNamespace()); DONT KNOW WHY DOESN'T WORK
|
||||
if (att != null) {
|
||||
cat.setDomain(att);
|
||||
}
|
||||
cat.setValue(e.getText());
|
||||
cats.add(cat);
|
||||
}
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
|
||||
protected Description parseItemDescription(Element rssRoot, Element eDesc) {
|
||||
Description desc = super.parseItemDescription(rssRoot, eDesc);
|
||||
desc.setType("text/html");
|
||||
return desc;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2004 Sun Microsystems, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License 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.
|
||||
*
|
||||
*/
|
||||
package dev.rsems.syndication.rome.io.impl;
|
||||
|
||||
import com.rometools.rome.feed.rss.Item;
|
||||
import org.jdom2.Element;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class RSS093Parser extends RSS092Parser {
|
||||
|
||||
public RSS093Parser() {
|
||||
this("rss_0.93");
|
||||
}
|
||||
|
||||
protected RSS093Parser(String type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
protected String getRSSVersion() {
|
||||
return "0.93";
|
||||
}
|
||||
|
||||
protected Item parseItem(Element rssRoot, Element eItem) {
|
||||
Item item = super.parseItem(rssRoot, eItem);
|
||||
Element e = eItem.getChild("pubDate", getRSSNamespace());
|
||||
if (e != null) {
|
||||
item.setPubDate(DateParser.parseDate(e.getText()));
|
||||
}
|
||||
e = eItem.getChild("expirationDate", getRSSNamespace());
|
||||
if (e != null) {
|
||||
item.setExpirationDate(DateParser.parseDate(e.getText()));
|
||||
}
|
||||
e = eItem.getChild("description", getRSSNamespace());
|
||||
if (e != null) {
|
||||
String type = e.getAttributeValue("type");
|
||||
if (type != null) {
|
||||
item.getDescription().setType(type);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2004 Sun Microsystems, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License 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.
|
||||
*
|
||||
*/
|
||||
package dev.rsems.syndication.rome.io.impl;
|
||||
|
||||
import com.rometools.rome.feed.WireFeed;
|
||||
import com.rometools.rome.feed.rss.Channel;
|
||||
import com.rometools.rome.feed.rss.Description;
|
||||
import com.rometools.rome.feed.rss.Guid;
|
||||
import com.rometools.rome.feed.rss.Item;
|
||||
import org.jdom2.Element;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class RSS094Parser extends RSS093Parser {
|
||||
|
||||
public RSS094Parser() {
|
||||
this("rss_0.94");
|
||||
}
|
||||
|
||||
protected RSS094Parser(String type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
protected String getRSSVersion() {
|
||||
return "0.94";
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnnecessarySemicolon")
|
||||
protected WireFeed parseChannel(Element rssRoot) {
|
||||
Channel channel = (Channel) super.parseChannel(rssRoot);
|
||||
Element eChannel = rssRoot.getChild("channel", getRSSNamespace());
|
||||
|
||||
List<Element> eCats = eChannel.getChildren("category", getRSSNamespace());
|
||||
channel.setCategories(parseCategories(eCats));
|
||||
|
||||
Element eTtl = eChannel.getChild("ttl", getRSSNamespace());
|
||||
if (eTtl != null && eTtl.getText() != null) {
|
||||
Optional<Integer> ttlValue = Optional.empty();
|
||||
try {
|
||||
ttlValue = Optional.of(Integer.valueOf(eTtl.getText()));
|
||||
} catch (NumberFormatException ignored) {
|
||||
;
|
||||
}
|
||||
if (ttlValue.isPresent()) {
|
||||
channel.setTtl(ttlValue.orElse(null));
|
||||
}
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
public Item parseItem(Element rssRoot, Element eItem) {
|
||||
Item item = super.parseItem(rssRoot, eItem);
|
||||
item.setExpirationDate(null);
|
||||
|
||||
Element e = eItem.getChild("author", getRSSNamespace());
|
||||
if (e != null) {
|
||||
item.setAuthor(e.getText());
|
||||
}
|
||||
|
||||
e = eItem.getChild("guid", getRSSNamespace());
|
||||
if (e != null) {
|
||||
Guid guid = new Guid();
|
||||
String att = e.getAttributeValue("isPermaLink");//getRSSNamespace()); DONT KNOW WHY DOESN'T WORK
|
||||
if (att != null) {
|
||||
guid.setPermaLink(att.equalsIgnoreCase("true"));
|
||||
}
|
||||
guid.setValue(e.getText());
|
||||
item.setGuid(guid);
|
||||
}
|
||||
|
||||
e = eItem.getChild("comments", getRSSNamespace());
|
||||
if (e != null) {
|
||||
item.setComments(e.getText());
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
protected Description parseItemDescription(Element rssRoot, Element eDesc) {
|
||||
Description desc = super.parseItemDescription(rssRoot, eDesc);
|
||||
String att = eDesc.getAttributeValue("type");//getRSSNamespace()); DONT KNOW WHY DOESN'T WORK
|
||||
if (att == null) {
|
||||
att = "text/html";
|
||||
}
|
||||
desc.setType(att);
|
||||
return desc;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2004 Sun Microsystems, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License 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.
|
||||
*
|
||||
*/
|
||||
package dev.rsems.syndication.rome.io.impl;
|
||||
|
||||
import com.rometools.rome.feed.WireFeed;
|
||||
import com.rometools.rome.feed.rss.Channel;
|
||||
import com.rometools.rome.feed.rss.Content;
|
||||
import com.rometools.rome.feed.rss.Description;
|
||||
import com.rometools.rome.feed.rss.Item;
|
||||
|
||||
import org.jdom2.Document;
|
||||
import org.jdom2.Element;
|
||||
import org.jdom2.Namespace;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class RSS10Parser extends RSS090Parser {
|
||||
|
||||
private static final String RSS_URI = "http://purl.org/rss/1.0/";
|
||||
private static final Namespace RSS_NS = Namespace.getNamespace(RSS_URI);
|
||||
|
||||
public RSS10Parser() {
|
||||
this("rss_1.0", RSS_NS);
|
||||
}
|
||||
|
||||
protected RSS10Parser(String type, Namespace ns) {
|
||||
super(type, ns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if a JDom document is an RSS instance that can be parsed with the parser.
|
||||
* <p/>
|
||||
* It checks for RDF (<a href="http://www.w3.org/1999/02/22-rdf-syntax-ns#">w3.org</a>) and
|
||||
* RSS (<a href="http://purl.org/rss/1.0/">purl.org</a>) namespaces being defined in the root element.
|
||||
*
|
||||
* @param document document to check if it can be parsed with this parser implementation.
|
||||
* @return <b>true</b> if the document is RSS1., <b>false</b> otherwise.
|
||||
*/
|
||||
public boolean isMyType(Document document) {
|
||||
boolean ok = false;
|
||||
|
||||
Element rssRoot = document.getRootElement();
|
||||
Namespace defaultNS = rssRoot.getNamespace();
|
||||
@SuppressWarnings("rawtypes")
|
||||
List additionalNSs = rssRoot.getAdditionalNamespaces();
|
||||
|
||||
ok = defaultNS != null && defaultNS.equals(getRDFNamespace());
|
||||
if (ok) {
|
||||
if (additionalNSs == null) {
|
||||
ok = false;
|
||||
} else {
|
||||
ok = false;
|
||||
for (int i = 0; !ok && i < additionalNSs.size(); i++) {
|
||||
ok = getRSSNamespace().equals(additionalNSs.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the namespace used by RSS elements in document of the RSS 1.0
|
||||
* <P>
|
||||
*
|
||||
* @return returns "<a href="http://purl.org/rss/1.0/">purl.org</a>".
|
||||
*/
|
||||
protected Namespace getRSSNamespace() {
|
||||
return Namespace.getNamespace(RSS_URI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an item element of an RSS document looking for item information.
|
||||
* <p/>
|
||||
* It first invokes super.parseItem and then parses and injects the description property if present.
|
||||
* <p/>
|
||||
*
|
||||
* @param rssRoot the root element of the RSS document in case it's needed for context.
|
||||
* @param eItem the item element to parse.
|
||||
* @return the parsed RSSItem bean.
|
||||
*/
|
||||
protected Item parseItem(Element rssRoot, Element eItem) {
|
||||
Item item = super.parseItem(rssRoot, eItem);
|
||||
Element e = eItem.getChild("description", getRSSNamespace());
|
||||
if (e != null) {
|
||||
item.setDescription(parseItemDescription(rssRoot, e));
|
||||
}
|
||||
Element ce = eItem.getChild("encoded", getContentNamespace());
|
||||
if (ce != null) {
|
||||
Content content = new Content();
|
||||
content.setType(Content.HTML);
|
||||
content.setValue(ce.getText());
|
||||
item.setContent(content);
|
||||
}
|
||||
|
||||
String uri = eItem.getAttributeValue("about", getRDFNamespace());
|
||||
if (uri != null) {
|
||||
item.setUri(uri);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
protected WireFeed parseChannel(Element rssRoot) {
|
||||
Channel channel = (Channel) super.parseChannel(rssRoot);
|
||||
|
||||
Element eChannel = rssRoot.getChild("channel", getRSSNamespace());
|
||||
String uri = eChannel.getAttributeValue("about", getRDFNamespace());
|
||||
if (uri != null) {
|
||||
channel.setUri(uri);
|
||||
}
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected Description parseItemDescription(Element rssRoot, Element eDesc) {
|
||||
Description desc = new Description();
|
||||
desc.setType("text/plain");
|
||||
desc.setValue(eDesc.getText());
|
||||
return desc;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package dev.rsems.syndication.rome.io.impl;
|
||||
|
||||
/*
|
||||
* Copyright 2004 Sun Microsystems, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License 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.
|
||||
*
|
||||
*/
|
||||
|
||||
import com.rometools.rome.feed.rss.Description;
|
||||
import org.jdom2.Attribute;
|
||||
import org.jdom2.Document;
|
||||
import org.jdom2.Element;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class RSS20Parser extends RSS094Parser {
|
||||
|
||||
public RSS20Parser() {
|
||||
this("rss_2.0");
|
||||
}
|
||||
|
||||
protected RSS20Parser(String type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
protected String getRSSVersion() {
|
||||
return "2.0";
|
||||
}
|
||||
|
||||
protected boolean isHourFormat24(Element rssRoot) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected Description parseItemDescription(Element rssRoot, Element eDesc) {
|
||||
Description desc = super.parseItemDescription(rssRoot, eDesc);
|
||||
desc.setType("text/html"); // change as per https://rome.dev.java.net/issues/show_bug.cgi?id=26
|
||||
return desc;
|
||||
}
|
||||
|
||||
public boolean isMyType(Document document) {
|
||||
boolean ok;
|
||||
Element rssRoot = document.getRootElement();
|
||||
ok = rssRoot.getName().equals("rss");
|
||||
if (ok) {
|
||||
ok = false;
|
||||
Attribute version = rssRoot.getAttribute("version");
|
||||
if (version != null) {
|
||||
// At this point, as far ROME is concerned RSS 2.0, 2.00 and
|
||||
// 2.0.X are all the same, so let's use startsWith for leniency.
|
||||
ok = version.getValue().startsWith(getRSSVersion());
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2004 Sun Microsystems, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License 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.
|
||||
*
|
||||
*/
|
||||
package dev.rsems.syndication.rome.io.impl;
|
||||
|
||||
import com.rometools.rome.feed.WireFeed;
|
||||
import org.jdom2.Document;
|
||||
import org.jdom2.Element;
|
||||
import org.jdom2.Namespace;
|
||||
|
||||
/**
|
||||
* To address issue with certain feeds (brought up by Charles Miller):
|
||||
* <br />
|
||||
* "During the debacle that was the rollout of RSS2.0, this namespace was tried,
|
||||
* and even appeared in Dave Winer's Scripting News feed for a while. It was
|
||||
* then withdrawn, but the wonderful thing about standards is the moment you
|
||||
* roll one out, even if it's marked as unfinished and subject to change,
|
||||
* someone will end up stuck with it forever."
|
||||
* <br />
|
||||
* Note that there is not counter part on the generator, we only generate the final RSS2
|
||||
*/
|
||||
public class RSS20wNSParser extends RSS20Parser {
|
||||
private static final String RSS20_URI = "http://backend.userland.com/rss2";
|
||||
|
||||
public RSS20wNSParser() {
|
||||
this("rss_2.0wNS");
|
||||
}
|
||||
|
||||
protected RSS20wNSParser(String type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
public boolean isMyType(Document document) {
|
||||
Element rssRoot = document.getRootElement();
|
||||
Namespace defaultNS = rssRoot.getNamespace();
|
||||
boolean ok = defaultNS != null && defaultNS.equals(getRSSNamespace());
|
||||
if (ok) {
|
||||
ok = super.isMyType(document);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
protected Namespace getRSSNamespace() {
|
||||
return Namespace.getNamespace(RSS20_URI);
|
||||
}
|
||||
|
||||
/**
|
||||
* After we parse the feed we put "rss_2.0" in it (so converters and generators work)
|
||||
* this parser is a phantom.
|
||||
*
|
||||
*/
|
||||
protected WireFeed parseChannel(Element rssRoot) {
|
||||
WireFeed wFeed = super.parseChannel(rssRoot);
|
||||
wFeed.setFeedType("rss_2.0");
|
||||
return wFeed;
|
||||
}
|
||||
|
||||
}
|
||||
145
src/main/java/dev/rsems/util/exceptions/ExceptionUtils.java
Normal file
145
src/main/java/dev/rsems/util/exceptions/ExceptionUtils.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package dev.rsems.util.exceptions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Exception utility class
|
||||
*
|
||||
* @author Robert Schroeder
|
||||
*/
|
||||
public final class ExceptionUtils {
|
||||
private static final String note = "***";
|
||||
|
||||
/**
|
||||
* Prints one line with all short class names and messages for a Throwable and its causes, separated by ", ", to stderr
|
||||
*
|
||||
* @param t the Throwable
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void printMessages(Throwable t) {
|
||||
System.err.println(note);
|
||||
System.err.println(getMessages(t));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints one line with all short class names and messages for a Throwable and its causes, separated by ", ", to stderr,
|
||||
* and throws a RuntimeException
|
||||
*
|
||||
* @param t the Throwable
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void printMessagesAndThrowRTE(Throwable t) {
|
||||
System.err.println(note);
|
||||
System.err.println(getMessages(t));
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates one line with all Throwable short class names and messages, separated by ", "
|
||||
*
|
||||
* @param t the Throwable
|
||||
* @return the line
|
||||
*/
|
||||
public static String getMessages(Throwable t) {
|
||||
StringBuilder message = new StringBuilder(t.getClass().getName().substring(t.getClass().getName().lastIndexOf('.') + 1));
|
||||
if (t.getMessage() != null) {
|
||||
message.append(": ").append(t.getMessage());
|
||||
}
|
||||
if (t.getCause() != null) {
|
||||
message.append("; ").append(getMessages(t.getCause()));
|
||||
}
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param t Throwable
|
||||
* @return message list
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static String[] getMessageList(Throwable t) {
|
||||
return getMessageArrayList(t).toArray(new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param t Throwable
|
||||
* @return message list
|
||||
*/
|
||||
public static ArrayList<String> getMessageArrayList(Throwable t) {
|
||||
ArrayList<String> messages = new ArrayList<>();
|
||||
String name = t.getClass().getName();
|
||||
String message = name.substring(name.lastIndexOf('.') + 1);
|
||||
if (t.getMessage() != null) {
|
||||
message += ": " + t.getMessage();
|
||||
}
|
||||
messages.add(message);
|
||||
if (t.getCause() != null) {
|
||||
messages.addAll(getMessageArrayList(t.getCause()));
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a complete stack trace for a Throwable and its causes to stderr
|
||||
*
|
||||
* @param t the Throwable
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void printStackTrace(Throwable t) {
|
||||
System.err.println(note);
|
||||
System.err.print(getStackTrace(t, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a complete stack trace for a Throwable and its causes to stderr
|
||||
*
|
||||
* @param t the Throwable
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void printStackTraceAndThrowRTE(Throwable t) {
|
||||
System.err.println(note);
|
||||
System.err.print(getStackTrace(t, 0));
|
||||
System.err.println();
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a complete stack trace for a Throwable and its causes as a String
|
||||
*
|
||||
* @param t the Throwable
|
||||
* @return the stack trace
|
||||
*/
|
||||
public static String getStackTrace(Throwable t) {
|
||||
return getStackTrace(t, 0);
|
||||
}
|
||||
|
||||
private static String getStackTrace(Throwable t, int depth) {
|
||||
StringBuilder message = new StringBuilder();
|
||||
if (depth == 0) {
|
||||
message.append("Exception in thread").append(" \"").append(Thread.currentThread().getName()).append("\" ");
|
||||
} else {
|
||||
message.append("Caused by: ");
|
||||
}
|
||||
message.append(t.getClass().getName());
|
||||
if (t.getMessage() != null) {
|
||||
message.append(": ").append(t.getMessage());
|
||||
}
|
||||
message.append("\n");
|
||||
StackTraceElement[] stackTrace = t.getStackTrace();
|
||||
if (stackTrace != null) {
|
||||
for (StackTraceElement stackTraceElement : stackTrace) {
|
||||
message.append("\tat ").append(stackTraceElement.getClassName()).append(".").append(stackTraceElement.getMethodName()).append("(");
|
||||
if (stackTraceElement.isNativeMethod()) {
|
||||
message.append("Native Method");
|
||||
} else {
|
||||
message.append(stackTraceElement.getFileName()).append(":").append(stackTraceElement.getLineNumber());
|
||||
}
|
||||
message.append(")\n");
|
||||
}
|
||||
}
|
||||
if (t.getCause() != null) {
|
||||
message.append(getStackTrace(t.getCause(), depth + 1));
|
||||
}
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
}
|
||||
17
src/main/java/dev/rsems/util/reflection/ReflectionUtils.java
Normal file
17
src/main/java/dev/rsems/util/reflection/ReflectionUtils.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package dev.rsems.util.reflection;
|
||||
|
||||
public class ReflectionUtils {
|
||||
private ReflectionUtils() {}
|
||||
|
||||
@SuppressWarnings("UnnecessarySemicolon")
|
||||
public static Object invokeMethod(Object o, String className, String methodName, Class<?>[] paramClasses, Object[] paramValues) {
|
||||
try {
|
||||
Class<?> c = Class.forName(className);
|
||||
return c.getMethod(methodName, paramClasses).invoke(o, paramValues);
|
||||
} catch (Exception ignored) {
|
||||
;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
98
src/main/java/dev/rsems/util/stopwatch/Stopwatch.java
Normal file
98
src/main/java/dev/rsems/util/stopwatch/Stopwatch.java
Normal file
@@ -0,0 +1,98 @@
|
||||
package dev.rsems.util.stopwatch;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/** Diese Klasse repr<70>sentiert eine Stoppuhr. */
|
||||
public class Stopwatch implements Serializable {
|
||||
@Serial private static final long serialVersionUID = -1L;
|
||||
@Getter private boolean running;
|
||||
@Getter private long timeStarted; // Zeitpunkt des letzten Startens (Wert ist eine Uhrzeit)
|
||||
private long timeElapsed; // Nur über Getter lesen! Vergangene Zeit (Wert ist Zeitdifferenz)
|
||||
private long timeLastChecked; // Zeitpunkt der letzten Zeitdifferenz-Ablesung (Wert ist eine Uhrzeit)
|
||||
|
||||
/** Erzeugt eine Stoppuhr und initialisiert sie, startet sie aber nicht. */
|
||||
public Stopwatch() {
|
||||
reset();
|
||||
}
|
||||
|
||||
/** Erzeugt eine Stoppuhr und startet sie, wenn <start> <b>true</b> ist.
|
||||
* @param start Gibt an, ob die erzeugte Stoppuhr sofort gestartet werden soll */
|
||||
public Stopwatch(boolean start) {
|
||||
this();
|
||||
if (start)
|
||||
start();
|
||||
}
|
||||
|
||||
/** Aktualisiert die Zeitvariablen zum Ablesen. */
|
||||
private void check() {
|
||||
if (running) {
|
||||
timeElapsed = timeElapsed + (new Date().getTime() - timeLastChecked);
|
||||
timeLastChecked = new Date().getTime();
|
||||
}
|
||||
}
|
||||
|
||||
/** Startet die Stoppuhr. */
|
||||
public void start() {
|
||||
timeStarted = new Date().getTime();
|
||||
timeLastChecked = timeStarted;
|
||||
running = true;
|
||||
}
|
||||
|
||||
/** Stoppt die Stoppuhr.
|
||||
* @throws StopwatchException if it's not started */
|
||||
@SuppressWarnings("unused")
|
||||
public void stop() throws StopwatchException {
|
||||
if (timeStarted > 0) {
|
||||
check();
|
||||
running = false;
|
||||
} else {
|
||||
throw new StopwatchException("Stopwatch must have been started once before it can be stopped");
|
||||
}
|
||||
}
|
||||
|
||||
/** Setzt die Stoppuhr zur<75>ck und stoppt sie, falls sie l<>uft. */
|
||||
public void reset() {
|
||||
timeStarted = 0;
|
||||
timeElapsed = 0;
|
||||
timeLastChecked = 0;
|
||||
running = false;
|
||||
}
|
||||
|
||||
/** Setzt die Stoppuhr zur<75>ck und startet sie neu */
|
||||
@SuppressWarnings("unused")
|
||||
public void restart() {
|
||||
reset();
|
||||
start();
|
||||
}
|
||||
|
||||
/** Liefert die abgelaufene Zeit in Millisekunden.
|
||||
* @return abgelaufene Zeit in Millisekunden */
|
||||
public long getTimeElapsed() {
|
||||
check();
|
||||
return timeElapsed;
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private String pad(int n, int length, char fill) {
|
||||
String s = Integer.toString(n);
|
||||
for (int i = s.length(); i < length; i++) s = fill + s;
|
||||
return s;
|
||||
}
|
||||
|
||||
/** Liefert die aktuell abgelaufene Zeit der Stoppuhr als formatierten String.
|
||||
* @return abgelaufene Zeit in Millisekunden */
|
||||
public String toString() {
|
||||
check();
|
||||
int h = (int) timeElapsed / (1000*60*60);
|
||||
int m = (int) (timeElapsed / (1000*60)) % 60;
|
||||
int s = (int) (timeElapsed / 1000) % 60;
|
||||
int t = (int) timeElapsed - h*1000*60*60 - m*1000*60 - s*1000;
|
||||
return pad(h, 2, '0') + ':' + pad(m, 2, '0') + ':' + pad(s, 2, '0') + "." + pad(t, 3, '0');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package dev.rsems.util.stopwatch;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/** Diese Klasse repräsentiert eine Ausnahmebedingung für Stopwatch-Objekte. */
|
||||
public class StopwatchException extends RuntimeException implements Serializable {
|
||||
@Serial private static final long serialVersionUID = -1L;
|
||||
|
||||
/** Konstruiert eine StopwatchException mit spezifizierter Detailnachricht.
|
||||
* @param msg Die Detailnachricht */
|
||||
public StopwatchException(String msg) { super(msg); }
|
||||
|
||||
}
|
||||
194
src/main/java/dev/rsems/util/strings/StringUtils.java
Normal file
194
src/main/java/dev/rsems/util/strings/StringUtils.java
Normal file
@@ -0,0 +1,194 @@
|
||||
package dev.rsems.util.strings;
|
||||
|
||||
import dev.rsems.util.reflection.ReflectionUtils;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document.OutputSettings;
|
||||
import org.jsoup.nodes.Entities.EscapeMode;
|
||||
import org.jsoup.safety.Safelist;
|
||||
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashSet;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class StringUtils {
|
||||
|
||||
/**
|
||||
* Returns a String made up of n times the String s.
|
||||
* @param s The source String.
|
||||
* @param n The number of times to repeat.
|
||||
* @return The resulting string.
|
||||
*/
|
||||
public static String repeat(String s, int n) {
|
||||
if (n <= 0)
|
||||
return "";
|
||||
else if (n == 1)
|
||||
return s;
|
||||
else
|
||||
return s + repeat(s, n - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string with appended blanks up to the specified length.
|
||||
* @param s The string.
|
||||
* @param len The length to reach.
|
||||
* @return The filed string.
|
||||
*/
|
||||
public static String fill(String s, int len) {
|
||||
return s + repeat(" ", len - s.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the longest of the strings passed as parameter.
|
||||
* @param strings The strings to be measured.
|
||||
* @return The length of the longest string or -1 if no strings were passed or an error occured.
|
||||
*/
|
||||
public static int getMaxLength(String... strings) {
|
||||
var length = -1;
|
||||
for (String string : strings) {
|
||||
length = Math.max(length, string.length());
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array Bytearray
|
||||
* @return String
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static String arrayToString(byte[] array) {
|
||||
StringBuilder sb = new StringBuilder("[");
|
||||
var i = 0;
|
||||
while (i < array.length - 1) {
|
||||
sb.append(array[i]).append(", ");
|
||||
i++;
|
||||
}
|
||||
if (i < array.length) {
|
||||
sb.append(array[i]);
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param objects Objects
|
||||
* @return Stringarray
|
||||
*/
|
||||
public static String arrayToString(Object... objects) {
|
||||
StringBuilder sb = new StringBuilder("[");
|
||||
var i = 0;
|
||||
while (i < objects.length - 1) {
|
||||
sb.append(objects[i]).append(", ");
|
||||
i++;
|
||||
}
|
||||
if (i < objects.length) {
|
||||
sb.append(objects[i]);
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static final String NO_QUOTE = "";
|
||||
private static final String DOUBLE_QUOTE = "\"";
|
||||
private static final String SINGLE_QUOTE = "'";
|
||||
|
||||
/**
|
||||
* Returns information about a URL
|
||||
* @param url The URL
|
||||
* @param info One of: "Protocol", "Authority", "Host", "Port", "Path", "File", "Query", "Ref", "UserInfo"
|
||||
*/
|
||||
public static void printUrlInfo(URL url, String info) {
|
||||
Object result = ReflectionUtils.invokeMethod(url, url.getClass().getName(), "get" + info,null, null);
|
||||
System.out.println("get" + info + " = " + quote(result));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representation of an object. It adds quotes, if the object is not of a primitive type,
|
||||
* a primitive wrapper type or null. Uses single quotes ['] for char or Character objects and double quotes
|
||||
* ["] for everything else.
|
||||
* @param o The object.
|
||||
* @return The possibly quoted string representation of the object.
|
||||
*/
|
||||
public static String quote (Object o) {
|
||||
String q = o == null ? NO_QUOTE : isChar(o) ? SINGLE_QUOTE : isPrimitive(o) ? NO_QUOTE : DOUBLE_QUOTE;
|
||||
return q + o + q;
|
||||
}
|
||||
|
||||
public static String unquote (String s) {
|
||||
if ((s.length() > 1 && s.charAt(0) == '"' && s.charAt(s.length()-1) == '"')
|
||||
|| (s.length() > 1 && s.charAt(0) == '\'' && s.charAt(s.length()-1) == '\'')) {
|
||||
return s.substring(1, s.length() - 1);
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
public static String concatenate(String[] strings) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < strings.length; i++) {
|
||||
sb.append(strings[i]);
|
||||
if (i < strings.length - 1) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String concatenateQuoted(String[] strings) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < strings.length; i++) {
|
||||
sb.append(quote(strings[i]));
|
||||
if (i < strings.length - 1) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String cleanHtml(String s, String baseUri) {
|
||||
OutputSettings jsoupOutputSettings = new OutputSettings().charset(StandardCharsets.UTF_8).escapeMode(EscapeMode.xhtml); // escape mode xhtml escapes only lt, gt, amp, apos, and quot, being the minimum JSoup#clean escapes
|
||||
String cleaned = Jsoup.clean(s, baseUri, Safelist.none(), jsoupOutputSettings);
|
||||
cleaned = cleaned.replace("<", "<").replace(">", ">").replace("&", "&").replace("'", "'").replace(""", "\""); // revert lt, gt, amp, apos, and quot entities to unescaped characters, since Wicket already takes care of that:
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
private static final HashSet<Class<?>> PRIMITIVE_TYPES = getPrimitiveTypes();
|
||||
private static final HashSet<Class<?>> PRIMITIVE_WRAPPER_TYPES = getPrimitiveWrapperTypes();
|
||||
|
||||
private static boolean isChar (Object o) {
|
||||
return /* (o.getClass().equals(char.class)) || */ (o instanceof Character);
|
||||
}
|
||||
|
||||
private static boolean isPrimitive (Object o) {
|
||||
return PRIMITIVE_TYPES.contains(o.getClass()) || PRIMITIVE_WRAPPER_TYPES.contains(o.getClass());
|
||||
}
|
||||
|
||||
private static HashSet<Class<?>> getPrimitiveTypes() {
|
||||
HashSet<Class<?>> set = new HashSet<>();
|
||||
set.add(boolean.class);
|
||||
set.add(char.class);
|
||||
set.add(byte.class);
|
||||
set.add(short.class);
|
||||
set.add(int.class);
|
||||
set.add(long.class);
|
||||
set.add(float.class);
|
||||
set.add(double.class);
|
||||
set.add(void.class);
|
||||
return set;
|
||||
}
|
||||
|
||||
private static HashSet<Class<?>> getPrimitiveWrapperTypes() {
|
||||
HashSet<Class<?>> set = new HashSet<>();
|
||||
set.add(Boolean.class);
|
||||
set.add(Character.class);
|
||||
set.add(Byte.class);
|
||||
set.add(Short.class);
|
||||
set.add(Integer.class);
|
||||
set.add(Long.class);
|
||||
set.add(Float.class);
|
||||
set.add(Double.class);
|
||||
set.add(Void.class);
|
||||
return set;
|
||||
}
|
||||
|
||||
}
|
||||
24
src/main/java/dev/rsems/util/version/Version.java
Normal file
24
src/main/java/dev/rsems/util/version/Version.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package dev.rsems.util.version;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
@SuppressWarnings("UnnecessarySemicolon")
|
||||
public final class Version {
|
||||
|
||||
public static String VERSION;
|
||||
|
||||
static {
|
||||
InputStream resourceAsStream = Version.class.getResourceAsStream("/META-INF/maven/dev.rsems.feedreader/feedreader/pom.properties");
|
||||
Properties prop = new Properties();
|
||||
try {
|
||||
prop.load(resourceAsStream);
|
||||
VERSION = prop.getProperty("version");
|
||||
} catch (Exception ignored) {
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
private Version() {}
|
||||
|
||||
}
|
||||
1
src/main/resources/fetcher.properties
Normal file
1
src/main/resources/fetcher.properties
Normal file
@@ -0,0 +1 @@
|
||||
# nothing
|
||||
10
src/main/resources/log4j.properties
Normal file
10
src/main/resources/log4j.properties
Normal file
@@ -0,0 +1,10 @@
|
||||
log4j.appender.Stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.Stdout.layout.conversionPattern=%d %-5p - %-26.26c{1} - %m\n
|
||||
|
||||
log4j.rootLogger=INFO,Stdout
|
||||
|
||||
log4j.logger.org.apache.wicket=INFO
|
||||
log4j.logger.org.apache.wicket.protocol.http.HttpSessionStore=INFO
|
||||
log4j.logger.org.apache.wicket.version=INFO
|
||||
log4j.logger.org.apache.wicket.RequestCycle=INFO
|
||||
13
src/main/resources/rome.properties
Normal file
13
src/main/resources/rome.properties
Normal file
@@ -0,0 +1,13 @@
|
||||
# Feed Parser implementation classes
|
||||
#
|
||||
WireFeedParser.classes=dev.rsems.syndication.rome.io.impl.RSS090Parser \
|
||||
com.sun.syndication.io.impl.RSS091NetscapeParser \
|
||||
com.sun.syndication.io.impl.RSS091UserlandParser \
|
||||
com.sun.syndication.io.impl.RSS092Parser \
|
||||
dev.rsems.syndication.rome.io.impl.RSS093Parser \
|
||||
dev.rsems.syndication.rome.io.impl.RSS094Parser \
|
||||
dev.rsems.syndication.rome.io.impl.RSS10Parser \
|
||||
dev.rsems.syndication.rome.io.impl.RSS20wNSParser \
|
||||
dev.rsems.syndication.rome.io.impl.RSS20Parser \
|
||||
com.sun.syndication.io.impl.Atom10Parser \
|
||||
com.sun.syndication.io.impl.Atom03Parser
|
||||
BIN
src/main/resources/static/favicon.ico
Normal file
BIN
src/main/resources/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
25
src/main/resources/static/styles/stylesheet.css
Normal file
25
src/main/resources/static/styles/stylesheet.css
Normal file
@@ -0,0 +1,25 @@
|
||||
@CHARSET "UTF-8";
|
||||
body { background-color: darkkhaki; font-family: Calibri, Tahoma, Helvetica, Arial, sans-serif; font-size: 0.85em; }
|
||||
pre { font-family: Consolas, 'Monospac821 BT', 'Courier New', Courier, Monospaced, monospace; font-size: 0.85em; }
|
||||
div.page { display: table; }
|
||||
div.feeds { display: table-row; }
|
||||
div.feed { display: table-cell; width: 308px; }
|
||||
table { border-collapse: collapse; background-color: darkkhaki; }
|
||||
table.feed { width: 308px; }
|
||||
td,th { text-align: left; vertical-align: top; border-bottom: 4px solid darkkhaki; border-right: 4px solid darkkhaki; padding-left: 2px; padding-right: 2px; }
|
||||
td { background-color: beige; }
|
||||
th { background-color: gold; }
|
||||
td.feedtitle { font-size: 1.6em; font-weight: bold; background: none; }
|
||||
td.day { background: gold; }
|
||||
td.feed { background-color: darkkhaki; }
|
||||
a { color: royalblue; text-decoration: none; }
|
||||
a:HOVER { color: royalblue; text-decoration: underline; }
|
||||
a:VISITED { color: dimgray; }
|
||||
td.feedtitle a { color: black; text-decoration: none; }
|
||||
td.feedtitle a:HOVER { color: black; text-decoration: underline; }
|
||||
td.feedtitle a:VISITED { color: black; text-decoration: none; }
|
||||
.subtitle { font-size: 0.8em; }
|
||||
.foot { font-size: 0.8em; text-align: right; }
|
||||
.code { border: 1px solid dimgray; padding-left: 10px; padding-right: 15px; margin-bottom: 0.5em; background-color: antiquewhite; overflow: auto; }
|
||||
hr { height: 2px; border: 0; color: dimgray; background-color: dimgray; }
|
||||
.message { display: block; }
|
||||
@@ -1,5 +1,8 @@
|
||||
<html lang="en">
|
||||
<head><title>Error Occurred</title></head>
|
||||
<head>
|
||||
<title>Error Occurred</title>
|
||||
<link th:href="@{/styles/stylesheet.css}" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Error!</h1>
|
||||
<b>[<span th:text="${status}">status</span>]
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<html lang="en">
|
||||
<head><title>Home Page</title></head>
|
||||
<body>
|
||||
<h1>Hello !</h1>
|
||||
<p>Welcome to <span th:text="${appName}">Our App</span></p>
|
||||
</body>
|
||||
</html>
|
||||
17
src/main/resources/templates/index.html
Normal file
17
src/main/resources/templates/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title th:text="${appName}"></title>
|
||||
<link th:href="@{/styles/stylesheet.css}" rel="stylesheet" />
|
||||
<link th:href="@{/favicon.ico}" rel="shortcut icon" type="image/x-icon" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="feeds">
|
||||
<div class="feed">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="foot" th:text="${appName}"></div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user