Clean and usable URLs in Stripes

Cool URIs don’t change wrote Tim Berners-Lee more than a decade ago, URLs should be clean, usable and persistent. However, the biggest percentage of URLs around the web still are not clean and usable and even less persistent. There are lots of reasons that URLs are not persistent and most of the times it is not in the hands of the developer or webmasters, under certain circumstances some domains may not continue to be alive or they may change hands, anyway there isn’t much a framework can do about that. But for the URLs to be clean and usable is totally in the hands of the developers and webmasters. Unfortunately most web frameworks makes things even worse by not providing the mechanism to handle URLs or by making it really difficult to change the default behavior.

Stripes is not one of those frameworks and it really makes it easy to create clean URLs and provides a very flexible mechanism for handling them.

The simplest way to do it is using the @UrlBinding annotation. Let’s assume we have the following action bean:


package com.project.action;

import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.DefaultHandler;
import net.sourceforge.stripes.action.ForwardResolution;
import net.sourceforge.stripes.action.Resolution;

/**
 * @author omer haderi
 * @version 1.0
 */
public class SimpleMathActionBean implements ActionBean {
	private static final String view = "/WEB-INF/jsp/index.jsp";

	private ActionBeanContext ctx;

	private double number;

	public ActionBeanContext getContext() {
		return ctx;
	}

	public void setContext(ActionBeanContext ctx) {
		this.ctx = ctx;
	}

	@DefaultHandler
	public Resolution view() {
		return new ForwardResolution(view);
	}

	public Resolution sqrt() {
		number = Math.sqrt(getNumber());
		return new ForwardResolution(view);
	}

	public double getNumber() {
		return number;
	}

	public void setNumber(double number) {
		this.number = number;
	}
}

The URL for this action bean to calculate the square root (assuming the default behavior) will be:
http://www.domain.com/SimpleMath.action?sqrt=&number=10

To simplify this we just need to mark the bean with the following annotation:
@UrlBinding(“/public/simple_math”)
after this the URL will be:
http://www.domain.com/public/simple_math?sqrt=&number=10

Of course the point is to remove the question mark, the ampersand and the equals sign. In the previews URL the [sqrt] is an event of the action bean as you can see from the action bean class. There is a special notation to identify the event in the UrlBinding which is {$event}, so we need to modify the annotation as follows:
@UrlBinding(“/public/simple_math/{$event}”)
after this the URL will be:
http://www.domain.com/public/simple_math/sqrt=&number=10

Let’s move on and remove the rest of symbols. In our URL the [number] is a parameter and just like the events it can be embedded in the UrlBinding as follows:
@UrlBinding(“/public/simple_math/{$event}/{number}”) and it can even have a default value
@UrlBinding(“/public/simple_math/{$event}/{number=5}”)
after this the URL will be:
http://www.domain.com/public/simple_math/sqrt/10/

That’s it just one annotation and the URL is simple, more readable and makes more sense.

This is how will the action bean class look like after marking it with the annotation that we just constructed:


/**
 * @author omer haderi
 * @version 1.0
 */
@UrlBinding("/public/simple_math/{$event}/{number=5}")
public class SimpleMathActionBean implements ActionBean {
...
}

Here are the two URLs:
before: http://www.domain.com/public/simple_math?sqrt=&number=10
after   : http://www.domain.com/public/simple_math/sqrt/10/
as you can see the later is much better.

You may be wondering what’s that /public over there!?
That’s the url-mapping in the servlet-mapping of the DispatcherServlet in the web.xml which can be anything that makes sence for your project:

<servlet-mapping>
	<servlet-name>DispatcherServlet</servlet-name>
	<url-pattern>/public/*</url-pattern>
</servlet-mapping>

When using UrlBinding annotation only the URL of those action beans that are marked by the annotation will change, the rest of the actions will still have the default URL binding as explained in the “URL Binding in Stripes“.

To change the default behavior to all Action Beans we would need to create a subclass of NameBaseActionResolver and override its methods:


package com.project.extensions;
/**
 * @author omer haderi
 * @version 1.0
 */
public class CustomActionResolver extends NameBasedActionResolver {
        // this is the url-pattern from the DispatcherServlet servlet-mapping in the web.xml
	private static final String URL_PATTERN = "/public";

	@Override
	protected String getBindingSuffix() {
		return "";
	}

