Announcement

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

    data consistency question?

    One of my data tables contains parent/child data relationships like this:

    1 -> 2 -> 3 -> 4 -> etc....

    such that, for example, 2 is the child record of 1 and is the parent record of 3 and these are all displayed in a TreeGrid.

    I need help on how to solve two problems:

    1. How do I prevent a user from deleting a record when it still has one or more child records? I came up with what I know is a very ugly hack to deal with this yesterday....but I'd prefer to discover the correct way of handling this.

    2. When multiple users are using the application, (using the example data above) it will let UserA delete record "4" and then allow UserB to add child records to "4" because the UserB client side cache of the data knows nothing about UserA having just deleted "4" from the database. How do I prevent this from happening?

    Any help to even point me in the right direction would be most appreciated by my entire family who would love it if I can solve this problem and remain employed :-)

    Thanks,
    Jerry

    #2
    not even a little hint?

    It's fast approaching the time when I'll need to upgrade my 60 day trial to the $1950 "Power" edition... something I will gladly do because I like everything else which I _have_ been able to figure out and get working - however I'd like to feel confident that when I run into the occasional legitimate problem, or more likely in this case, I am clueless about something everyone else considers to be common knowledge, that at the very least I'd get a hint about what to read up on or enlightened in some helpful way.

    Otherwise, thanks for everything I know you're doing behind the scenes to continue expanding and improving SmartClient.

    At this point I think both of my problems can be solved with some custom validation/scripting inside my .ds.xml file for this data model such that these data model rules are enforced on the server side and any needed error messages bubble up to the client. Of course I could still be entirely wrong and therefore benefit from a hint.... but until then I'll keep digging for what appears to be a non-existent comprehensive guide to what can be done inside the .ds.xml files - or worst case I'll stumble across enough examples to figure it out.
    Last edited by jerry426; 29 Mar 2013, 10:34.

    Comment


      #3
      You're question was way too vague. You didn't even mention what type of DataSource you're using, or how the user triggers the delete, or whether you intend a client or server check.

      Every property you can set on a .ds.xml file is exhaustively documented in the reference.

      Comment


        #4
        Originally posted by Isomorphic View Post
        You're question was way too vague. You didn't even mention what type of DataSource you're using, or how the user triggers the delete, or whether you intend a client or server check.

        Every property you can set on a .ds.xml file is exhaustively documented in the reference.
        Isomorphic, firstly - my apologies for being too vague - a byproduct of my lack of familiarity and not yet knowing enough to ask the correct questions while providing appropriate information (and without drowning you in senseless rambling). Of course you are correct about the exhaustive documentation which I eventually found for the .ds.xml files - and in fact the maturity of the SmartClient package and the depth and clarity of the documentation is pretty much second to none compared to everything else I looked at which is what prompted me to adopt it for my current project. However, like any documentation, there are sometimes information gaps that are difficult to fill without also finding some clear and complete example code.

        I will try to ask my current list of questions clearly now...

        I'm using version 8.3 downloaded a few weeks ago along with SmartClient Server Framework for all interaction with data. I want to enforce my data rules in the *.ds.xml files as long as it makes sense to do it that way (which may be entirely and forever in my case). The user initiates the removal of a record from a TreeGrid or a ListGrid (which are tied to my parent/child datasource shown below) via a context menu choice which then calls deleteSelectedRecord() on either the TreeGrid or the ListGrid.

        My questions may appear to be asking for you to "do my homework", but I've already tried many things, based on what examples I could find and the documentation - and failed at all of them. I could even dig them all out of my git commits if I had to.... but if you can give me a few precise bits of direction I am betting that will kickstart me enough to become self-reliant in short order such that I can forge ahead with my project, and confidently join the ranks of "Power Edition" users in the near future.

        Questions 1 and 2: Here's my Groups.ds.xml file which has two of my questions in there as comments:

        Code:
        <DataSource
           ID="Groups"
           serverType="sql"
           tableName="Groups"
        >
        
           <fields>
              <field name="groupId" type="sequence" title="ID" primaryKey="true"/>
              <field name="groupParentId" type="number" title="Parent ID" required="true"
                     foreignKey="groupId" rootValue="0" detail="true"/>
              <field name="groupCreatorId" type="number" title="Creator ID" foreignKey="Users.userId"/>
              <field includeFrom="Users.userName" title="Created By"/>      
              <field name="groupName" type="text" title="Group Name"/>
           </fields>
           
           <operationBindings>
              <operationBinding operationType="add">
                 <script language="javascript"><![CDATA[
                    
                    // Questions 1a and 1b:
                    // 1a. what code do I need here to set this to true only if
                    // the proposed parent record still exists ??? 
                    var desiredParentRecordExists = true;
                    
                    if (desiredParentRecordExists) {
                       dsRequest.execute();
                    } else {
                       
                       // 1b. What code do I need here to STOP the new record insertion
                       // and send an error message back to the client side ??
                     
                    };
                 ]]></script>         
              </operationBinding>      
              
              <operationBinding operationType="fetch"/>
              <operationBinding operationType="update"/>
              
              <operationBinding operationType="remove">
                 <script language="javascript"><![CDATA[
                    
                    // Questions 2a and 2b
                    // 2a. what code do I need here to set this to true only if
                    // the record being removed has no child records ??? 
                    var groupHasNoChildGroups = true;
                    
                    if (groupHasNoChildGroups) {
                       dsRequest.execute();
                    } else {
                       
                       // 2b. What code do I need here to STOP the removal
                       // and send an error message back to the client side ??
                     
                    };
                 ]]></script>
              </operationBinding>
              
           </operationBindings>
        </DataSource>
        (and as a side note, my system throws an error if I try to run any language="java" script in there - just an empty set of script tags causes it - I'll paste the error log at the bottom)

        Question 3: I could have put this question in the above .ds.xml file but I didn't want to make it too ugly with comments. Whenever the user changes and saves a record, I want to have a script section in the "update" operationBinding which saves a snapshot of the before-update state of the record to a history table such that I can keep a running history of all changes to each record. A JSON string written to my history table is all I need but I haven't been able to figure out exactly how to do it. Any example code you can give me or point me to?

        Question 4: If there's two separate users each with their own browser looking at the same UI and data (such as the Groups parent/child data above), whenever User1 removes a record, the caching for User2 still shows the stale removed record. The server-side data model rules I want to implement as mentioned above will prevent User2 from violating whatever constraints I code into the .ds.xml file - however the ideal solution for me would be to force the update of User2's cache and refresh all UI widgets tied to that data such that, for example, User2 is not still seeing a deleted record. What I've tried thus far is firing invalidateCache() in the click event of the involved UI widgets, but that seems to make it impossible for me to also implement saving and restoring the selectedState of the controls. I also tried the "Transparent Update" approach described in the wiki at https://isomorphic.atlassian.net/wiki/pages/viewpage.action?pageId=525074 but that didn't actually update the UI even though the RPC log clearly showed the requests being fulfilled.

        Question 5: Is there a reliable "blocking" way I can easily ask for a piece of data from the server in my client side code such that the client blocks and waits for the returned data which I can use in whatever client side conditional code I'm trying to implement?


        As mentioned above, whenever I attempt to use <script language="java"> in my ds.xml file OR run one of the SmartClient examples which uses "java", my system throws this error and I'm not sure where to start tracking it down:
        Code:
        [#|2013-03-29T20:54:41.369-0400|INFO|glassfish3.1.2|javax.enterprise.system.std.com.sun.enterprise.server.logging|_ThreadID=79;_ThreadName=Thread-2;|=== 2013-03-29 20:54:41,367 [0(1)] WARN  RequestContext - dsRequest.execute() failed: 
        com.isomorphic.scripting.ScriptException: Script execution failed. Cannot extract error message from stderr or stackTrace.
        	at com.isomorphic.scripting.ScriptJava.fillInStackTrace(ScriptJava.java:157)
        	at com.isomorphic.scripting.ScriptJava.evalScript(ScriptJava.java:378)
        	at com.isomorphic.scripting.ScriptXBase.eval(ScriptXBase.java:40)
        	at com.isomorphic.rpc.Scripting.evalServerScript(Scripting.java:100)
        	at com.isomorphic.rpc.BuiltinRPC.evalServerScript(BuiltinRPC.java:327)
        	at com.isomorphic.datasource.DataSourceDMI.evalInlineScript(DataSourceDMI.java:825)
        	at com.isomorphic.datasource.DataSourceDMI.execute(DataSourceDMI.java:202)
        	at com.isomorphic.datasource.DataSourceDMI.execute(DataSourceDMI.java:64)
        	at com.isomorphic.datasource.DSRequest.execute(DSRequest.java:2027)
        	at com.isomorphic.servlet.IDACall.handleDSRequest(IDACall.java:216)
        	at com.isomorphic.servlet.IDACall.processRPCTransaction(IDACall.java:173)
        	at com.isomorphic.servlet.IDACall.processRequest(IDACall.java:138)
        	at com.isomorphic.servlet.IDACall.doPost(IDACall.java:74)
        	at javax.servlet.http.HttpServlet.service(HttpServlet.java:688)
        	at com.isomorphic.servlet.BaseServlet.service(BaseServlet.java:152)
        	at javax.servlet.http.HttpServlet.service(HttpServlet.java:770)
        	at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1550)
        	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:343)
        	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:217)
        	at com.isomorphic.servlet.CompressionFilter.doFilter(CompressionFilter.java:259)
        	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
        	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:217)
        	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61)
        	at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
        	at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
        	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
        	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
        	at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
        	at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
        	at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
        	at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
        	at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383)
        	at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
        	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
        	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
        	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:217)
        	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:279)
        	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
        	at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:655)
        	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:595)
        	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:161)
        	at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:331)
        	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:231)
        	at com.sun.enterprise.v3.services.impl.ContainerMapper$AdapterCallable.call(ContainerMapper.java:317)
        	at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:195)
        	at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:860)
        	at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:757)
        	at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:1056)
        	at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:229)
        	at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:137)
        	at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:104)
        	at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:90)
        	at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:79)
        	at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:54)
        	at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:59)
        	at com.sun.grizzly.ContextTask.run(ContextTask.java:71)
        	at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:532)
        	at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:513)
        	at java.lang.Thread.run(Thread.java:722)
        Caused by: java.lang.NullPointerException
        	at com.isomorphic.scripting.ScriptJava.evalScript(ScriptJava.java:353)
        	... 57 more
        |#]

        Comment


          #5
          1,2,3: see QuickStart Guide, Adding DMI Business Logic. You can create and execute additional DSRequests in order to do things like check for children in the data model, or store additional data to other tables.

          4: SmartClient Messaging or a similar system needs to be used to do this - there is an example here. However, note, in most systems, it's not worthwhile to attempt to keep all clients up to date - that mostly happens on in real-time securities or military applications.

          5: if you set rpcRequest.showPrompt:true when sending a DSRequest, UI interaction will be blocked for the duration of the request. The programming model is still asynchronous (must use a callback to be notified of completion), and has to be.

          Stack trace: probably you don't have a correct set of .jars. See the installation instructions.

          Comment


            #6
            progress! but still need help please...

            Isomorphic (or anyone else with an answer):

            While you didn't give me actual code, you did prompt me to dig deeper into various documentation and a few more code examples I found by searching. As as result I have my 1,2,3 problems solved - almost!

            Here's my current .ds.xml file wherein all my new embedded javascript is working perfectly - except for how to force the client-side cache updates (perhaps triggered by click event on the TreeGrid I want to display updated data in). I've got 2 hopefully simple questions in there as comments I'm hoping to get cleared up.
            Code:
            <DataSource
               ID="Groups"
               serverType="sql"
               tableName="Groups">
            
               <fields>
                  <field name="groupId" type="sequence" title="ID" primaryKey="true"/>
                  <field name="groupParentId" type="number" title="Parent ID" required="true"
                         foreignKey="groupId" rootValue="0" detail="true"/>
                  <field name="groupCreatorId" type="number" title="Creator ID" foreignKey="Users.userId"/>
                  <field includeFrom="Users.userName" title="Created By"/>      
                  <field name="groupName" type="text" title="Group Name"/>
               </fields>
               
               <operationBindings>
                  <operationBinding operationType="add">
                     <script language="javascript"><![CDATA[
                        
                        //-----------------------------------------
                        // Allow addition of a Group only if the proposed parent record still
                        // exists (i.e. some other user has not deleted it)
                        
                        parentRecord = DSRequest("Groups", "fetch");
                        parentRecord.setCriteria("groupId", dsRequest.getFieldValue("groupParentId"));
                        
                        if (parentRecord.execute().getTotalRows()) {
                           dsRequest.execute();
                        } else {
                           resp = DSResponse();
                           resp.setInvalidateCache(true);
                           resp.setFailure("Sorry, another user session has deleted the parent record!");
                           resp;
                        
                           // QUESTION: how can I force invalidation of client-side cache from here
                           // such that user sees a fresh copy of correct data ???  My attempt above
                           // to setInvalidateCache(true) appears to have no effect in the client side.
                        
                           // QUESTION: is my approach with the above javascript within the realm of sanity
                           // or is there a better way I should be doing those operations ???
                        };
                        //-----------------------------------------
                        
                     ]]></script>         
                  </operationBinding>      
                  
                  <operationBinding operationType="fetch"/>
                  <operationBinding operationType="update"/>
                  
                  <operationBinding operationType="remove">
                     <script language="javascript"><![CDATA[
                        
                        //-----------------------------------------
                        // Allow removal of a Group only if no child Groups exist
                        
                        childGroups = DSRequest("Groups", "fetch");
                        childGroups.setCriteria("groupParentId", dsRequest.getFieldValue("groupId"));
                        
                        if (!childGroups.execute().getTotalRows()) {
                           dsRequest.execute();
                        } else {
                           resp = DSResponse();
                           resp.setFailure("Sorry, cannot remove a Group if it has child Groups!");
                           resp;
                        };
                        //-----------------------------------------
                        
                     ]]></script>
                  </operationBinding>
                  
               </operationBindings>
            </DataSource>
            Per your advice on my #4 where you mentioned SmartClient Messaging - I reviewed all the plumbing needed to make that work and agree that it's not worthwhile for me to worry about this now - especially now that I have figured out the basic recipe with the javascript in the .ds.xml files which I can use to help me enforce various rules on the server side.

            Regarding my stack trace where you suggested maybe I'm missing .jar files, I've got the entire set of them from the SmartClientSDK/..../WEB-INF/lib directory in my project's WEB-INF/lib directory. Are there others I need to track down ???

            Comment


              #7
              Signalling total failure will not invalidateCache, it will go to central error handling (see Error Handling overview).

              To invalidate cache correctly, send a success response with some new records, and also set invalidateCache on the response. This will remove the existing, stale cache and provide a replacement cache in one turnaround. See also DataSource.updateCaches() for a manual way to force the refresh from the client-side.

              On that error message - if you say it's not .jars, then we can't tell what wrong with your installation. First step would be to verify that the SDK examples run normally for you. If not, maybe you have a bad JDK or have only installed the JRE, not the JDK.

              Comment


                #8
                almost there...

                Isomorphic:

                Now I've got my operationBinding code sending a fresh copy of data back to the client and client is refreshing the TreeGrid UI exactly as I wanted. However, I also need to display a pop-up informing the user of what just happened ("Sorry, the record no longer exists"). Here's my current operationBinding - what do I need in order to have the pop-up appear on the client side ??? :
                Code:
                   <operationBindings>
                      <operationBinding operationType="add">
                         <script language="javascript"><![CDATA[
                            
                            //-----------------------------------------
                            // Allow addition of a Group only if the proposed parent record still
                            // exists (i.e. some other user has not deleted it)
                            
                            parentRecord = DSRequest("Groups", "fetch");
                            parentRecord.setCriteria("groupId", dsRequest.getFieldValue("groupParentId"));
                            
                            if (parentRecord.execute().getTotalRows()) {
                               dsRequest.execute();
                            } else {
                               resp = DSResponse();
                               resp.setSuccess();
                               resp.setInvalidateCache(true);
                               freshData = DSRequest("Groups", "fetch").execute();
                               resp.setData(freshData);
                               resp;
                            };
                            //-----------------------------------------
                            
                         ]]></script>         
                      </operationBinding>
                Looking at the javadoc for DSResponse, I tried to use resp.setProperty("customError", "Sorry, the record no longer exists") in the above javascript, but I could not figure out how to get the client-side code to detect the property and display a pop-up. (And not even sure if that's the correct approach).

                Can you point me at the answer and/or give me a code example?

                Thanks for all your help!

                Comment


                  #9
                  Sorry, we missed that you were doing this on an "add" operation.

                  If you're doing a fetch and you realize the cache is invalid (there are various ways for detecting this) then it's correct to set invalidateCache and return new data to repopulate the widget's cache.

                  In the case of an "add", you want to signal failure: the "add" operation clearly failed. As we mentioned previously, the invalidateCache flag doesn't apply here, and your UI typically does not want to just invalidateCache anyway - the UI may provide a way to recover (create new node under a different parent, for instance).

                  The Error Handling overview explains how you can get notified of this failure system-wide or via the callback at the particular point addData() was invoked (via willHandleError).

                  Comment

                  Working...
                  X