Stripes (English)

Z FI WIKI
Přejít na: navigace, hledání


Introduction

Stripes is a popular new framework for developing web applications. It has been developed learning from the mistakes of other frameworks like Apache Struts, where it is necessary to maintain a consistent state of several configuration files and any action processing data from the browser must be in a separate class.

Stripes needs no configuration file because it uses:

  • Annotations introduced in Java 5.0
  • The principle of convention-over-configuration introduced in Ruby-On-Rails.

Installation and dependencies

Stripes is available in a Maven repository, so you just need to add the project dependecy:

 
        <dependency>
            <groupId>net.sourceforge.stripes</groupId>
            <artifactId>stripes</artifactId>
            <version>1.5.6</version>
        </dependency>

If you want to use the functionality of uploading files, you need to add another dependency for parsing multipart HTTP request:

 
        <dependency>
            <groupId>servlets.com</groupId>
            <artifactId>cos</artifactId>
            <version>05Nov2002</version>
        </dependency>
 

Configuration

Stripes is implemented using the servlet filter in a servlet. In the deployment descriptor, in the file WEB-INF/web.xml, it is thus needed to provide their definitions. You can use the filter initialization parameters to provide more advanced settings.

  • ParameterActionResolver.Packages defines which packages will be searched for ActionBeans
  • ParameterExtension.Packages defines which packages are searched for extension implementation, e.g. LocalePicker etc...
  • ParameterLocalePicker.Locales contains a list of locale and character encodings that are supported by the application
  • ParameterInterceptor.Classes specifies the class of the so-called interceptors that can be activated at certain stages when processing an HTTP Request
 
    <!-- Stripes -->
    <filter>
        <display-name>Stripes Filter</display-name>
        <filter-name>StripesFilter</filter-name>
        <filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class>
        <!-- see http://www.stripesframework.org/display/stripes/Configuration+Reference -->
        <init-param>
            <param-name>ActionResolver.Packages</param-name>
            <param-value>cz.muni.fi.pa165.stripes</param-value>
        </init-param>
        <init-param>
            <param-name>Extension.Packages</param-name>
            <param-value>cz.muni.fi.pa165.stripes</param-value>
        </init-param>
        <init-param>
            <param-name>LocalePicker.Locales</param-name>
            <param-value>cs:utf-8,en:utf-8</param-value>
        </init-param>
        <init-param>
            <param-name>Interceptor.Classes</param-name>
            <param-value>net.sourceforge.stripes.integration.spring.SpringInterceptor</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>