	@Override
	protected String getUrlBinding(String actionBeanName) {
		String result = super.getUrlBinding(actionBeanName);
		result = separeteCamelCaseWithUnderscore(result).toLowerCase();

		return URL_PATTERN + result;
	}

	private static String separeteCamelCaseWithUnderscore(String s) {
		String pattern = "(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[^A-Za-z])";
		return s.replaceAll(pattern, "_");
	}
}

You need to add the extension package in the filter parameters in the web.xml see “URL Binding in Stripes” for more information.

From now on all the action beans will have this as default URL binding.

Advertisements

The Stripes framework

Choosing a java web framework is somehow a complex process, not because they are difficult to evaluate but because there are so many of them (in fact it seems that there are more java frameworks than java developers :) but having many frameworks is a good thing. It means that there is a lot of competition and the competition seems to be healthy since most of the frameworks are open source, so competition is based on quality and features instead of marketing and lobbing.

Different people have different criteria on selecting a framework and probably different needs, so I will list my criteria an the reasoning behind them for choosing Stripes.
(Disclaimer: this expresses only my views on what I expect from a framework, I don’t think there are good or bad frameworks, from my point of view there are only convenient frameworks – just trying to avoid flame war here, although flame wars entertains me a lot!).

I would like to start with what I consider the most annoying thing when working with a Java framework which is not other than XML configuration files. Having worked with JSF and Struts where you need to configure all your managed-beans/action-beans and pretty much everything else into an xml configuration file, which during development becomes a mess to keep it in sync, I enjoy that Stripes removes all this configuration hassle with autoloading feature. The only xml configuration file that you need is the web.xml but that is required by the Servlet specification. Configuration by exception (a.k.a convention over configuration) is the way to go, developers should focus on business solutions and not in framework configuration details that will just disturb the flow of thinking. Hopefully other framework will follow – or maybe some already does.

Testing is the most important part of development and therefore frameworks should come with a build-in support for testing to make this process easy. So Stripes comes with a set of mock objects that help developers to easily write unit tests.

Easy extension and customization. Stripes is designed in modular form it is quite easy to add a customization plugin to change the behavior of any part of the framework, because of this modular design the extensibility of Stripes is trivial. Actually This is one of the areas that the framework really make the difference.

I don’t think that there’s a need to explain why internationalization of web application is important, all frameworks need to have a built-in support for i18n. Stripes tags comes with a default bundle lookup strategy so that i18n is just a mutter of adding key/value pairs in the resource bundle.

Never trust the user input, therefore a strong and flexible validation mechanism is needed. Stripes provides a powerful validation mechanism which is based on annotations (remember there is no XML configuration).

When working with request parameters the most annoying process is converting Strings to objects and vise versa, the Stripes support for this process is just amazing. But it is not just conversion there is also support for formating an object to string and that’s as easy as the conversion process.

Stripes comes with a built in support for layouts, with three tags you can achieve pretty much everything. If the built in support for templating is not enough you can use freemarker.

Since we are talking about web framework and web applications it would be a pity to not take advantage of Ajax. Any Ajax framework can be used due to Stripes simple and transparent request/response nature.

These are some of the features that I like to see in a framework and surely you can find this features in most frameworks and probably more but the difference is that with Stripes is easy and development is fun again as the book of Frederic Daoud suggests and proves it.

If you want to learn Stripes I suggest the “Stripes … and Java web development is fun again” from The Pragmatic Programmers, in fact this is the only book about Stripes but even if there were more books I am totally sure that I would suggest this book again, it is very well written and it covers everything about Stripes.

The following is a quick list of Stripes features, which I will try to explain and give an example in future post for each one of them and link them back to this page:

URL Binding
Built in Validation
Custom Validation
Type Conversion
Formatting
Layouts
Using Freemarker
Localization
Exception Handling
Interceptors
URL Customization
Ajax Integration
Framework Extension
Framework Customization
Testing
Dependency Injection (Giuce/Spring)
JPA Integration
Security and Encryption
Cache-ing

The examples will be deployed on google app engine.

P.S: Now that the jsr 330 is approved maybe the next version of Stripes will come with build in Dependency Injection.

If you would like to start playing with Stripes and google app engine have a look at the following links:
https://110j.wordpress.com/2009/09/19/getting-started-with-stripes-google-app-engine/
https://110j.wordpress.com/2009/09/20/redoing-guestbook-example-with-stripes/

