Last active
October 10, 2017 02:00
-
-
Save jonnywray/594468 to your computer and use it in GitHub Desktop.
Wicket AsynchronousUpdatePanel: allows asynchronous rendering of expensive to create panels
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.apache.wicket.Component; | |
import org.apache.wicket.ajax.AjaxRequestTarget; | |
import org.apache.wicket.markup.html.basic.Label; | |
import org.apache.wicket.markup.html.panel.EmptyPanel; | |
import org.apache.wicket.markup.html.panel.Panel; | |
import org.apache.wicket.model.IModel; | |
import org.apache.wicket.model.Model; | |
import org.apache.wicket.request.cycle.RequestCycle; | |
import org.apache.wicket.request.resource.PackageResourceReference; | |
import org.apache.wicket.request.resource.ResourceReference; | |
import org.apache.wicket.util.time.Duration; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import uk.co.etx.wicket.behavior.FutureUpdateBehavior; | |
import java.util.concurrent.Callable; | |
import java.util.concurrent.ExecutorService; | |
import java.util.concurrent.Executors; | |
import java.util.concurrent.Future; | |
/** | |
* A abstract Wicket panel that allows data to be fetched in | |
* another thread and panel content updated as the data is | |
* obtained. Behavior is obtained via an Ajax timer that | |
* checks at a fixed interval to see if the future task has | |
* finished. If so it gets the future return value, sets the | |
* default model object with the value returned from the future, | |
* stops the timer and redraws the panel. | |
* | |
* The behavior of Wicket is to process requests for panel updates | |
* sequentially. Use of this panel allows multiple panels to fetch | |
* their data and update themselves when ready, all in parallel. | |
* | |
* @author Jonny Wray | |
*/ | |
public abstract class AsynchronousUpdatePanel<V, T> extends Panel { | |
private static final Logger logger = LoggerFactory.getLogger(AsynchronousUpdatePanel.class); | |
private static final ResourceReference LOADING_RESOURCE_BAR = new PackageResourceReference(AsynchronousUpdatePanel.class, "ajax-loader-bar.gif"); | |
//private static final ResourceReference LOADING_RESOURCE_CIRCLE = new PackageResourceReference(AsynchronousUpdatePanel.class, "ajax-loader-circle.gif"); | |
protected static final String STATUS_COMPONENT_ID = "status"; | |
protected static final int DEFAULT_TIMER_DURATION_SECS = 2; | |
protected static final ExecutorService executor = Executors.newFixedThreadPool(10); | |
private transient Future<T> future; | |
/** | |
* Construct panel with default timer interval of every 2 seconds | |
* | |
* @param id The panel ID | |
* @param callableParameterModel The model used to pass parameters to the callable | |
*/ | |
public AsynchronousUpdatePanel(String id, IModel<V> callableParameterModel){ | |
this(id, callableParameterModel, Duration.seconds(DEFAULT_TIMER_DURATION_SECS)); | |
} | |
public T getModelObject(){ | |
return (T)getDefaultModelObject(); | |
} | |
public IModel<T> getModel(){ | |
return (IModel<T>)getDefaultModel(); | |
} | |
/** | |
* Construct panel | |
* | |
* @param id The panel ID | |
* @param callableParameterModel The model used to pass parameters to the callable | |
* @param durationSecs The duration in seconds between each check of the future task and potential component update | |
*/ | |
public AsynchronousUpdatePanel(String id, IModel<V> callableParameterModel, Duration durationSecs){ | |
super(id, new Model()); | |
future = executor.submit(createCallable(callableParameterModel)); | |
FutureUpdateBehavior<T> behaviour = new FutureUpdateBehavior<T>(durationSecs, future){ | |
@Override | |
protected void onPostSuccess(AjaxRequestTarget target) { | |
AsynchronousUpdatePanel.this.replace(new EmptyPanel(STATUS_COMPONENT_ID)); | |
target.add(AsynchronousUpdatePanel.this); | |
} | |
@Override | |
protected void onUpdateError(AjaxRequestTarget target, Exception e) { | |
logger.error("Error during asynchronous data fetching", e); | |
String message = "Error occurred while fetching data: "+e.getMessage(); | |
Label errorLabel = new Label(STATUS_COMPONENT_ID, message); | |
AsynchronousUpdatePanel.this.replace(errorLabel); | |
target.add(AsynchronousUpdatePanel.this); | |
} | |
}; | |
add(behaviour); | |
add(getLoadingComponent(STATUS_COMPONENT_ID)); | |
} | |
/** | |
* Create a callable that encapsulates the actual fetching of the data needed | |
* by the panel for rendering. | |
* | |
* @param callableParameterModel Model providing access to parameters needed by the callable | |
* @return A callable instance that encapsulates the logic needed to obtain the panel data | |
*/ | |
protected abstract Callable<T> createCallable(IModel<V> callableParameterModel); | |
// Alternative with an overlay: http://javathoughts.capesugarbird.com/2008/03/ajax-button-with-overlay-div-and-wait.html | |
private Component getLoadingComponent(final String markupId){ | |
CharSequence url = RequestCycle.get().urlFor(LOADING_RESOURCE_BAR, null) ; | |
String loading = "<img style='vertical-align:middle;' alt=\"Loading...\" src=\"" + | |
url + "\"/>"; | |
return new Label(markupId, loading).setEscapeModelStrings(false); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment