The Problem
Earlier,
I wrote about having to build a solution using servlets to write out
the contents of the RSS feed for my blog. I am using JSF on this site.
There are solutions - like Pretty Faces - that work and provide a
lot of flexibility for mouting JSF logic at a given URL. So,
initially, my RSS and Atom feeds were being handled by a backing bean
in JSF. Being JSF, this necessitated the need for a session cookie
(even though, clearly, the RSS / Atom feeds are stateless.)
The cookie wouldn't have been so bad, except that - I think as a
consequence - RSS readers consuming the blog were being constantly
refreshed for the entire contents of the feed, because to them the
resultant blog requested was "different" than the previous version.
The Solution
So, I planned to write a servlet that
handled the requests and I expose that in my JSF application. This
would be a clear differentiator between the new Spring + JSF version
of the software running my blog and the old Spring MVC version: it's
uselessly difficult to support stateless endpoints with JSF, where as
it's as natural a goal as anything in Spring MVC.
Using a
servlet meant I'd lose two advantages I'd had in the JSF application
with Pretty Faces: bookmarkable URLs, and the convenient programming
environment that Spring Faces afforded me, namely, configuration,
dependency injection, etc.
Refinements
I decided to
use URLRewrite to solve
the boomkarkable URLs (yes, that means this site is hosting not one,
but two solutions for bookmarkable URLs in the same
.war!) and Spring's under appreciated
HttpRequestHandler.
HttpRequestHandler is an interface that, when
implemented, becomes serviceable by a Spring framework servlet. The
interface looks like:
package org.springframework.web;
public interface HttpRequestHandler {
void handleRequest (HttpServletRequest request,
HttpServletResponse response) throws
ServletException, IOException;
}
You then configure a Spring framework class
(org.springframework.web.context.support.HttpRequestHandlerServlet)
in your web.xml and name the servlet the same as the bean that
implements the HttpRequestHandler interface is named in
your Spring application context. By virtue of the fact that your
HttpRequestHandler bean is a Spring component, you get
all the advantages of using Spring or Spring MVC, without having to
load an entire framework.
For my code, the implementation looks something like the following:
package com.joshlong.blawg.site.view.servlets;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestHandler;
import com.joshlong.blawg.BlogService;
import com.joshlong.blawg.model.Blog;
import com.joshlong.blawg.site.view.util.RssFeedCreationUtils;
import com.joshlong.blawg.utils.LoggingUtils;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.SyndFeedOutput;
@Component ("feed")
public class BlogFeedServlet implements HttpRequestHandler {
static private String FEED_TYPE_VARIABLE = "feedType";
static private int START_FEED = 0, STOP_FEED = 500;
@Autowired
private RssFeedCreationUtils rssFeedCreationUtils;
@Autowired
private BlogService blogService;
@Autowired
private LoggingUtils loggingUtils;
private SyndFeed buildEntryList (HttpServletRequest req,
HttpServletResponse res, String type, int start, int length) {
SyndFeed feed = ... // build up the SyndFeed however you want
return feed;
}
private void showAllAtomBlogs (HttpServletRequest request,
HttpServletResponse response) throws Throwable {
SyndFeed feed = buildEntryList (request, response, "atom_0.3",
START_FEED, STOP_FEED);
response.setContentType ("text/xml");
SyndFeedOutput syndFeedOutput = new SyndFeedOutput ();
syndFeedOutput.output (feed, response.getWriter ());
}
private void showAllRssBlogs (HttpServletRequest request,
HttpServletResponse response) throws Throwable {
SyndFeed feed = buildEntryList (request, response, "rss_2.0",
START_FEED, STOP_FEED);
response.setContentType ("text/xml");
SyndFeedOutput syndFeedOutput = new SyndFeedOutput ();
syndFeedOutput.output (feed, response.getWriter ());
}
public void handleRequest (HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
try {
String feedType =
StringUtils.defaultString (request.getParameter (FEED_TYPE_VARIABLE));
if (StringUtils.isEmpty (feedType))
feedType = "rss";
if (feedType.equalsIgnoreCase ("rss"))
showAllRssBlogs (request, response);
else
showAllAtomBlogs (request, response);
} catch (Throwable th) {
loggingUtils.log (ExceptionUtils.getFullStackTrace (th));
}
}
}
Thus, it's simpler than a standard Spring MVC controller or a
struts controller (though admittedly lacking a lot of the frills -
this happens to be a simple use case.
I then configured the
web.xml for all of this:
<servlet>
<servlet-name>feed</servlet-name>
<servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>feed</servlet-name>
<url-pattern>/urlPatternForFeedServlet</url-pattern>
</servlet-mapping>
Note that we've used the name fo the bean (configured using bean
classpath scanning, so the name is in the @Component
stereotype at the top of the class - "feed".) for the
servlet name. Sure, it smells a bit like engineered coincadence, but
it works well.
From here, all that remained was configuring a
URLRewrite rule to map the servlet to the endpoints visible on the
site right now ("/jl/entries/blog.atom", and
"/jl/entries/blog.rss")
Hope this helps
somebody...