ICE09 . playing with java, scala, groovy and spring .

Playing with Spring

Spring 3, REST, XML – running on the Google App Engine

Posted by ice09 on March 15, 2010

Note: The complete sourcecode is available on Google Code here. The projects can be imported as Eclipse 3.5.x or SpringSource Tool Suite 2.3.x projects (the Google Eclipse Plugin has to be installed for the gae project).

Introduction

Having already used Spring 3 in combination with REST and JSON on the Google App Engine, I will sum it up once again to…

  1. …explore the changes between the Spring Milestones and the Release (3.0.1)
  2. …use a specified format (using XML Schema) for data exchange
  3. …experiment with XML, data persistence on the GAE and Spring 3 REST capabilities

Intention

We will create a simple REST service which takes User objects, which itself include Location and Buddy references. The format will be XML, since we want to specify the format explicitly with means of XML Schema. Usual REST mechanisms will be used, therefore HTTP-GET and HTTP-POST is supported.

The User Model

The User XML Schema

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/User"
	xmlns:tns="http://www.example.org/User" elementFormDefault="qualified">

	<element name="User">
		<complexType>
			<sequence>
				<element name="id" type="long" />
				<element name="firstName" type="string"></element>
				<element name="lastName" type="string"></element>
				<element name="birthday" type="dateTime"></element>
				<element name="buddies" type="tns:BuddyType" />
				<element name="locations" type="tns:LocationType" />
			</sequence>
		</complexType>
	</element>

	<complexType name="BuddyType">
		<sequence>
			<element name="id" type="long" />
			<element name="buddyid" type="long" minOccurs="0" maxOccurs="unbounded" />
		</sequence>
	</complexType>

	<complexType name="LocationType">
		<sequence>
			<element name="id" type="long" />
			<element name="location">
				<complexType>
					<sequence>
						<element name="longitude" type="int" minOccurs="1" maxOccurs="1" />
						<element name="latitude" type="int" minOccurs="1" maxOccurs="1" />
						<element name="timestamp" type="dateTime" minOccurs="1" maxOccurs="1" />
					</sequence>
				</complexType>
			</element>
		</sequence>
	</complexType>

</schema>

After a POST request has been sent, it will respond the submitted object, together with a Location attribute in the respond header, which identifies the new resource. It can be retrieved with a corresponding GET request.

The client will be using the RestTemplate. Furthermore, soapUI will be configured for client requests.
Finally, the generated project will be migrated to the Google App Engine.

Preparation

  1. Download either Eclipse IDE for Java EE Developers 3.5.2 or the SpringSource Tool Suite 2.3.1 (STS). Sample are done in STS, however the Eclipse JEE edition should be perfectly alright for these samples.
  2. Install the Google Plugin for Eclipse. This will enable Google App Engine (GAE) deployment later on.
  3. If you are not using the STS, you will have to install the m2eclipse plugin.

