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");
<a name="DDE_LINK1"></a> }
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.
<a href="http://domain/myapp/helloworld.action">http://domain/myapp/HelloWorld.action</a>
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.
<a href="http://domain/myapp/helloworld.action">http://domain/myapp/HelloWorld.action?save</a>
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
<%@ 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()); %>
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 |
|
|||
|
Memory usage Heap and Non Heap after a Full GC |
|
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