Feed Reading with Oddjob and Rome

Here’s a nice little demo of the kind of task automation Oddjob is great at – Sending me an email when it’s going to rain!

For this I will need a weather feed and something to read it with. Being British the weather feed I’ll use is the BBC’s weather service at news.bbc.co.uk/weather/ and being a Java programmer the something to read it will be Rome.

Here’s the feed reading code:

public class ReadFeedJob implements Callable<Integer>, Serializable {
    private static final long serialVersionUID = 2012012200L;

    private static final Logger logger = Logger.getLogger(ReadFeedJob.class);

    private String name;

    private URL url;

    private Date lastPublished;

    private String title;

    private Collection<SyndEntry> entries;

    @Override
    @SuppressWarnings("unchecked")
    public Integer call() throws FeedException, IOException {

        logger.info("Reading feed from " + url);

        SyndFeedInput input = new SyndFeedInput();
        SyndFeed feed = input.build(new XmlReader(url));

        if (logger.isDebugEnabled()) {
            logger.debug(feed);
        }

        title = feed.getTitle();

        Date newlastPublished = feed.getPublishedDate();

        entries = new ArrayList<SyndEntry>();

        if (lastPublished == null ||
                newlastPublished.after(lastPublished)) {

            int oldEntries = 0;

            for (SyndEntry entry : (Collection<SyndEntry>) feed.getEntries()) {

                Date entryLastPublished = entry.getPublishedDate();

                if (lastPublished == null ||
                        entryLastPublished.after(lastPublished)) {
                    entries.add(entry);
                }
                else {
                    ++oldEntries;
                }
            }

            lastPublished = newlastPublished;

            logger.info("Read " + entries.size() + " new entries from " + title + " last published at "
                    + lastPublished + " (ignored " + oldEntries + " old entries)");
        }
        else {
            logger.info("Already read " + title + " last published at "
                    + lastPublished);
        }

        if (entries.size() == 0) {
            return 1;
        }
        else {
            return 0;
        }
    }

    @Override
    public String toString() {
        if (name == null) {
            return getClass().getSimpleName();
        }
        else {
            return name;
        }
    }
}

(Getter and Setters have been removed for brevity)

The two important lines are highlighted. The other lines attempt to work out if there is anything new in the feed, and log interesting facts.

Here’s what it looks like when I fire it up with Oddjob Explorer:

Oddjob Feed Reading

I needed some configuration to tell Oddjob about my FeedReaderJob. I called it FirstRun.xml and it looked like this.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<bean class="org.oddjob.rome.ReadFeedJob" name="Read Weather Feed">
    <url>
        <value value="http://newsrss.bbc.co.uk/weather/forecast/2574/Next3DaysRSS.xml"/>
    </url>
</bean>

I also needed to tell Oddjob about my class path. There are many ways to do this – using the command line or placing jars where Oddjob can find them (see Oddjob’s Developer Guide for more information on this) however as I’d like my project to be nice and separate, I’m going to use Oddjob to launch itself with a new class path. It goes like this:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<oddjob id="this">
    <job>
        <oddjob file="${this.dir}/FirstRun.xml">
            <classLoader>
                <url-class-loader>
                    <files>
                        <files>
                            <list>
                                <file file="${this.dir}/classes"/>
                                <files files="${this.dir}/lib/*.jar"/>
                            </list>
                        </files>
                    </files>
                </url-class-loader>
            </classLoader>
        </oddjob>
    </job>
</oddjob>

Now for the rainy bit. My FeedReaderJob exposes the FeedItems using a Java Property. I can now add some simple JavaScript to parse this list for the word rain. If it finds any it builds a message.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<script id="script" language="JavaScript" resultForState="true" resultVariable="result">
    <input>
        <buffer><![CDATA[entryArray = entries.toArray();
var rainyDays = new Array();

for (var i= 0; i < entryArray.length; ++i) {
  var entry = entryArray[i];
  var title = entry.title;
  var match = title.match(/^(\w+)\W.*rain/i);
  if (match != null) {
    rainyDays.push(match[1]);
  }
}

if (rainyDays.length > 0) {
  var message = "It's going to rain " + rainyDays;
  var result=0;
}
else {
  var result=1;
}
]]></buffer>
    </input>
    <beans>
        <value key="entries" value="${feed.entries}"/>
    </beans>
</script>

The result property is used by Oddjob to set the state of the job which controls whether or not Oddjob continues on to send a mail.

Sending a mail is like this:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<mail:send from="${my.alert.from}" host="${my.mailhost}" subject="Rain Alert" to="${my.alert.to}" xmlns:mail="http://rgordon.co.uk/oddjob/mail"><![CDATA[${feed.title}

${script.variables(message)}]]></mail:send>

I have an Oddjob Service already set up so I just needed to add an Oddjob to the server configuration to link in my new Feed Reader configuration. I also add a Timer to run the jobs once an hour. I can do this all from the client, without the need to stop and start the service. When I’m done it looks like this:

Oddjob Rain Alert

And when it’s going to rain I get a mail:

From: oddjob@rgordon.co.uk 
Sent: Friday, January 27, 2012 10:12 AM 
To: contact@rgordon.co.uk 
Subject: Rain Alert 

BBC - Weather Centre - Forecast for Manchester, United Kingdom

It's going to rain Friday

Now, thanks to Oddjob, I never forget my Umbrella!

(All my source and config can be found in the Oddjob Oddments directory on Sourceforge).

Comments are closed.