/** * RSSFeed * Copyright 2007 by Michael Peter Christen * First released 16.7.2007 at http://yacy.net * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program in the file lgpl21.txt * If not, see <http://www.gnu.org/licenses/>. */ package net.yacy.cora.document.feed; import java.net.MalformedURLException; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import net.yacy.cora.document.id.MultiProtocolURL; public class RSSFeed implements Iterable<RSSMessage> { public static final int DEFAULT_MAXSIZE = 10000; // class variables private RSSMessage channel = null; // single required element see http://www.rssboard.org/rss-profile#element-channel private final Map<String, RSSMessage> messages; // a guid:Item map private final int maxsize; @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); sb.append("<rss version=\"2.0\"\n"); sb.append(" xmlns:opensearch=\"http://a9.com/-/spec/opensearch/1.1/\"\n"); sb.append(" xmlns:atom=\"http://www.w3.org/2005/Atom\"\n"); sb.append(">\n"); sb.append("<channel>\n"); if (this.channel != null) sb.append(this.channel.toString(false)); sb.append("<opensearch:startIndex>0</opensearch:startIndex>\n"); sb.append("<opensearch:itemsPerPage>" + this.size() + "</opensearch:itemsPerPage>\n"); sb.append("<opensearch:totalResults>" + this.size() + "</opensearch:totalResults>\n"); for (RSSMessage item: messages.values()) { sb.append(item.toString()); } sb.append("</channel>\n"); sb.append("</rss>\n"); return sb.toString(); } public RSSFeed(final int maxsize) { this.messages = Collections.synchronizedMap(new LinkedHashMap<String, RSSMessage>()); this.channel = null; this.maxsize = maxsize; } /** * make a RSS feed using a set of urls * the source string is assigned to all messages as author to mark the messages' origin * @param links * @param source */ public RSSFeed(Set<MultiProtocolURL> links, String source) { this(Integer.MAX_VALUE); String u; RSSMessage message; for (MultiProtocolURL uri: links) { u = uri.toNormalform(true); message = new RSSMessage(u, "", u); message.setAuthor(source); this.addMessage(message); } } public void setChannel(final RSSMessage channelItem) { this.channel = channelItem; } /** * Return Channel element * (This element is required and must contain three child elements: description, link and title) * see http://www.rssboard.org/rss-profile#element-channel * @return RSSMessage with channel elements or empty RSSMessage */ public RSSMessage getChannel() { // This element is required and must contain three child elements: description, link and title. if (this.channel==null) this.channel = new RSSMessage(); return this.channel; } public Set<MultiProtocolURL> getLinks() { Set<MultiProtocolURL> links = new HashSet<MultiProtocolURL>(); for (RSSMessage message: this.messages.values()) { try {links.add(new MultiProtocolURL(message.getLink()));} catch (final MalformedURLException e) {} } return links; } public void addMessage(final RSSMessage item) { final String guid = item.getGuid(); this.messages.put(guid, item); // in case that the feed is full (size > maxsize) flush the oldest element while (this.messages.size() > this.maxsize) pollMessage(); } public RSSMessage getMessage(final String guid) { // retrieve item by guid return this.messages.get(guid); } public boolean isEmpty() { return this.messages.isEmpty(); } public int size() { return this.messages.size(); } @Override public Iterator<RSSMessage> iterator() { return new messageIterator(); } public RSSMessage pollMessage() { // retrieve and delete item synchronized (this.messages) { if (this.messages.isEmpty()) return null; final String nextGUID = this.messages.keySet().size() == 0 ? null : this.messages.keySet().iterator().next(); if (nextGUID == null) return null; return this.messages.remove(nextGUID); } } public class messageIterator implements Iterator<RSSMessage>{ Iterator<String> GUIDiterator; String lastGUID; int t; public messageIterator() { this.t = RSSFeed.this.messages.size(); // termination counter this.GUIDiterator = RSSFeed.this.messages.keySet().iterator(); this.lastGUID = null; } @Override public boolean hasNext() { if (this.t <= 0) return false; // ensure termination return this.GUIDiterator.hasNext(); } @Override public RSSMessage next() { this.t--; // ensure termination try { this.lastGUID = this.GUIDiterator.next(); } catch (final ConcurrentModificationException e) { //ConcurrentLog.logException(e); this.lastGUID = null; } if (this.lastGUID == null) return null; return RSSFeed.this.messages.get(this.lastGUID); } @Override public void remove() { if (this.lastGUID == null) return; this.GUIDiterator.remove(); RSSFeed.this.messages.remove(this.lastGUID); } } }