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:
2024-02-11 01:40:32 +01:00
parent d7f12f8e80
commit 3a23ac4503
54 changed files with 3787 additions and 119 deletions

27
pom.xml
View File

@@ -76,6 +76,33 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.rometools</groupId>
<artifactId>rome</artifactId>
<version>1.19.0</version>
</dependency>
<dependency>
<groupId>com.rometools</groupId>
<artifactId>rome-fetcher</artifactId>
<version>1.19.0</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.jdom</groupId>-->
<!-- <artifactId>jdom</artifactId>-->
<!-- <version>1.1.3</version>-->
<!-- </dependency>-->
<!-- https://mvnrepository.com/artifact/org.jdom/jdom2 -->
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom2</artifactId>
<version>2.0.6.1</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.17.2</version>
</dependency>
</dependencies>
<build>

View 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); // &#160; 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");
}
}

View File

@@ -6,13 +6,13 @@ import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class SimpleController {
public class FeedReaderController {
@Value("${spring.application.name}")
String appName;
@GetMapping("/")
public String homePage(Model model) {
model.addAttribute("appName", appName);
return "home";
return "index";
}
}

View 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);
// }
}

View File

@@ -9,7 +9,6 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@EntityScan("dev.rsems.feedreader.model")
@SpringBootApplication
public class FeedreaderWebApplication {
public static void main(String[] args) {
SpringApplication.run(FeedreaderWebApplication.class, args);
}

View 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);
}

View 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);
}
}

View 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());
}

View 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;
}
}
}

View 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();
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}
}

View 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()
+ ">");
}
}
}

View File

@@ -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";
}
}

View File

@@ -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";
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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";
}
}

View File

@@ -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";
}
}

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -1,4 +1,4 @@
package rest;
package dev.rsems.feedreader.rest;
import dev.rsems.feedreader.exception.FeedIdMismatchException;
import dev.rsems.feedreader.exception.FeedNotFoundException;
@@ -18,7 +18,7 @@ public class FeedController {
private FeedRepository feedRepository;
@GetMapping
public Iterable findAll() {
public Iterable<?> findAll() {
return feedRepository.findAll();
}

View File

@@ -1,4 +1,4 @@
package rest;
package dev.rsems.feedreader.rest;
import dev.rsems.feedreader.exception.FeedIdMismatchException;
import dev.rsems.feedreader.exception.FeedNotFoundException;

View 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());
}
}

View 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;
}
}

View 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"));
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View 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();
}
}

View 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;
}
}

View 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 &lt;start&gt; <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');
}
}

View File

@@ -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); }
}

View 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("&lt;", "<").replace("&gt;", ">").replace("&amp;", "&").replace("&apos;", "'").replace("&quot;", "\""); // 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;
}
}

View 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() {}
}

View File

@@ -0,0 +1 @@
# nothing

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View 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; }

View File

@@ -1,5 +1,8 @@
<html lang="en">
<head><title>Error Occurred</title></head>
<head>
<title>Error Occurred</title>
<link th:href="@{/styles/stylesheet.css}" rel="stylesheet" />
</head>
<body>
<h1>Error!</h1>
<b>[<span th:text="${status}">status</span>]

View File

@@ -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>

View 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>