001package com.identityworksllc.iiq.common.task; 002 003import com.identityworksllc.iiq.common.iterators.TransformingIterator; 004import com.identityworksllc.iiq.common.task.BasicObjectRetriever.RetrievalType; 005import sailpoint.api.SailPointContext; 006import sailpoint.object.Attributes; 007import sailpoint.object.ResourceObject; 008import sailpoint.tools.CloseableIterator; 009import sailpoint.tools.GeneralException; 010 011import java.util.Iterator; 012import java.util.Map; 013 014/** 015 * A more advanced {@link AbstractThreadedTask} that implements a "do this to these" 016 * pattern. The task will retrieve its list of items via a rule, script, flat file, or 017 * search filter. Subclasses are still responsible for implementing the 018 * {@link #threadExecute(SailPointContext, Map, Object)} method. 019 * 020 * A 'retrievalType' must be specified that is one of rule, script, sql, file, connector, 021 * provided, or filter, along with the appropriate arguments for that type of retrieval. 022 * 023 * The other arguments are the ones expected by {@link BasicObjectRetriever}. See that class 024 * for details. 025 * 026 * If the output of your retrieval is not the type you want passed to your threadExecute, you must 027 * override {@link #convertObject(Object)} and return the appropriately translated object. This class 028 * assumes that the output of convertObject is the correct type. Returning a value of the incorrect 029 * type will result in ClassCastExceptions. 030 * 031 * If you only want to support a subset of outputs, you may override {@link #supportsRetrievalType(RetrievalType)} 032 * and return false for those you do not want to support. 033 * 034 * Subclasses must invoke super.parseArgs() if they override it to extract their own inputs. 035 * 036 * @param <ItemType> the type of object being iterated over 037 */ 038public abstract class AbstractThreadedObjectIteratorTask<ItemType> extends AbstractThreadedTask<ItemType> { 039 /** 040 * Proxy to convert a Closeable iterator into an iterator 041 */ 042 public static class CloseableIteratorWrapper implements Iterator<ResourceObject> { 043 044 private final CloseableIterator<ResourceObject> closeableIterator; 045 046 /** 047 * Constructs a new CloseableIteratorWrapper, wrapping the given CloseableIterator 048 * @param closeableIterator The iterator to wrap, usually from a Connector 049 */ 050 public CloseableIteratorWrapper(CloseableIterator<ResourceObject> closeableIterator) { 051 this.closeableIterator = closeableIterator; 052 } 053 054 @Override 055 public boolean hasNext() { 056 return closeableIterator.hasNext(); 057 } 058 059 @Override 060 public ResourceObject next() { 061 return closeableIterator.next(); 062 } 063 } 064 065 /** 066 * Wraps the output iterators for transformation purposes. 067 * This is how {@link #convertObject(Object)} gets called. 068 */ 069 public class ResultTransformingIterator extends TransformingIterator<Object, ItemType> { 070 071 /** 072 * Constructor 073 * 074 * @param input The iterator being wrapped by this transformer 075 */ 076 public ResultTransformingIterator(Iterator<?> input) { 077 super(input, AbstractThreadedObjectIteratorTask.this::convertObject); 078 } 079 } 080 081 /** 082 * The ObjectRetriever to use to get the inputs 083 */ 084 private ObjectRetriever<ItemType> retriever; 085 086 /** 087 * Converts the input to the expected type T. By default, this just 088 * returns the input as-is. If you know what you're doing, you can leave 089 * this implementation intact, but you probably want to convert things. 090 * 091 * If the result is null, the object will be ignored. 092 * 093 * @param input The input 094 * @return The output object 095 */ 096 @SuppressWarnings("unchecked") 097 protected ItemType convertObject(Object input) { 098 return (ItemType) input; 099 } 100 101 /** 102 * Gets the iterator over the given object type 103 * @param context Sailpoint context 104 * @param args The task arguments 105 * @return The iterator of items retrieved from whatever source 106 * @throws GeneralException if any failures occur 107 */ 108 protected Iterator<ItemType> getObjectIterator(SailPointContext context, Attributes<String, Object> args) throws GeneralException { 109 retriever.setTerminationRegistrar(this::addTerminationHandler); 110 return retriever.getObjectIterator(context, args); 111 } 112 113 /** 114 * Gets the ObjectRetriever associated with this task execution 115 * 116 * @param context The context of the object retriever 117 * @param args The arguments to the task 118 * @return The object retriever 119 * @throws GeneralException if any failure occurs 120 */ 121 protected ObjectRetriever<ItemType> getObjectRetriever(SailPointContext context, Attributes<String, Object> args) throws GeneralException { 122 return new BasicObjectRetriever<>(context, args, ResultTransformingIterator::new, taskResult); 123 } 124 125 /** 126 * Termination indicator to be used by the subclasses 127 * @return True if this task has been terminated 128 */ 129 protected final boolean isTerminated() { 130 return terminated.get(); 131 } 132 133 /** 134 * Parses the task arguments, determining the retrieval type and its arguments 135 * @param args The task arguments 136 * @throws Exception if any failures occur parsing the arguments 137 */ 138 protected void parseArgs(Attributes<String, Object> args) throws Exception { 139 super.parseArgs(args); 140 141 this.retriever = getObjectRetriever(context, args); 142 143 if (retriever instanceof BasicObjectRetriever) { 144 RetrievalType retrievalType = ((BasicObjectRetriever<ItemType>)retriever).getRetrievalType(); 145 146 if (!supportsRetrievalType(retrievalType)) { 147 throw new IllegalArgumentException("This task does not support retrieval type " + retrievalType); 148 } 149 } 150 } 151 152 /** 153 * This method should return false if this task executor does not want to support the 154 * given retrieval type. 155 * 156 * @param type The type of the retrieval 157 */ 158 protected boolean supportsRetrievalType(RetrievalType type) { 159 return true; 160 } 161 162}