Hot topics
eBook shop
PDF edition of articles
Window id concept
An approach to leverage the use of the HTTP session
Web Framework Test and Analysis
Article series on web technologies with detailed reviews.
Hibernate eBook
A continuously updated book on Hibernate and Java Persistence
Recent Posts » Feed
- JSF 2 can finally be used in cluster environments » 23 Aug 2010
- Imagine the web improves on 11.11.11 » 15 Jul 2010
- Hibernate Training Update » 20 Apr 2010
- Blogging like a developer » 24 Feb 2010
- Window Id - Practical use cases » 18 Jan 2010
- GWT tip - better exception logging on the server » 18 Dec 2009
- Oh JavaFX, Oh JavaFX - why don't you progress? » 01 Dec 2009
The Web Framework Evaluation - Testing Stripes Framework
In this article series, we are going to explore web frameworks from a Java point of view. It covers Java based frameworks and frameworks based on scripting languages that can run inside of a Java application server. The latter are for example Ruby, Python, PHP, Groovy based frameworks. This article is taken from my eBook 'The Web Framework Evaluation'.
You can get the eBook at http://www.laliluna.de/shop.
Difference between the free article and the eBook in PDF format.
- eBook is printable
- eBook includes the upcoming detailed framework evaluations about 1-2 months earlier
- eBook includes a number of performance and load tests showing rendering performance and memory consumption under load.
Introduction
Stripes is a Java based web framework. It is pretty light and has a very short list of dependencies. It is an action request oriented framework, making use of annotations and conventions for configuration.
We have tested version 1.5.1 which is released under the Apache License Version 2.0. The framework develops slow or is pretty stable, depending on how you want to describe it. There is a final release about every 6 to 9 months. The test took place in March 2009.
Website http://www.stripesframework.org/
Hello world
The hello world application intends to show how complex it is to setup a simple application. It renders an index page with a link. Following a link calls an action of the framework and stores a localized message. The user is send to a view displaying this message.
You can download the sample project helloworld at
http://www.laliluna.de/download/framework-evaluation-samples.zip
Required steps
Create a new web application.
Copy the following libraries of the Stripes download to the WEB-INF/lib folder.
commons-logging.jar
stripes.jar
cos.jar (optional, can be used for file upload)
Modify the web.xml
Stripes is integrated using a servlet filter. The most important setting is the ActionResolver.Packages. It defines where Stripes is going to look for so called ActionBean classes. These classes are called, if you send a request to the web application.
<display-name>helloworld</display-name>
<filter>
<display-name>Stripes Filter</display-name>
<filter-name>StripesFilter</filter-name>
<filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class>
<init-param>
<param-name>ActionResolver.Packages</param-name>
<param-value>de.laliluna.examples.www</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>StripesFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>StripesFilter</filter-name>
<servlet-name>StripesDispatcher</servlet-name>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<servlet>
<servlet-name>StripesDispatcher</servlet-name>
<servlet-class>net.sourceforge.stripes.controller.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>StripesDispatcher</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
Create a resource file
The resource bundle for messages is stored by default in the file StripesResources.properties in the class root folder. In order to have support for common error messages, copy the StripesResources.properties from the Stripes download.
Add the message for hello world.
hello.world=Hello world
Create an action class
All action classes need to implement the ActionBean interface to allow Stripes to inject the context.
Stripes uses a convention to map urls to ActionBean classes. If you use a package name like
de.foo.bar.HelloWorld, the URL to execute the class is /de/foo/bar/HelloWorld.action. If you add one of www, web, stripes or action to the path, the path starts from there.
Use de.foo.bar.www.HelloWorld to get a mapped URL like /HelloWorld.action.
@DefaultHandler defines which method is called by default.
package de.laliluna.examples.www;
import net.sourceforge.stripes.action.*;
public class HelloWorld implements ActionBean{
private ActionBeanContext context;
@DefaultHandler
public Resolution sayHello(){
context.getMessages().add(new LocalizableMessage("hello.world"));
return new ForwardResolution("/hello.jsp");
}
public ActionBeanContext getContext() {
return context;
}
public void setContext(ActionBeanContext context) {
this.context = context;
}
}
The index.jsp looks like
<%@ taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld"%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Simple jsp page</title></head>
<body>
<stripes:link href="/HelloWorld.action">Go to hello world</stripes:link>
</body>
</html>
The hello.jsp looks like
<%@ taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld"%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Simple jsp page</title></head>
<body>
<h2>Message</h2>
<stripes:messages />
</body>
</html>
That's it.
Architecture
Framework concepts
Core element of Stripes are the ActionBean interface and event methods. A class implementing this interface is executed if a corresponding request (URL) is send.
http://domain/myapp/HelloWorld.action
calls the default event method of the ActionBean
public class HelloWorld implements ActionBean{
private ActionBeanContext context;
@Validate(required = true, minlength = 2)
private String inputField;
public String getInputField() {
return inputField;
}
public void setInputField(String inputField) {
this.inputField = inputField;
}
@DefaultHandler
public Resolution sayHello(){
// do something
return new ForwardResolution("/hello.jsp");
}
If the request specifies an event, then Stripes will call a public method with the same name.
http://domain/myapp/HelloWorld.action?save
calls the save method in the corresponding ActionBean. You can overwrite the default event name for a method or specify that a method is executed for multiple events.
The ActionBean is at the same time the model. Stripes writes input values into this class using public setters and executes validations configured as annotations.
The request processing is handled by a servlet filter and a servlet. The filter provides access to the Stripes configuration and picks the language of the user. The request is wrapped in a Stripes request and the language is stored in that request as java.util.Locale.
The servlet makes use of the chain of responsibilities pattern to handle a request. The default chain consists of the following steps
-
Create a context (ActionBeanContext) which stores a number of information. Every step in the chain adds information.
-
Find a class (ActionBean) to be executed for the given URL
-
Find the method (event handler) to be executed for the request. The event handler can be specified by the URL or request params.
-
Convert, validate and store input values into properties of the ActionBean class
-
Execute custom validators or a custom validator method in the ActionBean class
-
Execute the event method of the ActionBean, which will return the page to render or an URL to redirect to
-
Dispatch the rendering or the redirect

Every step can stop the continuation by returning a so called Resolution. A Resolution can be a page for rendering or an URL for redirecting. If the DispatcherServlet receives a resolution from a chain it will execute it.
Extension points
While it is not possible to define a custom chain, there are a number of extension points. Firstly the class implementing the ActionBeanContext can be replaced. It is a useful place to store additional information like the current user instead of getting this information from the HTTPSession or a ThreadLocal.
Then the configuration can be customized using the web.xml. A configuration class is executed during startup. It loads a number of factories to create the ActionBeanContext, input converter, output formatters, do exception handling and many others. You can configure an alternative factory using the web.xml
In addition interceptors can be defined. Interceptors allow to intercept every element in the chain. Basically, you provide a class, implement and interface, add annotations and configure it in the web.xml.
@Intercepts(LifecycleStage.HandlerResolution)
public class SecurityInterceptor implements Interceptor{
public Resolution intercept(ExecutionContext executionContext) throws Exception {
// do something before
Resolution resolution = executionContext.proceed();
// do something after
return resolution;
}
Global interceptors are a good place to integrate security or your preferred AOP framework.
Finally, you can add interceptors per class. A use case is to provide information in the request, you always need during rendering.
// storing countries we always need in the JSP
@After(stages = LifecycleStage.ActionBeanResolution)
public void prepareRequest() {
List countries = Arrays.asList(new Country("de", "Germany"),
new Country("uk", "United Kingdom"));
context.getRequest().setAttribute("countries", countries);
}
How to integrate a business layer?
The classic option is to call your business logic from the event method of the ActionBean. In that case you might call a factory to create the business logic or just instantiate the business logic class.
@DefaultHandler
public Resolution sayHello(){
HelloService service = BusinessFactory.createHelloService();
String message = service.createHelloMessage();
context.getMessages().add(new Message(message));
return new ForwardResolution("/hello.jsp");
}
Alternatively you could replace the BusinessFactory with a call to your preferred AOP container (Guice, Pico, Spring, ...) to return the service.
A more beautiful approach is to use an interceptor and inject (Inversion of Control) the business logic class into your ActionBean.
There are already plugins following this approach and allowing to integrate EJB3 and Spring. To illustrate the approach, I would like to show you my implementation using Picocontainer.
Picocontainer is an Inversion of Control Container. You can add classes to the container to later pick up properly wired instances.
MutablePicoContainer container = new DefaultPicoContainer();
container.addComponent(PicoIntegration.class);
container.addComponent(new SampleServiceImpl()); // add as singleton
Picocontainer supports a lot of different approaches to wire dependencies, one is constructor injection. It uses the parameter of the constructor to inject dependencies.
The constructor of class PicoIntegration needs an implementation of SampleService, therefor Pico will look for an implementation of the SampleService interface in the container and pass it to the constructor.
public class PicoIntegration implements ActionBean {
private SampleService sampleService;
public PicoIntegration(SampleService sampleService) {
this.sampleService = sampleService;
}
When getting a class from the container, you will get a properly wired instance. Picocontainer creates a new instance of PicoIntegration and injects the SampleServiceImp before returning it to you.
PicoIntegration sample = container.getComponent(PicoIntegration.class);
The final step is to integrate Picocontainer with Stripes. We will use a Stripes extension package. It is configured as parameter to the Stripes filter in the web.xml
<filter>
<display-name>Stripes Filter</display-name>
.......
<init-param>
<param-name>Extension.Packages</param-name>
<param-value>de.laliluna.examples.pico.extensions</param-value>
</init-param>
The package has only one class which is an ActionBean resolver used by Stripes to create ActionBean classes. It extends the existing resolver and overwrites to methods.
public class PicoActionBeanResolver extends NameBasedActionResolver {
private static final MutablePicoContainer container =
new DefaultPicoContainer();
// Add all business services here
static {
container.addComponent(new SampleServiceImpl());
}
// called by Stripes when all ActionBeans are loaded
protected void addActionBean(Class<? extends ActionBean> clazz) {
super.addActionBean(clazz);
container.addComponent(clazz);
}
// called every time Stripes needs an ActionBean
protected ActionBean makeNewActionBean(Class<? extends ActionBean> type,
ActionBeanContext context)
throws Exception {
ActionBean result = container.getComponent(type);
return result;
}
}
Inside of the ActionBean you just use the service methods.
@DefaultHandler
public Resolution index(){
context.getMessages().add(new SimpleMessage(sampleService.hello()));
return new ForwardResolution("/pico.jsp");
}
All our ActionBean classes only need to define a constructor with all services they need. We add the services to the container and get a ready to run ActionBean.
Dependencies and libraries
Stripes tries to minimize the dependencies. As a consequence there are only three required libraries with a total size of about 600 K
-
stripes.jar
-
cos.jar (optional for file upload)
-
commons-logging.jar
The commons-logging.jar is a frequently used logging abstraction but at the same time well known candidate for class loading issues. It would be nice, if Stripes could migrate to the slf4j logging facade as many other frameworks has already done. But anyway, the short list of dependencies is a strong advantage.
Flow of development
A new dialog requires a new ActionBean, entries in a resource bundle and new JSP pages. The Stripes reloading extension allows to reload all this without restarting your application. You will still need to reload the web application, if you add a new domain model or business service.
Popularity and contributor distribution
Google pages on stripes framework: > 1,100,000
Books on Stripes: 1
Number of mails on mailing list per month: 220
Number of core developer: 3
Number of regular patcher/contributors: none
My impression of the framework
Stripes is an easy to learn and well structured web framework. It is action oriented and follows the model-view-controller pattern. The advantage to follow this action approach is that it is easy to understand and supports perfectly the underlying technology, i.e. HTTP. The web and HTTP are action / request oriented. A request is sent, logic is processed and depending on the result, the browser gets an error message or a response, which might be a HTML page or a redirect.
The disadvantage of this approach is that you are closely connected to the underlying technology. Frameworks that clearly abstract from the servlet API, for example Tapestry, shields you from the caveats of the technology and can provide you with useful additional functionality. I will try to back this with an example.
A input tag has a corresponding component class in Tapestry. I can add behavior to this tag using mixins. If I would like to have all input tags for date values have a nice Javascript date selector, I add a mixin for date values. If I want to change the date selector application wide, I need to change only a single place in my Java code and not all JSPs.
Of course, the abstraction and the provided additional features have the tradeoff of complexity. I am not valuing that one approach is better than the other, but try to make you aware of the consequences.
Going back to Stripes.
Stripes makes use of conventions to facilitate the development and to keep the developer from writing boiler plate code. Configuration with XML is only required, if you want to change the default components or Stripes or add interceptors. Instead of using XML you have the option to use a configuration class.
What I appreciated a lot is the architecture. It is pretty easy to understand, follows a clear concept and offers hooks to add custom behavior to the execution chain. The 'How do I do' part and the related sample projects illustrates how easy it is to add a custom security solution or even integrate a Inversion of Control container.
A weak point is the small number of developers and contributors. The bus rate (people who can be overrun by a bus) is relative bad. Stripes is well known but by far not as popular as frameworks like JSF, Tapestry or Struts. As a consequence, you will only find very little developers with Stripes experience. This is mostly outbalanced by the fact that, the learning curve is very flat and a new user is capable to build dialogs after one or two days of learning Stripes.
Stripes provides you with the minimum you need to do a web application. It doesn't come with a large collection of input widgets, security or even an integrated persistence layer. You are free to choose your own technologies to fulfill those requirements and Stripes offer you the hooks to integrate your stuff.
A weak area, is in my opinion the Ajax support. If you want to synchronize Ajax calls to session scoped variables, do client side validation using Javascript or need proper Ajax aware exception handling, you will always have to implement your own solution. This would be fine, if Stripes provides a very good documentation on all those topics but documentation is rather short. Ajax is more than generating JSON from an event method. This is an area where Stripes need to catch up.
The documentation is fine and covers all areas of Stripes but the structure could be improved. After a quickstart chapter, it looks like a wild collection of howtos which are partially translated to French.
To summarize, if you are looking for an easy to learn web framework with a well designed architecture and you want create your dialogs based on HTML and use external Ajax frameworks for more advanced input widgets, then Stripes is the right choice for you. If you are looking for a replacement for Struts 1.x, then Stripes is the perfect candidate. You will get along with Stripes easily and find that all the annoyances of Struts 1.x are solved.
If you are looking for something that clearly abstract from the underlying technology, providing you Ajax components for dialog development and providing ready to use functionality for security, workflows etc, then you might look for something else.
The community's opinion - beautiful features and concepts
I asked the following question on the mailing list:
What is your favorite feature or concept of Stripes, what is unique as compared to other things, what is an eye catcher?
Here are extracts ore rephrased parts of the feedbacks:
Freddy Daoud
What caught my eye about Stripes is that you can get a lot done without configuration nor boilerplate code. Also, to me it is an excellent balance of convention over configuration but without *too much* magic going on - it remains simple and straightforward.
The bottom line is that I found that I could get more done with less code with Stripes, and that it gave me the confidence that I would be able to get it to do whatever I needed. I wouldn't have to tell my customer that I couldn't implement a feature because the framework got in my way.
Oscar Westra van holthe - Kind
Conversion, validation, error handling, etc. is done very well. As a result, the ActionBean only sees validated values, without making custom validation too difficult.
Also, the type converters that can automatically load data objects from the database (like for example with Stripersist) are extremely powerful.
The life cycle and the interceptors make it possible to create all kinds of neat extensions. One of them is Stripersist, but security, authentication, logging, ... it's all possible. It resembles aspect oriented programming, but without breaking the basic assumptions one usually makes when workign with a programming language.
Yee
Indexed properties is a great feature. (Author: it allows table
based multi row input ). It was WOW when I first saw it.
Stripes
is simply beautify. Everything is nice and lean and perfectly placed.
There is not an ounce of unnecessary fat in it.
Morten Matras
The one thing that separates Stripes from other frameworks is the learning curve, which is very flat compared to other frameworks.
....
When I add new developers to teams working with Stripes it normally takes them less than 2 working days before they have added a new feature or enhancement to the product and all they need to get started is source code and an IDE to be set up.
Remi
It's simple, it does what you need (binding, first class FORM/validation support, extensibility etc.) without ever getting in your way, just like you'd have imagined it should be!
John W Newman
I have been pleased with the code activity and the quality of the commits, the project is always improving despite being small and close-knit (this is one of the best qualities of the project, more committers = more confusion and senseless feature bloat).
As far as I am concerned, the only thing stripes isn't perfect with now is ajax events. We (and probably everyone else) were forced to write our own set of glue code to make ajax work correctly with validation errors etc. Fortunately prototype 1.6 and the x-json header made this pretty simple, but I think the web framework could take responsibility and make this easier on everyone. This is a shortcoming and I'm not sure if there's any motivation right now to address it. Cleaner ajax event support should be thoroughly discussed.
Also I'd like to move all of our @Validate tags to fields in the entity beans where they belong. I know some work has been done on this and maybe we can have it as a core feature in a future release. I think after the JSR on a standard validator thing is released, stripes should take that and run with it (assuming they don't totally botch it like they did with type erasure).
Features
Render / page technologies
Stripes supports two technologies to create pages: JSP and Freemarker templates. It comes with a tag library which can be used with both rendering technologies. The Stripes tag library is a thin layer around HTML tags.
This does of course not prevent you from using other tag libraries. It is actually surprising that a lot of companies don't write their own tag library which renders a number of components at a time, sample: <mytag:text label=�Input the name�, property=�name�/> to render label, input and validation errors.
Developer comfort
There is a plugin to allow reloading of resource bundles, ActionBeans and converters. As long as you don't change your business layer or model, you don't have to reload your application. This improves development performance a lot.
http://www.stripesbook.com/stripes-reload.html
Ajax support
Stripes allows to take control over the response to render XML, JSON or whatever you like to send as answer to a Ajax request. In addition it provides a basic level of remoting. You can transform a Java object to a JSON form and rebuild it on the client as JavaScript object.
The tag libraries do not provide Ajax support but you can add your preferred JavaScript library to build Ajax application. In my opinion, this is the preferred approach as Ajax tag libraries like Icefaces, provide basic Ajax functionality and components and are more complex to adapt to advanced use cases.
There will be a number of things you would have to implement on your own. One thing is serializing concurrent access to methods. There is always a chance that Ajax requests are send multiple times and it is your responsibility to serialize those calls if required.
You can provide an exception handler in Stripes. Inside of this exception handler you need to distinguish between Ajax calls and normal requests. If you hit an error, you will probably show a friendly error page to the user. An Ajax call needs to receive an real HTTP error message like 500 or 404 if something failed. You have to implement this inside of your exception handler and make sure that the Ajax calls signal for example with a request parameter that it is an Ajax call.
Security
There is no security concept provided by Stripes itself but you can find two contributions showing how to solve the problem.
The first one offers to do a per method authorization using annotations and offers a tag library to be used inside of a JSP. The contribution was written for an older Stripes version and is to basic to be used in real world cases. The tag library contains only one tag to hide a part of the JSP if a role is not available. A suitable tag library should at least offer the following use cases:
-
check if the user has any of the required roles
-
check if the user has all of the required roles
-
check if the user has none of the required roles
This could be done using multiple tags or a single tag using functions. Here a sample API with functions:
<x:render check=�hasAnyRole('foo,bar')�>
shown if you have role foo or bar
</x:render>
<x:render check=�hasNotAnyRole('foo,bar')�>
shown if you have none of the roles foo or bar
</x:render>
<x:render check=�hasAllRoles('foo,bar')�>
shown if you have role foo and bar
</x:render>
The same conceptual limitations are true for the annotations provided by this contributions.
The second contribution integrates EJB3 security and makes use of the JEE annotations. The idea is nice but of course ties you to EJB3 libraries. The tag library allows to hide part of a JSP depending on the configured roles for a method of the referenced ActionBean. I understood the user case � showing a link only if I am allowed to follow this link � but still it is a kind of specialized use case.
To summarize, I think that the offered security is not enough for a larger application. Therefore you either use an external Security Framework or write your own security solution. This is not a complicated task, a little tag library, a couple of annotations and the logic to authenticate and authorize. It shouldn't take longer than a couple of days. The advantage of Stripes is, that it is easy to integrate security into the request processing.
How do I do ...?
The howto project provided with the sources illustrates all the functionality explained in this chapter.
Navigation
The stripes tags are a light abstraction on top of HTML. The link just add the context to the attribute value of href. The following link calls the default action of the HelloWorld.
<stripes:link href="/HelloWorld.action">Go to hello world</stripes:link>
The next link calls the save method of the HelloWorld class.
<stripes:link href="/HelloWorld.action" event="save">
Go to hello world
</stripes:link>
The form tag offers the same functionality.
<s:form action="/HelloWorld.action" method="post">
Events can be specified in the submit button, as well. The following button will execute the save method.
<s:submit name="save">Write to database</s:submit>
The default URL is taken from the ActionBean class name. If you use a package name like
de.foo.bar.HelloWorld, the URL to execute the class is /de/foo/bar/HelloWorld.action. If you add one of www, web, stripes or action to the path, the path starts from there.
Use de.foo.bar.web.HelloWorld to get a mapped URL like /HelloWorld.action.
You can overwrite the default URL mapping.
@UrlBinding("/HuhuWorld.action")
public class HelloWorld implements ActionBean{
Friendly URLs
Friendly URLs express that the user things 'Oh, that's a nice website, the URLs are locking friendly' and that search engines think 'Oh, that's a nice website, I can index it'.
Friendly URLs can be configured with the @URLbinding annotation.
The following binding will write xx into the foo attribute and yy into the bar attribute of the class.
Request: /action/Test/xx/yy/myevent
@UrlBinding("/action/Test/{foo}/{bar}/{$event}")
public class HelloWorld implements ActionBean{
private ActionBeanContext context;
private String foo;
private String bar;
Templates
The first step is to define a template with placeholders. The tag <stripes:layout-component> defines a placeholder.
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld" %>
<stripes:layout-definition>
<html>
<head>
<title>Layout Example</title>
<stripes:layout-component name="html_head"/>
</head>
<body>
<stripes:layout-component name="header">
<jsp:include page="/layout/header.jsp"/>
</stripes:layout-component>
<div class="pageContent">
<stripes:layout-component name="contents"/>
</div>
</body>
</html>
</stripes:layout-definition>
A page can reference the template and define content for the placeholders.
<%@ taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld" %>
<stripes:layout-render name="/layout/default.jsp">
<stripes:layout-component name="contents">
Hello World!
</stripes:layout-component>
</stripes:layout-render>
In addition you can pass variable to a template, used nested layouts or use a template just for a page fragment. The templating mechanism is simple but well done.
Forms and validation
The first step is to provide variables for the input. Inside of the ActionBean you add properties with public getter and setter.
public class FormsAndInput implements ActionBean{
private ActionBeanContext context;
private String foo;
private Date date;
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
.....
Then you add a method forwarding to the form and a method to handle the submit.
@DefaultHandler
public Resolution index(){
return new ForwardResolution("/input.jsp");
}
public Resolution save(){
// we just print the input
System.out.println(String.format("Date: %snFoo: %sn", date, foo));
return new RedirectResolution("/input.jsp");
}
Finally you create the form in the input.jsp
<%@ page import="java.util.Date" %>
<%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Simple jsp page</title></head>
<body>
<s:form action="/FormsAndInput.action" method="post">
<div>
Foo <s:text name="foo"/><s:errors field="foo"/>
</div>
<div>
Date <s:text name="date"/><s:errors field="date"/>
<% request.setAttribute("date", new Date()); %>
<br>Input sample: <fmt:formatDate type="date" value="${date}" />
</div>
<s:submit name="save">Save me</s:submit>
</s:form>
</body>
</html>
The final step is to add validation. Out of the box, Stripes only offers server side validation. This fulfills security requirements but does not offer the user friendly client side validation. You might select a Javascript based validation engine to complete this task. Stripes provides access to the validation annotation inside of the JSP. You can use this information to create the Javascript validation generically from this information. But now let's see what can be done already with Stripes.
There are three levels of validation
-
Annotation based for every field
-
Validation method inside of the ActionBean
-
Performing validation in the event method
Basic validations are defined as annotation to the fields of an ActionBean. You can define checks like length, size, required and masks.
@Validate(required = true, minlength = 2)
private String foo;
@Validate(field="zip", required=true, mask="\d{5}(-\d{4})?")
private String zip;
Custom validation
The next step is to define a custom validation method. There can be multiple of them, they can be sorted and you can specify if they should be executed, if an error has already happened.
@ValidationMethod()
public void validate() {
if (foo.equals("bad")) {
ValidationErrors errors = new ValidationErrors();
errors.add("foo", new SimpleError("Foos balue is bad"));
context.setValidationErrors(errors);
}
}
The validation is flexible and powerful. I can only imagine a simple improvement. I would like to define a custom validator inside of @Validate. Every business application has frequently used types that are specific to that company. Instead of writing a validation method everywhere, the following would be simpler and shorter.
@Validate(custom=de.laliluna.EmailValidator.class)
private String email;
@Validate(custom=de.laliluna.ArticleNumberValidator.class)
private String articleNumber;
Advanced converting
Enums and dates are properly parsed, but time and date+time converts are missing.
Customize converting of input
You can specify custom converters for every field. A custom converts just needs to implement the interface TypeConverter.
@Validate(required = true, minlength = 2, converter = FooConverter.class)
private String foo;
In addition you can replace the converter factory with your custom implementation to provide your own converter for all java.util.Date for example.
Common dialog tasks
Are more complex input widgets provided like calendars or tree tables.
Stripes provides only the basic HTML like input elements. But you can add Javascript based component libraries like jQuery UI, Yahoo etc. Have a look at the main article for further references.
Is there a simple way to iterate through collections?
You can use the JSTL tag library to do this task. Stripes does not mirror its functionality. The Freemarker templates have their own mechanism to provide loops.
JSTL sample
<c:forEach items="${persons}" var="person" varStatus="loop">
<tr>
<td>
${person.id}
</td>
<td>${person.username}</td>
</tr>
</c:forEach>
How do I hide a part of the page depending on a condition?
You can use JSTL to achieve this.
<c:if test="${myCondition}">bla bla</c:if>
How do I print a sorting table?
Stripes does not include more complex page elements. You might use the well know displaytag library or make use of a Javascript widget collection. Both provide you with powerful complex page elements.
Multi row input and indexed properties
Stripes has support for multi row input. You create a form and used index properties for input. The following JSP shows existing persons and adds an input row for new persons.
<table>
<c:forEach items="${actionBean.persons}" var="p" varStatus="loop">
<tr>
<td>
<s:text name="persons[${loop.index}].name" value="${p.name}"/>
</td>
<td>
<s:text name="persons[${loop.index}].email" value="${p.email}"/>
</td>
</tr>
<c:set var="newIndex" value="${loop.index + 1}" scope="page"/>
</c:forEach>
<tr>
<td><s:text name="persons[${newIndex}].name" /></td>
<td>
<s:text name="persons[${newIndex}].email" />
</td>
</tr>
</table>
Rendered HTML
<table>
<tbody><tr>
<td><input type="text" value="Peter22" name="persons[0].name"/></td>
<td>
<input type="text" value="email@test.de" name="persons[0].email"/>
</td>
</tr>
<tr>
<td><input type="text" value="" name="persons[2].name"/></td>
<td>
<input type="text" value="" name="persons[2].email"/>
</td>
</tr>
</tbody></table>
The ActionBean class has the following field to store the persons.
private List<Person> persons = new ArrayList<Person>();
Message resources and internationalization
Stripes makes use of Java resource bundles and places text resources for a language into a single property file. The file is postfixed with the language and/or the country.
StripesResources_en_US.properties
Resource bundles cannot be modularized which is quite inconvenient for large applications. The only way to change this is to replace the bundle factory of the configuration and provide your own mechanism to load the resource bundles
Reloading of resource bundles during development is possible with the reload plugin named in chapter 2.2.
Exception handling
You can provide your own exception handler. It is called from the servlet filter. This allows to handle an exception even if a JSP is called directly. You can get retrieve the ActionBean, if one exist already, from the request. This provides access to the ActionBeanContext, which has all relevant information.
public class MyExceptionHandler extends DefaultExceptionHandler{
/**
* An exception handler to handle RuntimeExceptions
*/
public Resolution handle(RuntimeException e, HttpServletRequest request, HttpServletResponse response){
// clean up, send HTTP error code for Ajax request
// or redirect to an error page
return new ForwardResolution("/error.jsp");
}
}
Redirect on post and flash scope
You have full control over the dispatch mechanism by returning a Resolution from an event method. There are a number of predefined resolutions to forward, redirect or stream data but you could create your own resolution as well.
return new ForwardResolution("/error.jsp");
// or
return new RedirectResolution("/edit.jsp");
A flash scope is implemented using an id which is added to URL in case of a redirect.
http://localhost:8080/stripes/edit.jsp?__fsk=-556973262
Conversation context, wizards and workflows
Stripes offers a simple mechanism to carry input fields from one page to another. It encrypts input fields from the first page and prints them encrypted as single hidden input on the next page. The advantage is that you don't have to fill up your session. The disadvantage is that this hidden field might become quite large.
There is no support for a conversation context or workflows. Of course you can plugin your own mechanism.
Double submit handling
There is no direct support for double submits. A possible manually implemented solution could look like the following:
-
Create a global counter to create form Ids
-
Add the hidden ID to every form
-
Create an interceptor method in the ActionBean
@After(stages = LifecycleStage.CustomValidation)
public Resolution doubleSubmit() {..}
-
The method checks if the id is in a cache of submitted Ids (ConcurrentHashMap)
-
If yes, it stops the processing and sends the user to an error page. If no, add the ID to the ID cache
-
Create a clean up mechanism for the cache to limit its growths
Performance and scalability
This chapter is included in the PDF document framework-evaluation-performance.pdf.
Caching
Infrastructure
I set up an infrastructure which guaranties that the client and the network is never saturated. As a full server is to fast to be tested with only one notebook as client, a virtual server was setup which uses only one CPU.
-
Server
-
Intel Core Duo 2,4 GH (E6600), 6 GB RAM, Disc Western Digital Raptor 10000 upm
-
Fedora Linux running a KVM based virtual server
-
Virtual Server OS: Linux Centos 5.2
-
Virtual server has 1 CPU and 2 GB of memory
-
Java 1.6 on Tomcat 6.0.18
-
JVM starts with 256 MB MaxPermGenSize and 512 MB Heap Size
-
Application logging is disabled but garbage collection logging is enabled.
-
-
Client
-
Mac Notebook
-
Intel Dual Core 2.4 GH
-
Operation System Apple Leopard
-
-
Network
-
Gigabit Network, 3com Switch 2916
-
Startup and reloading
The sample application contains 50 dialogs offering list, edit, delete, create of an item. In a real world application you need to add the time to initialize the business and the persistence layer.
Startup time
Reloading time after changes
Performance test
Get request of a static file
The test is used to provide a base to find out the processing time of the web framework. The following formula explains this:
Processing time = GET request to web framework � GET request of static file
Scenario
-
GET request of a static file with the same content as the GET request tests
-
100 000 requests
Get request and rendering performance
Scenario
-
Sample application from last test + defined dialog for the get request
-
10000 requests warm up
-
1 thread executing 100 000 requests to the defined dialog
-
thread is session aware
-
160 text entries are fetched from a resource bundle
-
Resource bundle contains about 1800 entries
-
400 attributes of domain objects are printed
-
40 conditional renderings
-
40 date formatting
-
40 double formatting
-
for loop with 20 entries
-
page size: 58 Kb
Get request and template rendering performance
Scenario is repeated but the rendered page uses a template
Post request and validation performance
Scenario
-
Sample application from last chapter + defined dialog
-
10000 GET and 20000 POST requests warm up
-
1 threads executing 10000 GET and 100 000 POST requests to the defined dialog
-
thread is session aware
-
24 input values, validation on 20 element, custom validation on 6 elements
-
Every second POST request has 50 % bad input values
|
|
Static file |
GET |
GET with template |
POST |
|---|---|---|---|---|
|
Average time of all request |
||||
|
Average time of 90 % fastest request |
||||
|
Average time for 1000 slowest request |
||||
|
Average memory consumption (GC log) |
|
|||
|
Max memory consumption (GC log) |
|
|||
|
Memory start/end (after GC) (GC log) |
|
|||
|
Total collected memory during garbage collection (GC log) |
|
|||
|
Memory usage Heap and Non Heap after a Full GC at the end (JVM info) |
|
Max scalability for given hardware
Scenario
-
Application with 50 list/create/read/update dialogs using templates
-
Warm up - every dialog is called once to trigger JSP compilation
-
User session is simulated
-
20 times the following sequence
-
10 GET and 2 form POSTs with failure 1 successful form POST with redirect
-
90 - 110 ms random pause between requests
-
-
Session time out 2 minutes
-
Scenario produces about 500 requests per thread per minute
-
Every minute an additional thread is started up to 40 threads
-
Final situation are 120 threads with theoretically about 60 000 requests per minute
