Technical blog

September 5, 2010

Annotation based Spring MVC, how tos

Filed under: java, spring — Tags: , , — paawak @ 11:26

I will list here the common how to-s for the new Annotation based Spring MVC. Please note that I am using Spring version 3.0.0.RELEASE. This is a bit, only a small bit, different from the version 2.5.x. So, if you are using 2.5.x, you have to tweak things a bit before you get the same effect.

Consider the following screen:

samplehtml

The fantatstic thing about Spring MVC is it lets you take an Object Oriented approach while designing the Model (Form Bean) and lets you bind that seamlessly to the View (JSP Form).  For the above scenario, we can break down the top section into a list of rows. I would denote the row with a class having select, itemName, price… attributes, as shown below (this is an inner class in my ItemBean class, which is my Form Bean):

    public static class ItemRow {
 
        private boolean selected;
        private String itemName;
        private float price;
        private int quantity;
 
        ...
    }

Then, in my ItemBean class, I can have a List of ItemRow, as shown:

public class ItemBean {
 
    private List<ItemRow> items;
    private float totalPrice;
    private Date expectedDelivery;
...
}

How do I display/bind my Model to View?

This is how the View (Item.jsp) looks like:

                                           <c:forEach var="item" items="${command.items}" varStatus="count">
						<tr>
							<td>
								<form:checkbox id="chkSelect" path="items[${count.index}].selected" />
							</td>
							<td>
								<c:out value="${item.itemName}"/>
							</td>
							<td>
								<input id="txtUnitPrice" type="text" disabled="disabled" value="${item.price}" size="4"/>
							</td>
							<td>								
								<form:input id="txtQuantity" path="items[${count.index}].quantity" size="3" disabled="${!item.selected}"/>
							</td>
							<td>
								<input id="txtItemPrice" type="text" readonly="readonly" value="${item.price * item.quantity}" size="6"/>
							</td>
						</tr>
						</c:forEach>
                                                <tr>
							<td style="height:30px;">
							</td>
						</tr>
						<tr>
							<td colspan="3">
								Total Price:
							</td>
							<td>
								<form:input id="txtTotalPrice" path="totalPrice" readonly="true" size="6" />
							</td>
						</tr>

Especially note the

path=”items[${count.index}].selected”

in the form:checkbox tag. This is the key. It binds a particular index of the items in the ItemBean class to the JSP.

How to map a given url to my Controller method

After the introduction of Annotation @Controller, all Spring MVC controllers have become like MultiActionController, that is, you can handle multiple requests in the same controller. Mapping a url is very simple: you should annotate any public method with the @RequestMapping(”/DesiredUrl.do”). Of course, you should make sure:

1.> The class is annotated with @Controller
2.> The following line is present in the Spring context file:

<context:component-scan base-package="com.swayam.demo.web.controller" />

The method can have a motley combination of parameters and return type. Read the javadocs for details. This annotation takes an optional parameter method, which can be any enum RequestMethod type. If you do not specify anything, your method will handle all types of requests like GET, POST, DELETE, etc.

How do I pre-load or customise my Form Bean before it reaches my controller method?

In Spring MVC, the framework creates an instance of the Form Bean and binds it to the fields in JSP in case of a POST. In many cases, I would like to customise the Form Bean before it starts binding to the JSP fields. In older versions of Spring MVC, this was done by overriding the formBackingObject() method in AbstractFormController.
With annotations, you do it by specifying the @ModelAttribute before the FormBean attribute in the request handler method and then again on a public method returning the customised Form Bean as shown below:

    @RequestMapping(value = "/checkout.htm", method = RequestMethod.POST)
    public ModelAndView checkout(
            @ModelAttribute("postBean") ItemBean formBean,
            BindingResult errors) {
 
        ModelAndView model = new ModelAndView();
        model.addObject("command", formBean);
...
        return model;
 
    }
 
    @ModelAttribute("postBean")
    public ItemBean initBeanForPost() {
 
        ItemBean bean = new ItemBean();
        populateBean(bean, true);
        return bean;
 
    }

Note that its always a good practice to specify the name in the @ModelAttribute annotation. This way you can pre-load different beans for different methods. As shown in the example above, the name specified in the @ModelAttribute annotation in the request handler method checkout() and that in the method initBeanForPost() should be an exact match. By the way, this works for GET as well as POST or any other request type.

How do I do the validation?

Spring version 3.x onwards, doing validations have become very easy. I will describe this in the following few steps:

1. Write a Validator

Your validator should implement the Validator interface. It has two methods that you need to override. The supports() method makes sure that the Validator can validate a given FormBean. The validate() method does the actual job of validating. A typical Validator will look like this:

