• If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • You already know Dokkio is an AI-powered assistant to organize & manage your digital files & messages. Very soon, Dokkio will support Outlook as well as One Drive. Check it out today!

View
 

Building with Spring Roo and JPA 2

Page history last edited by Richard Hightower 13 years, 7 months ago

 

You may be suprised how little code this takes. Roo uses the Spring 3 framework, JPA and AspectJ to make the Java code quite small and manageable despite Java's reputation for the opposite experience.

 

There are 13 steps with very little code as follows:

 

  • Step 1) Install maven 2.0.9 or higher.
  • Step 2) Install Roo. (I used 1.1BETA.)
  • Step 3) Create a new project with roo
  • Step 4) Next create a persistence provider and database backend connection.
  • Step 5) Create a new Entity and add some properties to it.
  • Step 6) Create your controller tier
  • Step 7) Include Jackson Jars (for serializing Java object into JSON object) into the maven pom.xml file
  • Step 8) Configure the webmvc-config.xml file.
  • Step 9) Modify the domain object to properly support the JSON that the todos app is expecting.
  • Step 10) Now create a wrapper object to match what Todos tutorial expects
  • Step 11) Now create a Spring REST controller object
  • Step 12) Run the REST server.
  • Step 13) Run the Sproutcore server

 

 

Step 1) Install maven 2.0.9 or higher.

 

Follow the link above then come back here.

 

Step 2) Install Roo. (I used 1.1BETA.)

 

Follow the link above then come back here.

 

Step 3) Create a new project with roo

 

In a new folder use Roo to create a new project.

 

From the command line:

 

$ roo project --topLevelPackage com.roo.todos

 

This will create a new project for your todo REST backend.

The project has a pom.xml file associated with it. The pom.xml file is a maven build file. 

 

 

Step 4) Next create a persistence provider and database backend connection.

 

$ roo persistence setup --provider HIBERNATE --database HYPERSONIC_PERSISTENT 

 

I used HYPERSONIC_PERSISTENT because it does not need any additional setup.

Other providers include Oracle, MySQL, etc. Please refer to Roo documentation for more information about database providers.

 

Step 5) Create a new Entity and add some properties to it.

 

$ roo entity --class ~.domain.Todo 

$ roo  field boolean isDone

$ roo  field string description

 

Notice that we use isDone. I decided to go with the Sproutcore flow to make things easier. There are ways to override what the property names are that get sent as JSON objects. However to mitigate the property naming convention mismatch between the Sproutcore naming convention and the JavaBean naming convention without invoking a lot of Jackson foo, I opted on the easy rename. (Jackson is completely capable, but more on this later.)

 

Step 6) Create your controller tier

 

$ roo controller all --package ~.web

 

Step 7) Include Jackson Jars (for serializing Java object into JSON object) into the maven pom.xml file

 

Jackson is a framework for serializing Java object graphs into JSON object graphs. Jackson is the fastest and most pervasive and easiest to use library for doing this type of JSON serialization in the Java world. It gets used by Spring to add JSON/REST support for serializing Java objects.

 

Edit the pom.xml file in the root of the project directory.

<dependencies>

 

    <!-- stuff I added -->

    <dependency>

        <groupId>org.codehaus.jackson</groupId>

        <artifactId>jackson-mapper-lgpl</artifactId>

        <version>1.5.5</version>

    </dependency>

    <dependency>

        <groupId>org.codehaus.jackson</groupId>

        <artifactId>jackson-mapper-asl</artifactId>

        <version>1.5.5</version>

    </dependency>

...

 

The pom.xml file is the maven project object model file. It manages the dependencies (i.e., jar files) in the project.

 

Hint: You can generate an Eclipse project file to edit this project by executing mvn eclipse:eclipse from the command line.

 

Step 8) Configure the webmvc-config.xml file.

 

Edit webmvc-config.xml  (/src/main/webapp/WEB-INF/spring/webmvc-config.xml to be exact).

 

    <bean

 

        class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"

 

        p:order="1">

 

        <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" />

 

            </list>

 

        </property>

 

    </bean>

 

</beans>

 

