Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Generic GwtRpcDataSource implementation

    Edit: the newest version 1.1 support server-side paging and sorting, as explained later in this thread:

    see http://forums.smartclient.com/showth...4370#post44370.

    -----------------------

    Hi everyone

    I'm new to Smart GWT, and I thought I contribute a bit by creating a generic GwtRcpDataSource to minimize the pain of implementing a GWT RPC DataSource. This is based on the excellent work of Aleksandras and the smartgwt-extensions examples.

    It's called GenericGwtRcpDataSource, and uses GenericGwtRpcDataSourceService and GenericGwtRpcDataSourceServiceAsync as counterparts.

    To implement a GenericGwtRpcDataSource, you'll have to:

    1. create a serializable data object (probably a POJO)

    Code:
    import com.google.gwt.user.client.rpc.IsSerializable;
    
    public class YourDataObject implements IsSerializable {
    
    	private int id;
    	
    	private String name;
    
    	public int getId() { return id; }
    
    	public void setId(int id) { this.id = id; }
    
    	public String getName() { return name; }
    
    	public void setName(String name) { this.name = name; }
    	
    }
    2. create a gwt rcp service, by extending GenericGwtRpcDataSourceService. Set its @RemoteServiceRelativePath annotation

    Code:
    import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
    
    @RemoteServiceRelativePath("yourservice")
    public interface YourService extends GenericGwtRpcDataSourceService<YourDataObject> {
    
    }
    3. create the asynchronous counterpart of the service by extending GenericGwtRpcDataSourceServiceAsync.

    Code:
    public interface YourServiceAsync extends GenericGwtRpcDataSourceServiceAsync<YourDataObject> {
    
    }
    4. create your GwtRpcDataSource by extending GenericGwtRpcDataSource<D, T, SA>

    Code:
    import java.util.ArrayList;
    import java.util.List;
    
    import com.google.gwt.core.client.GWT;
    import com.smartgwt.client.data.DataSourceField;
    import com.smartgwt.client.data.fields.DataSourceIntegerField;
    import com.smartgwt.client.data.fields.DataSourceTextField;
    import com.smartgwt.client.widgets.tile.TileRecord;
    
    public class YourGwtRcpDataSource extends
    		GenericGwtRpcDataSource<YourDataObject, TileRecord, YourServiceAsync> {
    
    	@Override
    	public List<DataSourceField> getDataSourceFields() {
    		List<DataSourceField> fields = new ArrayList<DataSourceField>();
    		DataSourceTextField itemNameField = new DataSourceTextField("itemName",
    				"Product Name");
    		itemNameField.setRequired(true);
    		fields.add(itemNameField);
    
    		DataSourceIntegerField idField = new DataSourceIntegerField("itemId",
    				"Product Id");
    		idField.setPrimaryKey(true);
    		idField.setRequired(true);
    		fields.add(idField);
    		
    		return (fields);
    	}
    
    	@Override
    	public void copyValues(TileRecord from, YourDataObject to) {
    		to.setName(from.getAttribute("itemName"));
    		to.setId(from.getAttributeAsInt("itemId"));
    	}
    
    	@Override
    	public void copyValues(YourDataObject from, TileRecord to) {
    		to.setAttribute("itemName", from.getName());
    		to.setAttribute("itemId", from.getId());
    	}
    
    	@Override
    	public YourDataObject getNewDataObjectInstance() {
    		return new YourDataObject();
    	}
    
    	@Override
    	public TileRecord getNewRecordInstance() {
    		return new TileRecord();
    	}
    
    	@Override
    	public YourServiceAsync getServiceAsync() {
    		return GWT.create(YourService.class);
    	}
    
    }
    5. Finally, code your Service implementation on the Server side, implementing your GenericGwtRpcService implementation:

    Code:
    public class YourServiceImpl extends RemoteServiceServlet implements YourService {
    
    @Override
    public YourDataObject add(YourDataObject data) {
      // your code here
    }
    
    @Override
    public List<YourDataObject> fetch() {
      // your code here
    }
    
    @Override
    public void remove(YourDataObject data) {
      // your code here
    }
    
    @Override
    public YourDataObject update(YourDataObject data) {
      // your code here
    }
    
    }
    Cheers!

    marbot
    Attached Files
    Last edited by marbot; 16 May 2010, 03:06.

    #2
    and here're the example files:
    Attached Files
    Last edited by marbot; 14 May 2010, 00:14.

    Comment


      #3
      Thanks for the example.

      Comment


        #4
        The example did not work for me with ListGridRecord used with the data source.

        The problem was in GenericGwtRpcDataSource.java, executeFetch, line 142:
        Code:
        response.setData(records.toArray(new Record[records.size()]));
        I replaced it with:
        Code:
        response.setData(records.toArray(getNewRecordsArray(records.size())));
        New abstract function:
        Code:
        public abstract R[] getNewRecordsArray(int size);

        Any other suggestions for a workaround?

        BTW, thanks for great example.

        Comment


          #5
          Hi madamovic

          Thanks for testing and optimizing GenericGwtRpcDataSource. I don't quite understand the problem, though: it normally shouldn't be a problem to put ListGridRecord into a Record[], as ListGridRecord extends Record.

          What kind of exception did you get? ... Could you please post the Stacktrace, or (even better) the whole code example?

          ... does a explicit cast, as shown below, work?:

          (GenericGwtRpcDataSource, executeFetch, line 142)
          Code:
          response.setData(records.toArray((R[]) new Record[records.size()]));
          that would made your new abstract method getNewRecordsArray(int size) obsolete. It this works, I'll modify the example accordingly.

          Thanks again
          marbot
          Last edited by marbot; 29 Apr 2010, 03:18.

          Comment


            #6
            Hi marbot,

            I have reverted it to your original code and it is working fine now. I don't remember what the original exception was, but I think that the grid was stuck with "loading data..." message. Anyhow, it does not matter now; it will remain a mystery.

            Thanks for your reply!

            On another topic, do you have any suggestion for standardized handling of server errors? I am currently going through various posts on the forum but haven't come up with an approach yet.

            Comment


              #7
              Hi Marbot,

              I like your generic approach and tried it out. Unfortunately, it seems to make trouble in non standard environments (e.g. using Grails (which is based on Spring) in combination with the GWT plugin). Here is what I get when trying to call a service which inherits from GenericGwtRpcDataSourceService:
              Code:
              2010-05-04 03:12:34,765 [http-8080-1] ERROR [localhost].[/mightyFleet]  - Exception while dispatching incoming RPC call
              org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'genericGwtRpcDataSourceService' is defined
              	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:504)
              	at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1041)
              	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:273)
              	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
              	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:265)
              	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:189)
              	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1004)
              	at org.springframework.beans.factory.BeanFactory$getBean.call(Unknown Source)
              	at org.codehaus.groovy.grails.plugins.gwt.GwtController.processCall(GwtController.groovy:59)
              	at com.google.gwt.user.server.rpc.RemoteServiceServlet.processPost(RemoteServiceServlet.java:224)
              	at com.google.gwt.user.server.rpc.AbstractRemoteServiceServlet.doPost(AbstractRemoteServiceServlet.java:62)
              It seems spring somehow does not like service inheritance. Any ideas on this?
              Thanks
              fatzopilot

              Comment


                #8
                Hi fatzopilot

                Thanks for using GenericGwtRpcDataSourceService and helping to improve it. A NoSuchBeanDefinitionException sounds to me more like a typo than a heritance problem, but that would probably be a too simple solution :-) . I'm not fluent on Grails, but I know Spring quite well: could you please post your ApplicationContext XML? How do you use the bean in the code? Are you trying to instanciate the abstract class GenericGwtRpcDataSourceService directly?

                cheers
                marbot

                Comment


                  #9
                  Hi madamovic

                  Well, server-side error handling would be quite out of the GenericGwtRpcDataSource-Topic, wouldn't it? :-) ...Anyway, what do you exactly mean by that?

                  - Logging errors on the server?
                  - Displaying server errors in the client?

                  I don't know how SmartGWT handles a server-side exception on the client, or a timeout when waiting for a RPC response... we could test it! :-)

                  cheers
                  marbot
                  Last edited by marbot; 3 May 2010, 23:49.

                  Comment


                    #10
                    Hi marbot,

                    Indeed, there is just the GenericGwtRpcDataSourceService Interface and no Impl of it on the server side.
                    Instead there is a MyService implementation:

                    Code:
                    package org.myApp.remote.server
                    
                    class MyService<MyDTO> extends RemoteServiceServlet implements org.myApp.remote.client.MyService{ //last is the interface
                    
                        static expose = ['gwt:org.myApp.remote.client'] //this is used to tell where to expose the service  
                    	
                        List<MyDTO> fetch (){
                    	log.info("This should be called")
                        };
                    
                        MyDTO add (MyDTO data){};
                    
                        MyDTO update (MyDTO data){};
                    
                        void remove (MyDTO data){};
                    I also tried with a GenericGwtRpcDataSourceService Impl from which the MyService inherits:

                    Code:
                    package org.myApp.remote.server
                    
                    class MyService<MyDTO> extends GenericGwtRpcDataSourceService implements org.myApp.remote.client.MyService{
                    
                        static expose = ['gwt:org.myApp.remote.client']                 
                    	
                        List<MyDTO> fetch (){
                    	log.info("This should be called")
                        };
                    
                        MyDTO add (MyDTO data){};
                    
                        MyDTO update (MyDTO data){};
                    
                        void remove (MyDTO data){};
                    The corresponding GenericGwtRpcDataSourceService:

                    Code:
                    class GenericGwtRpcDataSourceService<D> extends RemoteServiceServlet implements org.myApp.remote.client.GenericGwtRpcDataSourceService{
                    
                    	static expose = ['gwt:org.myApp.remote.client']   
                    	
                    	List<D>  fetch (){
                    		log.info("Wrong Service called")
                    	};
                    
                    	D add (D data){};
                    
                    	D update (D data){};
                    
                    	void remove (D data){};
                    Now the issue is, when I call MyService.fetch() on the client, GenericGwtRpcDataSourceService.fetch() is called.
                    This seems to be the case because the call to myService is done in the abstract GenericGwtRpcDataSource on the client and not in MyDataSource which inherits from it.
                    The service in MyDataSource is obtained as follows:

                    Code:
                    @Override
                    public MyServiceAsync getServiceAsync() {
                    	if(myService != null){
                    		return myService;
                    	} else {
                    		myService = (MyServiceAsync) GWT.create(MyService.class);
                    		ServiceDefTarget endpoint = (ServiceDefTarget) myService;
                    		String moduleRelativeURL = GWT.getModuleBaseURL() + "rpc";
                    		endpoint.setServiceEntryPoint(moduleRelativeURL);
                    		return myService;
                    	}
                    }
                    When I put everything in MyDataSource and do no inheritance from GenericGwtRpcDataSource it works, i.e. MyService is called instead of GenericGwtRpcDataSourceService.

                    Maybe somehow Grails' convention over configuration principle kicks in here.
                    I've attached the ApplicationContext but I fear it will not contain useful information for debugging here :(....

                    Thanks
                    fatzopilot
                    Attached Files

                    Comment


                      #11
                      Hi all,

                      adding
                      Code:
                        
                        List<MyDTO> fetch ();
                      
                         MyDTO add (MyDTO data);
                      
                         MyDTO update (MyDTO data);
                      
                         void remove (MyDTO data);
                      to MyService solved the issue for me (impairing generalizability a bit).

                      Cheers
                      fatzopilot

                      Comment


                        #12
                        Originally posted by marbot

                        what do you exactly mean by that?

                        - Logging errors on the server?
                        - Displaying server errors in the client?

                        I don't know how SmartGWT handles a server-side exception on the client, or a timeout when waiting for a RPC response... we could test it! :-)
                        I wanted to display server errors, e.g. DB errors, in the client. Smart GWT (or it is GWT in general) just ignores the error. In the development mode, you get something like "unhandled exception escaped".

                        Here is my solution:

                        1. On the server, handle all exceptions and wrap them into Exception (java.lang.Exception), because only Exception can propagate properly to the client.

                        2. Add the following piece of code in GenericGwtRpcDataSource, in each onFailure callback.

                        Code:
                        public void onFailure(Throwable caught) {
                        
                        response.setStatus(RPCResponse.STATUS_FAILURE);
                        		
                        DataSourceField pkf = getPrimaryKeyField();
                        Map<String, String> errors = new HashMap<String, String>();
                        errors.put(pkf.getName(), "Server Error: " + caught.getMessage());
                        response.setErrors(errors);
                        SC.warn("Server Error: " + caught.getMessage());
                        
                        processResponse(requestId, response);
                        }
                        It will do two things:
                        - display a dialog with the error message
                        - put the server error against the primary key field as if it has a validation error.

                        This worked well for handling database referential integrity errors. This can be improved further to distinguish between severe errors and data-related errors, and handle them differently.

                        Comment


                          #13
                          Hi Marbot,

                          i'm using the old GwtRpcDataSource, but your solution looks very nice even though i didn't test it yet, but i will...

                          thanks a lot anyway!

                          my question is, how to deal with huge amounts of data, lets say my grid does have over 10000 entries
                          com.smartgwt.client.data.DSRequest provides the getStartRow() und getEndRow(), but is there an example how to deal with them?

                          cheers
                          andre

                          Comment


                            #14
                            First thanks for your work.
                            I am kind a new to GWT and SmartGWT and I am in a mode of testing this technology to use in our company.

                            I just tested your code. Just changing the packages and also using ListGridRecord instead of TileRecord.

                            Everything seems to work well, but when editing a cell, instead of replacing the cell in the edited row of my ListGrid, it create a new one!
                            On the server side, the data is fine. And if I do a reload in the browser, the "duplicate" row is gone.
                            Any idea?
                            I set my datasource like this:
                            Code:
                            detailsModulesListGrid.setDataSource( new YourGwtRcpDataSource() );
                            
                                    ListGridField lModuleNameField = new ListGridField( "itemName" );
                                    ListGridField lModuleVersionField = new ListGridField( "itemId" );
                                    lModuleVersionField.setType( ListGridFieldType.INTEGER );
                            
                                    detailsModulesListGrid.setFields( lModuleNameField, lModuleVersionField );
                            
                                    detailsModulesListGrid.setAutoFetchData( true );
                                    detailsModulesListGrid.setCanEdit( true );
                                    detailsModulesListGrid.setEditEvent( ListGridEditEvent.DOUBLECLICK );
                            Same problem with the "Delete" action. The ListGrid is not updated.
                            Note that using a client side only datasource, everything is working well.
                            Last edited by tleveque; 12 May 2010, 06:55.

                            Comment


                              #15
                              Originally posted by tleveque
                              First thanks for your work.
                              I am kind a new to GWT and SmartGWT and I am in a mode of testing this technology to use in our company.

                              I just tested your code. Just changing the packages and also using ListGridRecord instead of TileRecord.

                              Everything seems to work well, but when editing a cell, instead of replacing the cell in the edited row of my ListGrid, it create a new one!
                              On the server side, the data is fine. And if I do a reload in the browser, the "duplicate" row is gone.
                              Any idea?
                              Just found myself the problem:
                              There is a typo in your "YourGwtRcpDataSource.java" file.
                              Code:
                              List<DataSourceField> fields = new ArrayList<DataSourceField>();
                                      DataSourceTextField itemNameField = new DataSourceTextField( "itemName", "Product Name" );
                                      itemNameField.setRequired( true );
                                      fields.add( itemNameField );
                              
                                      DataSourceIntegerField idField = new DataSourceIntegerField( "itemId", "Product Id" );
                                      idField.setPrimaryKey( true );
                                      idField.setRequired( true );
                                      fields.add( itemNameField );
                              In the declaration of the second field, you still add "itemNameField" instead of "idField"!
                              Probably a copy-paste error...

                              Fixing that, fixed all the problems!

                              Comment

                              Working...
                              X