ExtJS 4.2, Spring MVC 3.2.4, and Maven Example Using NetBeans IDE 7.3
Join the DZone community and get the full member experience.
Join For FreeThis 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
- Go to File > New Project or press ctrl+shift+N
- Select maven > Project from Archetype and click next
- In the search-box write extjs and select extjs-springmvc-webapp and click next
- Enter Project Name, Location, and Package and click finish
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.
- Create the CustomMappingJacksonHttpMessageConverter java Class.
- Instantiate that class inside the Spring Servlet configuration file ( in this case I am going to use XML)
- Add some Apache commons libraries to our pom.xml since are going to be required by our custom converter 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.
Opinions expressed by DZone contributors are their own.
Comments