Announcement

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

    #61
    I just wanted to point out that with the nifty use of generics you can only have to implement this class once. We've got an initial implementation up and running very nicely by using a Resource interface that bridges between Models and SmartGWT constructs.

    Here's a snippet, just enough to get you started thinking about it and yes, this requires the you have a pretty good handle on coding with generics.

    Code:
    public interface HasSmartGWTResources<M extends DTObjectModel> {
    	List<DataSourceField> getDataSourceFields();
    
    	M newModel();
    	ListGridRecord toListGridRecord(M model);
    	M toModel(ListGridRecord record);
    
    	DvTreeNode toTreeNode(M model, String parentid);
    	List<DvTreeNode> toTreeNodeWithChildren(M model, String parentid);
    }
    
    
    public interface DataService<M extends DTObjectModel> extends RemoteService {
    	List<M> fetch(int startIndex, int count);
    	M add(M model);
    	M update(M model);
    	void remove(M model);
    }
    
    
    public interface DataServiceAsync<M extends DTObjectModel> {
    	void fetch(int startIndex, int count, AsyncCallback<List<M>> async);
    	void add(M model, AsyncCallback<M> async);
    	void update(M model, AsyncCallback<M> async);
    	void remove(M model, AsyncCallback<M> async);
    }
    
    
    public class SmartGWTDataSource<M extends DTObjectModel, R extends HasSmartGWTResources<M>, S extends DataService<M>> extends DataSource {
    	private HasSmartGWTResources<M> resource;
    	private DataServiceAsync<M> service;
    	
        /**
         * Creates new data source which communicates with server by GWT RPC.
         * It is normal server side SmartClient data source with data protocol
         * set to <code>DSProtocol.CLIENTCUSTOM</code> ("clientCustom" - natively
         * supported by SmartClient but should be added to smartGWT) and with data
         * format <code>DSDataFormat.CUSTOM</code>.
         */
    	@SuppressWarnings("unchecked")
    	public SmartGWTDataSource(HasSmartGWTResources<M> resource, Class<S> serviceClass) {
    		super();
            setDataProtocol (DSProtocol.CLIENTCUSTOM);
            setDataFormat (DSDataFormat.CUSTOM);
            setClientOnly (false);
    		this.resource = resource;
    		service = (DataServiceAsync<M>) GWT.create(serviceClass);
    		for (DataSourceField field: resource.getDataSourceFields()) {
    			addField(field);
    		}
    		GWT.log("SourcebookDataSource() initialized.", null);
    	}
    
    .
    .
    .
    }

    Comment


      #62
      How to handle associations?

      Hi all,

      thanks a lot for this really helpful pattern! I got it working for my project in almost no time!

      But now I came across a problem. My domain objects have different relations to each other (OneToMany, ManyToMany) that are implemented as javax.persistence annotations. E.g. one User has a list of Roles (@ManyToMany(mappedBy="users")).

      When I now create a UserDataSource based on the GwtRpcDataSource, I can load the records and change the primitive members (first name, last name, ...). The changes are also saved properly, but also the list of roles is set to null and therefore deleted.

      My question is: how can I display (and modify) the roles? If that is not possible, how can I at least preserve the existing associations?

      If this problem cannot bo solved this pattern is IMHO quite useless for real-world domains.

      Thanks for your help!!
      Michael

      Comment


        #63
        Nested Datasources?

        Comment


          #64
          Originally posted by mnenchev
          Nested Datasources?
          Thanks, I'll have a look into this.

          But what about those associations that I do not want to be displayed or changed in the grid? Do those also need to be handled as (hidden) nested datasources?

          Thanks
          Michael

          Comment


            #65
            Well the DataSource only holds the field you defined, not the complete ValueObjects.
            So maybe you just need to store your list somewhere (inside your custom DS?) and for updating take the original object and replace the values from the DS.
            That way, you don't loose "hidden" data you do not manage with the DS.

            Comment


              #66
              Originally posted by mivola
              Hi all,

              thanks a lot for this really helpful pattern! I got it working for my project in almost no time!

              But now I came across a problem. My domain objects have different relations to each other (OneToMany, ManyToMany) that are implemented as javax.persistence annotations. E.g. one User has a list of Roles (@ManyToMany(mappedBy="users")).

              When I now create a UserDataSource based on the GwtRpcDataSource, I can load the records and change the primitive members (first name, last name, ...). The changes are also saved properly, but also the list of roles is set to null and therefore deleted.

              My question is: how can I display (and modify) the roles? If that is not possible, how can I at least preserve the existing associations?

              If this problem cannot bo solved this pattern is IMHO quite useless for real-world domains.

              Thanks for your help!!
              Michael
              Hi mivola,

              I think the problem is that you are trying to use domain objects at the client side. You can not load the whole object graph at the client, because the JPA merge destroys your relations for example (server side implementation details should not leak into the client).

              I have had the same problems and found some ways to solve them:

              1) Use RemoteFacade with DTO object model instead using directly the domain model. This has been discussed a lot. The main advatage is that you can change your domain model without changing the client code. The main disadvantage is that you have to map between domain model and DTOs and if you are doing it manually it is a nightmare, so better use some framework (I like dozer).

              OR

              2) Keep using the domain model, but fix all the relations into the RemoteFacade (on write). The RemoteFacade contract is for example:
              -readX(...) - returns domain object instance of X with prefetched relations x.a,x.b,x.c,x.a.m,x.a.m.p, x.a.m.p.q,...
              (you specify the exact island of the object graph returned by each read method, so that the client knows how deep it can explore each relation)
              -writeX(x) - expected domain object instance of X with related objects x.a, x.b,x.a.m and x.a.m.p (so here the RemoteFacade will first select all the relations of x object which you want to preserve, then merge the new changes and finally restore all relations broken by the merge)

              OR

              3) CQS - this is similar to DTOs approach, but the writes are done through commands/messages (which again are DTOs), so that you will not use JPA merge, but instead: select object(s), then throw command(s) into them (the effect of which can be create/update/delete) and finally commit.

              Hope this helps.

              Comment


                #67
                Originally posted by bodrin
                2) Keep using the domain model, but fix all the relations into the RemoteFacade (on write). The RemoteFacade contract is for example:
                -readX(...) - returns domain object instance of X with prefetched relations x.a,x.b,x.c,x.a.m,x.a.m.p, x.a.m.p.q,...
                (you specify the exact island of the object graph returned by each read method, so that the client knows how deep it can explore each relation)
                -writeX(x) - expected domain object instance of X with related objects x.a, x.b,x.a.m and x.a.m.p (so here the RemoteFacade will first select all the relations of x object which you want to preserve, then merge the new changes and finally restore all relations broken by the merge)
                Hi bodrin,

                Thanks for your help!

                Yes, i'm using the domain objects directly using hibernate4gwt/gilead (thats what it is designed for). And I'd like to keep it this way because I just dont like the idea of having additional DTO's.
                Therefore I'd like to explore the second alternative you described. Could you describe that in a more detailed way, possibly with a small example?

                Thanks in advance!
                Michael

                Comment


                  #68
                  Just a fyi that SmartGWT EE solves this problem most elegantly even compared to a gilead based solution.

                  Here's why :
                  - Only properties declared in the datasource are extracted from the (Hibernate managed) domain model without the use of any DTO's. Moreover the user does not have to worry about excessive subsets of the object graph being sent over to the client. With a gilead based solution, the user is still responsible of managing which subset of the domain model is sent to the client on a per-screen basis. Some screens might be displaying associations which are lazy loaded by Hibernate while others might only display information from top level bean properties.

                  - Updates are managed transparently with SmartGWT EE, even if the modified property is nested, or part of a lazy association.

                  For a better understanding check out the Hibernate based samples in SmartGWT EE.
                  Last edited by smartgwt.dev; 15 Apr 2009, 08:41.

                  Comment


                    #69
                    Hi Michael,

                    With RPC you are updating only entity itself.
                    You can use @ReadOnly annotation from Gilead on collection property.

                    Aleksandras

                    Originally posted by mivola
                    Hi bodrin,

                    Thanks for your help!

                    Yes, i'm using the domain objects directly using hibernate4gwt/gilead (thats what it is designed for). And I'd like to keep it this way because I just dont like the idea of having additional DTO's.
                    Therefore I'd like to explore the second alternative you described. Could you describe that in a more detailed way, possibly with a small example?

                    Thanks in advance!
                    Michael

                    Comment


                      #70
                      GWT-RPC Polling

                      Hello,

                      Thank you for the datasource. Works great.

                      Does anyone have any thoughts as far as the extending this datasource to accomplish regular polling ( say every 5 sec ) ?

                      Should the timer trigger happen within the datasource? i.e. every 5 seconds a GWT-RPC poll() call is made and the setData method called with the response.

                      Or should it happen at the widget level with a call to invalidate cache ( which would trigger a new fetch() ? )

                      Thanks

                      Comment


                        #71
                        Hi,

                        DataSource itself is passive.

                        Widget is initiating call.

                        Aleksandras

                        Comment


                          #72
                          Thanks for the response.

                          I am trying to update a widget based on a poll() where I am not getting an entire "set" back, but rather just those that have been Created, Updated, or Deleted server side after an initial fetch().

                          So, does this make sense?

                          1) Extend GwtRpcDataSource.java to add an abstract method "public void poll()"
                          2) In my GwtRPCDataSource implementation, implement the poll() method and have it make a GWT-RPC call to RPCPoll().
                          3) Set up a timer in the class holding the Widget to go off every X seconds. The poll() method is then called on the datasource.

                          My question is, how do I update the datasource with the results of the RPCPoll(). ( assume that I know the CRUD verb and the records Primary Key )

                          My guess is I will have to make a call to DataSource.getData() and manually mangle the data to create/update/delete the polled records.

                          Once I do this, and do a DataSource.setData() with this manually mangled list, will the widget be notified of the changes?

                          Thanks

                          Comment


                            #73
                            Looks like I can accomplish this with a poll() and add the records using:

                            DataSource.updateData(Record record)
                            DataSource.addData(Record record)
                            DataSource.removeData(Record record)

                            So, my ( hopefully ) last part of the puzzle is, how can I prevent data ringing for records added programmatically like this. If I call DataSource.addData, that triggers GwtRpcDataSource.executeAdd() sending the record back to the server ( which it just came from )

                            I found that in my Datasource implementation I can key off of request.getComponentId() to see if this came from the result of a UI modification or a programmatic modification.

                            Example executeAdd:

                            ...
                            if (null != request.getComponentId()){
                            //This came from the UI make the RPC call and process Response
                            }
                            else{
                            processResponse(requestId, response);
                            }

                            Comment


                              #74
                              Originally posted by mivola
                              Hi bodrin,

                              Thanks for your help!

                              Yes, i'm using the domain objects directly using hibernate4gwt/gilead (thats what it is designed for). And I'd like to keep it this way because I just dont like the idea of having additional DTO's.
                              Therefore I'd like to explore the second alternative you described. Could you describe that in a more detailed way, possibly with a small example?

                              Thanks in advance!
                              Michael
                              Ok, in your case may be it is better to do it with Gilead .. I haven't tried this.

                              For the other option - you might cluster your entites into aggregates (http://domaindrivendesign.org/freelinking/Aggregate)
                              and do read/create/update on aggregate level.
                              Here is a rough example. We have User, Profile and Phone. User has many Profile-s.
                              Each Profile has a Phone. Lets say the aggregates are (User) and (Profile + Phone) with the aggregate
                              roots User and Profile correspondingly.

                              Code:
                              @Entity
                              public class User {
                              	@Id
                              	private int id;
                              	
                              	private String firstName;	
                              	private String lastName;
                              		
                              	@OneToMany( mappedBy = "owner" )
                              	protected Set<Profile> profiles = new HashSet<Profile>();
                              	
                              	// ...
                              }
                              
                              @Entity
                              public class Profile {
                              	@Id
                              	private int id;
                              	
                              	@ManyToOne( optional = false, fetch = LAZY )
                              	private User owner;
                              	
                              	@OneToOne( optional = false )
                              	Phone phone;
                              	
                              	private Status status;
                              	// ...
                              }
                              
                              @Entity
                              public class Phone {
                              	@Id
                              	private int id;
                              	
                              	private String number;
                              	// ...
                              }
                              
                              public class RemoteFacadeImpl {
                              
                              	public void updateProfile(Profile newProfile) {
                               
                              		Profile oldProfile = em.find(Profile.class, newProfile.getId());
                              		
                              		// fix some relations
                              		newProfile.setUser(oldProfile.getUser());
                              		
                              		// do udpate - affects only Profile and Phone related objects
                              		newProfile = em.merge(newProfile);
                              	}
                              	
                              	// public void createProfile(Profile newProfile) {}
                              	// deleteProfile(int profileId){}
                              	
                              	// public void createUser(User newUser) {}
                              	// public void updateUser(User newUser) {}
                              	// public void deleteUser(int userId) {}
                                      // + methods to manage the User - Profile relation
                              }
                              If you are interested in this approach it might be better to find some other resources (like DDD), because in this talk we should discuss more SmartGWT related things.

                              Hope this helps.

                              Comment


                                #75
                                clientContext

                                I there some documentation on the (for me) strange:

                                Object clientContext = dsRequest.getAttributeAsObject("clientContext")

                                and later:

                                dsResponse.setAttribute("clientContext", clientContext);

                                why is this needed? if I remove it in my DS implementation, the DS stops working.

                                Comment

                                Working...
                                X