class ItemValidator implements Validator {
 
    @Override
    public void validate(Object target, Errors errors) {
 
        ItemBean bean = (ItemBean) target;
 
        if (bean.getTotalPrice() == 0) {
            errors.rejectValue("totalPrice", "noItemsSelected");
        }
 
    }
 
    @Override
    public boolean supports(Class<?> clazz) {
        return clazz == ItemBean.class;
    }
 
}

2. Set the Validator

You do this in a public method annotated with @InitBinder in the controller.

    @InitBinder
    public void initBinder(WebDataBinder binder, WebRequest webRequest) {
 
        if (binder.getTarget() instanceof ItemBean) {
 
            binder.setValidator(new ItemValidator());
 
        }
 
    }

I am setting the validator inside the if (binder.getTarget() instanceof ItemBean) block as otherwise I get an ugly exception in case there are any validation errors. I am putting the stack trace below:


java.lang.IllegalStateException: Invalid target for Validator [com.swayam.demo.web.controller.ItemController$1@11e7cc6]: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object ‘postBean’ on field ‘totalPrice’: rejected value [0.0]; codes [noItemsSelected.postBean.totalPrice,noItemsSelected.totalPrice,noItemsSelected.float,noItemsSelected]; arguments []; default message [null]] with root cause
java.lang.IllegalStateException: Invalid target for Validator [com.swayam.demo.web.controller.ItemController$1@11e7cc6]: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object ‘postBean’ on field ‘totalPrice’: rejected value [0.0]; codes [noItemsSelected.postBean.totalPrice,noItemsSelected.totalPrice,noItemsSelected.float,noItemsSelected]; arguments []; default message [null]
at org.springframework.validation.DataBinder.setValidator(DataBinder.java:472)
at com.swayam.demo.web.controller.ItemController.initBinder(ItemController.java:63)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.doInvokeMethod(HandlerMethodInvoker.java:710)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.initBinder(HandlerMethodInvoker.java:329)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.updateModelAttributes(HandlerMethodInvoker.java:691)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:417)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:402)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:771)

3. Use @Valid before the Form Bean

This is a new feature added in release 3.x. Now the validation happens with JSR-303 Bean Validation API. You can find the jar here. If you are using Maven, you can add the following dependency:

		<dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<version>1.0.0.GA</version>
		</dependency>

This is how you use it:

    @RequestMapping(value = "/checkout.htm", method = RequestMethod.POST)
    public ModelAndView checkout(
            @Valid ItemBean formBean,
            BindingResult errors) {
 
        ModelAndView model = new ModelAndView();
...
        return model;
 
    }

The Form Bean is validated with the set Validator by the framework. Note that for Spring 2.5.x, you need to make the call to validator manually inside the handler method:

...
        Validator validator = new ItemValidator();
        validator.validate(formBean, errors);    
...

In my Validator, how do I use messages from properties file for i18n?

Lets say, my properties file is Messages.properties located at /com/swayam/demo/web/res/. The contents are:

noItemsSelected=You have not selected any items

I have to create an instance of MessageSource in my Spring conf file as shown:

	<bean id="messageSource"
		class="org.springframework.context.support.ResourceBundleMessageSource">
		<property name="basenames">
			<list>
				<value>com.swayam.demo.web.res.Messages</value>
			</list>
		</property>
	</bean>

Now I can use the property key in my validate() method as shown:

    @Override
    public void validate(Object target, Errors errors) {
 
        ItemBean bean = (ItemBean) target;
 
        if (bean.getTotalPrice() == 0) {
            errors.rejectValue("totalPrice", "noItemsSelected");
        }
 
    }

How to display the Validation messages on my JSP?

First, from the controller, you have to pass an instance of BindingResult to the JSP.

    @RequestMapping(value = "/checkout.htm", method = RequestMethod.POST)
    public ModelAndView checkout(
            @Valid ItemBean formBean,
            BindingResult errors) {
 
        ModelAndView model = new ModelAndView();
        model.addObject("command", formBean);
 
        if (errors.hasErrors()) {
            model.setViewName("Item");
            model.addObject("errors", errors);
        } else {
            model.setViewName("Checkout");
        }
 
        return model;
 
    }

And this is how the JSP looks like:

		<c:if test="${ not empty errors.allErrors }"> 
		<div style="text-align:center; color:red;">
			<ul>
				<c:forEach var="error" items="${errors.allErrors}">
					<li><spring:message code="${error.code}"  text="${error.defaultMessage}"/></li>
				</c:forEach>
			</ul>
		</div>
		</c:if>

