简体   繁体   中英

ConcurrentModificationException using Richfaces ExtendedDataTable

I apologize for a lengthy question, but I really need your help. As a part of our project, I'm currently working on a search engine, that updates results list on the fly: the user types in the first 4 characters and up, and as he types, the results list changes. The search value is typed in a text box, while the results are displayed in a Richfaces component rich:extendedDataTable below. If the search value is removed, the result list is empty. I was able to get that working, however, after a few tries I get ConcurrentModificationException, thrown by the component itself. The initial list I am searching 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="#{ExtendedTableBean.searchValue}">
       <a4j:support reRender="resultsTable" onsubmit="
           if ((this.value.length<4 && this.value.length>0) || !returnunicode(event)) {
               return false;
           }" actionListener="#{ExtendedTableBean.searchForResults}" event="onkeyup" />
   </h:inputText>

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

   <rich:extendedDataTable tableState="#{ExtendedTableBean.tableState}" var="item"
                           id="resultsTable" value="#{ExtendedTableBean.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.

 /*
  * To change this template, choose Tools | Templates
  * and open the template in the editor.
  */

 package com.beans;

 import java.io.FileInputStream;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.ConcurrentModificationException;
 import java.util.List;
 import java.util.Properties;
 import java.util.concurrent.CopyOnWriteArrayList;
 import javax.faces.context.FacesContext;
 import javax.faces.event.ActionEvent;
 import org.richfaces.model.DataProvider;
 import org.richfaces.model.ExtendedTableDataModel;

 public class ExtendedTableBean {      
     private String sortMode="single";
     private ExtendedTableDataModel<ResultObject> dataModel;
     //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 

     private Object tableState;
     private List<ResultObject> results = new CopyOnWriteArrayList<ResultObject>();
     private List<ResultObject> selectedResults = 
                                          new CopyOnWriteArrayList<ResultObject>();

     private String searchValue;

     /**
      * This is the action listener that the user triggers, by typing the search value
      */
     public void searchForResults(ActionEvent e) {
        synchronized(results) {
           results.clear();
        }        

        //I don't think it's necessary to clear results list all the time, but here
        //I also make sure that we start searching if the value is at least 4 
        //characters long
        if (this.searchValue.length() > 3) {
           results.clear();
           updateTableList();
        } else {
           results.clear();
        }

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

     public List<ResultObject> getResultsPerValue(String searchValue) {
        List<ResultObject> resultsList = new CopyOnWriteArrayList<ResultObject>();

        //Logic for reading data from the properties file, populating ResultObject
        //and adding it to the list

        return resultsList;
     }

     /**
      * This method updates a firm list, based on a search value
      */
     public void updateTableList() {
         try {              
            List<ResultObject> searchedResults = getResultsPerValue(searchValue);

            //Once the results have been retrieved from the properties, empty 
            //current firm list and replace it with what was found.

            synchronized(firms) {
                firms.clear();
                firms.addAll(searchedFirms);
            }
         } catch(Throwable xcpt) {
            //Exception handling
         }
     }

     /**
      * This is a recursive method, that's used to constantly keep updating the 
      * table list.
      */
     public synchronized ExtendedTableDataModel<ResultObject> getDataModel() {
        try {
            if (dataModel == null) {
                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 (Exception ex) {
                                     //Exception handling
                                  }
                                  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 (Exception ex) {
            //Exception handling    
         }

         return dataModel;
     }

     //Getters and setters 

 }

And like I said, it works fine, but when the user types fast or deletes fast (it's hard to catch exactly when it happens), ConcurrentModificationException is thrown. Here's what it looks like exactly:

WARNING: executePhase(RENDER_RESPONSE 6,com.sun.faces.context.FacesContextImpl@4406b8) threw exception
java.util.ConcurrentModificationException
    at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
    at java.util.AbstractList$Itr.next(AbstractList.java:343)
    at org.richfaces.model.ExtendedTableDataModel.walk(ExtendedTableDataModel.java:108)
    at org.ajax4jsf.component.UIDataAdaptorBase.walk(UIDataAdaptorBase.java:1156)
    at org.richfaces.renderkit.AbstractExtendedRowsRenderer.encodeRows(AbstractExtendedRowsRenderer.java:159)
    at org.richfaces.renderkit.AbstractExtendedRowsRenderer.encodeRows(AbstractExtendedRowsRenderer.java:142)
    at org.richfaces.renderkit.AbstractExtendedRowsRenderer.encodeChildren(AbstractExtendedRowsRenderer.java:191)
    at javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:812)
    at org.ajax4jsf.renderkit.RendererBase.renderChild(RendererBase.java:277)
    at org.ajax4jsf.renderkit.AjaxChildrenRenderer.encodeAjaxComponent(AjaxChildrenRenderer.java:166)
    at org.ajax4jsf.renderkit.AjaxChildrenRenderer.encodeAjaxChildren(AjaxChildrenRenderer.java:83)
    at org.ajax4jsf.renderkit.AjaxChildrenRenderer.encodeAjaxComponent(AjaxChildrenRenderer.java:157)
    ...

Synchronization of the Java code has never been my strongest side, I don't know what would be causing the error and, most importantly, how can I get rid of it. I'm aware, that Richfaces 4.0 has made a lot of changes to rich:extendedDataTable component, I've heard that this was an issue before and now it's resolved. 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. If there is no way to resolve the issue, described above, then maybe there's a workaround? Or, perhaps, there are other ways to implement similar kind of search, using plain JSF (provided that it's are quick enough to implement). I will appreciate any kind of help or advice on that matter. I hope the code is understandable enough, but if not, let me know, I'll explain further. Thank you in advance, I really appreciate your help.

The problem is concurrent ajax calls to the server. Use the attribute "eventsQueue" in a4j:support. Usually, you should always use "eventsQueue" in any ajax component, with all "eventsQueue" in the same page referencing the same queue, unless you have a very good reason to not doing it.

Also, you'll probably want to look into another ajax attribute: "ajaxSingle".

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM