Delete Filtered Provisioning Transactions

We are on IIQ 8.3

I am using a Run Rule Task to run the follow rule I found on Compass. I’m trying to delete all Filtered Provisioning Transactions, currently around 60 million of our 90 million transactions seem to meet this criteria.
24 hours into the run and it has deleted 1 million, meaning it could take 2 months to complete the task!?!

Anyone know of a more efficient way to get this done, or just let it run and hope for the best?

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE Rule PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<Rule created="1731430519689" id="0aab09ca92e313d98193214d13897b5c" language="beanshell" modified="1732324253269" name="PurgeFilteredProvisioningTransactions">
  <Description>
    This rule cleans up Filtered provisioning transactions of access that already exists.
  </Description>
  <Source>
  
  import java.util.Calendar;
  import java.util.Collection;
  import java.util.Iterator;
  import java.util.List;

  import org.apache.commons.logging.Log;

  import sailpoint.api.IdIterator;
  import sailpoint.api.SailPointContext;
  import sailpoint.api.SailPointFactory;
  import sailpoint.api.Terminator;
  import sailpoint.object.Attributes;
  import sailpoint.object.ProvisioningPlan.AccountRequest;
  import sailpoint.object.ProvisioningPlan.AttributeRequest;
  import sailpoint.object.Capability;
  import sailpoint.object.Field;
  import sailpoint.object.Filter;
  import sailpoint.object.Form;
  import sailpoint.object.Identity;
  import sailpoint.object.ProvisioningProject;
  import sailpoint.object.ProvisioningTransaction;
  import sailpoint.object.QueryOptions;
  import sailpoint.object.TaskResult;
  import sailpoint.tools.GeneralException;
  import sailpoint.tools.Util;
  
  int count = 0;
  int delCount = 0;

  // ====================== IMPORTANT ==========================
  // For testing without deleting objects: set readOnly = true
  // Once fully tested, set readOnly = false
  // ====================== IMPORTANT ==========================
  boolean readOnly = false;

  Terminator terminatorObj = new Terminator(context);
  QueryOptions qo = new QueryOptions();
  qo.addFilter(Filter.eq("status", "Success"));
  qo.addFilter(Filter.eq("integration", "Filtered"));

  // If we don't want a specific time range, then set this flag to false.
  boolean useTimeFilter = false; 
  TaskResult taskResult = null;

  if (useTimeFilter) {
    Calendar beforeCal = Calendar.getInstance();
    beforeCal.set(Calendar.HOUR_OF_DAY, 0);
    beforeCal.set(Calendar.MINUTE, 0);
    beforeCal.set(Calendar.SECOND, 0);
    beforeCal.set(Calendar.MILLISECOND, 0);
    Calendar afterCal = Calendar.getInstance();

    // Default behavior: only scan provisioning transactions created in the past 30 days
    afterCal.add(Calendar.DAY_OF_MONTH, -30);
    qo.addFilter(Filter.and(Filter.gt("created", afterCal.getTime()), Filter.le("created", beforeCal.getTime())));
  }

  Iterator it = context.search(ProvisioningTransaction.class, qo, "id");
  IdIterator idIt = new IdIterator(context, it);  
  try {
    while (idIt.hasNext()) {
      if (taskResult != null &amp;&amp; taskResult.isTerminateRequested()) {
        return ("Terminated");
      }
      count++;
      String id = (String) idIt.next();
      ProvisioningTransaction pt = (ProvisioningTransaction) context.getObjectById(ProvisioningTransaction.class, id);
      if (pt != null) {
        Object filtered = pt.getAttributes().get(ProvisioningTransaction.ATT_FILTERED);
        boolean purgeTransaction = true;
        if (filtered instanceof AccountRequest) {
          List attList = ((AccountRequest) filtered).getAttributeRequests();
          for (AttributeRequest att : attList) {
            Object reasonVal = att.get(ProvisioningProject.ATT_FILTER_REASON);
            if (reasonVal != null) {
              if (reasonVal.equals(ProvisioningProject.FilterReason.Exists)) {
                log.debug("ProvisioningTransaction found: " + id + ", reason: " + reasonVal);
              } else {
                // If ProvTrans contains projects with a reason other than "exists", then don't purge it.
                // I'm not sure this is exactly what we want, so more testing needs to be done here.
                purgeTransaction = false;
              }
              // boolean flag is AND-ed for each attribute request; once the flag becomes false, it will remain false.
              purgeTransaction &amp;= purgeTransaction;
  	        }
          }
        } else {
          purgeTransaction = false;
        }
        if (purgeTransaction) {
          if (!readOnly) {
            terminatorObj.deleteObject(pt);
          }
          delCount++;
        }
      }
      if (count % 100 == 0) {
        if (!readOnly)
          context.commitTransaction();
          context.decache();
        System.out.println("Current deleted count is - " + delCount);
      }
    }
  } catch (Exception ex) {
    log.error("ProvisioningTransaction.Cleanup: ", ex);
  } finally {
    if (!readOnly)
      context.commitTransaction();
      context.decache();
      sailpoint.tools.Util.flushIterator(it);
  }

  if (taskResult != null) {
    Attributes resultAttributes = new Attributes();
    resultAttributes.put("provtrans_total", count);
    resultAttributes.put("provtrans_purged", delCount);
    taskResult.setAttributes(resultAttributes);
  }
  return "Total scanned: " + count + ", purged: " + delCount;
  
 </Source>
</Rule>

Thank you!

@chrisk
To add more efficiency, try to create custom partitions of this task and execute this in multiple nodes at a time

1 Like

Thanks - I found a few posts on how to partition a task. I’ll give that a try!

Appreciate your reply/suggestion!

Thanks again!

Hi @chrisk,

you can try to use a query on spt_provisioning_transaction table, those are the columns:


and you can filter on cration date, application, status ecc…

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.