001package com.identityworksllc.iiq.common.threads;
002
003import java.io.Externalizable;
004import java.io.IOException;
005import java.io.ObjectInput;
006import java.io.ObjectInputStream;
007import java.io.ObjectOutput;
008import java.io.ObjectOutputStream;
009import java.io.Serializable;
010import java.util.ArrayList;
011import java.util.HashMap;
012import java.util.List;
013import java.util.Map;
014import java.util.Objects;
015
016import org.apache.commons.logging.Log;
017
018import sailpoint.api.SailPointContext;
019import sailpoint.api.SailPointFactory;
020import sailpoint.object.Attributes;
021import sailpoint.object.Rule;
022import sailpoint.tools.GeneralException;
023import sailpoint.tools.xml.AbstractXmlObject;
024import sailpoint.tools.xml.XMLReferenceResolver;
025
026/**
027 * A worker that runs a rule and returns its result. The Rule object will be
028 * loaded by each {@link RuleWorker} instance (per thread).
029 */
030@SuppressWarnings("unused")
031public class RuleWorker extends SailPointWorker implements Serializable {
032        
033        /**
034         * Serial version UID
035         */
036        private static final long serialVersionUID = 3L;
037
038        /**
039         * Deep-copies a rule so that it can be used as a container for the worker arguments.
040         *
041         * @param input The input rule to copy
042         * @return The copied rule
043         */
044        private static Rule copyRule(Rule input) {
045                Rule output = new Rule();
046                output.setName(input.getName());
047                output.setLanguage(input.getLanguage());
048                output.setSource(input.getSource());
049                output.setType(input.getType());
050                if (input.getAttributes() != null) {
051                        output.setAttributes(new Attributes<>(input.getAttributes()));
052                } else {
053                        output.setAttributes(new Attributes<>());
054                }
055                if (input.getReferencedRules() != null) {
056                        output.setReferencedRules(new ArrayList<>(input.getReferencedRules()));
057                }
058                return output;
059        }
060
061        /**
062         * The arguments to pass to the rule
063         */
064        protected transient Map<String, Object> arguments;
065        /**
066         * The rule name to run
067         */
068        protected transient String ruleName;
069        /**
070         * The cached rule object (if that constructor is used)
071         */
072        protected transient Rule ruleObject;
073
074        /**
075         * Used by the Serializable interface to begin deserialization
076         */
077        public RuleWorker() {
078                /* nothing by default */
079        }
080
081        /**
082         * Constructor
083         * @param ruleName The rule name
084         * @param arguments The rule arguments
085         */
086        public RuleWorker(String ruleName, Map<String, Object> arguments) {
087                this.ruleName = ruleName;
088                if (arguments == null) {
089                        this.arguments = new HashMap<>();
090                } else {
091                        this.arguments = arguments;
092                }
093                this.ruleObject = null;
094        }
095
096        /**
097         * Constructor
098         * @param theRule The rule object
099         * @param arguments The rule arguments
100         */
101        public RuleWorker(Rule theRule, Map<String, Object> arguments) {
102                this(theRule.getName(), arguments);
103                this.ruleObject = theRule;
104        }
105
106        /**
107         * Executes the Rule using the arguments provided and the provided thread context.
108         *
109         * @param threadContext The private context to use for this thread worker
110         * @param logger The log attached to this Worker
111         * @return The result of the rule execution (usually ignored)
112         * @throws Exception if anything goes wrong
113         */
114        @Override
115        public Object execute(SailPointContext threadContext, Log logger) throws Exception {
116                try {
117                        Rule theRule;
118                        if (ruleObject != null) {
119                                theRule = ruleObject;
120                        } else {
121                                theRule = threadContext.getObject(Rule.class, ruleName);
122                        }
123                        Map<String, Object> amendedArguments = new HashMap<>(arguments);
124                        amendedArguments.put("context", threadContext);
125                        amendedArguments.put("log", logger);
126                        amendedArguments.put("logger", logger);
127                        return threadContext.runRule(theRule, amendedArguments);
128                } catch(Exception e) {
129                        logger.error("Unable to execute rule", e);
130                        throw e;
131                }
132        }
133
134        /**
135         * Reads this RuleWorker as follows:
136         *
137         * Reads the boolean flag indicating whether the worker stored a Rule object.
138         *
139         * Reads and parses the serialized XML string representing a Rule object.
140         *
141         * If the boolean flag is true, stores the resulting Rule in ruleObject.
142         *
143         * If the boolean flag is false, retrieves only the name from the resulting Rule and stores it in ruleName.
144         *
145         * The arguments are extracted from the deserialized Rule object.
146         *
147         * @param in The callback from the object input stream
148         * @throws IOException if anything goes wrong reading the object
149         * @throws ClassNotFoundException if anything goes wrong finding the appropriate classes (unlikely)
150         */
151        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
152                try {
153                        boolean hasRuleObject = in.readBoolean();
154                        String ruleXml = in.readUTF();
155                        Rule deserialized = (Rule) AbstractXmlObject.parseXml(SailPointFactory.getCurrentContext(), ruleXml);
156                        if (hasRuleObject) {
157                                ruleObject = deserialized;
158                        }
159                        ruleName = deserialized.getName();
160                        if (deserialized.getAttributes() != null) {
161                                String argKey = this.getClass().getName() + ".arguments";
162                                arguments = (Map<String, Object>) deserialized.getAttributeValue(argKey);
163                                deserialized.getAttributes().remove(argKey);
164                        }
165                } catch(GeneralException e) {
166                        throw new IOException(e);
167                }
168        }
169
170        /**
171         * Writes out this RuleWorker as follows:
172         *
173         * If this worker has a Rule object, a boolean true is written to the stream.
174         * If it has only a Rule name, a boolean false is written to the stream.
175         *
176         * If this worker has a Rule object, it is copied.
177         * If this worker has a Rule name, a new Rule object is created with only the name set.
178         *
179         * In both cases, the input Attributes map for this worker is added as an attribute on the
180         * Rule object. (This is why we copied the 'real' one.)
181         *
182         * The Rule object is serialized to XML and written to the stream.
183         *
184         * @param out The callback passed from the output stream
185         * @throws IOException if any failures occur serializing the object
186         */
187        private void writeObject(ObjectOutputStream out) throws IOException {
188                try {
189                        Rule writeRule;
190                        if (ruleObject != null) {
191                                out.writeBoolean(true);
192                                writeRule = copyRule(ruleObject);
193                        } else {
194                                out.writeBoolean(false);
195                                writeRule = new Rule();
196                                writeRule.setName(ruleName);
197                        }
198                        writeRule.setAttribute(this.getClass().getName() + ".arguments", arguments);
199                        out.writeUTF(writeRule.toXml());
200                } catch(GeneralException e) {
201                        throw new IOException(e);
202                }
203        }
204}