Optional, But Enforced, Parameters

There’s bound to be a better term than optional, enforced parameters. My definition is that these are URL parameters that you don’t really care about except for SEO (search engine optimization) but if you have to have them, then they may as well be correct. They help make friendlier URLs.

If I want to get to a details page of an object, the ideal URL parameter is a single integer mapping to an id. It’s simple, clean, and fast. It’s the default URL mapping in Wordpress. However, a single id tells a search engine nothing about the page. It’s a wasted opportunity and it’s important to take advantage of it.

Let’s get concrete. A good URL for a game details page may be http://www.atomicgamer.com/games/34/Diablo-III. It contains the game id (34) for quick lookups, but also contains the game name for SEO. I don’t care about the Diablo III portion when fetching the data, but I do want it to remain consistent for links.

I took a look into doing this with both Tapestry 5 and Wicket. I’m fair from an expert in either of them, so there could be a better way to do this. This is simply the way I determined to do it.

Tapestry 5

First, I need to put a link to my details page somewhere.


<t:grid source="games" row="game">
  <t:parameter name="nameCell">
    <a t:type="pagelink" t:page="games" context="list:game.id,game.name">${game.name}</a>
  </t:parameter>
</t:grid>

Pretty straightforward. That’ll create a link formatted as /games/{id}/{name}. Tapestry does all that for me, and in fact, if I didn’t care about enforcing the game name to be correct, I’d be done. (I’m using the list binding prefix for a quick shorthand here.)

All the rest of the work is done in the game details page’s onActivate method. This method will be called whenever a person goes to the page. I’ll need to fetch the game from the id, and then compare the name parameter to the game’s actual name.


Object onActivate(Long id, String name)
{
  game = gameDAO.findById(id);
  if (game == null)
  {
    return pagelinkSource.createPageRenderLink(Index.class);
  }

  if (!game.getName().equals(name))
  {
    return pagelinkSource.createPageRenderLinkWithContext(this.getClass(), id, game.getName());
  }

  return null;
}

The interesting part is the second if statement. If the names don’t match, I’m actually going to redirect the user to the page with the correct name. This costs a bit in terms of performance, but it will force it to stay consistent. If you’re ambitious, you could include a flash message here and tell the user to update their bookmarks.

That’s about it for Tapestry.

Wicket

The Wicket code is eerily similar in concept. First, I build up my bookmarkable link with the two parameters.


BookmarkablePageLink link = new BookmarkablePageLink("detailsLink", DetailsPage.class);
link.setParameter("id", game.getId());
link.setParameter("name", game.getName());
link.add(new Label("name", game.getName()));
item.add(link);

And somewhere down the line, I use that link.


<a wicket:id="detailsLink"><span wicket:id="name">Game Name</span></a>

Now, Wicket supports clean URLs out of the box, but you have to configure it first or you’re going to get some really ugly URLs. To make my life easier in the long run, I installed the wicketstuff-annotation library. With this, I can just add a couple annotations to the top of my details page to get clean URLs.


@MountPath(path = "games")
@MountMixedParam(parameterNames = {"id", "name"})
public class DetailsPage extends WebPage
{
...
}

With a nice clean URL in hand (/games/{id}/{name}), we’re just a redirect away from being finished. Like Tapestry, I’ll fetch the game and redirect.

public DetailsPage(PageParameters parameters)
{
  super(parameters);

  Game game = getGame(parameters.getAsLong("id"));
  if (!game.getName().equals(parameters.getString("name")))
  {
    parameters.put("name", game.getName());
    setResponsePage(this.getClass(), parameters);
    setRedirect(true);
    return;
}
...
}

And that’s it. I’ll disclaim again that I’m neither a Tapestry or Wicket guru, and I would love someone to point out a better way to do this.

For now though, it’s minor performance cost, but grants me lovely clean, friendly URLs with parameters I don’t have to worry about. If I change the name of Diablo III to Diablo 3, users with the old URL will be redirect to the new URL. I don’t have to think about it and that’s perfect for me.