This page is for Grouper developers who want to add a progress page for long running tasks


  1. Import members
  2. Provisioning diagnostics
  3. Run GSH template

Keep a cache of state

   * keep an expirable cache of import progress for 5 hours (longest an import is expected).  This has multikey of session id and some random uuid
   * uniquely identifies this import as opposed to other imports in other tabs
  private static ExpirableCache<MultiKey, GroupImportContainer> importThreadProgress = new ExpirableCache<MultiKey, GroupImportContainer>(300);

For new tasks, make ids and set in the cache

      String sessionId = request.getSession().getId();
      // uniquely identifies this task as opposed to other tasks in other tabs
      String uniqueImportId = GrouperUuid.getUuid();
      MultiKey reportMultiKey = new MultiKey(sessionId, uniqueImportId);
      importThreadProgress.put(reportMultiKey, groupImportContainer);

Keep progress bean in the state container somewhere

public class GshTemplateExec {
   * have a progress bean to be able to communicate progress to the UI
  private ProgressBean progressBean = new ProgressBean();
   * have a progress bean to be able to communicate progress to the UI
   * @return the progressBean
  public ProgressBean getProgressBean() {
    return this.progressBean;

Update progress as task runs

      // total records
      progressBean.setProgressTotalRecords(GrouperUtil.length(groups) * GrouperUtil.length(subjectSet));

      // progress
      progressBean.addProgressCompleteRecords(GrouperUtil.length(subjectSet) - GrouperUtil.length(subjectList));

      // done

Execute in thread

      GrouperCallable<Void> grouperCallable = new GrouperCallable<Void>("groupImportMembers") {

        public Void callLogic() {
          try {


            UiV2GroupImport.this.groupImportSubmitHelper(loggedInSubject, groupImportContainer, groups, subjectSet, 
                listInvalidSubjectIdsAndRow, removeMembers, importReplaceMembers, bulkAddOption, fileName[0], (List<CSVRecord>)csvEntriesObject[0]);
          } catch (RuntimeException re) {
            // log this since the thread will just end and will never get logged
            LOG.error("error", re);
          } finally {
            // we done
          return null;
      // see if running in thread
      boolean useThreads = GrouperUiConfig.retrieveConfig().propertyValueBooleanRequired("grouperUi.import.useThread");
      debugMap.put("useThreads", useThreads);

      if (useThreads) {
        GrouperFuture<Void> grouperFuture = GrouperUtil.executorServiceSubmit(GrouperUtil.retrieveExecutorService(), grouperCallable);
        Integer waitForCompleteForSeconds = GrouperUiConfig.retrieveConfig().propertyValueInt("grouperUi.import.progressStartsInSeconds");
        debugMap.put("waitForCompleteForSeconds", waitForCompleteForSeconds);

        GrouperFuture.waitForJob(grouperFuture, waitForCompleteForSeconds);
        debugMap.put("threadAlive", !grouperFuture.isDone());

      } else {
      groupImportReportStatusHelper(sessionId, uniqueImportId);

Progress JSP is specific to the ID used

<%@ include file="../assetsJsp/commonTaglib.jsp"%>
<div id="id_${grouperRequestContainer.groupImportContainer.uniqueImportId}">
  • No labels