de.nava.informa.utils.FeedManagerEntry.java Source code

Java tutorial

Introduction

Here is the source code for de.nava.informa.utils.FeedManagerEntry.java

Source

//
// Informa -- RSS Library for Java
// Copyright (c) 2002 by Niko Schmuck
//
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//

package de.nava.informa.utils;

import de.nava.informa.core.*;
import de.nava.informa.impl.basic.Feed;
import de.nava.informa.parsers.FeedParser;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Date;

/**
 * Holder class for feeds held in the manager. The purpose of this class is to
 * store the last time we loaded the feed, and determine if the feed needs to be
 * reread. Whilst we use the data provided by the feed where possible, if this
 * is not present defaults will be used.
 * <p>
 * Its also important to note that we do oversimply things a bit. We ignore the
 * updateBase even if specified by the feed.
 * </p>
 *
 * @author Sam Newman
 * @see FeedManager
 */
public class FeedManagerEntry {

    public static final long MILLISECONDS_IN_HOUR = 3600000L;

    public static final long MILLISECONDS_IN_DAY = 86400000L;

    public static final long MILLISECONDS_IN_MONTH = 2419200000L;

    /**
     * Over simplification here - assuming a non-leap year
     */
    public static final long MILLISECONDS_IN_YEAR = 31536000000L;

    /* logger handler */
    private static Log logger = LogFactory.getLog(FeedManagerEntry.class);

    private ChannelUpdatePeriod defaultUpdatePeriod;

    private int defaultUpdateFrequency;

    /**
     * Stores the number of milliseconds since the last update after which the
     * feed is out of date
     */
    private long timeToExpire;

    /**
     * The channel we hold
     */
    private FeedIF feed;

    /**
     * The last time we updated a feed
     */
    private long lastUpdate;

    /**
     * The URI for the feed
     */
    private String feedUri;

    private ChannelBuilderIF channelBuilder;

    /**
     * the wantedTtl for the feed
     */
    private long wantedTtl = -1;

    /**
     * stores the values necessary to make conditional GET
     */
    private ConditionalGetValues httpHeaders = new ConditionalGetValues();

    /**
     * Creates a new FeedManagerEntry object.
     */
    public FeedManagerEntry(String feedUri, ChannelBuilderIF builder, ChannelUpdatePeriod defaultUpdatePeriod2,
            int defaultUpdateFrequency) throws FeedManagerException {
        this.feedUri = feedUri;
        this.channelBuilder = builder;
        this.defaultUpdatePeriod = defaultUpdatePeriod2;
        this.defaultUpdateFrequency = defaultUpdateFrequency;
        this.feed = retrieveFeed(feedUri);
        this.lastUpdate = System.currentTimeMillis();
    }

    public ChannelUpdatePeriod getDefaultUpdatePeriod() {
        return defaultUpdatePeriod;
    }

    public void setDefaultUpdatePeriod(ChannelUpdatePeriod defaultUpdatePeriod) {
        this.defaultUpdatePeriod = defaultUpdatePeriod;
    }

    public int getDefaultUpdateFrequency() {
        return defaultUpdateFrequency;
    }

    public void setDefaultUpdateFrequency(int defaultUpdateFrequency) {
        this.defaultUpdateFrequency = defaultUpdateFrequency;
    }

    /**
     * Loads the channel and sets up the time to expire
     *
     * @param uri The location for the rss file
     * @return The Channel
     * @throws FeedManagerException If the feed specified by <code>uri</code> is invalid
     */
    private FeedIF retrieveFeed(String uri) throws FeedManagerException {
        try {
            URL urlToRetrieve = new URL(uri);

            URLConnection conn = null;
            try {
                conn = urlToRetrieve.openConnection();

                if (conn instanceof HttpURLConnection) {

                    HttpURLConnection httpConn = (HttpURLConnection) conn;

                    httpConn.setInstanceFollowRedirects(true); // not needed, default ?

                    //    Hack for User-Agent : problem for
                    // http://www.diveintomark.org/xml/rss.xml
                    HttpHeaderUtils.setUserAgent(httpConn, "Informa Java API");

                    logger.debug("retr feed at url " + uri + ": ETag" + HttpHeaderUtils.getETagValue(httpConn)
                            + " if-modified :" + HttpHeaderUtils.getLastModified(httpConn));

                    // get initial values for cond. GET in updateChannel
                    this.httpHeaders.setETag(HttpHeaderUtils.getETagValue(httpConn));
                    this.httpHeaders.setIfModifiedSince(HttpHeaderUtils.getLastModified(httpConn));
                }
            } catch (java.lang.ClassCastException e) {
                logger.warn("problem cast to HttpURLConnection " + uri, e);
                throw new FeedManagerException(e);
            } catch (NullPointerException e) {
                logger.error("problem NPE " + uri + " conn=" + conn, e);
                throw new FeedManagerException(e);
            }

            ChannelIF channel = FeedParser.parse(getChannelBuilder(), conn.getInputStream());
            this.timeToExpire = getTimeToExpire(channel);
            this.feed = new Feed(channel);

            Date currDate = new Date();
            this.feed.setLastUpdated(currDate);
            this.feed.setDateFound(currDate);
            this.feed.setLocation(urlToRetrieve);
            logger.info("feed retrieved " + uri);

        } catch (IOException e) {
            logger.error("IOException " + feedUri + " e=" + e);
            e.printStackTrace();
            throw new FeedManagerException(e);
        } catch (ParseException e) {
            e.printStackTrace();
            throw new FeedManagerException(e);
        }

        return this.feed;
    }