Setup

  1. If you want to use the Eclipse WTP features, like “Run on server…”, you will have to prepare your environment for a coexistence of Eclipse/WTP and Maven 2. Create a new project as described in this howto.
  2. Optionally:If you are having problems with Maven errors due to wring JRE settings, try setting the -vm argument in the eclipse.ini (or sts.ini) file as mentioned here.
  3. Depending on the setup of the maven-webapp-archetype, you might have to set the project compliance to Java 5 and create the src/main/java folder as well as add it to the classpath.

    The dependencies and plugin configurations in the main pom.xml

    <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>org.example</groupId>
    	<artifactId>blogrestsample</artifactId>
    	<packaging>war</packaging>
    	<version>0.0.1-SNAPSHOT</version>
    	<name>blogrestsample Maven Webapp</name>
    	<repositories>
    		<repository>
    			<id>maven2-repository.dev.java.net</id>
    			<name>Java.net Maven 2 Repository</name>
    			<url>http://download.java.net/maven/2</url>
    		</repository>
    		<repository>
    			<id>maven-repository.dev.java.net</id>
    			<name>Java.net Maven 1 Repository (legacy)</name>
    			<url>http://download.java.net/maven/1</url>
    			<layout>legacy</layout>
    		</repository>
    		<repository>
    			<id>springsource maven repo</id>
    			<url>http://maven.springframework.org/milestone</url>
    		</repository>
    	</repositories>
    	<pluginRepositories>
    		<pluginRepository>
    			<id>maven2-repository.dev.java.net</id>
    			<url>http://download.java.net/maven/2</url>
    		</pluginRepository>
    		<pluginRepository>
    			<id>maven-repository.dev.java.net</id>
    			<url>http://download.java.net/maven/1</url>
    			<layout>legacy</layout>
    		</pluginRepository>
    	</pluginRepositories>
    	<dependencies>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>org.springframework.web.servlet</artifactId>
    			<version>3.0.0.RC1</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>org.springframework.oxm</artifactId>
    			<version>3.0.0.RC1</version>
    		</dependency>
    		<dependency>
    			<groupId>commons-collections</groupId>
    			<artifactId>commons-collections</artifactId>
    			<version>3.2</version>
    		</dependency>
    		<dependency>
    			<groupId>javax.xml.bind</groupId>
    			<artifactId>jaxb-api</artifactId>
    			<version>2.0</version>
    		</dependency>
    		<dependency>
    			<groupId>javax.servlet</groupId>
    			<artifactId>servlet-api</artifactId>
    			<version>2.5</version>
    			<scope>provided</scope>
    		</dependency>
    	</dependencies>
    	<build>
    		<plugins>
    			<plugin>
    				<artifactId>maven-compiler-plugin</artifactId>
    				<configuration>
    					<source>1.5</source>
    					<target>1.5</target>
    				</configuration>
    			</plugin>
    			<plugin>
    				<groupId>org.mortbay.jetty</groupId>
    				<artifactId>maven-jetty-plugin</artifactId>
    				<version>6.1.10</version>
    				<configuration>
    					<scanIntervalSeconds>10</scanIntervalSeconds>
    					<stopKey>foo</stopKey>
    					<stopPort>9999</stopPort>
    				</configuration>
    				<executions>
    					<execution>
    						<id>start-jetty</id>
    						<phase>pre-integration-test</phase>
    						<goals>
    							<goal>run</goal>
    						</goals>
    						<configuration>
    							<scanIntervalSeconds>0</scanIntervalSeconds>
    							<daemon>true</daemon>
    						</configuration>
    					</execution>
    					<execution>
    						<id>stop-jetty</id>
    						<phase>post-integration-test</phase>
    						<goals>
    							<goal>stop</goal>
    						</goals>
    					</execution>
    				</executions>
    			</plugin>
    			<plugin>
    				<groupId>org.jvnet.jaxb2.maven2</groupId>
    				<artifactId>maven-jaxb2-plugin</artifactId>
    				<version>0.7.1</version>
    				<executions>
    					<execution>
    						<goals>
    							<goal>generate</goal>
    						</goals>
    					</execution>
    				</executions>
    				<configuration>
    					<schemaDirectory>${basedir}/src/main/resources</schemaDirectory>
    				</configuration>
    			</plugin>
    		</plugins>
    	</build>
    </project>
    
  4. Put the User.xsd into the /src/main/resources folder and generate the Java source code from the XML Schema with Maven.
  5. Add the generated sources in folder /target/generated-sources/xjc/ to the classpath.
  6. Add the web.xml to the /src/main/webapp/WEB-INF/.
  7. <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    	id="WebApp_ID" version="2.5">
    	<display-name>rest-blog-sample</display-name>
    	<servlet>
    		<servlet-name>rest</servlet-name>
    		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    		<load-on-startup>1</load-on-startup>
    	</servlet>
    
    	<servlet-mapping>
    		<servlet-name>rest</servlet-name>
    		<url-pattern>/*</url-pattern>
    	</servlet-mapping>
    
    </web-app>
    
  8. In the web.xml the servlet-name is set to rest, ie. by CoC a config file rest-servlet.xml is included.
  9. <?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:context="http://www.springframework.org/schema/context"
    	xmlns:oxm="http://www.springframework.org/schema/oxm"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    		http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">
    
    	<oxm:jaxb2-marshaller id="marshaller" contextPath="org.example.user"/>
    	
    	<context:annotation-config/>
    	<context:component-scan base-package="org.example.rest"/>
    	
    	<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    		<property name="messageConverters">
    			<list>
    				<bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
    					<constructor-arg ref="marshaller"/>
    				</bean>
    			</list>
    		</property>
    	</bean>
    	
    	<bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
    	
    	<bean name="user" class="org.springframework.web.servlet.view.xml.MarshallingView">
    		<constructor-arg ref="marshaller"/>
    	</bean>
    	
    </beans>
    
  10. The application context file rest-servlet.xml is self-explanatory, a marshaller is created and registered in the BeanNameViewResolver as well as the AnnotationMethodHandlerAdapter. The corresponding Controller has to declare the to-be-unmarshalled object User with the @RequestBody annotation to use the AnnotationMethodHandlerAdapter:
    package org.example.rest.controller;
    
    import static org.springframework.web.bind.annotation.RequestMethod.GET;
    import static org.springframework.web.bind.annotation.RequestMethod.POST;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.example.user.ObjectFactory;
    import org.example.user.User;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseStatus;
    import org.springframework.web.servlet.ModelAndView;
    
    @Controller
    public class UserController {
    	
    	@RequestMapping(value="/post/", method=POST)
    	@ResponseStatus(HttpStatus.CREATED)
    	public ModelAndView post(@RequestBody User userRequest, HttpServletRequest request, HttpServletResponse response) {
    		User userResponse = new ObjectFactory().createUser();
    		response.addHeader("Location", determineLocationUri(request, "/user/" + userRequest.getId()));
    		return new ModelAndView("user").addObject(userResponse);
    	}
    
    	@RequestMapping(value="/user/{id}", method=GET)
    	@ResponseStatus(HttpStatus.CREATED)
    	public ModelAndView get(@PathVariable String id, HttpServletRequest request, HttpServletResponse response) {
    		User userResponse = new ObjectFactory().createUser();
    		userResponse.setId(Long.valueOf(id));
    		return new ModelAndView("user").addObject(userResponse);
    	}
    	
    	String determineLocationUri(HttpServletRequest request,	String newPathInfo) {
    		StringBuffer url = request.getRequestURL();
    		url.replace(url.indexOf(request.getPathInfo()), url.length(), newPathInfo);
    		return url.toString();
    	}	
    
    }
    
  11. Depending on the project settings: “Web Project Settings”, the context root is set.

Migrating to a Google App Engine project

  1. Run the Maven goal mvn war:war in the project folder.
  2. From the generated war in folder target, copy the jars from the /WEB-INF/lib folder inside the jar.
  3. Copy the web.xml and rest-servlet.xml to the GAE project.
  4. Note:Use complex type as embedded XML Element, otherwise JAXBElement is used as marshalling type, which includes some string coupling where it does not have to be.
  5. Note:I wanted to use XMLBeans for this, however, I came across this “stream closed” error, which rendered XMLBeans useless for me.

Starting

You can start the application by either (1) running mvn jetty:run for the simple project, run out of eclipse (2) with “Run as…/Run on Server” or start the GAE project (3) with “Run as…/Web Application”. Depending on what way you choose, the URL changes (1: http://localhost:8080/post, 2: http://localhost:8080/…/post, 3: http://localhost:8888/post).

Testing

You can test the application in two ways:

  1. Using soapUI. It has REST support now, Using the sample project, you have to setup the project as follows:
  2. Using the RestTemplate. The pom.xml for the client project looks like this:
  3. <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>org.examples</groupId>
    	<artifactId>resttemplate</artifactId>
    	<packaging>jar</packaging>
    	<version>1.0-SNAPSHOT</version>
    	<name>Spring 3 RestTemplate Sample</name>
    	<build>
    		<plugins>
    			<plugin>
    				<artifactId>maven-compiler-plugin</artifactId>
    				<configuration>
    					<source>1.5</source>
    					<target>1.5</target>
    				</configuration>
    			</plugin>
    		</plugins>
    	</build>
    	<dependencies>
    		<dependency>
    			<groupId>org.springframework.ws</groupId>
    			<artifactId>spring-xml</artifactId>
    			<version>2.0.0-M1</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-web</artifactId>
    			<version>3.0.1.RELEASE</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-oxm</artifactId>
    			<version>3.0.1.RELEASE</version>
    		</dependency>
    	</dependencies>
    </project>
    
    package org.example.rest.resttemplate;
    
    import java.io.BufferedInputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.StringReader;
    import java.net.URI;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.xml.transform.Source;
    import javax.xml.transform.stream.StreamSource;
    
    import org.example.user.User;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.converter.xml.MarshallingHttpMessageConverter;
    import org.springframework.http.converter.xml.SourceHttpMessageConverter;
    import org.springframework.oxm.XmlMappingException;
    import org.springframework.oxm.jaxb.Jaxb2Marshaller;
    import org.springframework.web.client.RestTemplate;
    
    public class UserClient {
    
        public static void main(String[] args) throws XmlMappingException, IOException {
        	for (int i=1; i<1000; i++) {
       			postForLocation(String.valueOf(i));
        	}
        }
    
    	private static void postForLocation(String id) throws IOException {
    		RestTemplate restTemplate = new RestTemplate();
        	List<HttpMessageConverter<?>> list = new ArrayList<HttpMessageConverter<?>>();
        	list.add(new SourceHttpMessageConverter<Source>());
        	restTemplate.setMessageConverters(list);
        	String content = readFileAsString("src/main/resources/user-instance.xml").replaceAll("ID", id);
        	URI resultUrl = 
        		restTemplate.postForLocation("http://localhost:8888/post/", 
        			new StreamSource(new StringReader(content)));
        	getObject(resultUrl);
    	}
    	
    	private static void getObject(URI resultUrl) {
    		RestTemplate restTemplate = new RestTemplate();
        	List<HttpMessageConverter<?>> list = new ArrayList<HttpMessageConverter<?>>();
        	Jaxb2Marshaller jaxb2 = new Jaxb2Marshaller();
        	jaxb2.setContextPath("org.example.user");
        	list.add(new MarshallingHttpMessageConverter(jaxb2));
        	restTemplate.setMessageConverters(list);
    		User user = restTemplate.getForObject(resultUrl, User.class);
    		System.out.println(user.getId());
    	}
    
    	private static String readFileAsString(String filePath) throws java.io.IOException{
    	    byte[] buffer = new byte[(int) new File(filePath).length()];
    	    BufferedInputStream f = new BufferedInputStream(new FileInputStream(filePath));
    	    f.read(buffer);
    	    return new String(buffer);
    	}	
    
    }
    
Advertisements

6 Responses to “Spring 3, REST, XML – running on the Google App Engine”

  1. afon said

    Hi
    Sorry for an out of topic question , but..
    What tool do you use to create diagrams like first img in this tutorial (like that https://ice09.files.wordpress.com/2010/03/user.png)? It looks like it was created in Eclipse and i am very interested, how 🙂
    Thanks.

    • ice09 said

      Hi,

      I did this with the AmaterasUML Eclipse plugin.
      It is not really straightforward to install and a little “rough”, but quite useful and easy to use (the class diagram drag&drop feature is really nice).

      Best, ice09

      • afon said

        ice09, i am really grateful to you. Thanks. I hope, while i already have a UML2 plugins on my Eclipse it would not be so hard to install AmaterasUML. Have a nice day 🙂

  2. Mimi Tam said

    Please ignore my previous question. I can see it just fine now.

    New Question:

    This link in the example under Setup is no-longer:
    environment for a coexistence of Eclipse/WTP and Maven 2.

    Please help!

    Many Thanks in advance.

  3. […] 原文链接:https://ice09.wordpress.com/2010/03/15/spring-3-rest-xml-running-on-the-google-app-engine/ […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: