Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

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

DZone's Guide to

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

· Java Zone ·
Free Resource

The CMS developers love. Open Source, API-first and Enterprise-grade. Try BloomReach CMS for free.

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.

BloomReach CMS: the API-first CMS of the future. Open-source & enterprise-grade. - As a Java developer, you will feel at home using Maven builds and your favorite IDE (e.g. Eclipse or IntelliJ) and continuous integration server (e.g. Jenkins). Manage your Java objects using Spring Framework, write your templates in JSP or Freemarker. Try for free.

Topics:
java ,netbeans ,architecture ,tutorial ,tools & methods ,extjs ,apache maven

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}