Tapestry RSS released

  1. tapestry-rss
  2. ars-machina-project
  3. eloquentia
  4. syntax-highlight
  5. www

I've released Tapestry RSS, a simple package that implements an RSS 2.0 feed, in Apache Public License 2.0. Its sources are in https://github.com/thiagohp/tapestry-rss. The JAR is already in the Maven Central Repository. Here's the dependency description:

<dependency> 
     <groupId>br.com.arsmachina</groupId> 
     <artifactId>tapestry-rss</artifactId> 
     <version>0.0.1</version> 
</dependency>

Check Example section to see how this package is used in Eloquentia.

Why write Tapestry RSS when Tapestry5-Rome is available?

I've checked Tapestry5-Rome before deciding to write my own RSS feed package for Tapestry. I didn't want to reinvent the wheel, but:

  • Tapestry5-rome isn't in the Maven Central Repository, so it would make it hard for people wanting to use, customize and fork Eloquentia (my blog engine, which powers this very won blog).
  • Tapestry RSS has a dependency on Tapestry and nothing else, while Tapestry5-Rome has a dependency on Rome.
  • In Tapestry RSS, you don't need to care about how to include the link to the feed in the header: a mixin (rss/AddRssLink) already does that for you and it can be used in any component (including the Any one).
  • With Tapestry RSS, ou don't need to worry about creating a page or event to return the feed: tapestry-rss provides one out-of-the-box (/rss). You just need to implement a ChannelProvider, which receives the RSS page activation context and returns a Channel instance (class already included in tapestry-ioc), and contribute it to the ChannelProvider Tapestry-IoC service. This is how you'll provide the RSS channels and items for Tapestry-RSS.
  • RSS is actually a simple format and I knew I could implement it quickly.

Example: Tapestry RSS in Eloquentia

In Eloquentia, to date, there's only one RSS feed: posts by tag. The rendering of the list of posts with a given tag is done by the ViewPagesByTag component. Its template has a root element in which the Tapestry RSS's rss/AddLinkToRss mixin is applied:

<ol t:type="Any" t:mixins="rss/AddRssLink" t:context="['tag', tag.name]">...<ol>

Notice the rss/AddLinkToRss's context parameter: it's the page activation context that will be passed to the rss page, which in turn will pass the context to the ChannelProvider service. It will return a Channel instance to be used to build the RSS XML document. The page activation context is tag/xxx, where xxx is the tag name.

Here's the <link> tag generated by the rss/AddLinkToRss mixin in : <link href="http://machina.com.br/rss/tag/java" type="application/rss+xml" rel="alternate"/>

The next step is to implement a ChannelProvider:

package br.com.arsmachina.eloquentia.tapestry.rss;

/**
 * {@link ChannelProvider} implementation: tag as channel, pages as items.
 * 
 * @author Thiago H. de Paula Figueiredo (http://machina.com.br/thiago)
 */
public class TagChannelProvider implements ChannelProvider {
	
	final private TagController tagController;
	
	final private PageController pageController;
	
	final private PageRenderLinkSource pageRenderLinkSource;
	
	final private PageActivationContextService pageActivationContextService;
	
	/**
	 * Single constructor of this class.
	 * @param tagController a {@link TagController}.
	 * @param pageController a {@link PageController}.
	 * @param pageActivationContextService a {@link PageActivationContextService}.
	 * @param pageRenderLinkSource a {@link PageRenderLinkSource}.
	 */
	public TagChannelProvider(final TagController tagController, final PageController pageController,
			final PageRenderLinkSource pageRenderLinkSource,
			final PageActivationContextService pageActivationContextService) {
		assert pageController != null;
		assert tagController != null;
		assert pageRenderLinkSource != null;
		assert pageActivationContextService != null;
		this.tagController = tagController;
		this.pageController = pageController;
		this.pageRenderLinkSource = pageRenderLinkSource;
		this.pageActivationContextService = pageActivationContextService;
	}

	public Channel getChannel(EventContext context) {
		
		Channel channel = null;
		
		if (context.getCount() == 2 && "tag".equals(context.get(String.class, 0))) {
			final String tagName = context.get(String.class, 1);
			final Tag tag = tagController.findByName(tagName);
			if (tag != null) {
				final Link link = pageRenderLinkSource.createPageRenderLinkWithContext(Rss.class, "tag", tag.getName());
				channel = new Channel(tag.getTitle(), link.toAbsoluteURI(), tag.getSubtitle());
				final List<Page> pages = pageController.findByTag(tagName, 0, 10); // FIXME: let the maximum number of pages to be configurable
				Item item;
				for (Page page : pages) {
					item = new Item();
					item.setDescription(page.getTeaser());
					item.setLink(getLink(page));
					item.setPublicationDate(page.getPosted());
					item.setTitle(page.getTitle());
					channel.getItems().add(item);
				}
			}
		}
		
		return channel;
		
	}

	private String getLink(Page page) {
		final Object activationContext = pageActivationContextService.toActivationContext(page);
		Link link;
		if (activationContext instanceof List) {
			List list = (List) activationContext;
			link = pageRenderLinkSource.createPageRenderLinkWithContext(Index.class, list.toArray());
		}
		else {
			link = pageRenderLinkSource.createPageRenderLinkWithContext(Index.class, activationContext);
		}
		return link.toAbsoluteURI();
	}

}			

The final step is to contribute TagChannelProvider to the ChannelProvider service in AppModule (or any other Tapestry-IoC module class):

	/**
	 * Contributes to the {@link ChannelProvider} service.
	 * @param configuration an {@link OrderedConfiguration}.
	 */
	@Contribute(ChannelProvider.class)
	public static void addChannelProviders(OrderedConfiguration<ChannelProvider> configuration) {
		configuration.addInstance("Tag", TagChannelProvider.class);
	}
			
		

Comments: 0

You need to be logged in to post a comment.