Wednesday 27 January 2010

MVC on the Google App Engine with Spring 3.0

On the Google App Engine (GAE) I can deploy and test applications very quickly. What I need now is a programming "toolkit" wich is essential and efficient.
I thought about using Grails but there is a lot of dependencies I don't want.
An implementaion based solely on the Servlet API wolud be essential but time consuming.
I decide to use the last release of the Springframework to see if it is possible to set up a project template for the GAE.

The Application Template

At this time I need two modules:
  • home with the pages "home", "about" and "description".
  • stage with some different controllers service each a page called "list" and a page called "details".
Home
This section is display-only and supports the GET method only. "Welcome" contains the list of the RSS feeds into the HEADER, but if something goes wrong the feed are simply missing. No error message is displayed.
Stage
The stage is a restricted area containing lists and forms.
This section implements the feed registration where "list" contains a short summary of all the available feeds. From this page I may add or delete a feed.
The "details" page is a form containing all the fields. The feed may be modified or deleted.

The Project Layout

The different parts of the application have to be easily identified on the filesystem. By convention the path of the java packages is build on a prefix (application.web) and using the name of the section:
application.web.home 
application.web.stage
Each section has at least one controller. The name is composed by the section name and the word controller.
application.web.home.HomeController 
application.web.stage.StageController
The stage sections contains more controllers one for each editor with restricted access. At this time I have the only the "feed" editor.
application.web.stage.FeedController
I use a similar convention for the views (JSP pages). The views are placed under WEB-INF/views:
WEB-INF/views/home/about.jsp 
WEB-INF/views/home/description.jsp 
WEB-INF/views/home/home.jsp 
 
WEB-INF/views/stage/stage.jsp 
WEB-INF/views/stage/feed.jsp 
WEB-INF/views/stage/feedDetails.jsp
The whole layout was created using Maven ( mvn archetype:generate ). I did merge "maven-archetype-quickstart" and "maven-archetype-webapp".

The Controllers

The home controller is quite simple:
package ch.clx.application.web.home;
...
@Controller
@RequestMapping("/home")
public class HomeController {
    
  @RequestMapping(method = RequestMethod.GET)
  public String get(final ModelMap model) {
    ...
    model.addAttribute("version", Version.version);
    return "home";
  }
    
  @RequestMapping(value="/about", method = RequestMethod.GET)
  public String about(final ModelMap model) {
    model.addAttribute("version", Version.version);
    return "about";
  }
    
  @RequestMapping(value="/description", method = RequestMethod.GET)
  public String description(final ModelMap model) {
    model.addAttribute("version", Version.version);
    return "description";
  }
}
The feed controller is a simple CRUD editor. The GET method returns the list of the feeds (the feed view).
  • Add (C) creates a new entry and displays the form with all the fields (detail view).
  • Delete (D) removes an existing entry.
  • Edit (R) displays an existing entry (detail view).
  • Store (U) updates an existing entry.
package ch.clx.application.web.stage;  
...  
@Controller  
@RequestMapping("/stage/feed")  
public class FeedController {  
      
  @RequestMapping(method = RequestMethod.GET)  
  public String get(final ModelMap model) {  
    ...  
    model.addAttribute("version", Version.version);  
    return "feed";  
  }  
      
  @RequestMapping(value = "/add", method = RequestMethod.POST)  
  public String add(  
    @RequestParam("id") final String id,   
    final ModelMap model) {  
    ...  
    model.addAttribute("version", Version.version);  
    return "feedDetails";  
  }  
      
  @RequestMapping(value = "/delete", method = RequestMethod.POST)  
  public String add(  
    @RequestParam("id") final String id,   
    final ModelMap model) {  
    ...  
    model.addAttribute("version", Version.version);  
    return "feed";  
  }  
      
  @RequestMapping(value="/edit", method = RequestMethod.GET)  
  public String edit(  
    @RequestParam("id") final String id,   
    final ModelMap model) {  
    ... 
    model.addAttribute("version", Version.version);  
    return "feedDetails";  
  }  
      
  @RequestMapping(value="/store", method = RequestMethod.GET)  
  public String store(  
    @RequestParam("id") final String id, 
    ..., 
    final ModelMap model) {  
    ... 
    model.addAttribute("version", Version.version);  
    return "feed";  
  }  
}

The Context File

The context file is very simple because components and URL routing are automatically discovered through annotations.

<?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:p="http://www.springframework.org/schema/p" 
  xmlns:context="http://www.springframework.org/schema/context" 
  xsi:schemaLocation=" 
    http://www.springframework.org/schema/beans  
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-3.0.xsd" 
> 
<bean 
  class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/> 
<bean 
  class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/> 
 
<context:component-scan base-package="gae.application"/>

<bean 
  id="viewResolver" 
  class="org.springframework.web.servlet.view.ResourceBundleViewResolver"/>

</beans>

The Deployment Descriptor

web.xml
<?xml version="1.0" encoding="UTF-8"?> 
<web-app 
  id=2gae-application" 
  version="2.4" 
  xmlns="http://java.sun.com/xml/ns/j2ee" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee  
      http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"/> 
  <display-name>gae-application</display-name> 
  <servlet> 
    <display-name>Dispatcher Servlet</display-name> 
    <servlet-name>Dispatcher-Servlet</servlet-name> 
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/WebCtx.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping
    <servlet-name>Dispatcher-Servlet</servlet-name>
    <url-pattern>/bo/*</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

The GAE Deployment Descriptor

appengine-web.xml
<?xml version="1.0" encoding="UTF-8"?> 
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0"> 
<application>gae-application</application> 
<version>beta-1</version> 
<system-properties> 
<property 
  name="java.util.logging.config.file" 
  value="WEB-INF/classes/logging.properties"/>
</system-properties> 
</appengine-web-app>

The Maven Archetype

At the end of my experiment I pack my template application in a Maven archetype. I is enough to add the following pom.xml file to the project:
<?xml version="1.0" encoding="UTF-8"?>
<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>cloud.gae.application</groupId>
  <artifactId>gae-application</artifactId>
  <version>1.0</version>
  <packaging>war</packaging>
  ...
  <build>
    <finalName>${artifactId}</finalName>
    ...
  </build>
</project>
Now type mvn archetype:create-from-project to generate the archetype. Change the directory ( cd target/generated-sources/archetype ) and execute mvn install .
Using mvn archetype:generate now I may generate a GAE application ready to use. It does nothing but as starter kit for GAE applications is not bad.

No comments:

Post a Comment