Stripes resources:
Stripes Home Page
Stripes … and Java development is fun again

Redoing Guestbook example with Stripes

Google has created an example on how to use google app engine with java. In this example they demonstrate the basic usage of Servlet API and JDO, it’s a simple example that stores, retrieves and renders entities in google app engine.
After the first tutorial on how to deploy Stripes in google app engine I decided to re-write this example using the Stripes framework of course.

Download the source code!

Steps:

1. Create a project and name it Guestbook

2. Follow the Getting started with Stripes and google app engine to configure Stripes

3. Add the JSTL jars (standard.jar and jstl.jar) in the lib directory

4. Create an ActionBean that will handle the greeting requests


package com.guestbook.action;

import java.util.Date;
import java.util.List;

import javax.jdo.PersistenceManager;

import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.DefaultHandler;
import net.sourceforge.stripes.action.ForwardResolution;
import net.sourceforge.stripes.action.Resolution;

import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.guestbook.manager.PMF;
import com.guestbook.model.Greeting;

/**
 * @author 110j
 */
public class GreetingActionBean implements ActionBean {
	private static final String VIEW = "/guestbook.jsp";
	private ActionBeanContext ctx;
	private UserService userService = UserServiceFactory.getUserService();
	private Greeting greeting;

	@DefaultHandler
	public Resolution welcome() {
		return new ForwardResolution(VIEW);
	}

	public Resolution addGreeting() {
		if (greeting != null) {
			greeting.setAuthor(getUser());
			greeting.setDate(new Date());

			PersistenceManager pm = PMF.get().getPersistenceManager();
	        try {
	            pm.makePersistent(greeting);
	        } finally {
	            pm.close();
	        }
		}
		return new ForwardResolution(VIEW);
	}

	public Resolution signin() {
		ForwardResolution fd = new ForwardResolution(VIEW);
		if (!userService.isUserLoggedIn())
			fd = new ForwardResolution(userService.createLoginURL("/Greeting.action"));
		return fd;
	}

	public Resolution signout() {
		ForwardResolution ForwardResolution ">fd = new ForwardResolution(VIEW);
		if (userService.isUserLoggedIn())
			fd = new ForwardResolution(userService.createLogoutURL("/Greeting.action"));
		return fd;
	}

	@SuppressWarnings("unchecked")
	public List getGreetings() {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		String query = "select from " + Greeting.class.getName() + " order by date desc range 0,5";
	    return (List) pm.newQuery(query).execute();
	}

	public Greeting getGreeting() {
		return greeting;
	}

	public void setGreeting(Greeting greeting) {
		this.greeting = greeting;
	}

	public User getUser() {
		return userService.getCurrentUser();
	}

	public ActionBeanContext getContext() {
		return this.ctx;
	}

	public void setContext(ActionBeanContext ctx) {
		this.ctx = ctx;
	}
}

5. Create the model class called Greeting (as described in the google example, no changes here!)


package com.guestbook.model;

import java.util.Date;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.users.User;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Greeting {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

    @Persistent
    private User author;

    @Persistent
    private String content;

    @Persistent
    private Date date;

    public Greeting() {}

    public Greeting(User author, String content, Date date) {
        this.author = author;
        this.content = content;
        this.date = date;
    }

    public Long getId() {
        return id;
    }

    public User getAuthor() {
        return author;
    }

    public String getContent() {
        return content;
    }

    public Date getDate() {
        return date;
    }

    public void setAuthor(User author) {
        this.author = author;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public void setDate(Date date) {
        this.date = date;
    }
}

6. Create the utility class that will instantiate the EntityManager (no changes here also!)

package com.guestbook.manager;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;

public final class PMF {
    private static final PersistenceManagerFactory pmfInstance =
        JDOHelper.getPersistenceManagerFactory("transactions-optional");

    private PMF() {}

    public static PersistenceManagerFactory get() {
        return pmfInstance;
    }
}

7. Modify the guestbook.jsp, there are a lot of changes here since in their example google uses scriptlets, I have replaced the scriptlets with jstl tags and Stripes tags, all the logic has moved to the ActionBean.

8. Modify the welcome file list in the web.xml and set as welcome file the Greeting.action

Download the source code!