Use a Spring InitBinder to Resolve Type Mismatch and Bind Exceptions in POST from Spring Framework MVC Forms to Controller Actions

As a follow up to the previous article on binding entities and their children to form objects in Spring Framework it’s important to know how to submit the values of form objects in a standard form back to the controller. In the previous example we had a “Parent” entity, with a dropdown “select” on the form where you could choose one of the available “Children” objects. We also had a “Description” textbox that allowed the user to type in a suitable description.

The actual form, a very simple interface with both these elements and a submit button looks like:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>

<c:url var="saveParentUrl" value="/parent/save" />
<form:form modelAttribute="parent" method="POST" action="${saveParentUrl}">
Description:  <form:input path="description"/>
Child:  <form:select path="child" id="child" items="${children}" itemValue="id" itemLabel="name"/>
<input type="submit" value="Save Parent" />
</form:form>

In this example, when the user clicks on the “Save Parent” button the form will be posted to the “parent” controller action “saveParent” at “/parent/save” where we can save the updated “Parent” object.

Now the problem is that the POST header only contains text strings, not a full description of the actual “Child” object we have chosen for “Parent”. When you try to save the “Parent” object in the controller action the result is an exception,  “org.springframework.validation.BeanPropertyBindingResult”. The error message itself tells you exactly what is happening:

default message [Failed to convert property value of type ‘java.lang.String[]’ to required type ‘models.Child’ for property ‘Child’; nested exception is java.lang.IllegalStateException: Cannot convert value of type ] to required type [models.Child] for property ‘Child’: no matching editors or conversion strategy found]

The message tells you that Spring can’t automatically convert from the POST string value of the “child” select option (actually “id” in our case) to an actual “Child” object. We need to convert this string to a “Child” object in order to save the updated “Parent” entity. This is easily achieved using an “InitBinder” method within the controller class and a “@Validated” annotation on the “save” action’s “Parent” “@ModelAttribute” argument.

The code for the controller action looks like the following. Note that in this example I am using a “parentService” to pull the protocol to be updated from a “Parent” stored in session. It is likely that you will have something different or even pass in the id as part of the POST:

// ParentController.java
@RequestMapping(value = "/save", method=RequestMethod.GET)
public ModelAndView saveParent (@Validated @ModelAttribute("parent") Parent formparent, HttpServletRequest request )
{
    ...
    // find parent to update in database using parent service
    Parent parent = parentService.findById(sessionparent.getId());

    // set parent description and Child based on user input and save
    parent.setDescription(formparent.getDescription());
    parent.setChild(formparent.getChild());
    parentService.saveParent(parent);
    ...
}

The “formparent” is the “Parent” object passed in POST and mapped by Spring using the “@ModelAttribute” annotation. The “InitBinder” required to map the “Child” id passed in as part of POST to an actual “Child” object is:

@InitBinder
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
    binder.registerCustomEditor(Child.class, "child", new PropertyEditorSupport() {
    @Override
    public void setAsText(String text) {
        Child ch = childService.findById(Long.parseLong(text));
        setValue(ch);
    }
    });
}

What this does is register a binder for the “child” object when it is passed from the form to Spring that converts the value to an actual object. By overriding the “SetAsText” function I use a “childService” service to retrieve the correct “Child” entity based on the text value passed in POST. Spring can now understand and convert the POST values to actual objects and the “Parent” entity can be safely modified by the “saveParent” controller action mapped to “/parent/save”. More information and some alternative methods are available at Develop and Conquer and Empire5.

Quickly add a Virtual Host to Apache Tomcat to Map URLs to Java Web Applications

Adding a virtual host to Apache Tomcat is really easy and just involves pointing the address at the right directory. This means you can map web addresses pointing at your server IP to Java web applications running on Apache Tomcat. This assumes you already have a hostname such as “websiteaddress.com” pointing to your server with an IP (for example) of 1.2.3.4.

Just modify you server.xml file (in our Ubuntu Server tomcat7 setup in “/usr/share/tomcat7/conf/server.xml”) to include the following at the end of the file in the “Engine” element after the “Host” element for localhost:


      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

        <!-- SingleSignOn valve, share authentication between web applications
             Documentation at: /docs/config/valve.html -->
        <!--
        <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
        -->

        <!-- Access log processes all example.
             Documentation at: /docs/config/valve.html
             Note: The pattern used is equivalent to using pattern="common" -->
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

      </Host>

      <Host name="websiteaddress.com" appBase="/usr/share/tomcat7/webapps/myapplication">
        <Context path="" docBase="."/>
      </Host>

