1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.compatibility.common.util; 18 19 import com.android.compatibility.common.util.BusinessLogic.BusinessLogicRule; 20 import com.android.compatibility.common.util.BusinessLogic.BusinessLogicRuleAction; 21 import com.android.compatibility.common.util.BusinessLogic.BusinessLogicRuleCondition; 22 import com.android.compatibility.common.util.BusinessLogic.BusinessLogicRulesList; 23 24 import org.json.JSONArray; 25 import org.json.JSONException; 26 import org.json.JSONObject; 27 28 import java.io.BufferedReader; 29 import java.io.File; 30 import java.io.FileInputStream; 31 import java.io.IOException; 32 import java.io.InputStreamReader; 33 import java.io.Reader; 34 import java.text.ParseException; 35 import java.text.SimpleDateFormat; 36 import java.util.ArrayList; 37 import java.util.Date; 38 import java.util.HashMap; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.Scanner; 42 import java.util.TimeZone; 43 44 /** 45 * Factory for creating a {@link BusinessLogic} 46 */ 47 public class BusinessLogicFactory { 48 49 // Name of list object storing test-rules pairs 50 private static final String BUSINESS_LOGIC_RULES_LISTS = "businessLogicRulesLists"; 51 // Name of test name string 52 private static final String TEST_NAME = "testName"; 53 // Name of rules object (one 'rules' object to a single test) 54 private static final String BUSINESS_LOGIC_RULES = "businessLogicRules"; 55 // Name of rule conditions array 56 private static final String RULE_CONDITIONS = "ruleConditions"; 57 // Name of rule actions array 58 private static final String RULE_ACTIONS = "ruleActions"; 59 // Description of a rule list object 60 private static final String RULES_LIST_DESCRIPTION = "description"; 61 // Name of method name string 62 private static final String METHOD_NAME = "methodName"; 63 // Name of method args array of strings 64 private static final String METHOD_ARGS = "methodArgs"; 65 // Name of the field in the response object that stores that the auth status of the request. 66 private static final String AUTHENTICATION_STATUS = "authenticationStatus"; 67 public static final String CONDITIONAL_TESTS_ENABLED = "conditionalTestsEnabled"; 68 // Name of the timestamp field 69 private static final String TIMESTAMP = "timestamp"; 70 // Date and time pattern for raw timestamp string 71 private static final String TIMESTAMP_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; 72 // Name of the redacted regexes field 73 private static final String REDACTION_REGEXES = "redactionRegexes"; 74 75 /** 76 * Create a BusinessLogic instance from a {@link FileInputStream} of business logic data, 77 * formatted in JSON. This format is identical to that which is received from the Android 78 * Partner business logic service. 79 */ createFromFile(FileInputStream stream)80 public static BusinessLogic createFromFile(FileInputStream stream) { 81 try { 82 String businessLogicString = readStream(stream); 83 return createBL(businessLogicString); 84 } catch (IOException e) { 85 throw new RuntimeException("Business Logic failed", e); 86 } 87 } 88 89 /** 90 * Create a BusinessLogic instance from a file of business logic data, formatted in JSON. 91 * This format is identical to that which is received from the Android Partner business logic 92 * service. 93 */ createFromFile(File f)94 public static BusinessLogic createFromFile(File f) { 95 try { 96 String businessLogicString = readFile(f); 97 return createBL(businessLogicString); 98 } catch (IOException e) { 99 throw new RuntimeException("Business Logic failed", e); 100 } 101 } 102 createBL(String businessLogicString)103 private static BusinessLogic createBL(String businessLogicString) { 104 // Populate the map from testname to business rules for this new BusinessLogic instance 105 Map<String, List<BusinessLogicRulesList>> rulesMap = new HashMap<>(); 106 BusinessLogic bl = new BusinessLogic(); 107 try { 108 JSONObject root = new JSONObject(businessLogicString); 109 JSONArray jsonRulesLists = null; 110 if (root.has(AUTHENTICATION_STATUS)){ 111 String authStatus = root.getString(AUTHENTICATION_STATUS); 112 bl.setAuthenticationStatus(authStatus); 113 } 114 if (root.has(CONDITIONAL_TESTS_ENABLED)){ 115 boolean enabled = root.getBoolean(CONDITIONAL_TESTS_ENABLED); 116 bl.mConditionalTestsEnabled = enabled; 117 } 118 if (root.has(TIMESTAMP)) { 119 bl.mTimestamp = parseTimestamp(root.getString(TIMESTAMP)); 120 } 121 if (root.has(REDACTION_REGEXES)) { 122 bl.mRedactionRegexes = parseRedactionRegexes(root.getJSONArray(REDACTION_REGEXES)); 123 } 124 try { 125 jsonRulesLists = root.getJSONArray(BUSINESS_LOGIC_RULES_LISTS); 126 } catch (JSONException e) { 127 bl.mRules = rulesMap; 128 return bl; // no rules defined for this suite, leave internal map empty 129 } 130 for (int i = 0; i < jsonRulesLists.length(); i++) { 131 JSONObject jsonRulesList = jsonRulesLists.getJSONObject(i); 132 String testName = jsonRulesList.getString(TEST_NAME); 133 List<BusinessLogicRulesList> testRulesLists = rulesMap.get(testName); 134 if (testRulesLists == null) { 135 testRulesLists = new ArrayList<>(); 136 } 137 testRulesLists.add(extractRulesList(jsonRulesList)); 138 rulesMap.put(testName, testRulesLists); 139 } 140 } catch (JSONException e) { 141 throw new RuntimeException("Business Logic failed", e); 142 } 143 // Return business logic 144 bl.mRules = rulesMap; 145 return bl; 146 } 147 parseRedactionRegexes(JSONArray redactionRegexesJSONArray)148 private static List<String> parseRedactionRegexes(JSONArray redactionRegexesJSONArray) 149 throws JSONException { 150 List<String> redactionRegexes = new ArrayList<>(); 151 for (int i = 0; i < redactionRegexesJSONArray.length(); i++) { 152 redactionRegexes.add(redactionRegexesJSONArray.getString(i)); 153 } 154 return redactionRegexes; 155 } 156 157 /* Extract a BusinessLogicRulesList from the representative JSON object */ extractRulesList(JSONObject rulesListJSONObject)158 private static BusinessLogicRulesList extractRulesList(JSONObject rulesListJSONObject) 159 throws JSONException { 160 // First, parse the description for this rule list object, if one exists 161 String description = null; 162 try { 163 description = rulesListJSONObject.getString(RULES_LIST_DESCRIPTION); 164 } catch (JSONException e) { /* no description set, leave null */} 165 166 // Next, get the list of rules 167 List<BusinessLogicRule> rules = new ArrayList<>(); 168 JSONArray rulesJSONArray = null; 169 try { 170 rulesJSONArray = rulesListJSONObject.getJSONArray(BUSINESS_LOGIC_RULES); 171 } catch (JSONException e) { 172 // no rules defined for this test case, return new, rule-less BusinessLogicRulesList 173 return new BusinessLogicRulesList(rules, description); 174 } 175 for (int j = 0; j < rulesJSONArray.length(); j++) { 176 JSONObject ruleJSONObject = rulesJSONArray.getJSONObject(j); 177 // Build conditions list 178 List<BusinessLogicRuleCondition> ruleConditions = 179 extractRuleConditionList(ruleJSONObject); 180 // Build actions list 181 List<BusinessLogicRuleAction> ruleActions = 182 extractRuleActionList(ruleJSONObject); 183 rules.add(new BusinessLogicRule(ruleConditions, ruleActions)); 184 } 185 return new BusinessLogicRulesList(rules, description); 186 } 187 188 /* Extract all BusinessLogicRuleConditions from a JSON business logic rule */ extractRuleConditionList( JSONObject ruleJSONObject)189 private static List<BusinessLogicRuleCondition> extractRuleConditionList( 190 JSONObject ruleJSONObject) throws JSONException { 191 List<BusinessLogicRuleCondition> ruleConditions = new ArrayList<>(); 192 // Rules do not require a condition, return empty list if no condition is found 193 JSONArray ruleConditionsJSONArray = null; 194 try { 195 ruleConditionsJSONArray = ruleJSONObject.getJSONArray(RULE_CONDITIONS); 196 } catch (JSONException e) { 197 return ruleConditions; // no conditions for this rule, apply in all cases 198 } 199 for (int i = 0; i < ruleConditionsJSONArray.length(); i++) { 200 JSONObject ruleConditionJSONObject = ruleConditionsJSONArray.getJSONObject(i); 201 String methodName = ruleConditionJSONObject.getString(METHOD_NAME); 202 boolean negated = false; 203 if (methodName.startsWith("!")) { 204 methodName = methodName.substring(1); // remove negation from method name string 205 negated = true; // change "negated" property to true 206 } 207 List<String> methodArgs = new ArrayList<>(); 208 JSONArray methodArgsJSONArray = null; 209 try { 210 methodArgsJSONArray = ruleConditionJSONObject.getJSONArray(METHOD_ARGS); 211 } catch (JSONException e) { 212 // No method args for this rule condition, add rule condition with empty args list 213 ruleConditions.add(new BusinessLogicRuleCondition(methodName, methodArgs, negated)); 214 continue; 215 } 216 for (int j = 0; j < methodArgsJSONArray.length(); j++) { 217 methodArgs.add(methodArgsJSONArray.getString(j)); 218 } 219 ruleConditions.add(new BusinessLogicRuleCondition(methodName, methodArgs, negated)); 220 } 221 return ruleConditions; 222 } 223 224 /* Extract all BusinessLogicRuleActions from a JSON business logic rule */ extractRuleActionList(JSONObject ruleJSONObject)225 private static List<BusinessLogicRuleAction> extractRuleActionList(JSONObject ruleJSONObject) 226 throws JSONException { 227 List<BusinessLogicRuleAction> ruleActions = new ArrayList<>(); 228 // All rules require at least one action, line below throws JSONException if not 229 JSONArray ruleActionsJSONArray = ruleJSONObject.getJSONArray(RULE_ACTIONS); 230 for (int i = 0; i < ruleActionsJSONArray.length(); i++) { 231 JSONObject ruleActionJSONObject = ruleActionsJSONArray.getJSONObject(i); 232 String methodName = ruleActionJSONObject.getString(METHOD_NAME); 233 List<String> methodArgs = new ArrayList<>(); 234 JSONArray methodArgsJSONArray = null; 235 try { 236 methodArgsJSONArray = ruleActionJSONObject.getJSONArray(METHOD_ARGS); 237 } catch (JSONException e) { 238 // No method args for this rule action, add rule action with empty args list 239 ruleActions.add(new BusinessLogicRuleAction(methodName, methodArgs)); 240 continue; 241 } 242 for (int j = 0; j < methodArgsJSONArray.length(); j++) { 243 methodArgs.add(methodArgsJSONArray.getString(j)); 244 } 245 ruleActions.add(new BusinessLogicRuleAction(methodName, methodArgs)); 246 } 247 return ruleActions; 248 } 249 250 /* Pare a timestamp string with format TIMESTAMP_PATTERN to a date object */ parseTimestamp(String timestamp)251 private static Date parseTimestamp(String timestamp) { 252 SimpleDateFormat format = new SimpleDateFormat(TIMESTAMP_PATTERN); 253 format.setTimeZone(TimeZone.getTimeZone("UTC")); 254 try { 255 return format.parse(timestamp); 256 } catch (ParseException e) { 257 return null; 258 } 259 } 260 261 /* Extract string from file */ readFile(File f)262 private static String readFile(File f) throws IOException { 263 StringBuilder sb = new StringBuilder((int) f.length()); 264 String lineSeparator = System.getProperty("line.separator"); 265 try (Scanner scanner = new Scanner(f)) { 266 while(scanner.hasNextLine()) { 267 sb.append(scanner.nextLine() + lineSeparator); 268 } 269 return sb.toString(); 270 } 271 } 272 273 /** Extract string from stream */ readStream(FileInputStream stream)274 private static String readStream(FileInputStream stream) throws IOException { 275 int irChar = -1; 276 StringBuilder builder = new StringBuilder(); 277 try (Reader ir = new BufferedReader(new InputStreamReader(stream))) { 278 while ((irChar = ir.read()) != -1) { 279 builder.append((char) irChar); 280 } 281 } 282 return builder.toString(); 283 } 284 } 285