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        public CloseableIteratorWrapper(CloseableIterator<ResourceObject> closeableIterator) {
047            this.closeableIterator = closeableIterator;
048        }
049
050        @Override
051        public boolean hasNext() {
052            return closeableIterator.hasNext();
053        }
054
055        @Override
056        public ResourceObject next() {
057            return closeableIterator.next();
058        }
059    }
060
061    /**
062     * Wraps the output iterators for transformation purposes.
063     * This is how {@link #convertObject(Object)} gets called.
064     */
065    public class ResultTransformingIterator extends TransformingIterator<Object, ItemType> {
066
067        /**
068         * Constructor
069         *
070         * @param input The iterator being wrapped by this transformer
071         */
072        public ResultTransformingIterator(Iterator<?> input) {
073            super(input, AbstractThreadedObjectIteratorTask.this::convertObject);
074        }
075    }
076
077    /**
078     * The ObjectRetriever to use to get the inputs
079     */
080    private ObjectRetriever<ItemType> retriever;
081
082    /**
083     * Converts the input to the expected type T. By default, this just
084     * returns the input as-is. If you know what you're doing, you can leave
085     * this implementation intact, but you probably want to convert things.
086     *
087     * If the result is null, the object will be ignored.
088     *
089     * @param input The input
090     * @return The output object
091     */
092    @SuppressWarnings("unchecked")
093    protected ItemType convertObject(Object input) {
094        return (ItemType) input;
095    }
096
097    /**
098     * Gets the iterator over the given object type
099     * @param context Sailpoint context
100     * @param args The task arguments
101     * @return The iterator of items retrieved from whatever source
102     * @throws GeneralException if any failures occur
103     */
104    protected Iterator<ItemType> getObjectIterator(SailPointContext context, Attributes<String, Object> args) throws GeneralException {
105        retriever.setTerminationRegistrar(this::addTerminationHandler);
106        return retriever.getObjectIterator(context, args);
107    }
108
109    /**
110     * Gets the ObjectRetriever associated with this task execution
111     *
112     * @param context The context of the object retriever
113     * @param args The arguments to the task
114     * @return The object retriever
115     * @throws GeneralException if any failure occurs
116     */
117    protected ObjectRetriever<ItemType> getObjectRetriever(SailPointContext context, Attributes<String, Object> args) throws GeneralException {
118        return new BasicObjectRetriever<>(context, args, ResultTransformingIterator::new, taskResult);
119    }
120
121    /**
122     * Termination indicator to be used by the subclasses
123     * @return True if this task has been terminated
124     */
125    protected final boolean isTerminated() {
126        return terminated.get();
127    }
128
129    /**
130     * Parses the task arguments, determining the retrieval type and its arguments
131     * @param args The task arguments
132     * @throws Exception if any failures occur parsing the arguments
133     */
134    protected void parseArgs(Attributes<String, Object> args) throws Exception {
135        super.parseArgs(args);
136
137        this.retriever = getObjectRetriever(context, args);
138
139        if (retriever instanceof BasicObjectRetriever) {
140            RetrievalType retrievalType = ((BasicObjectRetriever<ItemType>)retriever).getRetrievalType();
141
142            if (!supportsRetrievalType(retrievalType)) {
143                throw new IllegalArgumentException("This task does not support retrieval type " + retrievalType);
144            }
145        }
146    }
147
148    /**
149     * This method should return false if this task executor does not want to support the
150     * given retrieval type.
151     *
152     * @param type The type of the retrieval
153     */
154    protected boolean supportsRetrievalType(RetrievalType type) {
155        return true;
156    }
157
158}