Skip to content

Instantly share code, notes, and snippets.

@jonnywray
Last active October 10, 2017 02:00
Show Gist options
  • Save jonnywray/594468 to your computer and use it in GitHub Desktop.
Save jonnywray/594468 to your computer and use it in GitHub Desktop.
Wicket AsynchronousUpdatePanel: allows asynchronous rendering of expensive to create panels
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