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}