Platinum Partner
java,netbeans,architecture,tutorial,tools & methods,extjs,apache maven

ExtJS 4.2, Spring MVC 3.2.4, and Maven Example Using NetBeans IDE 7.3

This tutorial will walk you through how to implement a CRUD (Create, Read, Update, Delete) DataGrid using ExtJS 4.2, Spring MVC 3.2.4 and Maven with NetBeans IDE 7.3.

What do we usually want to do with data?

  • Create (Insert)
  • Read / Retrieve (Select)
  • Update (Update)
  • Delete / Destroy (Delete)

In this example, I’m going to use JSON as a data format exchange between the browser and the server.

1. Create a new Project in NetBeans IDE

  1. Go to File > New Project or press ctrl+shift+N
  2. Select maven > Project from Archetype and click next
  3. In the search-box write extjs and select extjs-springmvc-webapp and click next
  4. Enter Project Name, Location, and Package and click finish
Finally you are going to get a project directory like this one:


2. Modify the project

At this moment you can run your project and it is going to show you a grid in which you can edit 2 users. However this archetype has a little problem. If you see the file UserService.java that is inside com.extjs.spring.services package, it uses a method called parseUserJson which is used by updateUser method. 

...
    /**
     * Update a user in the system
     */
    @RequestMapping(value = "/user/update", method = RequestMethod.POST)
    public Map updateUser( HttpServletRequest request, HttpServletResponse response, Principal principal
            , @RequestBody String json ) throws Exception
    {
        //TODO replace this with your real code here.
        Collection<User> parsedUsers = parseUserJson(json);

        // Update all of the users (client is sending us array of users in json)
        if ( parsedUsers != null )
        {
            for (User parsedUser : parsedUsers)
            {
                User localUser = users.get(parsedUser.getId());
                if ( localUser == null )
                {
                    throw new RuntimeException("Invalid User");
                }

                // save changes to local user
                localUser.setName(parsedUser.getName());
                localUser.setEmail(parsedUser.getEmail());
            }
        }


        Map results = new HashMap();
        results.put("succes", true);
        return results;
    }


    /**
     * Parse an json packet of user(s)
     */
    private Collection<User> parseUserJson( String json ) throws Exception
    {
        try
        {
            if ( json.startsWith("[") && json.endsWith("]") )
            {
                // array of users
                ObjectMapper mapper = new ObjectMapper();
                TypeReference ref = new TypeReference<Collection<User>>(){};
                Collection<User> user = (Collection<User>) mapper.readValue(json, ref);
                return user;
            }
            else
            {
                // Single object
                ObjectMapper mapper = new ObjectMapper();
                Collection<User> users = new ArrayList<User>();
                users.add( (User) mapper.readValue(json, User.class) );
                return users;
            }
        }
        catch (Exception ex)
        {
            throw new RuntimeException("Invalid USER Json");
        }
    }
...

The problem with that approach is that if we want to create more Models Objects or POJOs, we need to create a parser method for every controller. 

An option to solve this problem would be to create a base-controller and extend all new controllers from it. However, we still need to call the parser method every time that we read an object sent as @RequestBody parameter. Another option could be create a util or helper class that implements this method, and call it from controllers, but we still need to call that method every time that we need to deserialize a @RequestBody.

By default, in spring controllers, if we pass a class or a List<Class> to the @RequestBody parameter, it parses the string and converts the object directly. For example: 

    @RequestMapping(value = "/update", method = RequestMethod.POST)
    public void updateUser(@RequestBody List<User> inUsers) throws Exception    {
        for (User user : inUsers) {
            User localUser = users.get(user.getId());
            if (localUser == null) {
                throw new RuntimeException("Invalid User");
            }

            // save changes to local user
            localUser.setName(user.getName());
            localUser.setEmail(user.getEmail());
        }
    }

This could be the best solution, but we have another problem. When we only edit one row in our grid it sends the next json to the server.

{"id":"1","name":"Eds","email":"ed@sencha.com"}

As you can see, it sends the object without the square brackets "[..]" that represents an array. ie:

[{"id":"1","name":"Eds","email":"ed@sencha.com"}]

That means that it is only going to work when we edit two or more rows in the grid since it sends an array of objects, ie:

[{"id":"1","name":"Edss","email":"ed@sencha.com"},{"id":"2","name":"Tommys","email":"tommy@sencha.com"}]

There are two possible solutions:

  • Modify ExtJS Store to send always an array instead a single object. Since I don't know how to do that, and it could break other parts of the framework, I discarded this option
  • Create a CustomMappingJacksonHttpMessageConverter for Spring. To me this is a better option since doesn't affects anything in Spring Framework internally.
To do the second option we need to do 3 things:
  1. Create the CustomMappingJacksonHttpMessageConverter java Class.
  2. Instantiate that class inside the Spring Servlet configuration file ( in this case I am going to use XML)
  3. Add some Apache commons libraries to our pom.xml since are going to be required by our custom converter class.
1. Create the CustomMappingJacksonHttpMessageConverter java Class
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.springframework.http.converter.json;

import com.fasterxml.jackson.databind.JavaType;
import java.io.IOException;
import java.lang.reflect.Type;
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageNotReadableException;

