package org.ovirt.engine.ui.uicommonweb.models;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.SortedSet;
import java.util.logging.Logger;
import org.ovirt.engine.core.common.businessentities.IVdcQueryable;
import org.ovirt.engine.core.common.queries.VdcQueryParametersBase;
import org.ovirt.engine.core.common.queries.VdcQueryReturnValue;
import org.ovirt.engine.core.common.queries.VdcQueryType;
import org.ovirt.engine.core.searchbackend.ISyntaxChecker;
import org.ovirt.engine.core.searchbackend.SyntaxChecker;
import org.ovirt.engine.core.searchbackend.SyntaxContainer;
import org.ovirt.engine.core.searchbackend.SyntaxError;
import org.ovirt.engine.core.searchbackend.SyntaxObject;
import org.ovirt.engine.core.searchbackend.SyntaxObjectType;
import org.ovirt.engine.ui.frontend.AsyncCallback;
import org.ovirt.engine.ui.frontend.Frontend;
import org.ovirt.engine.ui.frontend.RegistrationResult;
import org.ovirt.engine.ui.frontend.communication.RefreshActiveModelEvent;
import org.ovirt.engine.ui.uicommonweb.ProvideTickEvent;
import org.ovirt.engine.ui.uicommonweb.UICommand;
import org.ovirt.engine.ui.uicommonweb.dataprovider.AsyncDataProvider;
import org.ovirt.engine.ui.uicompat.Event;
import org.ovirt.engine.ui.uicompat.EventArgs;
import org.ovirt.engine.ui.uicompat.NotifyCollectionChangedEventArgs;
import org.ovirt.engine.ui.uicompat.PropertyChangedEventArgs;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.shared.HandlerRegistration;
/**
* Represents a list model with ability to fetch items both sync and async.
*
* This list model has also an entity.
* This entity is useful to represent hierarchical parent of the list items.
* For example {@link org.ovirt.engine.ui.uicommonweb.models.clusters.ClusterVmListModel} has an entity of type
* Cluster and a list of items of type VM.
*
* @param <E> The type of the entity.
* @param <T> The type of list items.
*/
// TODO once all the children of this class will be refactored to use generics, change from <T> to <T extends IVdcQueryable>
public abstract class SearchableListModel<E, T> extends SortedListModel<T> implements HasEntity<E>, GridController {
private static final int UnknownInteger = -1;
private static final Logger logger = Logger.getLogger(SearchableListModel.class.getName());
private UICommand privateSearchCommand;
private HandlerRegistration timerChangeHandler;
public UICommand getSearchCommand() {
return privateSearchCommand;
}
private void setSearchCommand(UICommand value) {
privateSearchCommand = value;
}
private UICommand privateSearchNextPageCommand;
public UICommand getSearchNextPageCommand() {
return privateSearchNextPageCommand;
}
private void setSearchNextPageCommand(UICommand value) {
privateSearchNextPageCommand = value;
}
private UICommand privateSearchPreviousPageCommand;
public UICommand getSearchPreviousPageCommand() {
return privateSearchPreviousPageCommand;
}
private void setSearchPreviousPageCommand(UICommand value) {
privateSearchPreviousPageCommand = value;
}
private UICommand privateForceRefreshCommand;
public UICommand getForceRefreshCommand() {
return privateForceRefreshCommand;
}
private void setForceRefreshCommand(UICommand value) {
privateForceRefreshCommand = value;
}
private boolean privateIsQueryFirstTime;
public boolean getIsQueryFirstTime() {
return privateIsQueryFirstTime;
}
public void setIsQueryFirstTime(boolean value) {
privateIsQueryFirstTime = value;
}
private boolean privateIsTimerDisabled;
public boolean getIsTimerDisabled() {
return privateIsTimerDisabled;
}
public void setIsTimerDisabled(boolean value) {
privateIsTimerDisabled = value;
}
private String privateDefaultSearchString;
public String getDefaultSearchString() {
return privateDefaultSearchString;
}
public void setDefaultSearchString(String value) {
privateDefaultSearchString = value;
}
private String[] searchObjects;
public String[] getSearchObjects() {
return searchObjects;
}
public void setSearchObjects(String[] value) {
searchObjects = value;
}
private int privateSearchPageSize;
public int getSearchPageSize() {
return privateSearchPageSize;
}
public void setSearchPageSize(int value) {
privateSearchPageSize = value;
}
private String searchString;
public String getSearchString() {
return searchString;
}
public void setSearchString(String value) {
if (!Objects.equals(searchString, value)) {
searchString = value;
pagingSearchString = null;
currentPageNumber = 1;
searchStringChanged();
onPropertyChanged(new PropertyChangedEventArgs("SearchString")); //$NON-NLS-1$
}
}
private boolean caseSensitiveSearch = true;
public boolean isCaseSensitiveSearch() {
return caseSensitiveSearch;
}
public void setCaseSensitiveSearch(boolean value) {
caseSensitiveSearch = value;
}
private String pagingSearchString;
public int getSearchPageNumber() {
return this.currentPageNumber;
}
public String getItemsCountString() {
if (getItems() == null) {
return ""; //$NON-NLS-1$
}
int fromItemCount = getSearchPageSize() * (getSearchPageNumber() - 1) + 1;
int toItemCount = (fromItemCount - 1) + getItems().size();
if (toItemCount == 0 || fromItemCount > toItemCount) {
return ""; //$NON-NLS-1$
}
return fromItemCount + "-" + toItemCount; //$NON-NLS-1$
}
public int getNextSearchPageNumber() {
return getSearchPageNumber() + 1;
}
public int getPreviousSearchPageNumber() {
return getSearchPageNumber() == 1 ? 1 : getSearchPageNumber() - 1;
}
private final PrivateAsyncCallback<E, T> asyncCallback;
private final EntityModel<E> entityModel;
protected SearchableListModel() {
setSearchCommand(new UICommand("Search", this)); //$NON-NLS-1$
setSearchNextPageCommand(new UICommand("SearchNextPage", this)); //$NON-NLS-1$
setSearchPreviousPageCommand(new UICommand("SearchPreviousPage", this)); //$NON-NLS-1$
setForceRefreshCommand(new UICommand("ForceRefresh", this)); //$NON-NLS-1$
setSearchPageSize(UnknownInteger);
asyncCallback = new PrivateAsyncCallback<>(this);
// Most of SearchableListModels will not have paging. The ones that
// should have paging will set it explicitly in their constructors.
getSearchNextPageCommand().setIsAvailable(false);
getSearchPreviousPageCommand().setIsAvailable(false);
entityModel = new EntityModel<E>() {
@Override
protected void onEntityChanged() {
super.onEntityChanged();
SearchableListModel.this.onEntityChanged();
}
@Override
protected void entityPropertyChanged(Object sender, PropertyChangedEventArgs e) {
super.entityPropertyChanged(sender, e);
SearchableListModel.this.entityPropertyChanged(sender, e);
}
@Override
protected void entityChanging(E newValue, E oldValue) {
super.entityChanging(newValue, oldValue);
SearchableListModel.this.entityChanging(newValue, oldValue);
}
};
}
@Override
public Event<EventArgs> getEntityChangedEvent() {
return entityModel.getEntityChangedEvent();
}
@Override
public E getEntity() {
return entityModel.getEntity();
}
@Override
public void setEntity(E value) {
if (getEntity() == null) {
entityModel.setEntity(value);
return;
}
// Equals doesn't always has the same outcome as checking the ids of the elements.
if (value != null) {
if (!((IVdcQueryable) value).getQueryableId().equals(((IVdcQueryable) getEntity()).getQueryableId())) {
entityModel.setEntity(value);
return;
}
}
if (!getEntity().equals(value)) {
entityModel.setEntity(value);
return;
}
setEntity(value, false);
}
protected void setEntity(E value, boolean fireEvents) {
entityModel.setEntity(value, fireEvents);
}
protected void onEntityChanged() {
}
protected void entityPropertyChanged(Object sender, PropertyChangedEventArgs e) {
}
protected void entityChanging(E oldValue, E newValue) {
}
/**
* Returns value indicating whether the specified search string is matching this list model.
*/
public boolean isSearchStringMatch(String searchString) {
return true;
}
/**
* Grid refresh timer associated with this list model.
*/
private GridTimer timer;
private int currentPageNumber = 1; //Default to 1
/**
* Setter for the grid timer.
* @param value The new {@code GridTimer}.
*/
private void setTimer(final GridTimer value) {
timer = value;
}
@Override
public GridTimer getTimer() {
if (timer == null && getEventBus() != null) {
// The timer doesn't exist yet, and we have an event bus, create the timer and pass in the bus.
setTimer(new GridTimer(getListName(), getEventBus()) {
@Override
public void execute() {
// Execute the code, sub classes can override this method to get their own code run.
doGridTimerExecute();
}
});
//Always add a change handler, so we can properly synchronize the interval on all GridTimers.
replaceTimerChangeHandler();
}
return timer;
}
/**
* Sub classes can override this method if they need to do something different when the timer
* expires.
*/
protected void doGridTimerExecute() {
logger.fine(SearchableListModel.this.getClass().getName() + ": Executing search"); //$NON-NLS-1$
syncSearch();
}
/**
* Add a {@code ValueChangeHandler} to the timer associated with this {@code SearchableListModel}.
* The handler is used to update the refresh rate based on changes of other timers. So if another timer changes
* from lets say 5 seconds to 30 seconds interval. It will fire a {@code ValueChangeEvent} which this timer
* receives.
*
* If this timer is currently active (active tab/always active). It will stop this timer, change the interval,
* and start the timer again. If it is inactive, it will just update the interval so that the interval is correct
* for when the timer does become active (changing main tabs).
*/
private void addTimerChangeHandler() {
timerChangeHandler = timer.addGridTimerStateChangeEventHandler(event -> {
int newInterval = event.getRefreshRate();
if (timer.isActive()) {
//Immediately adjust timer and restart if it was active.
if (newInterval != timer.getRefreshRate()) {
timer.stop();
timer.setRefreshRate(newInterval, false);
timer.start();
}
} else {
//Update the timer interval for inactive timers, so they are correct when they become active
timer.setRefreshRate(newInterval, false);
}
});
}
protected void replaceTimerChangeHandler() {
if (timerChangeHandler != null) {
removeTimerChangeHandler();
}
addTimerChangeHandler();
}
@Override
public void refresh() {
getForceRefreshCommand().execute();
}
@Override
public void setSelectedItem(T value) {
setIsQueryFirstTime(true);
super.setSelectedItem(value);
setIsQueryFirstTime(false);
}
protected abstract String getListName();
protected void searchStringChanged() {
}
public void search() {
// Defer search if there max result limit was not yet retrieved.
if (getSearchPageSize() == UnknownInteger) {
asyncCallback.requestSearch();
}
else {
stopRefresh();
if (getIsQueryFirstTime()) {
setSelectedItem(null);
setSelectedItems(null);
}
if (!getIsTimerDisabled()) {
setIsQueryFirstTime(true);
onPropertyChanged(new PropertyChangedEventArgs(PropertyChangedEventArgs.PROGRESS));
syncSearch();
setIsQueryFirstTime(false);
startGridTimer();
}
else {
syncSearch();
}
}
}
protected void startGridTimer() {
if (getTimer() != null) {
//Timer can be null if the event bus hasn't been set yet (model hasn't been fully initialized)
startRefresh();
} else {
//Defer the start of the timer until after the event bus has been added to this model. Then we
//can pass the event bus to the timer and the timer can become active.
Scheduler.get().scheduleDeferred(() -> startRefresh());
}
}
private void startRefresh() {
if (getTimer() != null) {
getTimer().start();
}
}
public void forceRefresh() {
stopRefresh();
setIsQueryFirstTime(true);
syncSearch();
if (!getIsTimerDisabled()) {
startRefresh();
}
}
@Override
public void eventRaised(Event<? extends EventArgs> ev, Object sender, EventArgs args) {
super.eventRaised(ev, sender, args);
entityModel.eventRaised(ev, sender, args);
if (ev.matchesDefinition(RegistrationResult.RetrievedEventDefinition)) {
asyncResult_Retrieved();
}
if (ev.matchesDefinition(ProvideTickEvent.definition)) {
syncSearch();
}
}
private void asyncResult_Retrieved() {
// Update IsEmpty flag.
// Note: Do NOT use IList. 'Items' is not necissarily IList
// (e.g in Monitor models, the different ListModels' Items are
// of type 'valueObjectEnumerableList', which is not IList).
if (getItems() != null) {
Iterator<T> enumerator = getItems().iterator();
setIsEmpty(enumerator.hasNext() ? false : true);
}
else {
setIsEmpty(true);
}
}
private void resetIsEmpty() {
// Note: Do NOT use IList: 'Items' is not necissarily IList
// (e.g in Monitor models, the different ListModels' Items are
// of type 'valueObjectEnumerableList', which is not IList).
if (getItems() != null) {
Iterator<T> enumerator = getItems().iterator();
if (enumerator.hasNext()) {
setIsEmpty(false);
}
}
}
@Override
protected void itemsChanged() {
super.itemsChanged();
resetIsEmpty();
updatePagingAvailability();
}
@Override
protected void itemsCollectionChanged(Object sender, NotifyCollectionChangedEventArgs<T> e) {
super.itemsCollectionChanged(sender, e);
resetIsEmpty();
updatePagingAvailability();
}
protected void updatePagingAvailability() {
getSearchNextPageCommand().setIsExecutionAllowed(getSearchNextPageCommand().getIsAvailable()
&& getNextSearchPageAllowed());
getSearchPreviousPageCommand().setIsExecutionAllowed(getSearchPreviousPageCommand().getIsAvailable()
&& getPreviousSearchPageAllowed());
}
private void setSearchStringPage(int newSearchPageNumber) {
this.pagingSearchString = " page " + newSearchPageNumber; //$NON-NLS-1$
this.currentPageNumber = newSearchPageNumber;
}
protected void searchNextPage() {
searchString = stripPageKeyword(searchString);
setSearchStringPage(getNextSearchPageNumber());
getSearchCommand().execute();
}
protected void searchPreviousPage() {
searchString = stripPageKeyword(searchString);
setSearchStringPage(getPreviousSearchPageNumber());
getSearchCommand().execute();
}
private String stripPageKeyword(String str) {
int index = str.indexOf("page"); //$NON-NLS-1$
if (index == -1) {
return str;
}
return str.substring(0, index);
}
protected boolean getNextSearchPageAllowed() {
if (!getSearchNextPageCommand().getIsAvailable() || getItems() == null
|| !getItems().iterator().hasNext()) {
return false;
}
boolean retValue = true;
int pageSize = getSearchPageSize();
if (pageSize > 0) {
if (getItems().size() < pageSize) {
// current page contains results quantity smaller than
// the pageSize -> there is no next page:
retValue = false;
}
}
return retValue;
}
protected boolean getPreviousSearchPageAllowed() {
return getSearchPreviousPageCommand().getIsAvailable() && getSearchPageNumber() > 1;
}
/**
* Override this method to take care on sync fetching.
* <p>
* If server-side sorting via the search query is supported by this model:
* <ul>
* <li>override {@link #supportsServerSideSorting} to return {@code true}</li>
* <li>make sure {@code syncSearch} implementation uses {@link #applySortOptions}</li>
* </ul>
*/
protected void syncSearch() {
}
private String sortBy;
private boolean sortAscending;
/**
* Updates current server-side sort options, performing {@link #refresh} if necessary.
*
* @param sortBy
* Field to sort by via the search query or {@code null} for undefined sort.
* @param sortAscending
* Sort direction, effective only when {@code sortBy} is not {@code null}.
*/
public void updateSortOptions(String sortBy, boolean sortAscending) {
boolean shouldRefresh = !Objects.equals(this.sortBy, sortBy)
|| this.sortAscending != sortAscending;
this.sortBy = sortBy;
this.sortAscending = sortAscending;
if (shouldRefresh) {
searchString = stripPageKeyword(searchString);
setSearchStringPage(1);
refresh();
}
}
/**
* Clears current server-side sort options.
*/
public void clearSortOptions() {
this.sortBy = null;
this.sortAscending = false;
}
/**
* Returns the given search string with current server-side sort options applied.
*
* @param searchString
* Search string to update with current server-side sort options.
*/
protected String applySortOptions(String searchString) {
String result = searchString;
if (sortBy != null) {
result += " " + SyntaxChecker.SORTBY + " " + sortBy //$NON-NLS-1$ //$NON-NLS-2$
+ " " + (sortAscending ? SyntaxChecker.SORTDIR_ASC : SyntaxChecker.SORTDIR_DESC); //$NON-NLS-1$
}
if (result != null && pagingSearchString != null) {
result += " " + pagingSearchString; //$NON-NLS-1$
}
return result;
}
/**
* Returns {@code true} if this model's {@link #syncSearch} implementation supports server-side sorting.
*/
public boolean supportsServerSideSorting() {
return false;
}
/**
* Returns {@code true} if this model's {@linkplain #getSearchString search string}
* allows the use of server-side sorting.
* <p>
* This method returns {@code false} if:
* <ul>
* <li>search string contains syntax error(s)
* <li>search string contains {@code SORTBY} syntax object
* </ul>
* Otherwise, this method returns {@code true}.
*/
public boolean isSearchValidForServerSideSorting() {
ISyntaxChecker syntaxChecker = getConfigurator().getSyntaxChecker();
if (syntaxChecker == null) {
return true;
}
String search = getSearchString();
SyntaxContainer syntaxResult = syntaxChecker.analyzeSyntaxState(search, true);
if (syntaxResult.getError() != SyntaxError.NO_ERROR) {
return false;
}
for (SyntaxObject syntaxObject : syntaxResult) {
if (syntaxObject.getType() == SyntaxObjectType.SORTBY) {
return false;
}
}
return true;
}
@Override
public void setComparator(Comparator<? super T> comparator, boolean sortAscending) {
super.setComparator(comparator, sortAscending);
Collection<T> items = getItems();
if (items != null) {
Collection<T> maybeSortedItems = (comparator != null) ? sortItems(items) : new ArrayList<>(items);
setItems(maybeSortedItems);
}
}
@Override
public void setItems(Collection<T> value) {
if (items != value) {
T lastSelectedItem = getSelectedItem();
List<T> lastSelectedItems = new ArrayList<>();
if (getSelectedItems() != null) {
for (T item : getSelectedItems()) {
lastSelectedItems.add(item);
}
}
if (comparator == null || ((value instanceof SortedSet)
&& Objects.equals(((SortedSet<?>) value).comparator(), comparator))) {
itemsChanging(value, items);
items = value;
} else {
Collection<T> sortedItems = sortItems(value);
itemsChanging(sortedItems, items);
items = sortedItems;
}
updatePagingAvailability();
getItemsChangedEvent().raise(this, EventArgs.EMPTY);
onPropertyChanged(new PropertyChangedEventArgs("Items")); //$NON-NLS-1$
selectedItem = null;
if (getSelectedItems() != null) {
getSelectedItems().clear();
}
if (lastSelectedItem != null && items != null) {
T newSelectedItem = null;
List<T> newItems = new ArrayList<>();
for (T item : items) {
newItems.add(item);
}
if (newItems != null) {
newSelectedItem = determineSelectedItems(newItems, lastSelectedItem, lastSelectedItems);
}
if (newSelectedItem != null) {
selectedItem = newSelectedItem;
if (selectedItems != null) {
selectedItems.add(newSelectedItem);
}
}
}
onSelectedItemChanged();
}
}
protected T determineSelectedItems(List<T> newItems, T lastSelectedItem, List<T> lastSelectedItems) {
T newSelectedItem = null;
for (T newItem : newItems) {
// Search for selected item
if (itemsEqual(newItem, lastSelectedItem)) {
newSelectedItem = newItem;
} else {
// Search for selected items
for (T item : lastSelectedItems) {
if (itemsEqual(newItem, item)) {
selectedItems.add(newItem);
}
}
}
}
return newSelectedItem;
}
private static <T> boolean itemsEqual(T item1, T item2) {
if (item1 instanceof IVdcQueryable && item2 instanceof IVdcQueryable) {
return ((IVdcQueryable) item1).getQueryableId().equals(((IVdcQueryable) item2).getQueryableId());
}
return Objects.equals(item1, item2);
}
protected void syncSearch(
VdcQueryType vdcQueryType,
VdcQueryParametersBase vdcQueryParametersBase,
AsyncQuery<VdcQueryReturnValue> asyncCallback) {
vdcQueryParametersBase.setRefresh(getIsQueryFirstTime());
Frontend.getInstance().runQuery(vdcQueryType, vdcQueryParametersBase, asyncCallback);
setIsQueryFirstTime(false);
}
protected void syncSearch(VdcQueryType vdcQueryType, VdcQueryParametersBase vdcQueryParametersBase) {
syncSearch(vdcQueryType, vdcQueryParametersBase, new SetItemsAsyncQuery());
}
public void stopRefresh() {
if (getTimer() != null) {
//Timer can be null if the event bus hasn't been set yet. If the timer is null we can't stop it.
getTimer().stop();
}
}
protected void removeTimerChangeHandler() {
if (timerChangeHandler != null) {
timerChangeHandler.removeHandler();
timerChangeHandler = null;
}
}
@Override
public void executeCommand(UICommand command) {
super.executeCommand(command);
if (command == getSearchCommand()) {
search();
}
else if (command == getSearchNextPageCommand()) {
searchNextPage();
}
else if (command == getSearchPreviousPageCommand()) {
searchPreviousPage();
}
else if (command == getForceRefreshCommand()) {
forceRefresh();
}
if (command != null && command.isAutoRefresh()) {
getTimer().fastForward();
}
}
public static final class PrivateAsyncCallback<E, T> {
private final SearchableListModel<E, T> model;
private boolean searchRequested;
public PrivateAsyncCallback(SearchableListModel<E, T> model) {
this.model = model;
AsyncDataProvider.getInstance().getSearchResultsLimit(model.asyncQuery(result -> ApplySearchPageSize(result)));
}
public void requestSearch() {
searchRequested = true;
model.setItems(new ArrayList<T>());
model.getSelectedItemChangedEvent().raise(this, new EventArgs());
model.getSelectedItemsChangedEvent().raise(this, new EventArgs());
}
private void ApplySearchPageSize(int value) {
model.setSearchPageSize(value);
// If there search was requested before max result limit was retrieved, do it now.
if (searchRequested && !model.getTimer().isActive()) {
model.getSearchCommand().execute();
}
// Sure paging functionality.
model.updatePagingAvailability();
}
}
protected class SetItemsAsyncQuery extends AsyncQuery<VdcQueryReturnValue> {
public SetItemsAsyncQuery() {
super(new AsyncCallback<VdcQueryReturnValue>() {
@Override
public void onSuccess(VdcQueryReturnValue returnValue) {
setItems((Collection<T>) returnValue.getReturnValue());
}
});
}
}
protected class SetRawItemsAsyncQuery extends AsyncQuery<List<T>> {
public SetRawItemsAsyncQuery() {
super(new AsyncCallback<List<T>>() {
@Override
public void onSuccess(List<T> returnValue) {
setItems(returnValue);
}
});
}
}
protected class SetSortedItemsAsyncQuery extends AsyncQuery<VdcQueryReturnValue> {
public SetSortedItemsAsyncQuery(final Comparator<? super T> comparator) {
super(new AsyncCallback<VdcQueryReturnValue>() {
@Override
public void onSuccess(VdcQueryReturnValue returnValue) {
List<T> items = returnValue.getReturnValue();
Collections.sort(items, comparator);
setItems(items);
}
});
}
}
protected class SetSortedRawItemsAsyncQuery extends AsyncQuery<List<T>> {
public SetSortedRawItemsAsyncQuery(final Comparator<? super T> comparator) {
super(new AsyncCallback<List<T>>() {
@Override
public void onSuccess(List<T> items) {
Collections.sort(items, comparator);
setItems(items);
}
});
}
}
/**
* Sub classes that have an edit command will override this method.
*
* @return An edit {@code UICommand}
*/
public UICommand getEditCommand() {
// Returning null will result in no action. I can't make this
// method abstract like I want as not all sub classes will
// implement the edit command.
return null;
}
/**
* Get the double click command, in most cases this will be 'edit'. If sub
* classes want a different default command they can override this method
* and return the command they want.
*
* If a user double clicks in a grid or tree, this default command is
* invoked.
* @return The default {@code UICommand}
*/
public UICommand getDoubleClickCommand() {
return getEditCommand();
}
// ////////////////////////////
// GridController methods
// ///////////////////////////
@Override
public String getId() {
return getListName();
}
protected boolean handleRefreshActiveModel(RefreshActiveModelEvent event) {
return true;
}
protected boolean refreshOnInactiveTimer() {
return false;
}
@Override
protected void registerHandlers() {
// Register to listen for operation complete events.
registerHandler(getEventBus().addHandler(RefreshActiveModelEvent.getType(),
event -> {
if (getTimer().isActive() || refreshOnInactiveTimer()) { // Only if we are active should we refresh.
if (handleRefreshActiveModel(event)) {
syncSearch();
}
if (event.isDoFastForward()) {
// Start the fast refresh.
getTimer().fastForward();
}
}
}));
}
}