The above configuration binds only to the action URL ending with the suffix.action. If we use the so-called clean URLs you need to add more to the definition

 
 <filter>
      <description>Dynamically maps URLs to ActionBeans.</description>
      <display-name>Stripes Dynamic Mapping Filter</display-name>
      <filter-name>DynamicMappingFilter</filter-name>
      <filter-class>net.sourceforge.stripes.controller.DynamicMappingFilter</filter-class>
    </filter>
    <filter-mapping>
      <filter-name>DynamicMappingFilter</filter-name>
      <url-pattern>/*</url-pattern>
      <dispatcher>REQUEST</dispatcher>
      <dispatcher>FORWARD</dispatcher>
      <dispatcher>INCLUDE</dispatcher>
    </filter-mapping>

It can be a good idea to implement the business logic using Spring beans. In this case, you can add also the activation of Spring and the Spring beans are then injected into the variables marked with annotations @SpringBean.

 
    <!-- Spring -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring-context.xml</param-value>
    </context-param>


When using JSTL tags for internationalization, it is also better to set the localized error messages that Stripes will use when validating form data:

 
    <!-- JSTL fmt: tags will use Stripes resources-->
    <context-param>
        <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
        <param-value>StripesResources</param-value>
    </context-param>

ActionBeans

An ActionBean is a class that implements the interface ActionBean. The interface states also that the ActionBean will be bound to an URL (more on this later). The class must implement the two methods getContext() and SetContext() by which the ActionBean can access the Servlet APIs such as the request and response.

It is not necessary to have a configuration file for a link between the URL and the class because the framework itself finds all classes that implement ActionBean. A link to the URL can be created in two ways:

  • By default, by using the class name name (cz.neco.action.bla.MujActionBean -> /bla/Muj.action)
  • With the annotation class @UrlBinding

A method for processing an HTTP request may be defined in any way, it needs just to be public and with a return type Resolution. Such methods may be in one of several ActionBeans. You can use the @DefaultHandler to specify the default handler for an ActionBean.

The return value of the method is an object of type [Resolution] that directly determines where to forward the process after the method has returned. The specific subtype specifies whether to proceed internally within the same web application ( ForwardResolution ), or to redirect your browser ( RedirectResolution).

Stripes does not need a separate class for a data form (form bean) but uses an ActionBean directly. Same examples as used in Struts would thus be defined by using ActionBeans:

 
package cz.muni.fi.pa165.stripes;
 
import net.sourceforge.stripes.action.*;
import java.util.List;
 
@UrlBinding("/zbozi.action")
public class ZboziNaSkladeActionBean implements ActionBean {
 
    private ActionBeanContext ctx;
    public ActionBeanContext getContext() { return ctx; }
    public void setContext(ActionBeanContext ctx) { this.ctx = ctx; }
 
    public Resolution mameZbozi() {
        if (Sklad.mameNejakeZbozi()) {
            return new ForwardResolution("/seznamzbozi.jsp");
        } else {
            return new ForwardResolution("/zadneneni.jsp");
        }
    }
 
    public List getSeznamZbozi() {
        return Sklad.getZbozi();
    }
 
}

and the page seznamzbozi.jsp would look something like this:

 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %>
 ...
 <s:useActionBean var="actionBean" beanClass="cz.muni.fi.pa165.stripes.ZboziNaSkladeActionBean" />
 <table>
 <c:forEach var="z" items="${actionBean.seznamZbozi}">
   <tr>
     <td><c:out value="${z.nazev}"/></td>
     <td><c:out value="${z.mnozstvi}"/></td>
   </tr>
 </c:forEach>
 </table>

Stripes can be integrated with different frameworks to generate pages. The standard is to use JSP. (by using the symbol <s:useActionBean>, the instance of the ActionBean is automatically available as a request attribute named actionBean).

Forms and data validation

Stripes in the form handler assumes that there is no need to create new objects just for the web tier and so objects can be accessed directly from the model. e.g. let's have a class for the object being edited:

 
public class Zaznam {
  private String jmeno;
  private int pocet;
  public void setJmeno(String jmeno) { this.jmeno = jmeno; }
  ...
}

The editing form may look like:

 
 <!-- form bound to an ActionBean -->
 <s:form beanclass=cz.muni.fi.pa165.stripes.ZaznamActionBean" method="post">
 
   <!-- display errors if they occur -->
   <s:errors/>
 
   <!-- Localized description field and editable form field -->
   <s:label name="zaznam.jmeno"/>:  <s:text name="zaznam.jmeno" size="30"/> <br/>
   <s:label name="zaznam.pocet"/>:  <s:text name="zaznam.pocet" size="3"/> <br/>
   
   <!-- Submit button with a localized description -->
   <s:submit name="save"><f:message key="submit"/></s:submit>
 </s:form>
 

with appropriate ActionBean and the annotations for validation:

 
package cz.muni.fi.pa165.stripes;
 
public class ZaznamActionBean implements ActionBean {
 //...
@ValidateNestedProperties(
   value = {
     @Validate(on = {"pridej","uloz"}, field = "jmeno", required = true),
     @Validate(on = {"pridej","uloz"}, field = "pocet", required = true, minvalue = 1)
     }
 )
 private Zaznam zaznam;
 public Zaznam getZaznam() { return zaznam; }
 public void setZaznam(Zaznam zaznam) { this.zaznam = zaznam; }
 
 @SpringBean
 ZaznamManager zaznamManager;
 
 public Resolution uloz() {
     zaznamManager.uloz(zaznam):
     return new RedirectResolution(this.getClass());
 }


The bean is usded to to take data from a form property type Zaznam(Record, that has a nested property name (pridej()) and count (uloz()). Both fields are required, with one that must be a number and greater than 1.

The conversion between strings and the corresponding types is performed automatically. The symbol <s:errors> is used to provide where the error messages should appear. The form field containing the erroneous values ​​are set as the CSS class .error, so it is possible to graphically customize and highlight the error messages.

More complex validation checks can be put into separate methods, the method of generating localized error messages is similar to the one of Struts:

 
@ValidationMethod(on = "pridej")
 public void kontrola(ValidationErrors errors) {
    if (zaznam.getJmeno().equals("diamanty")&&zaznam.getPocet()>10) {
        errors.add("diam", new LocalizableError("klic.chyby.moc"));
    }
 }

Pokud stránka s formulářem potřebuje nějaká předpřipravená data k úspěšnému zobrazení, je třeba tyto data předpřipravit i při neúspěšné validaci. TO lze jednoduše udělat tak, že třída bude implementovat rozhraní ValidationErrorHandler a implementovat jeho metodu handleValidationErrors(), ve které můžeme data předpřipravit. Například: If a page with a form needs some pre-processed data to a successful show, you must pre-arrange these data and the validation failed. You can do so by implementing the interface ValidationErrorHandler and implementing the method handleValidationErrors(), in which you can pre-arrange data. For example:

 
@Override
    public Resolution handleValidationErrors(ValidationErrors errors) throws Exception {
        username = userManager.getUserName(userId);
        return null;
    }

Layout Tags

Stripes also includes an improved tool for composing pages that is the equivalent to Struts Tiles. There is no need to have a separate configuration file as tiles-defs.xml. Just create a JSP page as a page template, e.g.:

 
<%@ taglib prefix="f" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld" %>
 
<stripes:layout-definition>
 <html>
 <head>
   <title><f:message key="${klicTitulkuStranky}"/></title>
   <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/default.css"/>
 </head>
 <body>
  ... menu atd. ...
  <stripes:layout-component name="contents"/>
 </body>
</html>
</s:layout-definition>

and the JSP pages can refer to this template:

 
<%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %>
<s:layout-render name="/layout.jsp" klicTitulkuStranky="seznam">
  <s:layout-component name="contents">
      ... content here ...
  </s:layout-component>
</s:layout-render>

All attributes of the tag s:layout-render are passed to the template as attributes so that can use them.

Best Practices

When creating a web application, it is a good idea to follow the recommended procedures as described in

Stripes Best Practices:

Access of the attributes of Session and ServletContext

ActionBeans are shielded from the Servlet API classes that receive an instance of ActionBeanContext that can then be accessed through objects such as HttpServletRequest, HttpSession or ServletContext. This is useful for testing. This can be used for a controlled access to HttpSession and ServletContext attributes. You can create your own descendant classes of ActionBeanContext, that can have methods such as:

 
public class MujBeanContext extends ActionBeanContext {
    @SuppressWarnings({"unchecked"})
    public MujTyp getMujTyp() {
        return (MujTyp) getServletContext().getAttribute("muj_typ");
    }
}

@Before annotation to load objects

If you need to edit a persistent object it is obviously better to avoid copying all the properties. You can just bind the values from the request by using the @Before annotation. This can be simply done so that the form has an hidden field with the object ID and within a method marked with @Before the needed object is loaded:

 
@Before(LifecycleStage.BindingAndValidation)
public void natahnout() {
    this.neco = getModel().findNecoById(context.getRequest().getParameter("id"));
}


An example of a complete application can be found in the article Cvičení Stripes