All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.bordertech.wcomponents.addons.polling.PollingServicePanel Maven / Gradle / Ivy

The newest version!
package com.github.bordertech.wcomponents.addons.polling;

import com.github.bordertech.taskmaster.TaskFuture;
import com.github.bordertech.taskmaster.service.ResultHolder;
import com.github.bordertech.taskmaster.service.ServiceAction;
import com.github.bordertech.taskmaster.service.ServiceHelper;
import com.github.bordertech.taskmaster.service.exception.RejectedServiceException;
import com.github.bordertech.taskmaster.service.exception.ServiceException;
import com.github.bordertech.taskmaster.service.impl.ResultHolderDefault;
import com.github.bordertech.taskmaster.service.util.ServiceCacheUtil;
import com.github.bordertech.wcomponents.BeanProvider;
import com.github.bordertech.wcomponents.BeanProviderBound;
import com.github.bordertech.wcomponents.Request;
import com.github.bordertech.wcomponents.addons.common.WDiv;
import java.io.Serializable;
import java.util.concurrent.ExecutionException;
import javax.cache.Cache;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This panel is used to load data via a threaded service action and polling AJAX.
 * 

* Expects the following to be set before polling:- *

*
    *
  • {@link #setServiceCriteria(java.io.Serializable)} to provide the service criteria
  • *
  • {@link #setServiceCacheKey(java.lang.String) (key)} to provide the cache key
  • *
  • {@link #setServiceAction(com.github.bordertech.taskmaster.service.ServiceAction)} to provide the service action
  • *
*

* The successful polling result will be set as the bean available to the panel. The content of the panel will only be displayed if the polling action * was successful. If the polling action fails, then the error message will be displayed along with a retry button. *

*

* Methods commonly overridden:- *

*
    *
  • {@link #getServiceCacheKey()} - provides the cache key used for the service result.
  • *
  • {@link #handleInitResultContent(com.github.bordertech.wcomponents.Request)} - init the result content on successful service call.
  • *
  • {@link #handleInitPollingPanel(com.github.bordertech.wcomponents.Request) } - init the polling panel.
  • *
* * @param the polling criteria type * @param the polling result type * @author Jonathan Austin * @since 1.0.0 */ public class PollingServicePanel extends PollingPanel { private static final Log LOG = LogFactory.getLog(PollingServicePanel.class); private final WDiv contentResultHolder = new WDiv() { @Override protected void preparePaintComponent(final Request request) { super.preparePaintComponent(request); if (!isInitialised()) { handleInitResultContent(request); setInitialised(true); } } }; /** * Default constructor. */ public PollingServicePanel() { this(174); } /** * Construct polling panel. * * @param delay the AJAX polling delay */ public PollingServicePanel(final int delay) { super(delay); getContentHolder().add(contentResultHolder); contentResultHolder.setVisible(false); // The content holder will use the "result" from the result holder getContentHolder().setBeanProperty("result"); // Set a BEAN Provider on the content that retrieves the service result getContentHolder().setBeanProvider(new BeanProvider() { @Override public Object getBean(final BeanProviderBound bound) { return getServiceResult(); } }); } /** * @return the container holding the result components */ public final WDiv getContentResultHolder() { return contentResultHolder; } /** * @return the service criteria to use in the service call */ public S getServiceCriteria() { return getComponentModel().criteria; } /** * @param criteria the service criteria to use in the service call */ public void setServiceCriteria(final S criteria) { getOrCreateComponentModel().criteria = criteria; } /** * @return the service action to use for polling */ public ServiceAction getServiceAction() { return getComponentModel().serviceAction; } /** * @param serviceAction the service action to use for polling */ public void setServiceAction(final ServiceAction serviceAction) { getOrCreateComponentModel().serviceAction = serviceAction; } /** * @return the service cache key */ public String getServiceCacheKey() { return getComponentModel().cacheKey; } /** * @param cacheKey the service cache key */ public void setServiceCacheKey(final String cacheKey) { getOrCreateComponentModel().cacheKey = cacheKey; } /** * @param threadPool the service thread pool or null for default */ public void setServiceThreadPool(final String threadPool) { getOrCreateComponentModel().threadPool = threadPool; } /** * @return the service thread pool, or null for default */ public String getServiceThreadPool() { return getComponentModel().threadPool; } /** * @return true if use the cache to hold the service result */ public boolean isUseCachedResult() { return getComponentModel().useCachedResult; } /** * @param useCachedResult true if use the cache to hold the service result */ public void setUseCachedResult(final boolean useCachedResult) { getOrCreateComponentModel().useCachedResult = useCachedResult; } /** * @return the service result, or null if still processing. */ public ResultHolder getServiceResult() { if (isUseCachedResult()) { ResultHolder resultHolder = getServiceCache().get(getServiceCacheKey()); if (resultHolder == null && getContentResultHolder().isVisible()) { return handleCacheExpired(); } return resultHolder; } else { return getComponentModel().serviceResult; } } /** * @param serviceResult the service result */ public void setServiceResult(final ResultHolder serviceResult) { String key = getServiceCacheKey(); if (isUseCachedResult()) { if (key == null) { throw new IllegalStateException("A cache key must be provided for a cached service result"); } if (serviceResult == null) { getServiceCache().remove(key); } else { getServiceCache().put(getServiceCacheKey(), serviceResult); } } else { getOrCreateComponentModel().serviceResult = serviceResult; } } /** * * @return the result for an expired cache item */ protected ResultHolder handleCacheExpired() { // TODO Maybe try and reload if expired throw new IllegalStateException("Cache entry has expired"); } @Override public void doStartPolling() { // Check not started if (getPollingStatus() == PollingStatus.PROCESSING) { return; } // Start the service call ResultHolder result = handleASyncServiceCall(); if (result == null) { super.doStartPolling(); } else { handleResult(result); } } @Override public void doRefreshContent() { handleClearServiceCache(); // Clear the result setServiceResult(null); super.doRefreshContent(); } /** * Manually set the criteria and the result. * * @param criteria the criteria * @param result the result */ public void doManuallyLoadResult(final S criteria, final T result) { // Check we have a cache key if using a cached service response if (isUseCachedResult() && getServiceCacheKey() == null) { throw new IllegalStateException("No cache key provided for the result to be held against."); } getContentHolder().reset(); getStartButton().setVisible(false); setServiceCriteria(criteria); ResultHolder resultHolder = new ResultHolderDefault(criteria, result); handleSaveServiceResult(resultHolder); handleResult(resultHolder); } @Override protected boolean checkForStopPolling() { ResultHolder result; if (isServiceRunning()) { // Check if Service Finished result = handleAsyncCheckProcess(); } else { // Try and start service (usually means no threads were available) result = handleASyncServiceCall(); if (isServiceRunning()) { // Started service successfully. LOG.info("Successfully started service on ajax poll in pool [" + getServiceThreadPool() + "]."); } } // If have result, stop polling if (result != null) { setPollingStatus(PollingStatus.STOPPED); handleSaveServiceResult(result); } return super.checkForStopPolling(); } @Override protected void handleStoppedPolling() { super.handleStoppedPolling(); // Make sure the task is cleared clearTaskFuture(); // Process result ResultHolder result = getServiceResult(); if (result == null) { // This state should not happen throw new IllegalStateException("Service result is not available."); } handleResult(result); } @Override protected void handleTimeoutPolling() { super.handleTimeoutPolling(); // Make sure the task is cleared clearTaskFuture(); } /** * Save the service result locally or keep it in the cache. * * @param resultHolder the service result */ protected void handleSaveServiceResult(final ResultHolder resultHolder) { setServiceResult(resultHolder); } /** * Start the async service call. * * @return the result if already cached, or null */ protected ResultHolder handleASyncServiceCall() { // Check we have a service action ServiceAction action = getServiceAction(); if (action == null) { throw new IllegalStateException("No service action provided for polling."); } // Check we have a cache key if (isUseCachedResult() && getServiceCacheKey() == null) { throw new IllegalStateException("No cache key provided for polling."); } // Clear previous task (if any) clearTaskFuture(); // Start Service action. try { TaskFuture> future; if (isUseCachedResult()) { // Cached service call (and cache exceptions) future = ServiceHelper.submitAsync(getServiceCriteria(), getServiceAction(), getServiceCache(), getServiceCacheKey(), getServiceThreadPool(), true); } else { // Clear current result setServiceResult(null); // Service call with no caching future = ServiceHelper.submitAsync(getServiceCriteria(), getServiceAction(), getServiceThreadPool()); } if (future.isDone()) { // Result might have been cached so return it immediately return extractResultFromTask(future); } setTaskFuture(future); } catch (ServiceException e) { clearTaskFuture(); return new ResultHolderDefault(e); } catch (RejectedServiceException e) { // Could not start service (usually no threads available). Try and start on the next poll. LOG.info("Could not start service in pool [" + getServiceThreadPool() + "]. Will try next poll.", e); } return null; } /** * Check if the service has finished. * * @return the result or null if still running */ protected ResultHolder handleAsyncCheckProcess() { TaskFuture> future = getTaskFuture(); if (future == null) { throw new IllegalStateException("No future set for async processing"); } if (future.isDone()) { // Clear the task clearTaskFuture(); // Extract the result form the future try { return extractResultFromTask(future); } catch (ServiceException e) { return new ResultHolderDefault(e); } } return null; } /** * Extract the result form the future. * * @param future the task future * @return the result holder * @throws ServiceException exception in processing */ protected ResultHolder extractResultFromTask(final TaskFuture> future) throws ServiceException { if (future.isDone()) { try { return future.get(); } catch (InterruptedException e) { // Restore interrupted state... Thread.currentThread().interrupt(); throw new ServiceException("Getting result from Future but thread was interrupted. " + e.getMessage(), e); } catch (ExecutionException e) { throw new ServiceException("Could not get result from the future. " + e.getMessage(), e); } } return null; } /** * @return true if service is running */ protected boolean isServiceRunning() { return getTaskFuture() != null; } /** * @return the task future running the process or null */ protected TaskFuture> getTaskFuture() { return getComponentModel().taskFuture; } /** * @param future the task future running the service call */ protected void setTaskFuture(final TaskFuture> future) { getOrCreateComponentModel().taskFuture = future; } /** * Clear the current task. */ protected void clearTaskFuture() { TaskFuture current = getTaskFuture(); // No task to clear if (current == null) { return; } // Check if task can be cancelled if (!current.isDone()) { current.cancel(true); } setTaskFuture(null); } /** * Initialise the result content. * * @param request the request being processed */ protected void handleInitResultContent(final Request request) { // Do Nothing } /** * Clear the result cache if necessary (eg Service Layer). */ protected void handleClearServiceCache() { // Do nothing } /** * Handle the result from the polling action. * * @param resultHolder the polling action result */ protected void handleResult(final ResultHolder resultHolder) { if (resultHolder.isResult()) { // Successful Result handleResultSuccessful(resultHolder.getResult()); } else { // Exception message Exception excp = resultHolder.getException(); handleResultException(excp); LOG.error("Error loading data. " + excp.getMessage()); } } /** * Handle the service returned an exception. * * @param excp the exception that occurred */ protected void handleResultException(final Exception excp) { addErrorMessage(excp.getMessage()); doShowRetry(); } /** * Handle the service had a successful result. * * @param result the service result */ protected void handleResultSuccessful(final T result) { getContentResultHolder().setVisible(true); } /** * @return the service cache instance */ protected Cache getServiceCache() { return ServiceCacheUtil.getDefaultResultHolderCache(); } @Override protected PollingServiceModel newComponentModel() { return new PollingServiceModel(); } @Override protected PollingServiceModel getOrCreateComponentModel() { return (PollingServiceModel) super.getOrCreateComponentModel(); } @Override protected PollingServiceModel getComponentModel() { return (PollingServiceModel) super.getComponentModel(); } /** * This model holds the state information. * * @param the criteria type * @param the service action */ public static class PollingServiceModel extends PollingModel { private S criteria; private String cacheKey; private String threadPool; private boolean useCachedResult = true; private TaskFuture> taskFuture; private ServiceAction serviceAction; private ResultHolder serviceResult; } }