RSS Entries RSS
RSS Subscribe by Email

Archive for September, 2008

Struts 2 AJAX Tutorial – Dojo Autocompleter Example

This tutorial was written for Struts 2.1.2. A lot has changed in newer versions. The Dojo Plugin is no longer officially supported by the Struts 2 team. Also, please see Muhammad’s comment in the comment section if you’re using the latest version of Struts 2.  You may want to check out Google Closure if you’re looking for a good JS library.

Struts 2 comes with a Dojo Toolkit plugin which makes developing an AJAX application easier than ever.  In addition, the AJAX plugin I would most recommend is the Struts 2 JSON plugin.  In this example, we will use the two side-by-side to create an autocompleter.  In order to use the Ajax support provided in Struts 2, I would recommend Struts 2.1.2 or later.  Include struts2-dojo-plugin-2.1.2.jar and jsonplugin-0.30.jar in your WEB-INF/lib directory.

First, we will create the action for the field that we wish to autocomplete:

package com.lumidant.tutorial.struts2.action;

import java.util.HashMap;
import java.util.Map;

import com.opensymphony.xwork2.ActionSupport;
import com.lumidant.tutorial.struts.dao.*;

public class AutocompleteField extends ActionSupport {

    private String city;
    private Map<String,String> json;

    public String execute() throws Exception() {
        return SUCCESS;
    }

    public String getCities() throws Exception() {
        json = new HashMap<String,String>();

        if(city != null && city.length() > 0) {
            CityDao dao = new CityDao();
            List<City> cities = dao.getCitiesStartingWith(city);
            for(City city : cities) {
                json.put(city.getId(), city.getName() + ", " + city.getState().getAbbreviation());
            }
        }

        return SUCCESS;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public Map<String,String> getJson() {
        return json;
    }

}

The getCities method in our action acts on the input String city that we’re given and creates a Map of cities starting with that text.  The key of our Map is going to be the hidden option value while the value of our Map is going to be our option text.

Next, we will modify our struts.xml configuration file to utilize the JSON plugin by extending json-default:

<package name="example" extends="json-default">
  <action name="AutocompleteField" class="com.lumidant.tutorial.struts2.action.AutocompleteField">
    <result type="json><param name="root">json</param></result>
  </action>
</package>

The root parameter that we specify is the name of the variable from our Action that we want to have converted and serialized to a JSON output.

If instead of serializing a Java object to JSON you wish to create the JSON string directly yourself, you can instead use a result type of “stream”:

<action name="jsondata" class="com.lumidant.tutorial.struts2.action.JsonDataAction">
  <result type="stream">
    <param name="contentType">application/json</param>
  </result>
</action>

And finally, we get to create our .jsp view:

<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib prefix="sx" uri="/struts-dojo-tags" %>

<html>
  <head>
    <sx:head />
  </head>
  <body>
    <s:url id="cityList" action="AutocompleteField" method="getCities" />
    <sx:autocompleter name="city" theme="ajax" href="%{cityList}" size="24" />
  </body>
</html>

For any of the Dojo plug-in features that you use, you will need to include the <sx:head /> tag, which includes the relevant JavaScript tags into your page’s head tag.  Then you can see that the autocompleter’s name attribute aligns with the city field in our Action and the url’s method attribute aligns with our Action’s getCities method.  Now, when you visit your new page and start typing in the text box, you should see the field autocompleting.  There are some other cool built-in Dojo tags that you should look at as well like the datetimepicker, which provides a really nice calendar.

Comments (27)

Struts 2 Tutorial – Validation and Error Handling

Validation is an important part of any web framework because it is one of the most painfully repetitive things to have to continually recreate.  In Struts 2, validation is handled by creating an Action-validation.xml file.  So, if we have an AddUser Action then we would create an AddUser-validation.xml file:

<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
    "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">

<validators>
  <field name="userId">
    <field-validator type="requiredstring">
      <message>Username is required</message>
    </field-validator>
  </field>
</validators>

It is important that you have both a getter and setter for the field you are validating.  For example, AddUser would need both of the following method signatures for validation to work properly: “public String getUserId()” and “public void setUserId(String userId)”.  You can get more information about the other types of validators from the Struts 2 documentation.

You can also control error handling from your Action class.  For example, let’s say that the user picks a username which is already registered.  We wouldn’t be able to determine this with validation alone, but would need to check the database in our Action class.  So when we determine that we can call “addActionError(String message)” from our class.  Then when we want to print any errors out on our page, we simply use the tag:

<s:actionerror />

Comments (1)

Struts 2 Tutorial – Struts Configuration

The struts.xml file defines the relationship between actions and .jsp views, the inclusion of interceptors, and possible result types.  It’s essential that you be able to handle at least basic configuration changes, so we’ll demonstrate the most important and frequently used below.

Packages: Extension, Namespaces, and More

For more complex applications, you will likely want to split the configuration between multiple packages.  In Struts 2.0.x, the the default package is struts-default.  Most of the time, extending this package is a pretty good place to start from.

<package name="example" extends="struts-default">
  <interceptors>
    <interceptor name="authorization" class="com.lumidant.tutorial.struts2.interceptor.AuthorizationInterceptor">

