0 Replies Latest reply on Jan 29, 2012 8:17 PM by bgolman

    Seamless search using richfaces extendedDataTable

    bgolman

      Hello, everyone.  I apologize for being wordy and I do hope I came to the right place, but I really need your help.  In our project we have a seamless search (I hope it's the right term ;)).  That is, a user types the value in a text box and while he types, the table gets updated with new results, based on what's typed.  I was able to get that working, using Richfaces 3.3.3 component rich:extendedDataTable, however, after a few tries I get ConcurrentModificationException, thrown by the component itself.   The initial list I use for my searches comes from the properties file (because I don't want the search to hit database every time the user types something).  I've been banging my head over it for months.  What am I missing?  Let me show you what I've done:

      This is the input text that should trigger the search logic (I make sure that the table does not get updated when the value is less than 4 characters or if the user presses keys, like arrows, shift, and ctrl - this function is "returnunicode(event)"):

      <h:inputText id="firmname" value="#{ExtendedTable.searchValue}" >
             <a4j:support reRender="resultsTable"
                                 onsubmit="if ((this.value.length<4 && this.value.length>0) || !returnunicode(event)) {
                                                                               return false;
                                                 }"
                                                 actionListener="#{ExtendedTable.searchForResults}" event="onkeyup" />
      </h:inputText>


      Action listener is what should update the list.  Here is the extendedDataTable, right below the inputText:

      <rich:extendedDataTable selection="#{ExtendedTable.selection}"
      tableState="#{ExtendedTable.tableState}" var="item" id="resultsTable" value="#{ExtendedTable.dataModel}">

      ... <%--  I'm listing columns here  --%>

      </rich:extendedDataTable>


      Now, if it's ok, I would like to show you the back-end code.  I only left the logic that's important to my issue.

      //The action listener itself
      public void searchForResult(ActionEvent e) {
        //"results" is an ArrayList - a local variable, defined as an empty ArrayList, with getter and setter
               synchronized(results) {
                        results.clear();
               }

               if (this.searchValue.length() > 3) {
                       results.clear();
                       updateTableList();
               } else {
                       results.clear();
               }

               dataModel = null; // to force the dataModel to be updated.

      }

      public void updateTableList() {
               try {
                           //ResultObject is a simple pojo and getResultsPerValue is a method that read the data from the properties file, assigns it to this pojo, and
                           //adds a pojo to the list
                           List<ResultObject> searchedResults = getResultsPerValue(this.searchValue);
                           synchronized(results) {
                                      results.clear();
                                      results.addAll(Collections.unmodifiableList(searchedResults));
                           }
               } catch(Throwable xcpt) {
            lgr.error(TM, xcpt);
               }
        lgr.debug("Exit");

      }

      //And finally, a dataModel getter, which is what causes the list to constantly get updated.  DataModel
      //(ExtendedTableDataModel<ResultObject> dataModel) is also defined as a local variable, but it doesn't have a setter, only this getter, which
      //should get updated automatically, every time the value is changing and the results list is refreshed
      public synchronized ExtendedTableDataModel<ResultObject> getDataModel() {
               try {
                   if (dataModel == null) {
                       //If data model is empty, initialize it and fill it with the results currently in a result list
                       dataModel = new ExtendedTableDataModel<ResultObject>(new DataProvider<ResultObject>() {
                           public ResultObject getItemByKey(Object key) {
                               try {
                                   for(ResultObject c : results) {
                                       if (key.equals(getKey(c))){
                                           return c;
                                       }
                                   }
                               } catch (ConcurrentModificationException cme) {
                                   lgr.error(TM+ "$ExtendedTableDataModel.getItemByKey", cme);
                               }
                               return null;
                           }

                           public List<ResultObject> getItemsByRange(int firstRow, int endRow) {
                               return Collections.unmodifiableList(results.subList(firstRow, endRow));
                           }

                           public Object getKey(ResultObject item) {
                               return item.getResultName();
                           }

                           public int getRowCount() {
                               return results.size();
                           }
                       });
                   }
               } catch (ConcurrentModificationException cme) {
                   return getDataModel();
               }
               return dataModel;
      }

       

      And like I said, it works fine, but either when too many calls are made to the back-end or the user types fast (it's hard to catch exactly when it happens), ConcurrentModificationException is thrown.  Synchronization of the Java code has never been my strongest side, I don't know what would be causing it and, most importantly, how can I go about avoiding it.  I'm aware, that Richfaces 4.0 has made a lot of changes to rich:extendedDataTable component, I don't know if that was an ongoing issue with this component, which is now resolved, or I'm simply doing something wrong.  However, I don't have time to upgrade the whole application to Richfaces 4.0 (it's going to be done in phase 2), this is just the small part of the whole project.  Maybe there is a workaround?  Or, perhaps, there are other ways to implement similar kind of search, using plain JSF (provided that they are quick enough to implement).  I will appreciate any kind of help or advice on that.  I hope the code is understandable enough, but if not, let me know, I'll explain further.  Thank you in advance, I really appreciate it.