/**
 *
 * @author luis
 */
public class CustomMappingJacksonHttpMessageConverter extends MappingJackson2HttpMessageConverter {

    @Override
    public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {

        JavaType javaType = getJavaType(type, contextClass);
        return readJavaType(javaType, inputMessage);
    }

    private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) {
        try {
            String inputJson =  IOUtils.toString(inputMessage.getBody());
            if(javaType.isCollectionLikeType()
                    && !(inputJson.startsWith("[") && inputJson.endsWith("]")) ) {
                inputJson = "[" + inputJson + "]";
            }
            return getObjectMapper().readValue(inputJson, javaType);
        } catch (IOException ex) {
            throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
        }
    }
}

2. Instantiate that class inside the Spring Servlet configuration file ( Web Pages > WEB-INF > services-servlet.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">


    <!--<mvc:annotation-driven/>-->

    <bean class='org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter'>
        <property name='messageConverters'>
            <list>
                <bean class='org.springframework.http.converter.json.CustomMappingJacksonHttpMessageConverter'/>
            </list>
        </property>
    </bean>

    <bean name='handlerMapping' class='org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'>
        <property name='useTrailingSlashMatch' value='false'></property>
    </bean>

    <!-- ****** REST API Service ****** -->
    <bean id="userService" class="com.extjs.spring.services.UserService" />


    <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <property name="mediaTypes">
            <map>
                <entry key="json" value="application/json" />
            </map>
        </property>
        <property name="defaultViews">
            <list>
                <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
                </bean>
            </list>
        </property>
    </bean>


</beans>

3. Add some Apache commons libraries to our pom.xml since are going to be required by our custom converter class.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.lvargas</groupId>
    <artifactId>extjs-spring</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>extjs-spring-webapp</name>
    <url>http://maven.apache.org</url>

    <properties>
        <java-version>1.6</java-version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <org.springframework.version>3.2.4.RELEASE</org.springframework.version>
        <org.springframework.security.version>3.2.4.RELEASE</org.springframework.security.version>
        <spring.integration.version>2.2.6.RELEASE</spring.integration.version>
    </properties>

    <dependencies>
        <!-- link to tomcat to get reference to servlet api -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>servlet-api</artifactId>
            <version>6.0.33</version>
        </dependency>


        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit-dep</artifactId>
            <version>4.9</version>
            <scope>test</scope>
        </dependency>


        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>apache-log4j-extras</artifactId>
            <version>1.1</version>
        </dependency>




        <!--
                Core utilities used by other modules.
                Define this if you use Spring Utility APIs (org.springframework.core.*/org.springframework.util.*)
        -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <!--
                Bean Factory and JavaBeans utilities (depends on spring-core)
                Define this if you use Spring Bean APIs (org.springframework.beans.*)
        -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <!--
                Aspect Oriented Programming (AOP) Framework (depends on spring-core, spring-beans)
                Define this if you use Spring AOP APIs (org.springframework.aop.*)
        -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <!--
                Application Context (depends on spring-core, spring-expression, spring-aop, spring-beans)
                This is the central artifact for Spring's Dependency Injection Container and is generally always defined
        -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <!--
                Various Application Context utilities, including EhCache, JavaMail, Quartz, and Freemarker integration
                Define this if you need any of these integrations
        -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <!--
                Transaction Management Abstraction (depends on spring-core, spring-beans, spring-aop, spring-context)
                Define this if you use Spring Transactions or DAO Exception Hierarchy
                (org.springframework.transaction.*/org.springframework.dao.*)
        -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <!--
                JDBC Data Access Library (depends on spring-core, spring-beans, spring-context, spring-tx)
                Define this if you use Spring's JdbcTemplate API (org.springframework.jdbc.*)
        -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <!--
                Object-to-Relation-Mapping (ORM) integration with Hibernate, JPA, and iBatis.
                (depends on spring-core, spring-beans, spring-context, spring-tx)
                Define this if you need ORM (org.springframework.orm.*)
        -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <!--
                Object-to-XML Mapping (OXM) abstraction and integration with JAXB, JiBX, Castor, XStream, and XML Beans.
                (depends on spring-core, spring-beans, spring-context)
                Define this if you need OXM (org.springframework.oxm.*)
        -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-oxm</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <!--
                Web application development utilities applicable to both Servlet and Portlet Environments
                (depends on spring-core, spring-beans, spring-context)
                Define this if you use Spring MVC, or wish to use Struts, JSF, or another web framework with Spring (org.springframework.web.*)
        -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <!--
                Spring MVC for Servlet Environments (depends on spring-core, spring-beans, spring-context, spring-web)
                Define this if you use Spring MVC with a Servlet Container such as Apache Tomcat (org.springframework.web.servlet.*)
        -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>


        <!--
                Support for testing Spring applications with tools such as JUnit and TestNG
                This artifact is generally always defined with a 'test' scope for the integration testing framework and unit testing stubs
        -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${org.springframework.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>extjs-spring-1.0-SNAPSHOT</finalName>
    </build>
    <description>A maven Archetype to create new EXTJS 4 project powered by a spring MVC 3.2.4 service.</description>
</project>

Finally you can run again and test your project.

{{ tag }}, {{tag}},

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}