    <interceptor-stack name="authStack">
      <interceptor-ref name="authorization" />
      <interceptor-ref name="defaultStack" />
    </interceptor-stack>
  </interceptors>

  <global-results>
    <result name="error">/WEB-INF/pages/error.jsp</result>
  </global-results>

</package>

In the package above, we did not define any action mappings, but rather we defined some more general properties that we can share between other packages.   We can now extend the package we just defined when creating new packages.  This package and any that extend it will have all Action.ERROR results go to error.jsp.  We also defined a new interceptor and interceptor stack that our actions and packages can make use of.  In this example, you could imagine that we would have two other packages extending this one.  One package would contain pages that any user could visit.  The other package could contain pages requiring the user to be logged in, so we would set the default-interceptor-ref to “authStack” as shown below:

<package name="admin" namespace="/admin" extends="example">
  <default-interceptor-ref name="authStack"/>

  <action name="DeleteSomething_*" method="{1}" class="com.lumidant.tutorial.struts2.action.DeleteSomething">
    <result name="input">/WEB-INF/pages/deleteSomething.jsp</result>
    <result>/WEB-INF/pages/deleteSuccessful.jsp</result>
  </action>
</package>

We also introduced additional new topics in this example.  Firstly, you’ll see that we created a namespace.  That means that all the actions in this package will be accessed through a spearate URL.  In this example it is an “/admin” URL.  Also, you’ll see that we put the “*” wildcard in the action name.  What this is doing is taking what was located in the * and replacing the “{1}” with that string.  Since the “{1}” is located in the method attribute, we will be calling a method on our action.  So let’s say the user accesses /admin/DeleteSomething_input.action.  Then the input method of the DeleteSomething.java action will be called.  Assuming we did not define our own input method, then we will inherit the input method from ActionSupport, which will take us to the result named “input”, which in this example is the deleteSomething.jsp page.

Better URL Structure

By adding the following a child of the struts tag, you no longer need to append .action to your URLs.

<constant name="struts.action.extension" value="action,,"/>

Action Chaining

<action name="LogIn_*" method="{1}" class="com.lumidant.tutorial.struts2.action.LogIn">
  <result type="chain">ShowHomepage</result>
</action>

What happens when we don’t want to show a .jsp page as the result of our action, but instead want to continue on directly to another action?  We simply use the result type “chain” to forward to another action.

Redirect After Post

<action name="SubmitOrder_*" method="{1}" class="com.lumidant.tutorial.struts2.action.SubmitOrder">
  <result name="redirect" type="redirect-action">
    <param name="actionName">DisplayOrder</param>
    <param name="namespace">/user</param>
  </result>
</action>

Very often, after an action is taken, we want to redirect the user to a separate action.  For example, let’s say the user submits an order.  If we leave the URL in their browser as SubmitOrder.action and they hit refresh, then the order could be submitted a second time.  So after they submit an order, it’s probably better if we redirect them to another action, such as one displaying the order they just placed with a thank you message.

Comments (3)

Struts 2 Tutorial – Interceptors

Interceptors are my favorite aspect of Struts 2. They inspect and/or act on a user’s request. There are three main uses cases that I’ll discuss here: intercepting before the action, between the action and view, and after the view. At the end, I’ll show you how to add your brand spankin’ new interceptor to your struts.xml file so that it is called on each request.

Intercepting Before the Action:

package org.lumidant.tutorial.struts2.interceptor;

import java.util.Map;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ValidationAware;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

public class AuthorizationInterceptor extends AbstractInterceptor {

	private static final String USER_KEY = "user";

	public String intercept(ActionInvocation invocation) throws Exception {
		Map session = invocation.getInvocationContext().getSession();
		if(session.get(USER_KEY) == null) {
			addActionError(invocation, "You must be authenticated to access this page");
			return Action.ERROR;
		}

		return invocation.invoke();
	}

