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}