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>
|
<artifactId>junit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<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;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class SimpleController {
|
public class FeedReaderController {
|
||||||
@Value("${spring.application.name}")
|
@Value("${spring.application.name}")
|
||||||
String appName;
|
String appName;
|
||||||
|
|
||||||
@GetMapping("/")
|
@GetMapping("/")
|
||||||
public String homePage(Model model) {
|
public String homePage(Model model) {
|
||||||
model.addAttribute("appName", appName);
|
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")
|
@EntityScan("dev.rsems.feedreader.model")
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class FeedreaderWebApplication {
|
public class FeedreaderWebApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(FeedreaderWebApplication.class, 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.FeedIdMismatchException;
|
||||||
import dev.rsems.feedreader.exception.FeedNotFoundException;
|
import dev.rsems.feedreader.exception.FeedNotFoundException;
|
||||||
@@ -18,7 +18,7 @@ public class FeedController {
|
|||||||
private FeedRepository feedRepository;
|
private FeedRepository feedRepository;
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public Iterable findAll() {
|
public Iterable<?> findAll() {
|
||||||
return feedRepository.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.FeedIdMismatchException;
|
||||||
import dev.rsems.feedreader.exception.FeedNotFoundException;
|
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">
|
<html lang="en">
|
||||||
<head><title>Error Occurred</title></head>
|
<head>
|
||||||
|
<title>Error Occurred</title>
|
||||||
|
<link th:href="@{/styles/stylesheet.css}" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Error!</h1>
|
<h1>Error!</h1>
|
||||||
<b>[<span th:text="${status}">status</span>]
|
<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