	private void addActionError(ActionInvocation invocation, String message) {
		Object action = invocation.getAction();
		if(action instanceof ValidationAware) {
			((ValidationAware) action).addActionError(message);
		}
	}

}

You can see that we check to see if the user is authorized and present an error message if he/she is not logged in. We do this by adding an ActionError, which can be displayed on your view. If the user is logged in, then he/she proceeds normally.

Intercepting Between the Action and Generation of View:

package org.lumidant.tutorial.struts2.interceptor;

import java.util.Map;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.interceptor.PreResultInterceptor;
import org.apache.struts2.ServletActionContext;

public class WirelessInterceptor extends AbstractInterceptor {

	private static final String RESULT_CODE_SUFFIX_WIRELESS = "Wireless";
	private static final String REQUEST_HEADER_ACCEPT = "Accept";
	private static final String ACCEPT_HEADER_WIRELESS = "vnd.wap";

	public String intercept(ActionInvocation invocation) throws Exception {

		invocation.addPreResultListener()(new PreResultListener() {
			public void beforeResult(ActionInvocation invocation, String resultCode) {

				// check if a wireless version of the page exists
				// by looking for a wireless action mapping in the struts.xml
				Map results = invocation.getProxy().getConfig().getResults();
				if(!results.containsKey(resultCode + RESULT_CODE_SUFFIX_WIRELESS)) {
					return;
				}

				// send to wireless version if wireless device is being used
				final String acceptHeader = ServletActionContext.getRequest().getHeader(REQUEST_HEADER_ACCEPT);
				if(acceptHeader != null && acceptHeader.toLowerCase().contains(ACCEPT_HEADER_WIRELESS)) {
					invocation.setResultCode(resultCode + RESULT_CODE_SUFFIX_WIRELESS);
				}
			}
		});

		return invocation.invoke();
	}
}

In this example, the action has already been taken, but we have not generated the view that the user will see yet. This allows us to send the user to a separate page if he/she is using a Blackberry. We check the Accept HTML header to see if the string “vnd.wap” is present and if so then we append “Wireless” to the result code. So if the action was going to send the user to the result “Success” then we will instead show them the result “SuccessWireless”. We could easily adopt this technique for a Google Android or an iPhone as well.

Intercepting after View Creation:

package org.lumidant.tutorial.struts2.interceptor;

import java.util.Map;

import org.hibernate.Transaction;
import org.marketcharts.data.dao.util.HibernateSessionFactory;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

public class HibernateInterceptor extends AbstractInterceptor {

	public String intercept(ActionInvocation invocation) throws Exception {
		try {
			return invocation.invoke();
		} catch(Exception e) {
			Transaction tx = HibernateSessionFactory.getSession().getTransaction();
			if(tx != null && tx.isActive()) {
				tx.rollback();
			}
			return Action.ERROR;
		} finally {
			HibernateSessionFactory.getSession().close();
		}
	}

}

This example uses Hibernate, an Object Relational Mapper (ORM), which is library for easy database access. Hibernate needs an open session when the view is created (Open Session in View Pattern) because it lazily accesses the database. If the session is closed and we go to access the database, then Hibernate will throw an Exception. So this interceptor provides a method to close the session after the view has already been created and all the database calls have been made.

Adding an Interceptor to your struts.xml:

<package name="example" extends="struts-default">
  <interceptors>
    <!--
      One possible use of an interceptor is to send the user to special version of the .jsp
  	view if they are using a Blackberry as shown above.
    -->
    <interceptor name="wireless" class="com.lumidant.tutorial.struts2.interceptor.WirelessInterceptor">

    <interceptor-stack name="wirelessStack">
      <interceptor-ref name="exception" />
      <interceptor-ref name="servlet-config" />
      <interceptor-ref name="i18n" />
      <interceptor-ref name="chain" />
      <interceptor-ref name="params" />
      <interceptor-ref name="wireless" />
    </interceptor-stack>
  </interceptors>

  <default-interceptor-ref name="wirelessStack" />
</package>

In this example, we’ve change the default interceptor stack to be a custom stack which we’ve created. You can also extend existing stacks.

Comments (16)

Struts 2 Tutorial – Creating Views

One of the first things you’ll want to do after getting started with Struts 2 is create more complex views.  For this example, let’s assume we have an action that returns a list of all the employees at our company:

package com.lumidant.tutorial.struts2.action;

import com.opensymphony.xwork2.ActionSupport;
import com.lumidant.tutorial.struts.dao.*;
import com.lumidant.tutorial.struts.model.*;

public class ExampleAction extends ActionSupport {

    private static final long SerialVersionUID = 1L;
    private List<Employee> employees;

    public String execute() throw Exception {
        EmployeeDao dao = new EmployeeDao();
        employees = dao.getAllEmployees();
        return SUCCESS;
    }

    public List<Employee> getEmployees() {
        return employees;
    }

}

Now let’s create a page that iterates through our list of employees and prints each employee’s name in alternating row colors:

<%@ taglib prefix="s" uri="/struts-tags" %>

<html>
  <head>
    <title>Struts 2 Tutorial Example</title>
    <style type="text/css">
      .odd td { background-color: #fff; }
      .even td { background-color: #eee; }
    </style>
  </head>
  <body>
    <h2>All Employees:</h2>
    <table>
      <s:iterator value="employees" status="rowstatus">
        <tr <s:if test="#rowstatus.odd">class="odd"</s:if><s:else>class="even"</s:else>>
          <td class="fn n">
            <span class="given-name"><s:property value="firstName" /></span>
            <span class="additional-name"><s:property value="middleName" /></span>
            <span class="family-name"><s:property value="lastName" /></span>
          </td>
        </tr>
      </s:iterator>
    </table>
  </body>
</html>

What we just demonstrated is a fairly common use case and should provide a good example for doing the same in the future.  Also notice our use of the if tag, which you will undoubtedly find a need for.  Another common use case is to access the session from within the view.  For example, you may want to show a link only to logged in users.  Again, we will use OGNL to accomplish this.

<s:if test="%{#session.user.isAdmin()}">
  <a href="Admin.action">Only Admins Should See This Link</a>
</s:if>

That should give you a pretty good idea of the basics, so we’ll move onto an internationalization example.  You’ll want to create a package.properties file in the directory where your actions reside:

form.label.firstName=First Name:
form.label.lastName=Last Name:

This file contains text for the default translation of your site.  All static text on your site should be contained in an internationalization file.  You can then include the text in your JSP with the Struts 2 text tag in the following manner: <s:text name=”form.label.firstName” />.  You can create a package.properties file for each translation and append the suffix for each translation language (and optionally country – eg. en_US). For example, below is a package_pt.properties file, which contains a Portuguese translation of our example site:

form.label.firstName=Primeiro Nome:
form.label.lastName=Último Nome:

Now when the user visits the site, Struts will detect their browser language and serve the appropriate page to them.

That should give you pretty decent idea of how to create views for your site.  Please check out our other Struts 2 tutorials for information on other aspects of Struts 2.

Comments (3)

Struts 2 Tutorial – Getting Started

Struts 2 is an MVC web development framework.  It is based off of Web Work and has far less configuration than the original Struts.  I would strongly recommend Struts 2 over Struts for new development due to the faster development times it allows.  This is a very simple example and follow up posts will be coming shortly to help you develop more complex apps with Struts 2.

Getting Started

Below is an example project layout that you can use for your project.  It may be useful in helping you to decide where in your project to place your various files.

Struts 2 Project Layout

To get started, you’ll need to download the Struts libraries.  At a minimum, you will need to put the following in your WEB-INF/lib directory:

  • struts2-core.jar (The framework itself)
  • xwork.jar (Struts 2 is built on the XWork 2 framework)
  • ognl.jar (Object Graph Notation Language is used throughout the framework to access object properties)
  • freemarker.jar (Freemarker is used to create UI tag templates in Struts 2)
  • commons-logging.jar (Used to log to log4j or JDK logging)

Now we need to add a filter mapping to your web.xml file in order to have the Struts called whenever a page on your site is accessed:

<?xml version="1.0"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

It’s now time to make your first action.

package com.lumidant.tutorial.struts2.action;

import com.opensymphony.xwork2.ActionSupport;
import com.lumidant.tutorial.struts2.dao.*;
import com.lumidant.tutorial.struts2.model.*;

public class ExampleAction extends ActionSupport {

    private static final long SerialVersionUID = 1L;
    private String id;
    private Employee employee;

    public String execute() throw Exception {
        EmployeeDao dao = new EmployeeDao();
        employee = dao.getEmployeeById(id);
        return SUCCESS;
    }

    public Employee getEmployee() {
        return employee;
    }

    public void setId(String id) {
        this.id = id;
    }

}

There are a couple important things to note here.  The first is that our Action extends ActionSupport, which is a pattern you’ll want to continue for future actions.  Secondly, the input to our page has a setter which the framework needs to have present and the output has a getter.  Finally, our Action returns the result type SUCCESS.  The other predefined result types are ERROR, INPUT, LOGIN, and NONE.

Struts places its configuration in a struts.xml file. A good place to put it is at the root of your source folder. We’ll need to add our new action to the struts.xml file.

<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
  <!-- Configuration for the default package. -->
  <package name="default" extends="struts-default">
    <action name="ExampleAction" class="com.lumidant.tutorial.struts.action.ExampleAction">
      <result name="success">/WEB-INF/pages/displayEmployee.jsp</result>
    </action>
  </package>
</struts>

And finally, we need to create a .jsp view for our results to be rendered in.

<%@ taglib prefix="s" uri="/struts-tags" %>

<html>
  <head>
    <title>Struts 2 Tutorial Example</title>
  </head>
  <body>
    <h2>Selected Employee:</h2>
    <span class="fn n">
      <span class="given-name"><s:property value="employee.firstName" /></span>
      <span class="additional-name"><s:property value="employee.middleName" /></span>
      <span class="family-name"><s:property value="employee.lastName" /></span>
    </span>
  </body>
</html>

The important takeaway for the view is the manner in which properties can be accessed from the view.  Struts 2 uses OGNL, which for the majority of cases means you’ll just leave the get off of your method name.  For example <s:property value=”employee.firstName” /> will call your Action’s getEmployee() method and then will call the Employee’s getFirstName() method.

So now you should have an end-to-end working example.  Depending on which application server you’re using, you should be able to access your sample page at a url like http://localhost:9090/ExampleAction.action  You’ll of course need to create some sort of dummy EmployeeDao if you’re going to use this exact example, but you should be well on your way.

You’ll also want to continue reading about Struts 2 in the continuation of this tutorial:
Struts 2 Tutorial – Creating Views
Struts 2 Tutorial – Interceptors
Struts 2 Tutorial – Struts Configuration

As an optional, but nice complement, I’d also recommend SiteMesh Tutorial with Examples.

Comments (10)

SiteMesh Tutorial with Examples

SiteMesh is a web layout framework for Java.  It differs from from frameworks such as Tiles in that it utilizes the decorator pattern.  For example, you create a number of pages and then you tell SiteMesh that you’d like to add the same header, footer, and menus to each of those pages.  This tutorial will give you a simple example of how SiteMesh can be used to give you a cleaner layout architecture and speed development times.

Start by downloading SiteMesh and adding sitemesh-2.3.jar to your WEB-INF/lib directory.  Then add the SiteMesh filter to your web.xml file like so:

<filter>
    <filter-name>sitemesh<filter-name>
    <filter-class>
        com.opensymphony.module.sitemesh.filter.PageFilter
    </filter-class>
<filter>

<filter-mapping>
    <filter-name>sitemesh<filter-name>
    <url-pattern>/*</url-pattern>
<filter-mapping>

This will call the SiteMesh filter whenever a page on your site is accessed.  Now we’ll need to create a /WEB-INF/decorators.xml file:

<decorators defaultdir="/WEB-INF/decorators">
    <decorator name="main" page="main.jsp">
        <pattern>/WEB-INF/pages/*</pattern>
    </decorator>
</decorators>

This will decorate all of the pages located under /WEB-INF/pages/ with the decorator /WEB-INF/decorators/main.jsp, which we’ll create next:

<%@ taglib prefix="decorator" uri="http://www.opensymphony.com/sitemesh/decorator" %>

<head>
  <title>
    Lumidant.com - <decorator:title default="SiteMesh Tutorial Example" />
  </title>
  <style type="text/css">@import "css/global.css";</style>
  <decorator:head />
  <body>
    <div id="header">
      <h2><a href="http://www.lumidant.com/">Lumidant.com</a> Tutorials</h2>
    </div>
    <div id="content">
      <decorator:body />
    </div>
  </body>
</html>

This decorator does a couple of things.  First off, it create an HTML title tag, which starts as “Lumidant.com – ” and then appends the title of the page that is being decorated.  If that page does not have a title tag, then a default is used to render “Lumidant.com – SiteMesh Tutorial Example”.  It then adds a global style sheet to every page being decorated and appends whatever is in that page’s head tag, which is useful for page-specific JavaScript, etc.  The decorator continues by adding a header to each page reading “Lumidant.com Tutorials” followed by the decorated page’s content.

Pretty cool, right?  If anyone is familiar with a similar decorator framework in PHP, please let me know.

Comments (14)