How do I bind a complex object to my JSP form field?

Often we have to display a complex object as text. The best example I can take is that of a Date. Its a java.util.Date in the model. How do I map this to a text field in my JSP? You have to extend the PropertyEditorSupport and override the following methods:
1. getAsText(): Converts an Object to its String representation. Used for displaying a model object in the JSP.
2. setAsText(String text): Converts the text from the input field to the complex object that the model understands.
A typical implementation would look like this:

class DateEditorSupport extends PropertyEditorSupport {
 
    private static final Logger LOG = Logger.getLogger(DateEditorSupport.class);
 
    private final Format formatter;
 
    DateEditorSupport(String dateFormat) {
        formatter = new SimpleDateFormat(dateFormat);
    }
 
    public String getAsText() {
 
        String date = null;
 
        Object value = getValue();
 
        if (value instanceof Date) {
 
            date = formatter.format(value);
 
        } else {
            throw new java.lang.IllegalArgumentException("Expecting a "
                    + Date.class.getName() + " class, got "
                    + value.getClass().getName());
        }
 
        return date;
 
    }
 
    public void setAsText(String text) {
 
        try {
 
            Date date = (Date) formatter.parseObject(text);
            setValue(date);
 
        } catch (ParseException e) {
            LOG.fatal("error setting date for String: " + text, e);
        }
 
    }
 
}

Then, in the @InitBinder method, you need to register this against the class:

    @InitBinder
    public void initBinder(WebDataBinder binder, WebRequest webRequest) {
 
        binder.registerCustomEditor(Date.class, new DateEditorSupport(
                "dd/MM/yyyy"));
 
    }

Putting it all together

You will find the sources here. Its an Eclipse project, using Maven. In order to get all the libraries, install Maven and run:
mvn eclipse:clean eclipse:eclipse -DdownloadSources=true -DdownloadJavadocs=true -Dwtpversion=2.0
You can also run the war file directly and go to http://localhost:8080/SpringMVC/ to see it in action.

August 24, 2009

Integrating Spring (MVC) and BlazeDS (flex)

Filed under: flex, java, spring — Tags: , , , — paawak @ 22:33

Disclaimer:

I am not an expert in either Spring MVC or Flex and this article is not about fundamentals of Flex/BlazeDS. Its just how to get it working. Quickly.

Prologue:

I was playing around with Flex for the past couple of days. Being a Java developer, I was naturally inclined towards trying out BlazeDS so that I can use Flex as a front-end to my Java backend. The thing that I suffered from most was a huge information deluge. I was flooded by loads of blogs, articles and tutorials which made my life miserable. Finally, I saw a saviour in: Bare Bones BlazeDS Object Remoting, which explains how to create a BlazeDS and Java integration. Of course this does not include spring integration. Then I downloaded Spring BlazeDS, and went through the samples. Then I kind of combined these two and figured out how to make it work. The challenge here is that I have existing screens which are pure Spring MVC stuff. I am trying to embed flex in some of them, and have BlazeDS integration with my existing spring beans. Read on…

First things first: directory structure

My project directory looks like:

directory-structure1

Details:

  1. src/main/java contains all my java files
  2. src/main/resources has all config files like hibernate.cfg.xml, property files, etc.
  3. src/main/webapp has the WEB-INF folder and the jsps
  4. src/main/webapp/flash has all the compiled .swf files to be embedded in the jsps
  5. src/main/flex has all the .mxml and action script files

Config files required for BlazeDS

You would need these three config files under the WEB-INF/flex directory:

services-config.xml

This is the most important file. This defines the various channels that would be used for client/server communication by the BlazeDS

<?xml version="1.0" encoding="UTF-8"?>
<services-config>
	<services>
		<service-include file-path="remoting-config.xml" />
		<default-channels>
			<channel ref="my-amf" />
		</default-channels>
	</services>
 
	<channels>
		<channel-definition id="my-amf"
			class="mx.messaging.channels.AMFChannel">
			<endpoint
				url="http://{server.name}:{server.port}/{context.root}/spring/messagebroker/amf"
				class="flex.messaging.endpoints.AMFEndpoint" />
			<properties>
				<polling-enabled>false</polling-enabled>
			</properties>
		</channel-definition>
	</channels>
 
	<logging>
		<!-- You may also use flex.messaging.log.ServletLogTarget -->
		<target class="flex.messaging.log.ConsoleTarget" level="info">
			<properties>
				<prefix>[BlazeDS] </prefix>
				<includeDate>false</includeDate>
				<includeTime>false</includeTime>
				<includeLevel>true</includeLevel>
				<includeCategory>false</includeCategory>
			</properties>
			<filters>
				<pattern>Endpoint.*</pattern>
				<pattern>Service.*</pattern>
				<pattern>Configuration</pattern>
			</filters>
		</target>
	</logging>
 
