Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migration of unmigrated content due to installation of a new plugin

...

Due to the export of the wiki to static pages, attachements are not visible. You can find the .war with the source

...

here

...

The .zip file you can download on this site can be dropped in your servlet container (rename it to .war then) and contains the source code under the WEB-INF directory. The layout is also kept as simple as possible:

...

...

Configuration files

WEB-INF/web.xml

The webapplication's descriptor file contains one filter and its mapping. By default, the filter is mapped to /*, meaning all requests will be intercepted, but only those ending with a specific suffix (.action, by default) and certain special paths (for static files) will be processed and handled by Struts 2.

...

...

See web.xml for further details.

...

struts.xml contains the configuration for XWork: actions, validation, interceptors and results are defined in there.
To understand these terms, we'll need to take a look at Struts 2's (and XWork's) architecture. A basic request goes a bit like this:
A request is submitted, and our ActionMapper will try to determine the correct Action to execute. To find it, it will look up the registered names in your struts.xml. If one is found, before executing the Action, it will loop through a defined stack of interceptors.

...

Interceptors are a very important part of S2 - they will be invoked before and after your action is executed and the view is rendered, and as such, they are perfect for validation, authentication, open-session-in-view patterns, catching exceptions, setting or altering parameters, hiding complex operations, and more. S2 provides a number of prebuilt stacks with a ranging number of features, but nothing keeps you from defining your own interceptor stack with custom interceptors.

...

Let us take a more detailed look at our struts.xml:

...

There are four major elements covered in this example: package, interceptor, action, and result.

...

This file only contains two lines, and is used to set Struts 2 specific settings (such as which IoC container to use, which fileuploader, what templates, etc ..). You don't really need it, but for i18n reasons, we use it to register our resource bundle 'guest.properties'. Note that you can register properties file on different levels in Struts 2.

...

...

More information on struts.properties

...

...

It is recommended that configuration be handled with your struts.xml file: see Constant Configuration for details.

...

WEB-INF/classes/guest.properties

...

Since 90% of the code is identical to the other CRUD examples displayed on this site, we'll only analyze the Action class, EmployeeAction.
As always, first things first - the class definition:

...

...

Now, first there is the extending of the ActionSupport class - although you don't have to extend it, it provides a lot of useful extras, so you are encouraged to extend and override parts of it, but you don't have to. The interface we are implementing is a bit more interesting. The Preparable interface only defines one method, public void prepare(). By implementing this interface in your Action, you tell the prepare interceptor to call this method on your Action - so that makes it perfect to, for example, retrieve objects from a database, which is what we're 'faking' here by requesting an Employee object from the EmployeeService.

...

Let's explore the doList method, which is the default method we specified in our struts.xml for our index action.

...

...

Surprisingly simple, no? Simply fill in the employees object, and return "success" (defined as a final static variable SUCCESS). That's it? That's it. The only thing you need now to show the list in your view layer is a simple getter for employees in your Action. The reason for this is another Struts 2 feature: the ValueStack.

...

S2 defines a lot of tags that will dramatically speed up your development. They can alter or query the value stack, render input forms, javascript widgets, iterate over collection, and so on. On top of that, they have different themes, which add even more functionality/layout by the switch of a parameter. Themes are out of the scope of this example, but you should definitely check them out; see Themes and Templates.

...

The s:url tag allows you to build urls for actions, possibly with parameters. This saves you from having to type them out manually, remember (or change) what action suffix you're using, or include the web-app context. See Url for further details.

The s:text tag will look up keys in resource bundles in the valueStack (depending on the locale). So adding a new language would be a breeze. In these cases we build an url '/css/main' and we display the value of a key 'label.employees' from our guest.properties resource bundle. See Text for further details.

...

...

Here we use s:url to generate a URL and assign it to an ID so we can refer to it later.

...

The S2 ActionMapper doesn't just map names to Actions - it can also change the method, or even action, by using special requests. The weird value we encountered above, crud!input, tells the ActionMapper to invoke the input()/doInput() method of the Action known as crud. So for example, you could have several slighly different methods, and rather than having to register each of them in the struts.xml file, you can use the ! notation to specify which method to execute.

...

...

You may also use Wildcard Mappings to run specific methods rather than use the ! character.

...

Another thing is the fact that you can override which action/method to invoke based on a special name:action parameter, which we'll use later on in our employeeForm.jsp to make a nice cancel button.

...

Take a look at the following lines from our employees.jsp:

...

The s:iterator tag does pretty much what you expect from it: it loops through an array/list/collection, and pushes each object it encounters on the stack on each iteration. It also has a helper attribute called status, which generates a status object that keep track of the index, tells you if you're in an even or odd row, and so on, while you're inside the s:iterator tag. See iterator for further details.

...

Unlike JSTL's c:choose/c:when/c:otherwise tags you do not nest s:if/s:else inside a "parent" tag.

...

Finally, there's the s:property tag, which is used for displaying objects/expressions from the ValueStack. Examine this:

...

This might be a little confusing at first, but it's very simple. This actually translates to top.getAge(), meaning we'll execute a getAge() on the top object on our ValueStack. Which happens to be .. the Action? Nope. The employees List? Closer. The current Employee object in the employee list? Bingo.

...

Easy enough: return "input" from your Action, and register the result in your struts.xml with a dispatcher to employeeForm.jsp.

...

...

Nothing to it. So let's take a look at how we are going to edit an Employee.

...

Register the DepartmentAction in your struts.xml as 'department', and you can simply call it in your page, and use the id approach we used in the s:url tag to store the result on the stack under a custom name called allDepartments:

...

Now, this, by itself, does not do much. We'll show you later when we talk about the employeeForm page how to use it as an alternative approach.

...

But first, we find another useful tag: s:set.

...

...

The set tag allows you to store certain objects on the stack (as well as their scope - request/session/page/...), which is what we're going to do here because out of sheer laziness (and performance reasons) we refuse to do the same if/else more than once. As you can guess, it relies on the same principle as the id attribute of the s:url tag we saw earlier, meaning I can access it with #title on the ValueStack.

More information on the s:if tag, s:else tag

The actual form

...

...

Wow - a lot of code at once. Let's dissect it tag by tag. It may seem complicated, but as you'll see, it's actually really easy.

The s:form tag generates a standard html form, while the first s:textfield will generate an <input type="text" .. /> - but wait, it does more. It transforms your first tag from this:

...

...

Into this:

...

Let's analyze that s:textfield tag in greater detail. First thing we encounter is the name attribute, which is similar to the HTML input tag's name attribute. So it is by itself, not very special. However, as we've seen above, this allows S2 to call the getEmployee().setFirstName() method - and even, if necessary, create the Employee object for you.

...

Now, when such an expression returns null (since our Employee pojo is NOT initialised - we are doing an insert here, remember ?), S2 creates the Employee object for you, and its getFirstName() returns, of course, null. So, we're actually cheating here, because it will allow us to reuse that same form when we are going to edit an Employee. In that case, our getEmployee() would return an initialised object, so its getFirstName() would return a value, and thus display it in our input field ! Great - reusal of forms is always nice (of course, you can always use two seperate forms, but you don't need to).unmigrated-wiki-markup

