I decided to give SmartGWT a run, and specifically the RestDataSource functionality using Jersey. To make things easier, I'm using Spring Roo to set up my project and scaffold much of the boilerplate code, configurations etc.
My approach is to have my domain objects exposed as RESTful web services and create a matching SmartGWT DataSource in order to show them in a GUI.
Let's get started, shall we?
First of all, you'll need Spring Roo (unless you just download the project source code and want to run it directly). I'm using the following Roo script to start things off:
You'll need to add the repositories for Jersey/SmartGWT manually in the genereated pom.xml:
Unfortunately the jersey-spring dependency depends on Spring 2.5.6, so that needs to be exluded (manually):
Add the jersey-servlet to web.xml and the corresponding jersey-servlet.xml into webapp/WEB-INF/spring (in accordance to how Spring Roo does it).
(The jersey-servlet.xml doesn't contain anything else than a component scan for Jersey web services).
We also have to update the urlrewrite.xml being used by Roo, to handle our servlet mapping of "/rest/*":
In the generated Message domain object, add instructions to represent it in XML using an @XmlRootElement annotation:
The only thing we're missing on the backend now is the actual implementation of our service... A RestDataSource e.g. expects a response like the following in response to a "fetch" request (taken from the JavaDoc):
I've created some POJOs for Jersey to marshall/unmarshall that would mimic this format in the com.technowobble.ws.util-package. Essentially, there's two abstract classes (DSRequest and DSResponse) containing all common attributes, and two specialized classes (MessageDSResponse, MessageDSRequest) handling the specific marshalling of each domain object. I'm not posting the entire source here, but ask you to look at it from the source code instead...
Before you comment on this - yes, there are probably a smarter way to do this using generics etc, but that's an improvement to be done in a real-world project!
With the xml-mapping in place, now it's time to look at the Jersey resource implementation:
As you can see, there's one method per CRUD-reqeust from the SmartGWT RestDataSource (add, update, remove, read). They all basically parse the
incoming DSRequest and answers with a DSResponse, according to the specs of SmartGWT.
Now that the backend is in place, let's create a small GWT application to take it for a test drive!
By using Roo, there are some things to consider when adding a new module to the project, which I have blogged about previously. As with any other module, create a module file (in our case Application.gwt.xml), a host page (Application.html), configure the gwt-maven-plugin to use it (in pom.xml) and add two rules to urlrewrite.xml...
With that in place, let's have a look at the entry-point class:
All in all, a ListGrid making use of a customized RestDataSource (MessageDS) - which might be more interesting than the actual code above...
Let's have a look at it:
No surprises here... The RestDataSource is configured to match the operations of the RESTful backend, including the properties of the domain object we're exposing.
Done already? Then download the full source code here and give it a try!
"mvn jetty:run-war" should do the trick, although on my Windows machine I have to force a gwt-compilation using "mvn package gwt:compile jetty:run-war"
Enjoy!
My approach is to have my domain objects exposed as RESTful web services and create a matching SmartGWT DataSource in order to show them in a GUI.
Let's get started, shall we?
First of all, you'll need Spring Roo (unless you just download the project source code and want to run it directly). I'm using the following Roo script to start things off:
project --topLevelPackage com.technowobble persistence setup --provider HIBERNATE --database HYPERSONIC_IN_MEMORY entity --class ~.domain.Message field string --fieldName value controller class --class ~.ws.MessageResource --preferredMapping rest dependency add --groupId com.sun.jersey --artifactId jersey-server --version 1.3 dependency add --groupId com.sun.jersey.contribs --artifactId jersey-spring --version 1.3 dependency add --groupId com.smartgwt --artifactId smartgwt --version 2.2 gwt setup
You'll need to add the repositories for Jersey/SmartGWT manually in the genereated pom.xml:
<repository> <id>smartgwt</id> <url>http://www.smartclient.com/maven2</url> </repository> <repository> <id>maven2-repository.dev.java.net</id> <name>Java.net Repository for Maven</name> <url>http://download.java.net/maven/2/</url> </repository>
Unfortunately the jersey-spring dependency depends on Spring 2.5.6, so that needs to be exluded (manually):
<dependency> <groupId>com.sun.jersey.contribs</groupId> <artifactId>jersey-spring</artifactId> <version>1.3</version> <!-- jersey-spring depends on Spring 2.5.6, so exluding as we're on 3.0.0X --> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> </exclusion> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> </exclusion> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> </exclusion> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </exclusion> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> </exclusion> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </exclusion> </exclusions> </dependency>
Add the jersey-servlet to web.xml and the corresponding jersey-servlet.xml into webapp/WEB-INF/spring (in accordance to how Spring Roo does it).
<!-- Initialize the Jersey servlet --> <servlet> <servlet-name>jersey</servlet-name> <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/jersey-servlet.xml</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>jersey</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping>
(The jersey-servlet.xml doesn't contain anything else than a component scan for Jersey web services).
We also have to update the urlrewrite.xml being used by Roo, to handle our servlet mapping of "/rest/*":
<rule enabled="true"> <from casesensitive="false">/rest/**</from> <to last="true" type="forward">/rest/1</to> </rule>
In the generated Message domain object, add instructions to represent it in XML using an @XmlRootElement annotation:
package com.technowobble.domain; import javax.persistence.Entity; import javax.xml.bind.annotation.XmlRootElement; import org.springframework.roo.addon.entity.RooEntity; import org.springframework.roo.addon.javabean.RooJavaBean; import org.springframework.roo.addon.tostring.RooToString; @XmlRootElement @Entity @RooJavaBean @RooToString @RooEntity public class Message { private String value; }
The only thing we're missing on the backend now is the actual implementation of our service... A RestDataSource e.g. expects a response like the following in response to a "fetch" request (taken from the JavaDoc):
<response> <status>0</status> <startRow>0</startRow> <endRow>76</endRow> <totalRows>546</totalRows> <data> <record> <field1>value</field1> <field2>value</field2> </record> <record> <field1>value</field1> <field2>value</field2> </record> ... 75 total records ... </data> </response>
I've created some POJOs for Jersey to marshall/unmarshall that would mimic this format in the com.technowobble.ws.util-package. Essentially, there's two abstract classes (DSRequest and DSResponse) containing all common attributes, and two specialized classes (MessageDSResponse, MessageDSRequest) handling the specific marshalling of each domain object. I'm not posting the entire source here, but ask you to look at it from the source code instead...
Before you comment on this - yes, there are probably a smarter way to do this using generics etc, but that's an improvement to be done in a real-world project!
With the xml-mapping in place, now it's time to look at the Jersey resource implementation:
package com.technowobble.ws; import java.util.Collection; import javax.ws.rs.Consumes; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.apache.commons.beanutils.BeanUtils; import org.springframework.stereotype.Component; import com.technowobble.domain.Message; import com.technowobble.ws.util.DSResponse; import com.technowobble.ws.util.MessageDSRequest; import com.technowobble.ws.util.MessageDSResponse; import com.technowobble.ws.util.OperationType; /** * Jersey resource for a SmartGWT {@link MessageDS}. * <p> * * @see http://docs.sun.com/app/docs/doc/820-4867/ggnxo?l=en&a=view * @see http://blogs.sun.com/enterprisetechtips/entry/jersey_and_spring */ @Component @Path("/message") public class MessageResource { @Produces( { MediaType.APPLICATION_XML }) @Consumes( { MediaType.TEXT_XML }) @POST @Path("/add") public MessageDSResponse create(MessageDSRequest request) { MessageDSResponse response = new MessageDSResponse(); if (request.getOperationType() != OperationType.ADD || request.getMessages().size() != 1) { response.setStatus(DSResponse.STATUS_FAILURE); } else { Message message = request.getMessages().iterator().next(); try { // create the message message.persist(); response.addMessage(message); response.setStatus(DSResponse.STATUS_SUCCESS); } catch (Exception e) { response.setStatus(DSResponse.STATUS_FAILURE); e.printStackTrace(); } } return response; } @Produces( { MediaType.APPLICATION_XML }) @Consumes( { MediaType.TEXT_XML }) @POST @Path("/update") public MessageDSResponse update(MessageDSRequest request) { MessageDSResponse response = new MessageDSResponse(); if (request.getOperationType() != OperationType.UPDATE || request.getMessages().size() != 1) { response.setStatus(DSResponse.STATUS_FAILURE); } else { try { Message data = (Message) request.getMessages().iterator().next(); Message message = Message.findMessage(data.getId()); BeanUtils.copyProperties(message, data); message.merge(); response.addMessage(message); response.setStatus(DSResponse.STATUS_SUCCESS); } catch (Exception e) { response.setStatus(DSResponse.STATUS_FAILURE); e.printStackTrace(); } } return response; } @Produces( { MediaType.APPLICATION_XML }) @Consumes( { MediaType.TEXT_XML }) @POST @Path("/remove") public MessageDSResponse delete(MessageDSRequest request) { MessageDSResponse response = new MessageDSResponse(); if (request.getOperationType() != OperationType.REMOVE || request.getMessages().size() != 1) { response.setStatus(DSResponse.STATUS_FAILURE); } else { try { Message data = request.getMessages().iterator().next(); Message message = Message.findMessage(data.getId()); message.remove(); response.addMessage(message); response.setStatus(DSResponse.STATUS_SUCCESS); } catch (Exception e) { response.setStatus(DSResponse.STATUS_FAILURE); e.printStackTrace(); } } return response; } @POST @Produces( { MediaType.APPLICATION_XML}) @Consumes( { MediaType.TEXT_XML }) @Path("/read") public MessageDSResponse read(MessageDSRequest request) { MessageDSResponse response = new MessageDSResponse(); response.setStartRow(request.getStartRow()); if (request.getOperationType() != OperationType.FETCH) { response.setStatus(DSResponse.STATUS_FAILURE); } else { try { Collection<Message> messages = Message.findMessageEntries(request.getStartRow(), 1 + (request.getEndRow() - request.getStartRow())); long count = Message.countMessages(); response.setEndRow(response.getStartRow()+messages.size()-1); response.setTotalRows((int)count); for (Message message : messages) { response.addMessage(message); } } catch (Exception e) { response.setStatus(DSResponse.STATUS_FAILURE); } response.setStatus(DSResponse.STATUS_SUCCESS); } return response; } }
As you can see, there's one method per CRUD-reqeust from the SmartGWT RestDataSource (add, update, remove, read). They all basically parse the
incoming DSRequest and answers with a DSResponse, according to the specs of SmartGWT.
Now that the backend is in place, let's create a small GWT application to take it for a test drive!
By using Roo, there are some things to consider when adding a new module to the project, which I have blogged about previously. As with any other module, create a module file (in our case Application.gwt.xml), a host page (Application.html), configure the gwt-maven-plugin to use it (in pom.xml) and add two rules to urlrewrite.xml...
With that in place, let's have a look at the entry-point class:
package com.technowobble.gwt.client; import com.google.gwt.core.client.EntryPoint; import com.smartgwt.client.data.Record; import com.smartgwt.client.data.RestDataSource; import com.smartgwt.client.types.Alignment; import com.smartgwt.client.types.RowEndEditAction; import com.smartgwt.client.widgets.IButton; import com.smartgwt.client.widgets.grid.ListGrid; import com.smartgwt.client.widgets.grid.ListGridField; import com.smartgwt.client.widgets.grid.ListGridRecord; import com.smartgwt.client.widgets.layout.HLayout; import com.smartgwt.client.widgets.layout.VLayout; import com.technowobble.gwt.client.datasources.MessageDS; /** * Entry point classes define <code>onModuleLoad()</code>. */ public class Application implements EntryPoint { /** * This is the entry point method. */ public void onModuleLoad() { VLayout layout = new VLayout(15); layout.setAutoHeight(); RestDataSource dataSource = new MessageDS(); final ListGrid messageGrid = new ListGrid(); messageGrid.setHeight(300); messageGrid.setWidth(500); messageGrid.setTitle("Messages"); messageGrid.setDataSource(dataSource); messageGrid.setAutoFetchData(true); messageGrid.setCanEdit(true); messageGrid.setCanRemoveRecords(true); messageGrid.setListEndEditAction(RowEndEditAction.NEXT); ListGridField idField = new ListGridField("id", "Id", 40); idField.setAlign(Alignment.LEFT); ListGridField messageField = new ListGridField("value", "Message"); messageGrid.setFields(idField, messageField); layout.addMember(messageGrid); HLayout hLayout = new HLayout(15); IButton updateButton = new IButton("Add message"); updateButton.addClickHandler(new com.smartgwt.client.widgets.events.ClickHandler() { public void onClick(com.smartgwt.client.widgets.events.ClickEvent event) { Record message = new ListGridRecord(); message.setAttribute("value", "..."); messageGrid.addData(message); } }); hLayout.addMember(updateButton); layout.addMember(hLayout); layout.draw(); } }
All in all, a ListGrid making use of a customized RestDataSource (MessageDS) - which might be more interesting than the actual code above...
Let's have a look at it:
package com.technowobble.gwt.client.datasources; import com.smartgwt.client.data.OperationBinding; import com.smartgwt.client.data.RestDataSource; import com.smartgwt.client.data.fields.DataSourceTextField; import com.smartgwt.client.types.DSOperationType; import com.smartgwt.client.types.DSProtocol; import com.technowobble.domain.Message; /** * SmartGWT datasource for accessing {@link Message} entities over http in a RESTful manner. */ public class MessageDS extends RestDataSource { public MessageDS() { setID("MessageDS"); DataSourceTextField messageId = new DataSourceTextField("id"); messageId.setPrimaryKey(true); messageId.setCanEdit(false); DataSourceTextField messageValue = new DataSourceTextField("value"); setFields(messageId, messageValue); OperationBinding fetch = new OperationBinding(); fetch.setOperationType(DSOperationType.FETCH); fetch.setDataProtocol(DSProtocol.POSTMESSAGE); OperationBinding add = new OperationBinding(); add.setOperationType(DSOperationType.ADD); add.setDataProtocol(DSProtocol.POSTMESSAGE); OperationBinding update = new OperationBinding(); update.setOperationType(DSOperationType.UPDATE); update.setDataProtocol(DSProtocol.POSTMESSAGE); OperationBinding remove = new OperationBinding(); remove.setOperationType(DSOperationType.REMOVE); remove.setDataProtocol(DSProtocol.POSTMESSAGE); setOperationBindings(fetch, add, update, remove); setFetchDataURL("rest/message/read"); setAddDataURL("rest/message/add"); setUpdateDataURL("rest/message/update"); setRemoveDataURL("rest/message/remove"); } }
No surprises here... The RestDataSource is configured to match the operations of the RESTful backend, including the properties of the domain object we're exposing.
Done already? Then download the full source code here and give it a try!
"mvn jetty:run-war" should do the trick, although on my Windows machine I have to force a gwt-compilation using "mvn package gwt:compile jetty:run-war"
Enjoy!
I'm in the process of trying out smartgwt with grails but roo is also on my wish-list. This will be a good help to get starting. Thanks for posting!
ReplyDeleteeconomy like this, where แทงบอลออนไลน์ war Where is the Corona virus (COVID-19), Bangkok is closed again. We
DeleteI am currently exploring the idea to use SmartGWT, Spring restful, and Spring roo for my project. This post is great which give me an example on how other people are doing it. However, I have couple questions:
ReplyDeleteWould you consider to use the JSON instead of XML as your message protocol? Spring 3 has JSON restful support out of box. I see that you are creating controller class with Spring roo command. Can you just have multiple @RequestMapping in the controller to handle additional urls?
Or maybe I am missing something important in your example.
Thanks.
Yes, I would definitely consider json, but I guess you'd still need to create a mapping that fits the structure of a JSONified RestDataSource from SmartGWT (if there is one).
ReplyDeleteRegarding the controller class, I'm only creating one controller that acts as a Jersey resource, with different @Path-annotation. It would be exactly the same approach with @RequestMapping-annotations. If you're thinking of depending on Roo, the only thing you get for free is a json-representation of you business objects, which won't map to what a RestDataSource in SmartGWT is expecting.
Anyway, good luck with the experiment!
Hi Mattias,
ReplyDeleteI'm very much new to Springsource & Roo.
And I am starting a project with smartgwt & gae. But came across certain hindrances when running both gwt & gae. I have the frontend SmartGWT app working independently by itself. But there's not backend capabilities to it yet, for CRUD operations :(
Hence I'm looking for alternatives & found ROO as shown in google i/o
The tutorial looks very helpful, thanks.
But I'm not able to run the project nor know how to go about when I do go thru this article step by step. Can you please mail me a direct project which I can import into STS which has the whole project & how to run the app. So i can get a starting point & tailor my existing UI.
All I need a simple CRUD operation with SmartGWT
I would really appreciate the help, cause this project is very much important as a major part of my school work.
My contact details is on my website
http://www.supreeth.net
Hope to hear from you soon :)
Thanks,
Supreeth
Hi, I can't really give you free consultancy hours, nor do I have the time to do so.
ReplyDeleteMy advise is to learn how to use Maven and download the source code from the link in my post.
You might also be able to import the source code into STS using "Import Maven project".
i'm working on a project where i want a rest backend that i can use for web presentation and as a service-like layer for an android app. except for the addition of spring-security and sprind data jpa this is exactly what i'm going for.
ReplyDeleteyou saved me a lot of time, thanks :-)
/janne
Neat. Looks to be something I am trying to implement as well (SmartGWT over Roo).
ReplyDeleteOne Question-
I've never worked with Jersey, so please help me- what did you gain by having Jersey being thrown into the mix? To avoid using both GWT and MVC controllers?
Also, the link to your source code is broken (at least to me).
Hi, I've decided to use Jersey to give it a spin, and due to the fact that it's a standard framework for creating RESTful applications.
ReplyDeleteThe link works for me.
this is NOTHING! LOOK FOR X7CLOUD!
ReplyDeletebbkjhjkhjhjk
ReplyDeletecan you give a simple example for smartgwt+spring?
ReplyDeleteNo, sorry. It shouldn't be too hard to port this to a 100% Spring backend, instead of Jersey, but I really don't have the time to do this.
ReplyDeleteThank you this is a nice post. I've tried this approach and it work well as long as on the smartgwt client side you have grid.setAutoSaveEdits(true);
ReplyDeleteBecause if you don't, the xml serialized POST request to the server won't be parsed by JAXB since the struct of the xml will ressembling this:
KLJKLJLKJ
DummyDS
add
isc_ListGrid_1
UIYUIYUI
DummyDS
add
isc_ListGrid_1
The annoying thing is that if you edit a single object you won't the transaction wrapping so the XXXXDSRequest cannot deal with the two different cases.
I wonder whether there is a solution to allow
grid.setAutoSaveEdits(false);
How a lot you'd be prepared to placed on a stake, relies upon entirely in your price range and economic situation. Some gamers imagine that they want three to four occasions the amount received after they hit the most effective hand in poker. However, we realize that such ideas might easily lead to unnecessary expenses, which is why we would not advocate you to observe it. If would possibly be} playing in} at an real money video 카지노사이트.online poker online on line casino, you should also to|must also} profit from the corresponding bonuses.
ReplyDelete