</services-config>

remoting-config.xml

This defines the remoting adapter used by the flex client.

<?xml version="1.0" encoding="UTF-8"?>
<service id="remoting-service"
    class="flex.messaging.services.RemotingService">
    <adapters>
        <adapter-definition id="java-object" class="flex.messaging.services.remoting.adapters.JavaAdapter" default="true"/>
    </adapters>
 
    <default-channels>
        <channel ref="my-amf"/>
    </default-channels>
 
  </service>

BlazeDS/Spring integration

To integrate Spring with BalzeDS, you have to touch upon these existing files:

web.xml

1. Adding the flex listener

Add the flex.messaging.HttpFlexSession listener.

2. URL mapping

Note that in the services-config.xml, the url of the default channel my-amf is http://{server.name}:{server.port}/{context.root}/spring/messagebroker/amf. You have to pass any url with this pattern to the Spring front end handler servlet, the org.springframework.web.servlet.DispatcherServlet. I assume that you already have a similar servlet defined for your Spring MVC. You should use the same servlet (so that you can re-use the same beans in flex) to map these urls. I am assuming the name of the existing DispatcherServlet is dispatcher.

This is how the modified web.xml looks like:

...
    <!-- *******************     START flex       **************************** -->
 
    <!-- Http Flex Session attribute and binding listener support -->
    <listener>
        <listener-class>flex.messaging.HttpFlexSession</listener-class>
    </listener>
 
    <!-- The front controller of this Spring Web application, responsible for handling all application requests -->
 
    <!-- Map all *.spring requests to the DispatcherServlet for handling -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/spring/messagebroker/*</url-pattern>
    </servlet-mapping>    
 
    <!-- *******************     END flex       **************************** -->
...

dispatcher-servlet.xml

This xml already has the existing Spring MVC beans. This has to be modified to expose existing beans to the BlazeDS remoting as services. The xml header has to be modified as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:flex="http://www.springframework.org/schema/flex"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/flex 
           http://www.springframework.org/schema/flex/spring-flex-1.0.xsd"
           default-lazy-init="true">
 
...

Then, the body has to be modified by adding:

...
    <!-- The default id of this bean is *_messageBroker*. It takes all config files by default.-->
    <flex:message-broker/>
    <bean class="org.springframework.flex.servlet.MessageBrokerHandlerAdapter"/>
 
    <!-- if this is not present, it gives a big exception -->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
<!-- Maps all flex requests from blaze-ds to the flex message broker. You can re-use an existing bean instance -->
    <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <value>                                
                /messagebroker/*=_messageBroker
            </value>
        </property>
        <property name="order" value="0"/>
    </bean>
...

Finally, this is how you expose a bean as a service for remoting:

...
    <bean id="accountGroupManager" class="com.swayam.exp.AccountGroupManager"/>       
    <!-- Expose spring bean to blazeds for remoting. This will be available as a remote object with an id *account* -->
    <flex:remoting-destination ref="accountGroupManager" destination-id="account" />
...

This is how the AccountGroupManager looks like:

public class AccountGroupManager {
 
 
    public AccountGroupManager() {
 
        System.out
                .println("*****************AccountGroupManager.AccountGroupManager()");
    }
 
    public String save(String name, String desc) {        
// dummy code, returning static string
        return "40";
    }
 
 
}

Coding the mxml

I am using eclipse to code the NewAccountGroup.mxml. This is how it looks like:

<?xml version="1.0" encoding="UTF-8" ?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
 
<!-- Associate client with BlazeDS destination via RemoteObject. -->
<mx:RemoteObject id="accountGroupRemote" destination="account" />
 
    <mx:Form>
        <mx:FormItem label="Group Name:">
            <mx:TextInput id="groupName"/>
        </mx:FormItem>
 
        <mx:FormItem label="Description:">
            <mx:TextInput id="description"/>
        </mx:FormItem>
 
        <mx:FormItem>
            <mx:Button label="Submit" click="accountGroupRemote.save(groupName.text, description.text);" />
        </mx:FormItem>
 
        <mx:FormItem label="Server's Response">
            <mx:Label text="{accountGroupRemote.save.lastResult}" />
        </mx:FormItem>
 
    </mx:Form>
</mx:Application>

Compiling mxmls to swf

The one thing you have to keep in mind is that, you have to compile the mxmls along with the services-config.xml. Note that you have something like url=”http://{server.name}:{server.port}/{context.root}/spring/messagebroker/amf” in there. While the server.name and server.port will be taken by default by the client, you have to specify the context.root during the compile time, as an argument. This is the context of your webapp. I am using the mxmlc that comes with the Flex SDK from the command prompt (you can add it to the PATH). First you cd to the src/main/flex directory. Here you go:

mxmlc -services ../webapp/WEB-INF/flex/services-config.xml -strict=true -debug=true -context-root=/ -show-actionscript-warnings=true -output ../webapp/flash/NewAccountGroup.swf NewAccountGroup.mxml

Note how you specify the services-config.xml.

So far so good. Now we will have to embed the generated flash into our jsp. This how it is done:

...
<p>
 
    <object id="NewAccountGroup" align="middle"
    codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0" 
    classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">
    <param name="allowScriptAccess" value="sameDomain" />
    <param name="quality" value="high" />
    <param name="bgcolor" value="#ffffff" />
    <embed src="/flash/NewAccountGroup.swf" width="1000" height="400"
    quality="high" bgcolor="#ffffff" name="NewAccountGroup" align="middle" 
    allowScriptAccess="sameDomain" type="application/x-shockwave-flash" 
    pluginspage="http://www.macromedia.com/go/getflashplayer" />
    </object>
 
</p>
 
...

Finally, just a word for Maven users, these are the arifacts you have to use:

...
    <dependencies>
...
        <!-- *******************     START flex       **************************** -->
 
        <!-- blaze ds -->
        <dependency>
            <groupId>com.adobe.blazeds</groupId>
            <artifactId>blazeds-common</artifactId>
            <version>${blazeds.version}</version>
        </dependency>
        <dependency>
            <groupId>com.adobe.blazeds</groupId>
            <artifactId>blazeds-core</artifactId>
            <version>${blazeds.version}</version>
        </dependency>
        <dependency>
            <groupId>com.adobe.blazeds</groupId>
            <artifactId>blazeds-opt</artifactId>
            <version>${blazeds.version}</version>
        </dependency>
        <dependency>
            <groupId>com.adobe.blazeds</groupId>
            <artifactId>blazeds-proxy</artifactId>
            <version>${blazeds.version}</version>
        </dependency>
        <dependency>
            <groupId>com.adobe.blazeds</groupId>
            <artifactId>blazeds-remoting</artifactId>
            <version>${blazeds.version}</version>
        </dependency>
 
        <!-- spring-flex -->
        <dependency>
            <groupId>org.springframework.flex</groupId>
            <artifactId>spring-flex</artifactId>
            <version>${spring.flex.version}</version>
        </dependency>
 
        <dependency>
			<groupId>xalan</groupId>
			<artifactId>xalan</artifactId>
			<version>${xalan.version}</version>
        </dependency>
 
        <dependency>
            <groupId>edu.oswego.util</groupId>
            <artifactId>concurrent</artifactId>
            <version>${concurrent.version}</version>
        </dependency>
 
        <!-- *******************     END flex       **************************** -->
...        
    </dependencies>
...
    <properties>
        <blazeds.version>3.2.0.3978</blazeds.version>
        <spring.flex.version>1.0.0.RELEASE</spring.flex.version>
        <xalan.version>2.7.0</xalan.version>
        <concurrent.version>1.3.3</concurrent.version>
    </properties>
...

For others, you can always expand the blazeds.war and put all the jars inside the WEB-INF/lib.

The only pain point for this approach is that every time you change your context root, you have to recompile all your flex against the services-config.xml along with a new context.root argument.

This is how the end screen looks like (in the browser):

newaccountgroup

July 13, 2008

How to pass an object from the controller to the view in SpringMVC?

Filed under: java, spring — Tags: , — paawak @ 11:38

The scenario is: I want to show a list of items on my view from the database. My controller picks up the data, passes it to the view which displays it. I am using a sub class of org.springframework.web.servlet.mvc.SimpleFormController:

@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {

ModelAndView view = new ModelAndView(”complaintListings”);
List<String> strings = new ArrayList<String>();
strings.add(”AAAAAAAA”);
strings.add(”BBBBBBB”);
strings.add(”CCCCCCCC”);
strings.add(”EEEEEEEE”);
strings.add(”FFFFFFFFF”);

//set the object to view
view.addObject(”testStrings”, strings);

return view;

}

The view looks like this:

<c:forEach var=”string” items=”${testStrings}”>
<c:out value=”${string}”/>
<br />
</c:forEach>

Its that simple!

Powered by WordPress