diff --git a/License.txt b/License.txt
new file mode 100644
index 0000000..f43cdb1
--- /dev/null
+++ b/License.txt
@@ -0,0 +1,14 @@
+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.
+
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..63e5da4
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,13 @@
+
+
+
The default user agent. It is not marked final so + * buggy java compiler will not write this string + * into all classes that reference it.
+ * + *http://tinyurl.com/64t5n points to https://rome.dev.java.net/ + * Some servers ban user agents with "Java" in the name.
+ * + */ + public static String DEFAULT_USER_AGENT = "Rome Client (http://tinyurl.com/64t5n)"; + + /** + * @return the User-Agent currently being sent to servers + */ + public abstract String getUserAgent(); + /** + * @param string The User-Agent to sent to servers + */ + public abstract void setUserAgent(String string); + /** + * Retrieve a feed over HTTP + * + * @param feedUrl A non-null URL of a RSS/Atom feed to retrieve + * @return A {@link com.sun.syndication.feed.synd.SyndFeed} object + * @throws IllegalArgumentException if the URL is null; + * @throws IOException if a TCP error occurs + * @throws FeedException if the feed is not valid + * @throws FetcherException if a HTTP error occurred + */ + public abstract SyndFeed retrieveFeed(URL feedUrl) throws IllegalArgumentException, IOException, FeedException, FetcherException; + + /** + *Add a FetcherListener.
+ * + *The FetcherListener will receive an FetcherEvent when + * a Fetcher event (feed polled, retrieved, etc) occurs
+ * + * @param listener The FetcherListener to recieve the event + */ + public abstract void addFetcherEventListener(FetcherListener listener); + + /** + *Remove a FetcherListener
+ * + * @param listener The FetcherListener to remove + */ + public abstract void removeFetcherEventListener(FetcherListener listener); + + /** + *Is this fetcher using rfc3229 delta encoding?
+ * + * @return + */ + public abstract boolean isUsingDeltaEncoding(); + + /** + *Turn on or off rfc3229 delta encoding
+ * + *See http://www.ietf.org/rfc/rfc3229.txt and http://bobwyman.pubsub.com/main/2004/09/using_rfc3229_w.html
+ * + *NOTE: This is experimental and feedback is welcome!
+ * + * @param useDeltaEncoding + */ + public abstract void setUsingDeltaEncoding(boolean useDeltaEncoding); + + /** + * If set to true, the WireFeed will be made accessible from the SyndFeed object returned from the Fetcher + * via the originalWireFeed() method. Each Entry in the feed will have the corresponding wireEntry property set. + */ + void setPreserveWireFeed(boolean preserveWireFeed); + +} \ No newline at end of file diff --git a/src/java/com/sun/syndication/fetcher/FetcherEvent.java b/src/java/com/sun/syndication/fetcher/FetcherEvent.java new file mode 100644 index 0000000..8c705a3 --- /dev/null +++ b/src/java/com/sun/syndication/fetcher/FetcherEvent.java @@ -0,0 +1,85 @@ +package com.sun.syndication.fetcher; + +import java.util.EventObject; + +import com.sun.syndication.feed.synd.SyndFeed; + +/** + * Implementation note: FetcherEvent is not thread safe. Make sure that + * they are only ever accessed by one thread. If necessary, make all getters + * and setters synchronized, or alternatively make all fields final. + * + * @author nl + */ +public class FetcherEvent extends EventObject { + + private static final long serialVersionUID = 3985600601904140103L; + + public static final String EVENT_TYPE_FEED_POLLED = "FEED_POLLED"; + public static final String EVENT_TYPE_FEED_RETRIEVED = "FEED_RETRIEVED"; + public static final String EVENT_TYPE_FEED_UNCHANGED = "FEED_UNCHANGED"; + + private String eventType; + private String urlString; + private SyndFeed feed; + + public FetcherEvent(Object source) { + super(source); + } + + + public FetcherEvent(Object source, String urlStr, String eventType) { + this(source); + setUrlString(urlStr); + setEventType(eventType); + } + + public FetcherEvent(Object source, String urlStr, String eventType, SyndFeed feed) { + this(source, urlStr, eventType); + setFeed(feed); + } + + + /** + * @return Returns the feed. + * + *The feed will only be set if the eventType is EVENT_TYPE_FEED_RETRIEVED
+ */ + public SyndFeed getFeed() { + return feed; + } + + /** + * @param feed The feed to set. + * + *The feed will only be set if the eventType is EVENT_TYPE_FEED_RETRIEVED
+ */ + public void setFeed(SyndFeed feed) { + this.feed = feed; + } + + /** + * @return Returns the eventType. + */ + public String getEventType() { + return eventType; + } + /** + * @param eventType The eventType to set. + */ + public void setEventType(String eventType) { + this.eventType = eventType; + } + /** + * @return Returns the urlString. + */ + public String getUrlString() { + return urlString; + } + /** + * @param urlString The urlString to set. + */ + public void setUrlString(String urlString) { + this.urlString = urlString; + } +} diff --git a/src/java/com/sun/syndication/fetcher/FetcherException.java b/src/java/com/sun/syndication/fetcher/FetcherException.java new file mode 100644 index 0000000..c263bfb --- /dev/null +++ b/src/java/com/sun/syndication/fetcher/FetcherException.java @@ -0,0 +1,51 @@ +/* + * 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 com.sun.syndication.fetcher; + +/** + * @author Nick Lothian + * + */ +public class FetcherException extends Exception { + private static final long serialVersionUID = -7479645796948092380L; + + int responseCode; + + public FetcherException(Throwable cause) { + super(); + initCause(cause); + } + + public FetcherException(String message, Throwable cause) { + super(message); + initCause(cause); + } + + public FetcherException(String message) { + super(message); + } + + public FetcherException(int responseCode, String message) { + this(message); + this.responseCode = responseCode; + } + + public int getResponseCode() { + return responseCode; + } + +} diff --git a/src/java/com/sun/syndication/fetcher/FetcherListener.java b/src/java/com/sun/syndication/fetcher/FetcherListener.java new file mode 100644 index 0000000..25bfda1 --- /dev/null +++ b/src/java/com/sun/syndication/fetcher/FetcherListener.java @@ -0,0 +1,15 @@ +package com.sun.syndication.fetcher; + +import java.util.EventListener; + + +public interface FetcherListener extends EventListener { + + /** + *Called when a fetcher event occurs
+ * + * @param event the event that fired + */ + public void fetcherEvent(FetcherEvent event); + +} diff --git a/src/java/com/sun/syndication/fetcher/impl/AbstractFeedFetcher.java b/src/java/com/sun/syndication/fetcher/impl/AbstractFeedFetcher.java new file mode 100644 index 0000000..12d843f --- /dev/null +++ b/src/java/com/sun/syndication/fetcher/impl/AbstractFeedFetcher.java @@ -0,0 +1,219 @@ +/* + * 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 com.sun.syndication.fetcher.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URLConnection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Properties; +import java.util.Set; + +import com.sun.syndication.feed.synd.SyndFeed; +import com.sun.syndication.fetcher.FeedFetcher; +import com.sun.syndication.fetcher.FetcherEvent; +import com.sun.syndication.fetcher.FetcherException; +import com.sun.syndication.fetcher.FetcherListener; + + +public abstract class AbstractFeedFetcher implements FeedFetcher { + private final Set fetcherEventListeners; + private String userAgent; + private boolean usingDeltaEncoding; + private boolean preserveWireFeed; + + + + public AbstractFeedFetcher() { + fetcherEventListeners = Collections.synchronizedSet(new HashSet()); + + Properties props = new Properties(System.getProperties()); + String resourceName = "fetcher.properties"; + + try { + InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(resourceName); + if (inputStream == null) { + inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName); + } + if (inputStream != null) { + props.load(inputStream); + System.getProperties().putAll(props); + inputStream.close(); + } else { + System.err.println("Could not find " + resourceName + " on classpath"); + } + } catch (IOException e) { + // do nothing - we don't want to fail just because we could not find the version + System.err.println("Error reading " + resourceName + " from classpath: " + e.getMessage()); + } + + + setUserAgent(DEFAULT_USER_AGENT + " Ver: " + System.getProperty("rome.fetcher.version", "UNKNOWN")); + } + + /** + * @return the User-Agent currently being sent to servers + */ + public synchronized String getUserAgent() { + return userAgent; + } + + /** + * @param string The User-Agent to sent to servers + */ + public synchronized void setUserAgent(String string) { + userAgent = string; + } + + /** + * @param eventType The event type to fire + * @param connection the current connection + */ + protected void fireEvent(String eventType, URLConnection connection) { + fireEvent(eventType, connection.getURL().toExternalForm(), null); + } + + + /** + * @param eventType The event type to fire + * @param connection the current connection + * @param feed The feed to pass to the event + */ + protected void fireEvent(String eventType, URLConnection connection, SyndFeed feed) { + fireEvent(eventType, connection.getURL().toExternalForm(), feed); + } + + /** + * @param eventType The event type to fire + * @param urlStr the current url as a string + */ + protected void fireEvent(String eventType, String urlStr) { + fireEvent(eventType, urlStr, null); + } + + /** + * @param eventType The event type to fire + * @param urlStr the current url as a string + * @param feed The feed to pass to the event + */ + protected void fireEvent(String eventType, String urlStr, SyndFeed feed) { + FetcherEvent fetcherEvent = new FetcherEvent(this, urlStr, eventType, feed); + synchronized(fetcherEventListeners) { + Iterator iter = fetcherEventListeners.iterator(); + while ( iter.hasNext()) { + FetcherListener fetcherEventListener = (FetcherListener) iter.next(); + fetcherEventListener.fetcherEvent(fetcherEvent); + } + } + } + + /** + * @see com.sun.syndication.fetcher.FeedFetcher#addFetcherEventListener(com.sun.syndication.fetcher.FetcherListener) + */ + public void addFetcherEventListener(FetcherListener listener) { + if (listener != null) { + fetcherEventListeners.add(listener); + } + + } + + /** + * @see com.sun.syndication.fetcher.FeedFetcher#removeFetcherEventListener(com.sun.syndication.fetcher.FetcherListener) + */ + public void removeFetcherEventListener(FetcherListener listener) { + if (listener != null) { + fetcherEventListeners.remove(listener); + } + } + + /** + * @return Returns the useDeltaEncoding. + */ + public synchronized boolean isUsingDeltaEncoding() { + return usingDeltaEncoding; + } + /** + * @param useDeltaEncoding The useDeltaEncoding to set. + */ + public synchronized void setUsingDeltaEncoding(boolean useDeltaEncoding) { + this.usingDeltaEncoding = useDeltaEncoding; + } + + /** + *Handles HTTP error codes.
+ * + * @param responseCode the HTTP response code + * @throws FetcherException if response code is in the range 400 to 599 inclusive + */ + protected void handleErrorCodes(int responseCode) throws FetcherException { + // Handle 2xx codes as OK, so ignore them here + // 3xx codes are handled by the HttpURLConnection class + if (responseCode == 403) { + // Authentication is required + throwAuthenticationError(responseCode); + } else if (responseCode >= 400 && responseCode < 500) { + throw4XXError(responseCode); + } else if (responseCode >= 500 && responseCode < 600) { + throw new FetcherException(responseCode, "The server encounted an error. HTTP Response code was:" + responseCode); + } + } + + protected void throw4XXError(int responseCode) throws FetcherException { + throw new FetcherException(responseCode, "The requested resource could not be found. HTTP Response code was:" + responseCode); + } + + protected void throwAuthenticationError(int responseCode) throws FetcherException { + throw new FetcherException(responseCode, "Authentication required for that resource. HTTP Response code was:" + responseCode); + } + + /** + *Combine the entries in two feeds into a single feed.
+ * + *The returned feed will have the same data as the newFeed parameter, with + * the entries from originalFeed appended to the end of its entries.
+ * + * @param originalFeed + * @param newFeed + * @return + */ + public static SyndFeed combineFeeds(SyndFeed originalFeed, SyndFeed newFeed) { + SyndFeed result; + try { + result = (SyndFeed) newFeed.clone(); + + result.getEntries().addAll(result.getEntries().size(), originalFeed.getEntries()); + + return result; + } catch (CloneNotSupportedException e) { + IllegalArgumentException iae = new IllegalArgumentException("Cannot clone feed"); + iae.initCause(e); + throw iae; + } + } + + public boolean isPreserveWireFeed() { + return preserveWireFeed; + } + + public void setPreserveWireFeed(boolean preserveWireFeed) { + this.preserveWireFeed = preserveWireFeed; + } + +} diff --git a/src/java/com/sun/syndication/fetcher/impl/AbstractFeedFetcherBeanInfo.java b/src/java/com/sun/syndication/fetcher/impl/AbstractFeedFetcherBeanInfo.java new file mode 100644 index 0000000..d8303a5 --- /dev/null +++ b/src/java/com/sun/syndication/fetcher/impl/AbstractFeedFetcherBeanInfo.java @@ -0,0 +1,29 @@ +package com.sun.syndication.fetcher.impl; + +import java.beans.EventSetDescriptor; +import java.beans.SimpleBeanInfo; +import java.lang.reflect.Method; + +import com.sun.syndication.fetcher.FetcherEvent; +import com.sun.syndication.fetcher.FetcherListener; + +public class AbstractFeedFetcherBeanInfo extends SimpleBeanInfo { + + public EventSetDescriptor[] getEventSetDescriptors() { + try { + Class clz = AbstractFeedFetcher.class; // get the class object which we'll describe + Method addMethod = clz.getMethod("addFetcherEventListener", new Class[] { FetcherListener.class }); + Method removeMethod = clz.getMethod("removeFetcherEventListener", new Class[] { FetcherListener.class }); + Method listenerMethod = FetcherListener.class.getMethod("fetcherEvent", new Class[] { FetcherEvent.class }); + + EventSetDescriptor est = new EventSetDescriptor("fetcherEvent", clz, new Method[] { listenerMethod }, addMethod, removeMethod); + EventSetDescriptor[] results = new EventSetDescriptor[] { est }; + + return results; + } catch (Exception e) { + // IntrospectionException, SecurityException and/or NoSuchMethodException can be thrown here + // the best we can do is to convert them to runtime exceptions + throw new RuntimeException(e); + } + } +} diff --git a/src/java/com/sun/syndication/fetcher/impl/DiskFeedInfoCache.java b/src/java/com/sun/syndication/fetcher/impl/DiskFeedInfoCache.java new file mode 100644 index 0000000..93c0d2b --- /dev/null +++ b/src/java/com/sun/syndication/fetcher/impl/DiskFeedInfoCache.java @@ -0,0 +1,134 @@ +/* + * Copyright 2005 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 com.sun.syndication.fetcher.impl; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.URL; +import javax.swing.text.Utilities; + + +/** + * Disk based cache. + */ +public class DiskFeedInfoCache implements FeedFetcherCache { + + protected String cachePath = null; + public DiskFeedInfoCache(String cachePath) { + this.cachePath = cachePath; + } + public SyndFeedInfo getFeedInfo(URL url) { + SyndFeedInfo info = null; + String fileName = cachePath + File.separator + "feed_" + + replaceNonAlphanumeric(url.toString(),'_').trim(); + FileInputStream fis; + try { + fis = new FileInputStream(fileName); + ObjectInputStream ois = new ObjectInputStream(fis); + info = (SyndFeedInfo)ois.readObject(); + fis.close(); + } catch (FileNotFoundException fnfe) { + // That's OK, we'l return null + } catch (ClassNotFoundException cnfe) { + // Error writing to cache is fatal + throw new RuntimeException("Attempting to read from cache", cnfe); + } catch (IOException fnfe) { + // Error writing to cache is fatal + throw new RuntimeException("Attempting to read from cache", fnfe); + } + return info; + } + + public void setFeedInfo(URL url, SyndFeedInfo feedInfo) { + String fileName = cachePath + File.separator + "feed_" + + replaceNonAlphanumeric(url.toString(),'_').trim(); + FileOutputStream fos; + try { + fos = new FileOutputStream(fileName); + ObjectOutputStream oos = new ObjectOutputStream(fos); + oos.writeObject(feedInfo); + fos.flush(); + fos.close(); + } catch (Exception e) { + // Error writing to cache is fatal + throw new RuntimeException("Attempting to write to cache", e); + } + } + + public static String replaceNonAlphanumeric(String str, char subst) { + StringBuffer ret = new StringBuffer(str.length()); + char[] testChars = str.toCharArray(); + for (int i = 0; i < testChars.length; i++) { + if (Character.isLetterOrDigit(testChars[i])) { + ret.append(testChars[i]); + } else { + ret.append( subst ); + } + } + return ret.toString(); + } + + /** + * Clear the cache. + */ + public synchronized void clear() { + final File file = new File(this.cachePath); + //only do the delete if the directory exists + if( file.exists() && file.canWrite() ) { + //make the directory empty + final String[] files = file.list(); + final int len = files.length; + for( int i=0; iA very simple implementation of the {@link com.sun.syndication.fetcher.impl.FeedFetcherCache} interface.
+ * + *This implementation uses a HashMap to cache retrieved feeds. This implementation is + * most suitible for sort term (client aggregator?) use, as the memory usage will increase + * over time as the number of feeds in the cache increases.
+ * + * @author Nick Lothian + * + */ +public class HashMapFeedInfoCache implements FeedFetcherCache, Serializable { + private static final long serialVersionUID = -1594665619950916222L; + + static HashMapFeedInfoCache _instance; + + private Map infoCache; + + /** + *Constructor for HashMapFeedInfoCache
+ * + *Only use this if you want multiple instances of the cache. + * Usually getInstance() is more appropriate.
+ * + */ + public HashMapFeedInfoCache() { + setInfoCache(createInfoCache()); + } + + /** + * Get the global instance of the cache + * @return an implementation of FeedFetcherCache + */ + public static synchronized FeedFetcherCache getInstance() { + if (_instance == null) { + _instance = new HashMapFeedInfoCache(); + } + return _instance; + } + + protected Map createInfoCache() { + return (Collections.synchronizedMap(new HashMap())); + } + + + protected Object get(Object key) { + return getInfoCache().get(key); + } + + /** + * @see extensions.io.FeedFetcherCache#getFeedInfo(java.net.URL) + */ + public SyndFeedInfo getFeedInfo(URL feedUrl) { + return (SyndFeedInfo) get(feedUrl.toString()); + } + + protected void put(Object key, Object value) { + getInfoCache().put(key, value); + } + + /** + * @see extensions.io.FeedFetcherCache#setFeedInfo(java.net.URL, extensions.io.SyndFeedInfo) + */ + public void setFeedInfo(URL feedUrl, SyndFeedInfo syndFeedInfo) { + put(feedUrl.toString(), syndFeedInfo); + } + + protected synchronized final Map getInfoCache() { + return infoCache; + } + + /** + * The API of this class indicates that map must thread safe. In other + * words, be sure to wrap it in a synchronized map unless you know + * what you are doing. + * + * @param map the map to use as the info cache. + */ + protected synchronized final void setInfoCache(Map map) { + infoCache = map; + } + + /** + * @see com.sun.syndication.fetcher.impl.FeedFetcherCache#clear() + */ + public void clear() { + synchronized( infoCache ) { + infoCache.clear(); + } + } + + /** + * @see com.sun.syndication.fetcher.impl.FeedFetcherCache#remove(java.net.URL) + */ + public SyndFeedInfo remove( final URL url ) { + if( url == null ) return null; + + return (SyndFeedInfo) infoCache.remove( url.toString() ); + } + +} diff --git a/src/java/com/sun/syndication/fetcher/impl/HttpClientFeedFetcher.java b/src/java/com/sun/syndication/fetcher/impl/HttpClientFeedFetcher.java new file mode 100644 index 0000000..30517be --- /dev/null +++ b/src/java/com/sun/syndication/fetcher/impl/HttpClientFeedFetcher.java @@ -0,0 +1,353 @@ +/* + * 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 com.sun.syndication.fetcher.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.zip.GZIPInputStream; + +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.params.HttpClientParams; + +import com.sun.syndication.feed.synd.SyndFeed; +import com.sun.syndication.fetcher.FetcherEvent; +import com.sun.syndication.fetcher.FetcherException; +import com.sun.syndication.io.FeedException; +import com.sun.syndication.io.SyndFeedInput; +import com.sun.syndication.io.XmlReader; + +/** + * @author Nick Lothian + */ +public class HttpClientFeedFetcher extends AbstractFeedFetcher { + + private FeedFetcherCache feedInfoCache; + private CredentialSupplier credentialSupplier; + private volatile HttpClientMethodCallbackIntf httpClientMethodCallback; + private volatile HttpClientParams httpClientParams; + + public HttpClientFeedFetcher() { + super(); + setHttpClientParams(new HttpClientParams()); + } + + /** + * @param cache + */ + public HttpClientFeedFetcher(FeedFetcherCache cache) { + this(); + setFeedInfoCache(cache); + } + + + public HttpClientFeedFetcher(FeedFetcherCache cache, CredentialSupplier credentialSupplier) { + this(cache); + setCredentialSupplier(credentialSupplier); + } + + + /** + * @return Returns the httpClientParams. + */ + public synchronized HttpClientParams getHttpClientParams() { + return this.httpClientParams; + } + /** + * @param httpClientParams The httpClientParams to set. + */ + public synchronized void setHttpClientParams(HttpClientParams httpClientParams) { + this.httpClientParams = httpClientParams; + } + + /** + * @param timeout Sets the connect timeout for the HttpClient but using the URLConnection method name. + * Uses the HttpClientParams method setConnectionManagerTimeout instead of setConnectTimeout + * + */ + public synchronized void setConnectTimeout(int timeout) { + httpClientParams.setConnectionManagerTimeout(timeout); + } + + /** + * @return The currently used connect timeout for the HttpClient but using the URLConnection method name. + * Uses the HttpClientParams method getConnectionManagerTimeout instead of getConnectTimeout + * + */ + public int getConnectTimeout() { + return (int) this.getHttpClientParams().getConnectionManagerTimeout(); + } + + /** + * @return The currently used read timeout for the URLConnection, 0 is unlimited, i.e. no timeout + */ + public synchronized void setReadTimeout(int timeout) { + httpClientParams.setSoTimeout(timeout); + } + + /** + * @param timeout Sets the read timeout for the URLConnection to a specified timeout, in milliseconds. + */ + public int getReadTimeout() { + return (int) this.getHttpClientParams().getSoTimeout(); + } + + public HttpClientMethodCallbackIntf getHttpClientMethodCallback() { + return httpClientMethodCallback; + } + + public synchronized void setHttpClientMethodCallback(HttpClientMethodCallbackIntf httpClientMethodCallback) { + this.httpClientMethodCallback = httpClientMethodCallback; + } + + /** + * @return the feedInfoCache. + */ + public synchronized FeedFetcherCache getFeedInfoCache() { + return feedInfoCache; + } + + /** + * @param feedInfoCache the feedInfoCache to set + */ + public synchronized void setFeedInfoCache(FeedFetcherCache feedInfoCache) { + this.feedInfoCache = feedInfoCache; + } + + /** + * @return Returns the credentialSupplier. + */ + public synchronized CredentialSupplier getCredentialSupplier() { + return credentialSupplier; + } + /** + * @param credentialSupplier The credentialSupplier to set. + */ + public synchronized void setCredentialSupplier(CredentialSupplier credentialSupplier) { + this.credentialSupplier = credentialSupplier; + } + + /** + * @see com.sun.syndication.fetcher.FeedFetcher#retrieveFeed(java.net.URL) + */ + public SyndFeed retrieveFeed(URL feedUrl) throws IllegalArgumentException, IOException, FeedException, FetcherException { + if (feedUrl == null) { + throw new IllegalArgumentException("null is not a valid URL"); + } + // TODO Fix this + //System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog"); + HttpClient client = new HttpClient(httpClientParams); + + if (getCredentialSupplier() != null) { + client.getState().setAuthenticationPreemptive(true); + // TODO what should realm be here? + Credentials credentials = getCredentialSupplier().getCredentials(null, feedUrl.getHost()); + if (credentials != null) { + client.getState().setCredentials(null, feedUrl.getHost(), credentials); + } + } + + + System.setProperty("httpclient.useragent", getUserAgent()); + String urlStr = feedUrl.toString(); + + HttpMethod method = new GetMethod(urlStr); + method.addRequestHeader("Accept-Encoding", "gzip"); + method.addRequestHeader("User-Agent", getUserAgent()); + method.setFollowRedirects(true); + + if (httpClientMethodCallback != null) { + synchronized (httpClientMethodCallback) { + httpClientMethodCallback.afterHttpClientMethodCreate(method); + } + } + + FeedFetcherCache cache = getFeedInfoCache(); + if (cache != null) { + // retrieve feed + + try { + if (isUsingDeltaEncoding()) { + method.setRequestHeader("A-IM", "feed"); + } + + // get the feed info from the cache + // Note that syndFeedInfo will be null if it is not in the cache + SyndFeedInfo syndFeedInfo = cache.getFeedInfo(feedUrl); + if (syndFeedInfo != null) { + method.setRequestHeader("If-None-Match", syndFeedInfo.getETag()); + + if (syndFeedInfo.getLastModified() instanceof String) { + method.setRequestHeader("If-Modified-Since", (String)syndFeedInfo.getLastModified()); + } + } + + int statusCode = client.executeMethod(method); + fireEvent(FetcherEvent.EVENT_TYPE_FEED_POLLED, urlStr); + handleErrorCodes(statusCode); + + SyndFeed feed = getFeed(syndFeedInfo, urlStr, method, statusCode); + + syndFeedInfo = buildSyndFeedInfo(feedUrl, urlStr, method, feed, statusCode); + + cache.setFeedInfo(new URL(urlStr), syndFeedInfo); + + // the feed may have been modified to pick up cached values + // (eg - for delta encoding) + feed = syndFeedInfo.getSyndFeed(); + + return feed; + } finally { + method.releaseConnection(); + method.recycle(); + } + + } else { + // cache is not in use + try { + int statusCode = client.executeMethod(method); + fireEvent(FetcherEvent.EVENT_TYPE_FEED_POLLED, urlStr); + handleErrorCodes(statusCode); + + return getFeed(null, urlStr, method, statusCode); + } finally { + method.releaseConnection(); + method.recycle(); + } + } + } + + + /** + * @param feedUrl + * @param urlStr + * @param method + * @param feed + * @return + * @throws MalformedURLException + */ + private SyndFeedInfo buildSyndFeedInfo(URL feedUrl, String urlStr, HttpMethod method, SyndFeed feed, int statusCode) throws MalformedURLException { + SyndFeedInfo syndFeedInfo; + syndFeedInfo = new SyndFeedInfo(); + + // this may be different to feedURL because of 3XX redirects + syndFeedInfo.setUrl(new URL(urlStr)); + syndFeedInfo.setId(feedUrl.toString()); + + Header imHeader = method.getResponseHeader("IM"); + if (imHeader != null && imHeader.getValue().indexOf("feed") >= 0 && isUsingDeltaEncoding()) { + FeedFetcherCache cache = getFeedInfoCache(); + if (cache != null && statusCode == 226) { + // client is setup to use http delta encoding and the server supports it and has returned a delta encoded response + // This response only includes new items + SyndFeedInfo cachedInfo = cache.getFeedInfo(feedUrl); + if (cachedInfo != null) { + SyndFeed cachedFeed = cachedInfo.getSyndFeed(); + + // set the new feed to be the orginal feed plus the new items + feed = combineFeeds(cachedFeed, feed); + } + } + } + + Header lastModifiedHeader = method.getResponseHeader("Last-Modified"); + if (lastModifiedHeader != null) { + syndFeedInfo.setLastModified(lastModifiedHeader.getValue()); + } + + Header eTagHeader = method.getResponseHeader("ETag"); + if (eTagHeader != null) { + syndFeedInfo.setETag(eTagHeader.getValue()); + } + + syndFeedInfo.setSyndFeed(feed); + + return syndFeedInfo; + } + + /** + * @param client + * @param urlStr + * @param method + * @return + * @throws IOException + * @throws HttpException + * @throws FetcherException + * @throws FeedException + */ + private SyndFeed retrieveFeed(String urlStr, HttpMethod method) throws IOException, HttpException, FetcherException, FeedException { + + InputStream stream = null; + if ((method.getResponseHeader("Content-Encoding") != null) && ("gzip".equalsIgnoreCase(method.getResponseHeader("Content-Encoding").getValue()))) { + stream = new GZIPInputStream(method.getResponseBodyAsStream()); + } else { + stream = method.getResponseBodyAsStream(); + } + try { + XmlReader reader = null; + if (method.getResponseHeader("Content-Type") != null) { + reader = new XmlReader(stream, method.getResponseHeader("Content-Type").getValue(), true); + } else { + reader = new XmlReader(stream, true); + } + SyndFeedInput syndFeedInput = new SyndFeedInput(); + syndFeedInput.setPreserveWireFeed(isPreserveWireFeed()); + + return syndFeedInput.build(reader); + } finally { + if (stream != null) { + stream.close(); + } + } + } + + private SyndFeed getFeed(SyndFeedInfo syndFeedInfo, String urlStr, HttpMethod method, int statusCode) throws IOException, HttpException, FetcherException, FeedException { + + if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED && syndFeedInfo != null) { + fireEvent(FetcherEvent.EVENT_TYPE_FEED_UNCHANGED, urlStr); + return syndFeedInfo.getSyndFeed(); + } + + SyndFeed feed = retrieveFeed(urlStr, method); + fireEvent(FetcherEvent.EVENT_TYPE_FEED_RETRIEVED, urlStr, feed); + return feed; + } + + public interface CredentialSupplier { + public Credentials getCredentials(String realm, String host); + } + + public interface HttpClientMethodCallbackIntf { + /** + * Allows access to the underlying HttpClient HttpMethod object. + * Note that in most cases, method.setRequestHeader(String, String) + * is what you want to do (rather than method.addRequestHeader(String, String)) + * + * @param method + */ + public void afterHttpClientMethodCreate(HttpMethod method); + } + +} diff --git a/src/java/com/sun/syndication/fetcher/impl/HttpURLFeedFetcher.java b/src/java/com/sun/syndication/fetcher/impl/HttpURLFeedFetcher.java new file mode 100644 index 0000000..c08def1 --- /dev/null +++ b/src/java/com/sun/syndication/fetcher/impl/HttpURLFeedFetcher.java @@ -0,0 +1,295 @@ +/* + * 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 com.sun.syndication.fetcher.impl; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.zip.GZIPInputStream; + +import com.sun.syndication.feed.synd.SyndFeed; +import com.sun.syndication.fetcher.FetcherEvent; +import com.sun.syndication.fetcher.FetcherException; +import com.sun.syndication.io.FeedException; +import com.sun.syndication.io.SyndFeedInput; +import com.sun.syndication.io.XmlReader; + +/** + *Class to retrieve syndication files via HTTP.
+ * + *If passed a {@link com.sun.syndication.fetcher.impl.FeedFetcherCache} in the + * constructor it will use conditional gets to only retrieve modified content.
+ * + *The class uses the Accept-Encoding: gzip header to retrieve gzipped feeds where + * supported by the server.
+ * + *Simple usage: + *
+ * // create the cache + * FeedFetcherCache feedInfoCache = HashMapFeedInfoCache.getFeedInfoCache(); + * // retrieve the feed the first time + * // any subsequent request will use conditional gets and only + * // retrieve the resource if it has changed + * SyndFeed feed = new HttpURLFeedFetcher(feedInfoCache).retrieveFeed(feedUrl); + *+ * + * + * + * @see http://fishbowl.pastiche.org/2002/10/21/http_conditional_get_for_rss_hackers + * @see http://diveintomark.org/archives/2003/07/21/atom_aggregator_behavior_http_level + * @see http://bobwyman.pubsub.com/main/2004/09/using_rfc3229_w.html + * @author Nick Lothian + */ +public class HttpURLFeedFetcher extends AbstractFeedFetcher { + static final int POLL_EVENT = 1; + static final int RETRIEVE_EVENT = 2; + static final int UNCHANGED_EVENT = 3; + + private FeedFetcherCache feedInfoCache; + + + /** + * Constructor to use HttpURLFeedFetcher without caching of feeds + * + */ + public HttpURLFeedFetcher() { + super(); + } + + /** + * Constructor to enable HttpURLFeedFetcher to cache feeds + * + * @param feedCache - an instance of the FeedFetcherCache interface + */ + public HttpURLFeedFetcher(FeedFetcherCache feedInfoCache) { + this(); + setFeedInfoCache(feedInfoCache); + } + + /** + * Retrieve a feed over HTTP + * + * @param feedUrl A non-null URL of a RSS/Atom feed to retrieve + * @return A {@link com.sun.syndication.feed.synd.SyndFeed} object + * @throws IllegalArgumentException if the URL is null; + * @throws IOException if a TCP error occurs + * @throws FeedException if the feed is not valid + * @throws FetcherException if a HTTP error occurred + */ + public SyndFeed retrieveFeed(URL feedUrl) throws IllegalArgumentException, IOException, FeedException, FetcherException { + if (feedUrl == null) { + throw new IllegalArgumentException("null is not a valid URL"); + } + + URLConnection connection = feedUrl.openConnection(); + if (!(connection instanceof HttpURLConnection)) { + throw new IllegalArgumentException(feedUrl.toExternalForm() + " is not a valid HTTP Url"); + } + HttpURLConnection httpConnection = (HttpURLConnection)connection; + // httpConnection.setInstanceFollowRedirects(true); // this is true by default, but can be changed on a claswide basis + + FeedFetcherCache cache = getFeedInfoCache(); + if (cache != null) { + SyndFeedInfo syndFeedInfo = cache.getFeedInfo(feedUrl); + setRequestHeaders(connection, syndFeedInfo); + httpConnection.connect(); + try { + fireEvent(FetcherEvent.EVENT_TYPE_FEED_POLLED, connection); + + if (syndFeedInfo == null) { + // this is a feed that hasn't been retrieved + syndFeedInfo = new SyndFeedInfo(); + retrieveAndCacheFeed(feedUrl, syndFeedInfo, httpConnection); + } else { + // check the response code + int responseCode = httpConnection.getResponseCode(); + if (responseCode != HttpURLConnection.HTTP_NOT_MODIFIED) { + // the response code is not 304 NOT MODIFIED + // This is either because the feed server + // does not support condition gets + // or because the feed hasn't changed + retrieveAndCacheFeed(feedUrl, syndFeedInfo, httpConnection); + } else { + // the feed does not need retrieving + fireEvent(FetcherEvent.EVENT_TYPE_FEED_UNCHANGED, connection); + } + } + + return syndFeedInfo.getSyndFeed(); + } finally { + httpConnection.disconnect(); + } + } else { + fireEvent(FetcherEvent.EVENT_TYPE_FEED_POLLED, connection); + InputStream inputStream = null; + setRequestHeaders(connection, null); + httpConnection.connect(); + try { + inputStream = httpConnection.getInputStream(); + return getSyndFeedFromStream(inputStream, connection); + } catch (java.io.IOException e) { + handleErrorCodes(((HttpURLConnection)connection).getResponseCode()); + } finally { + if (inputStream != null) { + inputStream.close(); + } + httpConnection.disconnect(); + } + // we will never actually get to this line + return null; + } + } + + protected void retrieveAndCacheFeed(URL feedUrl, SyndFeedInfo syndFeedInfo, HttpURLConnection connection) throws IllegalArgumentException, FeedException, FetcherException, IOException { + handleErrorCodes(connection.getResponseCode()); + + resetFeedInfo(feedUrl, syndFeedInfo, connection); + FeedFetcherCache cache = getFeedInfoCache(); + // resetting feed info in the cache + // could be needed for some implementations + // of FeedFetcherCache (eg, distributed HashTables) + if (cache != null) { + cache.setFeedInfo(feedUrl, syndFeedInfo); + } + } + + protected void resetFeedInfo(URL orignalUrl, SyndFeedInfo syndFeedInfo, HttpURLConnection connection) throws IllegalArgumentException, IOException, FeedException { + // need to always set the URL because this may have changed due to 3xx redirects + syndFeedInfo.setUrl(connection.getURL()); + + // the ID is a persistant value that should stay the same even if the URL for the + // feed changes (eg, by 3xx redirects) + syndFeedInfo.setId(orignalUrl.toString()); + + // This will be 0 if the server doesn't support or isn't setting the last modified header + syndFeedInfo.setLastModified(new Long(connection.getLastModified())); + + // This will be null if the server doesn't support or isn't setting the ETag header + syndFeedInfo.setETag(connection.getHeaderField("ETag")); + + // get the contents + InputStream inputStream = null; + try { + inputStream = connection.getInputStream(); + SyndFeed syndFeed = getSyndFeedFromStream(inputStream, connection); + + String imHeader = connection.getHeaderField("IM"); + if (isUsingDeltaEncoding() && (imHeader!= null && imHeader.indexOf("feed") >= 0)) { + FeedFetcherCache cache = getFeedInfoCache(); + if (cache != null && connection.getResponseCode() == 226) { + // client is setup to use http delta encoding and the server supports it and has returned a delta encoded response + // This response only includes new items + SyndFeedInfo cachedInfo = cache.getFeedInfo(orignalUrl); + if (cachedInfo != null) { + SyndFeed cachedFeed = cachedInfo.getSyndFeed(); + + // set the new feed to be the orginal feed plus the new items + syndFeed = combineFeeds(cachedFeed, syndFeed); + } + } + } + + syndFeedInfo.setSyndFeed(syndFeed); + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + } + + /** + *
Set appropriate HTTP headers, including conditional get and gzip encoding headers
+ * + * @param connection A URLConnection + * @param syndFeedInfo The SyndFeedInfo for the feed to be retrieved. May be null + */ + protected void setRequestHeaders(URLConnection connection, SyndFeedInfo syndFeedInfo) { + if (syndFeedInfo != null) { + // set the headers to get feed only if modified + // we support the use of both last modified and eTag headers + if (syndFeedInfo.getLastModified() != null) { + Object lastModified = syndFeedInfo.getLastModified(); + if (lastModified instanceof Long) { + connection.setIfModifiedSince(((Long)syndFeedInfo.getLastModified()).longValue()); + } + } + if (syndFeedInfo.getETag() != null) { + connection.setRequestProperty("If-None-Match", syndFeedInfo.getETag()); + } + + } + // header to retrieve feed gzipped + connection.setRequestProperty("Accept-Encoding", "gzip"); + + // set the user agent + connection.addRequestProperty("User-Agent", getUserAgent()); + + if (isUsingDeltaEncoding()) { + connection.addRequestProperty("A-IM", "feed"); + } + } + + private SyndFeed readSyndFeedFromStream(InputStream inputStream, URLConnection connection) throws IOException, IllegalArgumentException, FeedException { + BufferedInputStream is; + if ("gzip".equalsIgnoreCase(connection.getContentEncoding())) { + // handle gzip encoded content + is = new BufferedInputStream(new GZIPInputStream(inputStream)); + } else { + is = new BufferedInputStream(inputStream); + } + + //InputStreamReader reader = new InputStreamReader(is, ResponseHandler.getCharacterEncoding(connection)); + + //SyndFeedInput input = new SyndFeedInput(); + + XmlReader reader = null; + if (connection.getHeaderField("Content-Type") != null) { + reader = new XmlReader(is, connection.getHeaderField("Content-Type"), true); + } else { + reader = new XmlReader(is, true); + } + + SyndFeedInput syndFeedInput = new SyndFeedInput(); + syndFeedInput.setPreserveWireFeed(isPreserveWireFeed()); + + return syndFeedInput.build(reader); + + } + + private SyndFeed getSyndFeedFromStream(InputStream inputStream, URLConnection connection) throws IOException, IllegalArgumentException, FeedException { + SyndFeed feed = readSyndFeedFromStream(inputStream, connection); + fireEvent(FetcherEvent.EVENT_TYPE_FEED_RETRIEVED, connection, feed); + return feed; + } + + /** + * @return The FeedFetcherCache used by this fetcher (Could be null) + */ + public synchronized FeedFetcherCache getFeedInfoCache() { + return feedInfoCache; + } + + /** + * @param cache The cache to be used by this fetcher (pass null to stop using a cache) + */ + public synchronized void setFeedInfoCache(FeedFetcherCache cache) { + feedInfoCache = cache; + } +} diff --git a/src/java/com/sun/syndication/fetcher/impl/LinkedHashMapFeedInfoCache.java b/src/java/com/sun/syndication/fetcher/impl/LinkedHashMapFeedInfoCache.java new file mode 100644 index 0000000..736f10d --- /dev/null +++ b/src/java/com/sun/syndication/fetcher/impl/LinkedHashMapFeedInfoCache.java @@ -0,0 +1,70 @@ +package com.sun.syndication.fetcher.impl; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + *An implementation of the {@link com.sun.syndication.fetcher.impl.FeedFetcherCache} interface.
+ * + *Unlike the HashMapFeedInfoCache this implementation will not grow unbound
+ * + * @author Javier Kohen + * @author Nick Lothian + * + */ +public class LinkedHashMapFeedInfoCache extends HashMapFeedInfoCache { + private final class CacheImpl extends LinkedHashMap { + private static final long serialVersionUID = -6977191330127794920L; + + public CacheImpl() { + super(16, 0.75F, true); + } + + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > getMaxEntries(); + } + } + + private static final int DEFAULT_MAX_ENTRIES = 20; + + private static final long serialVersionUID = 1694228973357997417L; + + private int maxEntries = DEFAULT_MAX_ENTRIES; + + private final static LinkedHashMapFeedInfoCache _instance = new LinkedHashMapFeedInfoCache(); + + + /** + * Get the global instance of the cache + * @return an implementation of FeedFetcherCache + */ + public static final FeedFetcherCache getInstance() { + return _instance; + } + + /** + *Constructor for HashMapFeedInfoCache
+ * + *Only use this if you want multiple instances of the cache. + * Usually {@link #getInstance()} is more appropriate.
+ * + * @see #getInstance() + */ + public LinkedHashMapFeedInfoCache() { + super(); + } + + protected Map createInfoCache() { + return Collections.synchronizedMap(new CacheImpl()); + } + + public synchronized final int getMaxEntries() { + return maxEntries; + } + + public synchronized final void setMaxEntries(int maxEntries) { + this.maxEntries = maxEntries; + } + +} diff --git a/src/java/com/sun/syndication/fetcher/impl/ResponseHandler.java b/src/java/com/sun/syndication/fetcher/impl/ResponseHandler.java new file mode 100644 index 0000000..db877fe --- /dev/null +++ b/src/java/com/sun/syndication/fetcher/impl/ResponseHandler.java @@ -0,0 +1,57 @@ +/* + * 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 com.sun.syndication.fetcher.impl; + +import java.net.URLConnection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Utility class to help deal with HTTP responses + * + */ +public class ResponseHandler { + public static final String defaultCharacterEncoding = "ISO-8859-1"; + + private final static Pattern characterEncodingPattern = Pattern.compile("charset=([.[^; ]]*)"); + + public static String getCharacterEncoding(URLConnection connection) { + return getCharacterEncoding(connection.getContentType()); + } + + /** + * + *Gets the character encoding of a response. (Note that this is different to + * the content-encoding)
+ * + * @param contentTypeHeader the value of the content-type HTTP header eg: text/html; charset=ISO-8859-4 + * @return the character encoding, eg: ISO-8859-4 + */ + public static String getCharacterEncoding(String contentTypeHeader) { + if (contentTypeHeader == null) { + return defaultCharacterEncoding; + } + + Matcher m = characterEncodingPattern.matcher(contentTypeHeader); + //if (!m.matches()) { + if (!m.find()) { + return defaultCharacterEncoding; + } else { + return m.group(1); + } + } +} diff --git a/src/java/com/sun/syndication/fetcher/impl/SyndFeedInfo.java b/src/java/com/sun/syndication/fetcher/impl/SyndFeedInfo.java new file mode 100644 index 0000000..a19f5fe --- /dev/null +++ b/src/java/com/sun/syndication/fetcher/impl/SyndFeedInfo.java @@ -0,0 +1,149 @@ +/* + * 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 com.sun.syndication.fetcher.impl; + +import java.io.Serializable; +import java.net.URL; + +import com.sun.syndication.feed.impl.ObjectBean; +import com.sun.syndication.feed.synd.SyndFeed; + +/** + *A class to represent a {@link com.sun.syndication.feed.synd.SyndFeed} + * and some useful information about it.
+ * + *This class is thread safe, as expected by the different feed fetcher + * implementations.
+ * + * @author Nick Lothian + */ +public class SyndFeedInfo implements Cloneable, Serializable { + private static final long serialVersionUID = -1874786860901426015L; + + private final ObjectBean _objBean; + private String id; + private URL url; + private Object lastModified; + private String eTag; + private SyndFeed syndFeed; + + public SyndFeedInfo() { + _objBean = new ObjectBean(this.getClass(),this); + } + + /** + * Creates a deep 'bean' clone of the object. + *+ * @return a clone of the object. + * @throws CloneNotSupportedException thrown if an element of the object cannot be cloned. + * + */ + public Object clone() throws CloneNotSupportedException { + return _objBean.clone(); + } + + /** + * Indicates whether some other object is "equal to" this one as defined by the Object equals() method. + *
+ * @param other he reference object with which to compare. + * @return true if 'this' object is equal to the 'other' object. + * + */ + public boolean equals(Object other) { + return _objBean.equals(other); + } + + /** + * Returns a hashcode value for the object. + *
+ * It follows the contract defined by the Object hashCode() method. + *
+ * @return the hashcode of the bean object. + * + */ + public int hashCode() { + return _objBean.hashCode(); + } + + /** + * Returns the String representation for the object. + *
+ * @return String representation for the object. + * + */ + public String toString() { + return _objBean.toString(); + } + + + /** + * @return the ETag the feed was last retrieved with + */ + public synchronized String getETag() { + return eTag; + } + + /** + * @return the last modified date for the feed + */ + public synchronized Object getLastModified() { + return lastModified; + } + + /** + * @return the URL the feed was served from + */ + public synchronized URL getUrl() { + return url; + } + + public synchronized void setETag(String string) { + eTag = string; + } + + public synchronized void setLastModified(Object o) { + lastModified = o; + } + + public synchronized void setUrl(URL url) { + this.url = url; + } + + public synchronized SyndFeed getSyndFeed() { + return syndFeed; + } + + public synchronized void setSyndFeed(SyndFeed feed) { + syndFeed = feed; + } + + /** + * @return A unique ID to identify the feed + */ + public synchronized String getId() { + return id; + } + + /** + * @param string A unique ID to identify the feed. Note that if the URL of the feed + * changes this will remain the same + */ + public synchronized void setId(String string) { + id = string; + } + +} diff --git a/src/java/com/sun/syndication/fetcher/samples/FeedAggregator.java b/src/java/com/sun/syndication/fetcher/samples/FeedAggregator.java new file mode 100644 index 0000000..05e160c --- /dev/null +++ b/src/java/com/sun/syndication/fetcher/samples/FeedAggregator.java @@ -0,0 +1,92 @@ +/* + * 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 com.sun.syndication.fetcher.samples; + +import java.io.PrintWriter; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import com.sun.syndication.feed.synd.SyndFeedImpl; +import com.sun.syndication.feed.synd.SyndFeed; +import com.sun.syndication.fetcher.FeedFetcher; +import com.sun.syndication.fetcher.impl.FeedFetcherCache; +import com.sun.syndication.fetcher.impl.HashMapFeedInfoCache; +import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher; +import com.sun.syndication.io.SyndFeedOutput; + +/** + *
It aggregates a list of RSS/Atom feeds (they can be of different types) + * into a single feed of the specified type.
+ * + *Converted from the original FeedAggregator sample
+ * + * @author Alejandro Abdelnur + * @author Nick Lothian + * + */ +public class FeedAggregator { + + public static void main(String[] args) { + boolean ok = false; + if (args.length>=2) { + try { + String outputType = args[0]; + + SyndFeed feed = new SyndFeedImpl(); + feed.setFeedType(outputType); + + feed.setTitle("Aggregated Feed"); + feed.setDescription("Anonymous Aggregated Feed"); + feed.setAuthor("anonymous"); + feed.setLink("http://www.anonymous.com"); + + List entries = new ArrayList(); + feed.setEntries(entries); + + FeedFetcherCache feedInfoCache = HashMapFeedInfoCache.getInstance(); + FeedFetcher feedFetcher = new HttpURLFeedFetcher(feedInfoCache); + + for (int i=1;i