The above adds JSON support for the Java object. It does it in such a way so that we can later add support for XML instead of JSON. Java XML serialization it out of scope for this tutorial. 

 

Step 9) Modify the domain object to properly support the JSON that the todos app is expecting.

 

package com.roo.todos.domain;

 

import javax.persistence.Entity;

 

import org.codehaus.jackson.annotate.JsonIgnoreProperties;

import org.springframework.roo.addon.javabean.RooJavaBean;

import org.springframework.roo.addon.tostring.RooToString;

import org.springframework.roo.addon.entity.RooEntity;

 

@Entity

@RooJavaBean

@RooToString

@JsonIgnoreProperties("id")

@RooEntity(versionField = "")

public class Todo {

 

    private Boolean isDone;

    private String description;

 

    public Todo() {

    }

 

    public Todo(Boolean done, String description) {

        this.isDone = done;

        this.description = description;

    }

 

    public String getGuid() {

        return String.format("/tasks/%d", this.getId());

    }

 

    public void setGuid(String guid) {

        String sId = guid.substring(guid.lastIndexOf("/") + 1, guid.length());

        this.setId(Long.valueOf(sId));

    }

 

}

 

Essentially we tell it to ignore the id property so we can send the guid property instead. We add some setter/getter for guid that copies the id over into the right spot. We do this to make the object more friendly to what the Sproutcore Todos demo expects.

 

We use the @RooEntity to take away support for JPA versioning, which roo adds by default. The Sproutcore Todos demo does not expect to see a version field. 

 

We are trying to make an object that gets converted into the Task object that the Sproutcore todos example is expecting. Nothing more. Nothing less. (In as simple as possible using the least amount of steps). I might have done things differently if I was starting from scratch instead of trying to make this fit the existing frontend without changes.

 

Step 10) Now create a wrapper object

The Sproutcore todos example expect results to be wrapped in a content key/property. I created a little helper class that wraps the Todo object (the Task object) into a format that will match the JSON object payload that the todos Sproutcore example expects.

 

package com.roo.todos.web;

 

import java.util.HashMap;

import java.util.List;

import java.util.Map;

 

import com.roo.todos.domain.Todo;

 

public class TodoWrapper {

 

    public static TodoWrapper wrap(Todo todo) {

        TodoWrapper wrap = new TodoWrapper();

        wrap.content = todo;

        return wrap;

    }

 

    public static Map<String, List<Todo>> wrap(List<Todo> list) {

        Map<String, List<Todo>> map = new HashMap<String, List<Todo>>();

        map.put("content", list);

        return map;

    }

 

    private Todo content;

 

    public Todo getContent() {

        return content;

    }

 

    public void setContent(Todo content) {

        this.content = content;

    }

 

}

 

There is no real magic above.

 

Step 11) Now create a Spring REST controller object

 

Add this class to your project, and notice it uses the overridden wrap method from the TodosWrapper class. 

 

package com.roo.todos.web;

 

import com.roo.todos.domain.Todo;

 

import java.util.List;

import java.util.Map;

 

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.RequestMethod;

import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletResponse; 

import static com.roo.todos.web.TodoWrapper.wrap;

 

@RequestMapping("/tasks")

@Controller

class TodoRESTController {

 

    @RequestMapping(method = RequestMethod.POST) 

    public @ResponseBody Todo create(@RequestBody Todo todo,          

                                      HttpServletResponse response) {

        todo.persist();

        response.setHeader("Location", todo.getGuid());

        return todo;

    }

 

 

 

    @RequestMapping(method = RequestMethod.GET)

    public @ResponseBody Map<String, List<Todo>> list() {

        return wrap(Todo.findAllTodoes());

    }

 

    @RequestMapping(value="/{id}", method = RequestMethod.GET)

    public @ResponseBody TodoWrapper read(@PathVariable Long id) {

        Todo todo = Todo.findTodo(id);

        return wrap(todo);

    }

 

    @RequestMapping(method = RequestMethod.PUT)

    public @ResponseBody Todo update(@RequestBody Todo todo) {

        todo.merge();

        return todo;

    }

 

    @RequestMapping(value="/{id}", method = RequestMethod.DELETE)