    /**
     * Updates the channel associated with this feed use conditional get stuff.
     * http://fishbowl.pastiche.org/2002/10/21/http_conditional_get_for_rss_hackers
     *
     * @throws FeedManagerException
     */
    private synchronized void updateChannel() throws FeedManagerException {
        try {
            String feedUrl = this.feed.getLocation().toString();

            URL aURL = new URL(feedUrl);
            URLConnection conn = aURL.openConnection();

            if (conn instanceof HttpURLConnection) {
                HttpURLConnection httpConn = (HttpURLConnection) conn;

                httpConn.setInstanceFollowRedirects(true);
                //    Hack for User-Agent : problem for
                // http://www.diveintomark.org/xml/rss.xml
                HttpHeaderUtils.setUserAgent(httpConn, "Informa Java API");
                HttpHeaderUtils.setETagValue(httpConn, this.httpHeaders.getETag());
                HttpHeaderUtils.setIfModifiedSince(httpConn, this.httpHeaders.getIfModifiedSince());
                httpConn.connect();
                if (httpConn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {

                    logger.info("cond. GET for feed at url " + feedUrl + ": no change");
                    this.feed.setLastUpdated(new Date());
                    // TODO : add a property in FeedIF interface for lastGet ?
                    this.lastUpdate = System.currentTimeMillis();
                    return;
                }
                logger.info("cond. GET for feed at url " + feedUrl + ": changed");
                logger.debug(
                        "feed at url " + feedUrl + " new values : ETag" + HttpHeaderUtils.getETagValue(httpConn)
                                + " if-modified :" + HttpHeaderUtils.getLastModified(httpConn));

                this.httpHeaders.setETag(HttpHeaderUtils.getETagValue(httpConn));
                this.httpHeaders.setIfModifiedSince(HttpHeaderUtils.getLastModified(httpConn));
            }

            ChannelIF channel;
            if (conn == null) {
                channel = FeedParser.parse(getChannelBuilder(), feedUrl);
            } else {
                channel = FeedParser.parse(getChannelBuilder(), conn.getInputStream());
            }

            this.feed.setChannel(channel);
            this.feed.setLastUpdated(new Date());
            this.lastUpdate = System.currentTimeMillis();
            logger.info("feed updated " + feedUrl);
        } catch (IOException | ParseException e) {
            throw new FeedManagerException(e);
        }
    }

    /**
     * Checks to see if the feed is out of date - if it is the feed is reloaded
     * from the URI, otherwise the cached version is returned.
     *
     * @return The up-to-date feed
     * @throws FeedManagerException
     */
    public FeedIF getFeed() throws FeedManagerException {
        if (isOutOfDate()) {
            updateChannel();
        }
        return this.feed;
    }

    public void setWantedTtl(long ms) {
        this.wantedTtl = ms;
        //recalculate the timeToExpire
        this.timeToExpire = this.getTimeToExpire(this.feed.getChannel());
    }

    /**
     * Based on the update period and update frequceny and on the optional
     * wantedTtl for the feed, calculate how many milliseconds after the
     * <code>lastUpdate</code> before this feed is considered out of date
     *
     * @return The number of milliseconds before we can consider the feed invalid
     * @throws IllegalArgumentException
     */
    private long getTimeToExpire(ChannelIF channel) {
        return (new CacheSettings()).getTtl(channel, this.wantedTtl);
    }

    /**
     * Determines if the feed is out of date.
     *
     * @return false if the feed is up to date, else true
     */
    private boolean isOutOfDate() {
        boolean outOfDate = false;
        logger.info(this + " isOutOfDate " + this.feedUri + " last Update: " + lastUpdate + ", tte=" + timeToExpire
                + "<?" + (System.currentTimeMillis() - lastUpdate));
        if ((lastUpdate + timeToExpire) < System.currentTimeMillis()) {
            outOfDate = true;
        }
        return outOfDate;
    }

    private ChannelBuilderIF getChannelBuilder() {
        return channelBuilder;
    }

}