Parsing the PubSubHubbub Notifications

I’ve updated my prototype to use the excellent Rome feed parser library. Instead of dumping 20kB of ‘useful’ raw feed on you, it now formats the entries nicely.

I’ve hooked it up to deliver me real-time headlines from my Google Reader feed and from TechCrunch, both of which work flawlessly.

With all the building blocks I’ve strung together, this really wasn’t any work at all. All of the complexity lies in the cloud: Google’s AppEngine and XMPP implementation and the PubSubHubbub hub. The rest is done with a feed-parsing library.

Here’s the new code:

package com.grack.pubsubhubbub.xmpp;

import java.io.IOException;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.xmpp.JID;
import com.google.appengine.api.xmpp.MessageBuilder;
import com.google.appengine.api.xmpp.XMPPService;
import com.google.appengine.api.xmpp.XMPPServiceFactory;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.XmlReader;

@SuppressWarnings("serial")
public class Subscribe extends HttpServlet {
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		resp.setStatus(204);
		XMPPService xmpp = XMPPServiceFactory.getXMPPService();
		JID jid = new JID(req.getPathInfo().substring(1));

		SyndFeedInput input = new SyndFeedInput();
		SyndFeed feed;
		try {
			feed = input.build(new XmlReader(req.getInputStream()));
		} catch (IllegalArgumentException e) {
			throw new ServletException(e);
		} catch (FeedException e) {
			xmpp.sendMessage(new MessageBuilder().withBody(
					"Feed exception: " + e.toString()).withRecipientJids(jid)
					.build());
			throw new ServletException(e);
		}

		@SuppressWarnings("unchecked")
		List entries = feed.getEntries();

		StringBuilder message = new StringBuilder("Got update: \n");
		for (SyndEntry entry : entries) {
			message.append(entry.getTitle()).append(": ").append(
					entry.getLink()).append('\n');
		}
		xmpp.sendMessage(new MessageBuilder().withBody(message.toString())
				.withRecipientJids(jid).build());
	}

	public void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {
		resp.setStatus(200);
		resp.setContentType("text/plain");

		XMPPService xmpp = XMPPServiceFactory.getXMPPService();
		JID jid = new JID(req.getPathInfo().substring(1));

		if (req.getParameter("hub.mode").equals("subscribe"))
			xmpp.sendMessage(new MessageBuilder().withBody(
					"Subscribing to " + req.getParameter("hub.topic"))
					.withRecipientJids(jid).build());
		else
			xmpp.sendMessage(new MessageBuilder().withBody(
					"Unsubscribing from " + req.getParameter("hub.topic"))
					.withRecipientJids(jid).build());

		resp.getOutputStream().print(req.getParameter("hub.challenge"));
		resp.getOutputStream().flush();
	}
}

9 Responses to “Parsing the PubSubHubbub Notifications”

  1. mr. c. says:

    this looks like exactly what i’ve been looking for. however i’m struggling with a java.lang.NullPointerException on the call – req.getPathInfo().substring(1)

    could you give a bit more detail on how to use this for real newbies please?

  2. mr. c. says:

    awesome. this works a treat.

    had a couple of issues along the way, which i’ll repeat here in case it helps any newbies (hope that’s ok?).

    - had to change the line: List entries = feed.getEntries(); to: List entries = feed.getEntries();
    - needed to include jdom.jar, and xercres.jar(s) along with rome-1.0.jar.
    - changed the default JVM to 1.6.0 (leopard defaulted to 1.5.0 but xercres went bang).

    everything else worked as expected. many thanks for this post matt, couldn’t have done it without your help.

    i (hearts) pubsubhubbub

  3. mr. c. says:

    hmm the List entries line got eaten. i’ll try again:

    List entries = feed.getEntries(); to: List<SyndEntry> entries = feed.getEntries();

    am guessing that’s what happened to your post too?

  4. dani says:

    Hi,
    One question: I have included all the libraries, add them to the build path, it compiles fine. But on run, I get this error,
    Do you have any idea what might be causing it?

    java.lang.ClassNotFoundException: com.sun.syndication.io.XmlReader
    at java.net.URLClassLoader$1.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at com.google.appengine.tools.development.IsolatedAppClassLoader.loadClass(IsolatedAppClassLoader.java:151)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at java.lang.ClassLoader.loadClassInternal(Unknown Source)
    at java.lang.Class.getDeclaredConstructors0(Native Method)
    at java.lang.Class.privateGetDeclaredConstructors(Unknown Source)
    at java.lang.Class.getConstructor0(Unknown Source)
    at java.lang.Class.newInstance0(Unknown Source)
    at java.lang.Class.newInstance(Unknown Source)
    at org.mortbay.jetty.servlet.Holder.newInstance(Holder.java:153)
    at org.mortbay.jetty.servlet.ServletHolder.getServlet(ServletHolder.java:339)
    at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:463)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1093)
    at com.google.apphosting.utils.servlet.TransactionCleanupFilter.doFilter(TransactionCleanupFilter.java:43)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
    at com.google.appengine.tools.development.StaticFileFilter.doFilter(StaticFileFilter.java:121)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:360)
    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:712)
    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
    at com.google.apphosting.utils.jetty.DevAppEngineWebAppContext.handle(DevAppEngineWebAppContext.java:54)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:139)
    at com.google.appengine.tools.development.JettyContainerService$ApiProxyHandler.handle(JettyContainerService.java:342)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:139)
    at org.mortbay.jetty.Server.handle(Server.java:313)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:506)
    at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:830)
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:514)
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:211)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:381)
    at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:396)
    at org.mortbay.thread.BoundedThreadPool$PoolThread.run(BoundedThreadPool.java:442)

Leave a Reply

You must be logged in to post a comment.