    public @ResponseBody void delete(@PathVariable Long id) {

        Todo.findTodo(id).remove();

    }

 

}

 

The above  uses Spring 3's mechanism for mapping requests, parameters and URL paths to back end REST handlers. To read more about this, please read this introductory blog from the Spring team

 

While getting this to actually run, I noticed that I had to delete the TodoController that roo created. The controller and view did not like when I removed the version field from the domain object, which I needed to do to match the Sproutcore demo. In short, if you have a problem with the controller delete the TodoController that roo generated.

 

Step 12) Run the REST server.

 

Modify the jetty plugin in the pom.xml:

 

    <plugin>

        <groupId>org.mortbay.jetty</groupId>

        <artifactId>jetty-maven-plugin</artifactId>

        <version>7.1.2.v20100523</version>

 

        <configuration>

            <webAppConfig>

                <contextPath>/</contextPath>

            </webAppConfig>

        </configuration>

    </plugin>

</plugins>

 

 

From the command line run the plugin:

 

$ mvn jetty:run

 

Step 13) Run the sproutcore server

 

Modify Buildfile and append the following line:

 

proxy '/tasks', :to => 'localhost:8080'

 

Now rerun the sc-server

 

$ sc-server

 

You should have a Sproutcore frontend speaking REST/JSON to a Java backend. Look out for flying pigs.

 

Thanks to Mark S. for giving permission for this.

 

 

 

 

 

 

 

 

Comments (6)

Richard Hightower said

at 11:20 am on Aug 17, 2010

Has anyone tried this yet?
Any feedback?

Richard Hightower said

at 12:16 pm on Aug 18, 2010

Seems I forgot to add the Location header in the create result.

@RequestMapping(method = RequestMethod.POST)
public @ResponseBody Todo create(@RequestBody Todo todo, HttpServletResponse response) {
todo.persist();
response.setHeader("Location", todo.getGuid());
return todo;
}

Without the Location, it is hard for the object to be marked so it stays in the busy state.

You end up getting this because the record is never unlocked.
uncaught exception: SC.Error:sc299:Busy (-1)


This corresponds to this code in the DataSource:

didCreateTask: function(response, store, storeKey) {
if (SC.ok(response)) {
// Adapted from parseUri 1.2.2
// (c) Steven Levithan <stevenlevithan.com>
// MIT License
var parser = /^(?:(?![^:@]+:[^:@/]*@)([^:/?#.]+):)?(?://)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:/?#]*)(?::(d*))?)(((/(?:[^?#](?![^?#/]*.[^?#/.]+(?:[?#]|$)))*/?)?([^?#/]*))(?:?([^#]*))?(?:#(.*))?)/;
var url = parser.exec(response.header('Location'))[8];
store.dataSourceDidComplete(storeKey, null, url); // update url

} else store.dataSourceDidError(storeKey, response);
},

Richard Hightower said

at 4:36 pm on Aug 18, 2010

It seemed like it was all working then it the updates stopped working and it look like it was not PUT the guid in the update.

So I figured it was not getting the id from the return of the create, so I added the following:

didCreateTask: function(response, store, storeKey) {
if (SC.ok(response)) {
var guid = response.header('Location');
store.dataSourceDidComplete(storeKey, response.get("body"), guid); // update url
} else store.dataSourceDidError(storeKey, response);
},

Now it seems to really work.

gokool said

at 6:18 pm on Sep 24, 2010

I followed your instructions but in the end when I brought up my SC Todos application, it still shows the three tasks that are in the fixture. I am guessing that with the backend hooked now I should see no tasks when I first load the application?
I have already modified the BuildFile to look like this:

# ===========================================================================
# Project: Todos
# Copyright: ©2010 My Company, Inc.
# ===========================================================================

# Add initial buildfile information here
config :all, :required => :sproutcore
proxy '/tasks', :to => 'localhost:8080'

gokool said

at 6:21 pm on Sep 24, 2010

I just figured that my Spring-Roo application is not even compiling:

cannot find symbol
symbol: class RequestMapping
@RequestMapping("/tasks")

gokool said

at 6:21 pm on Sep 24, 2010

Which Jar am i missing?

You don't have permission to comment on this page.