Note here that the web address is http://websiteaddress.com and is being resolved to the application at “/usr/share/tomcat7/webapps/myapplication”. More information about this “Host” element is available at the Apache Tomcat website.

Easily set up and automatically start Apache Tomcat 7 Java web server in Ubuntu Linux

Apache Tomcat is actually easier than the standard Apache webserver to set up, which is great news if you are working with Java based web applications. All you need to do is download it and make sure it starts with whichever linux distribution you are using. Deploying applications in standard WAR format is really easy as well due to the simple web based management interface.

In my case I wanted Tomcat to start with Ubuntu and sit on the default port 8080 so I could have it running alongside my standard Apache webserver for PHP. We were developing a Spring application and used Maven to build and compile to a single deployable WAR file. You must have Java installed and set up for this to work. To check you have Java set up type:

java -version

This should tell you what version of java you have installed (hopefully Java 1.7). You also need to check that the “JAVA_HOME” variable is set by typing:

echo $JAVA_HOME

If you don’t get something like “/usr/lib/jvm/jdk1.7.0_09″ please install Java following my installation instructions in a previous post.

To install Apache Tomcat first of all I downloaded the latest copy of Tomcat 7 from mirrorservice.org using wget run from my home directory:

wget http://www.mirrorservice.org/sites/ftp.apache.org/tomcat/tomcat-7/v7.0.32/bin/apache-tomcat-7.0.32.tar.gz

Please note that the version I downloaded may not be available or there may be a newer version so check http://www.mirrorservice.org/sites/ftp.apache.org/tomcat/tomcat-7/ first before running the wget.

“wget” will download the file, which then needs to be extracted:

tar xvzf apache-tomcat-7.0.32.tar.gz

Now you will have a folder “apache-tomcat-7.0.32″ in your home directory. This needs placing somewhere sensible so copy it to “/usr/share/tomcat7″ using:

sudo mv apache-tomcat-7.0.32/ /usr/share/tomcat7

Now you can test your Tomcat install works with its default settings by starting it up. Note: before you do this you need to set the “JAVA_HOME” variable otherwise you will get errors (see my previous post).

To start up Tomcat navigate to “/usr/share/tomcat7″ and run “startup.sh”:

cd /usr/share/tomcat7

./startup.sh

With the default settings you should now be able to reach your Tomcat server home page by navigating to “http://your.ip.add.ress:8080″ where you should hopefully see the homepage and a nice message saying:

“If you’re seeing this, you’ve successfully installed Tomcat. Congratulations!”

Now we need to set up management users for the manager app so we can easily deploy our WAR files containing our Java web applications. You need to edit “/usr/share/tomcat7/conf/tomcat-users.xml”:

sudo nano /usr/share/tomcat7/conf/tomcat-users.xml

Now add the following lines within the “<tomcat-users>” block to give access to the manager GUI:

<role rolename=”manager-gui”/>
<user username=”MANAGERUSER” password=”YOURPASSWORD” roles=”manager-gui”/>

Now you will be able to log in to the manager GUI at “http://your.ip.add.ress:8080/manager/html” using the login details MANAGERUSER and password YOURPASSWORD. You can deploy applications and generally manage your Tomcat install from here.

The final thing to do is to set up Tomcat so that it starts every time your server starts. This is pretty easy as all you need to do in Ubuntu is edit the “/etc/init.d/tomcat7″ file:

sudo nano /etc/init.d/tomcat7

Now enter the following lines:


# Tomcat auto-start
#
# description: Auto-starts tomcat
# processname: tomcat
# pidfile: /var/run/tomcat.pid

case $1 in
start)
sh /usr/share/tomcat7/bin/startup.sh
;;
stop)
sh /usr/share/tomcat7/bin/shutdown.sh
;;
restart)
sh /usr/share/tomcat7/bin/shutdown.sh
sh /usr/share/tomcat7/bin/startup.sh
;;
esac
exit 0

Set the permissions for the file:

sudo chmod 755 /etc/init.d/tomcat7

Add Tomcat to system startup as a service using the command:

sudo update-rc.d tomcat7 defaults

Now you can test that Tomcat is set up as a service using:

sudo service tomcat7 restart

Now to check everything is working on system startup reboot your machine using:

sudo reboot now

Navigate to “http://your.ip.add.ress:8080″ where the Tomcat home page should appear with no problems. Note: If you are having problems reaching your Tomcat home page make sure you have opened port 8080 on your server’s firewall.

It’s definitely worth reading some of the documentation on Tomcat, plenty of which is linked off your newly installed Tomcat home page. You should now have all you need to deploy your Java web applications as WAR files which is really easy using the manager GUI provided by Tomcat.