Я прошу прощения за длинный вопрос, но мне действительно нужна ваша помощь. Как часть нашего проекта, я в настоящее время работаю над поисковой системой, которая обновляет список результатов на лету: пользователь вводит первые 4 символа и выше, и, по мере ввода, список результатов изменяется. Значение поиска вводится в текстовом поле, а результаты отображаются в расширенном компоненте Richfaces: extendedDataTable ниже. Если искомое значение удалено, список результатов будет пустым. Я смог заставить это работать, однако после нескольких попыток я получаю исключение ConcurrentModificationException, выдаваемое самим компонентом. Начальный список, который я ищу, взят из файла свойств (потому что я не хочу, чтобы поиск попадал в базу данных каждый раз, когда пользователь что-то вводит). Я бился головой об этом в течение нескольких месяцев. Что мне не хватает? Позвольте мне показать вам, что я сделал:
Это входной текст, который должен запускать логику поиска (я убеждаюсь, что таблица не обновляется, когда значение меньше 4 символов или если пользователь нажимает клавиши, такие как стрелки, shift и ctrl - эта функция is "returnunicode (событие)"):
<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>
Слушатель действий - это то, что должно обновлять список. Вот расширенная таблица данных, прямо под inputText:
<rich:extendedDataTable tableState="#{ExtendedTableBean.tableState}" var="item"
id="resultsTable" value="#{ExtendedTableBean.dataModel}">
... <%-- I'm listing columns here --%>
</rich:extendedDataTable>
Теперь, если все в порядке, я хотел бы показать вам внутренний код. Я оставил только логику, которая важна для моей проблемы.
/*
* 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
}
И, как я уже сказал, он работает нормально, но когда пользователь быстро вводит или удаляет быстро (трудно точно определить, когда это происходит), возникает исключение ConcurrentModificationException. Вот как это выглядит точно:
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)
...
Синхронизация Java-кода никогда не была моей самой сильной стороной, я не знаю, что вызвало бы ошибку и, самое главное, как я могу от нее избавиться. Мне известно, что Richfaces 4.0 внес много изменений в компонент rich: extendedDataTable, я слышал, что раньше это было проблемой, и теперь она решена. Однако у меня нет времени обновить все приложение до Richfaces 4.0 (это будет сделано на этапе 2), это лишь малая часть всего проекта. Если нет способа решить проблему, описанную выше, то, возможно, есть обходной путь? Или, возможно, есть другие способы реализовать подобный вид поиска, используя простой JSF (при условии, что он достаточно быстр для реализации). Я буду признателен за любую помощь или совет по этому вопросу. Я надеюсь, что код достаточно понятен, но если нет, дайте мне знать, я объясню дальше. Заранее спасибо, я очень ценю вашу помощь.