Ok, so %\{..} indicates an expression on the ValueStack, and returns a blank if a null is returned, or the toString() value otherwise. The OGNL analyzer is pretty powerful, so you can do things like value="my_special_valentine_is_%\{girlfriend.name}", %\{100 * loan.tax}, or even %\{new int\[100\]} and %\{new java.util.Date()}.

Now, we saw earlier that we could use the s:text tag to retrieve values from resource bundles for i18n reasons. Now that begs the question, how do we use those same values in our tags? Let's say we want to i18n'ize our textfield label. Something like this:

...

Ugh. No, that's ugly, and it wouldn't work either. The solution is much cleaner and simpler: use another expression! If you were to check the extra methods provided by making our EmployeeAction extends ActionSupport, you would see a method called getText(String key). This method will look up a value in the resource bundle by its key, which is exactly what we need. So, the label would become something like this: %{getText('our_key')}. Makes sense? Thought so.

Meet the next tag, s:select, which renders a select box using an iteratable collection:

...

...

...

There currently is a small bug in Struts 2.0.5 that does not select the correct value by default. You can solve this by using value="%{employee.department.departmentId.toString()}".

We already covered the name attribute, so let's look at the list attribute: departments. Hmm, this might seems strange at first. Where does it come from? Well, perhaps it makes much more sense to see this: list="%{departments}". Yes, it is in fact an expression on the ValueStack that will query our action for the departments we've setup before ! Then, why are we missing the %{..} ? The answer is twofold: you can still write %{departments}, and it would work as you expected. But you have to realize, that whatever you are going to iterate is going to be an expression! So, being pretty lazy and with all this Ruby-on-rails 'minimalistic' code, we decided we might as well save you a couple of RSI-related finger moves and let you discard the %{..}.

Another quick intermezzo: you can use expressions to make your own list in an expression - which is perfect for small yes/no and male/female/eunuch selections - like this:

...

...

By the way, did you remember the s:action tag we used as an alternative to the prepare method to fill up the select box? We can now use the action we executed and stored on the ValueStack by referencing it by its id:

...

...

So, instead of getting the departments from the current action, we used s:action to execute a different action that retrieves the department List. This allows you to split up, and reuse Actions (Note: you can go even further and program your own components with built-in actions, but that's out of scope).

...

Finally, the last two tags, the submission tags. Submission tags, yes. One for submitting the form, and another one to cancel it. The first submit button, submits the form to the form's action attribute, in case crud!save.

...

The second one is more interesting though. It also submits the form to the same crud!save action as the first one, but lists a special name attribute. This name attribute will cause the ActionMapper to intercept the call, and in this case, redirect it to another action. No more fiddling with javascript to have forms with multiple submit buttons - it's all done for you, without any javascript or the troubles that come with it.

...

Since we only want to validate the crud action, we create a file EmployeeAction-crud-validation.xml and place it in our classpath (mostly next to our compiled Action class).

...

...

A very important reminder: validation is once again done by an interceptor, so it should be in your stack. Even more important, validators 'query your Action/Model' and NOT your request! Keep this in mind at all times.

...

First we apply a requiredstring validator to the getEmployee().getFirstName() return value. There are quite a few validators, ranging from requiredstring, required, intrange, emailaddress, url, .. etc. Writing your own validator is not hard, and it can be reused easily. Here we use the requiredstring, which does 2 checks:

  • check the availabilty of the string (!= null)
  • check the length of the string to be > 0

Why the stringlength greater than 0? HTML forms will submit an empty string (not a null!) for an empty text input, so simply testing for null would fail if we required at least one character to be submitted.

...

Take a look at the value we get back from the 'errors.required.age.limit'-key:

...

And guess what: min and max are indeed the two parameters we just set before ! Using OGNL expressions you can retrieve whatever you want from your validator/action/context, giving you really nice error messages (we like nice error messages). In fact, you can not only use these expressions in your error messages, but you can even set the min and max parameters dynamically. Different types of employees could have different age requirements - OGNL and polymorphy to the rescue !

...