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 73 /** 74 * Create a BusinessLogic instance from a {@link FileInputStream} of business logic data, 75 * formatted in JSON. This format is identical to that which is received from the Android 76 * Partner business logic service. 77 */ createFromFile(FileInputStream stream)78 public static BusinessLogic createFromFile(FileInputStream stream) { 79 try { 80 String businessLogicString = readStream(stream); 81 return createBL(businessLogicString); 82 } catch (IOException e) { 83 throw new RuntimeException("Business Logic failed", e); 84 } 85 } 86 87 /** 88 * Create a BusinessLogic instance from a file of business logic data, formatted in JSON. 89 * This format is identical to that which is received from the Android Partner business logic 90 * service. 91 */ createFromFile(File f)92 public static BusinessLogic createFromFile(File f) { 93 try { 94 String businessLogicString = readFile(f); 95 return createBL(businessLogicString); 96 } catch (IOException e) { 97 throw new RuntimeException("Business Logic failed", e); 98 } 99 } 100 createBL(String businessLogicString)101 private static BusinessLogic createBL(String businessLogicString) { 102 // Populate the map from testname to business rules for this new BusinessLogic instance 103 Map<String, List<BusinessLogicRulesList>> rulesMap = new HashMap<>(); 104 BusinessLogic bl = new BusinessLogic(); 105 try { 106 JSONObject root = new JSONObject(businessLogicString); 107 JSONArray jsonRulesLists = null; 108 if (root.has(AUTHENTICATION_STATUS)){ 109 String authStatus = root.getString(AUTHENTICATION_STATUS); 110 bl.setAuthenticationStatus(authStatus); 111 } 112 if (root.has(CONDITIONAL_TESTS_ENABLED)){ 113 boolean enabled = root.getBoolean(CONDITIONAL_TESTS_ENABLED); 114 bl.mConditionalTestsEnabled = enabled; 115 } 116 if (root.has(TIMESTAMP)) { 117 bl.mTimestamp = parseTimestamp(root.getString(TIMESTAMP)); 118 } 119 try { 120 jsonRulesLists = root.getJSONArray(BUSINESS_LOGIC_RULES_LISTS); 121 } catch (JSONException e) { 122 bl.mRules = rulesMap; 123 return bl; // no rules defined for this suite, leave internal map empty 124 } 125 for (int i = 0; i < jsonRulesLists.length(); i++) { 126 JSONObject jsonRulesList = jsonRulesLists.getJSONObject(i); 127 String testName = jsonRulesList.getString(TEST_NAME); 128 List<BusinessLogicRulesList> testRulesLists = rulesMap.get(testName); 129 if (testRulesLists == null) { 130 testRulesLists = new ArrayList<>(); 131 } 132 testRulesLists.add(extractRulesList(jsonRulesList)); 133 rulesMap.put(testName, testRulesLists); 134 } 135 } catch (JSONException e) { 136 throw new RuntimeException("Business Logic failed", e); 137 } 138 // Return business logic 139 bl.mRules = rulesMap; 140 return bl; 141 } 142 143 /* Extract a BusinessLogicRulesList from the representative JSON object */ extractRulesList(JSONObject rulesListJSONObject)144 private static BusinessLogicRulesList extractRulesList(JSONObject rulesListJSONObject) 145 throws JSONException { 146 // First, parse the description for this rule list object, if one exists 147 String description = null; 148 try { 149 description = rulesListJSONObject.getString(RULES_LIST_DESCRIPTION); 150 } catch (JSONException e) { /* no description set, leave null */} 151 152 // Next, get the list of rules 153 List<BusinessLogicRule> rules = new ArrayList<>(); 154 JSONArray rulesJSONArray = null; 155 try { 156 rulesJSONArray = rulesListJSONObject.getJSONArray(BUSINESS_LOGIC_RULES); 157 } catch (JSONException e) { 158 // no rules defined for this test case, return new, rule-less BusinessLogicRulesList 159 return new BusinessLogicRulesList(rules, description); 160 } 161 for (int j = 0; j < rulesJSONArray.length(); j++) { 162 JSONObject ruleJSONObject = rulesJSONArray.getJSONObject(j); 163 // Build conditions list 164 List<BusinessLogicRuleCondition> ruleConditions = 165 extractRuleConditionList(ruleJSONObject); 166 // Build actions list 167 List<BusinessLogicRuleAction> ruleActions = 168 extractRuleActionList(ruleJSONObject); 169 rules.add(new BusinessLogicRule(ruleConditions, ruleActions)); 170 } 171 return new BusinessLogicRulesList(rules, description); 172 } 173 174 /* Extract all BusinessLogicRuleConditions from a JSON business logic rule */ extractRuleConditionList( JSONObject ruleJSONObject)175 private static List<BusinessLogicRuleCondition> extractRuleConditionList( 176 JSONObject ruleJSONObject) throws JSONException { 177 List<BusinessLogicRuleCondition> ruleConditions = new ArrayList<>(); 178 // Rules do not require a condition, return empty list if no condition is found 179 JSONArray ruleConditionsJSONArray = null; 180 try { 181 ruleConditionsJSONArray = ruleJSONObject.getJSONArray(RULE_CONDITIONS); 182 } catch (JSONException e) { 183 return ruleConditions; // no conditions for this rule, apply in all cases 184 } 185 for (int i = 0; i < ruleConditionsJSONArray.length(); i++) { 186 JSONObject ruleConditionJSONObject = ruleConditionsJSONArray.getJSONObject(i); 187 String methodName = ruleConditionJSONObject.getString(METHOD_NAME); 188 boolean negated = false; 189 if (methodName.startsWith("!")) { 190 methodName = methodName.substring(1); // remove negation from method name string 191 negated = true; // change "negated" property to true 192 } 193 List<String> methodArgs = new ArrayList<>(); 194 JSONArray methodArgsJSONArray = null; 195 try { 196 methodArgsJSONArray = ruleConditionJSONObject.getJSONArray(METHOD_ARGS); 197 } catch (JSONException e) { 198 // No method args for this rule condition, add rule condition with empty args list 199 ruleConditions.add(new BusinessLogicRuleCondition(methodName, methodArgs, negated)); 200 continue; 201 } 202 for (int j = 0; j < methodArgsJSONArray.length(); j++) { 203 methodArgs.add(methodArgsJSONArray.getString(j)); 204 } 205 ruleConditions.add(new BusinessLogicRuleCondition(methodName, methodArgs, negated)); 206 } 207 return ruleConditions; 208 } 209 210 /* Extract all BusinessLogicRuleActions from a JSON business logic rule */ extractRuleActionList(JSONObject ruleJSONObject)211 private static List<BusinessLogicRuleAction> extractRuleActionList(JSONObject ruleJSONObject) 212 throws JSONException { 213 List<BusinessLogicRuleAction> ruleActions = new ArrayList<>(); 214 // All rules require at least one action, line below throws JSONException if not 215 JSONArray ruleActionsJSONArray = ruleJSONObject.getJSONArray(RULE_ACTIONS); 216 for (int i = 0; i < ruleActionsJSONArray.length(); i++) { 217 JSONObject ruleActionJSONObject = ruleActionsJSONArray.getJSONObject(i); 218 String methodName = ruleActionJSONObject.getString(METHOD_NAME); 219 List<String> methodArgs = new ArrayList<>(); 220 JSONArray methodArgsJSONArray = null; 221 try { 222 methodArgsJSONArray = ruleActionJSONObject.getJSONArray(METHOD_ARGS); 223 } catch (JSONException e) { 224 // No method args for this rule action, add rule action with empty args list 225 ruleActions.add(new BusinessLogicRuleAction(methodName, methodArgs)); 226 continue; 227 } 228 for (int j = 0; j < methodArgsJSONArray.length(); j++) { 229 methodArgs.add(methodArgsJSONArray.getString(j)); 230 } 231 ruleActions.add(new BusinessLogicRuleAction(methodName, methodArgs)); 232 } 233 return ruleActions; 234 } 235 236 /* Pare a timestamp string with format TIMESTAMP_PATTERN to a date object */ parseTimestamp(String timestamp)237 private static Date parseTimestamp(String timestamp) { 238 SimpleDateFormat format = new SimpleDateFormat(TIMESTAMP_PATTERN); 239 format.setTimeZone(TimeZone.getTimeZone("UTC")); 240 try { 241 return format.parse(timestamp); 242 } catch (ParseException e) { 243 return null; 244 } 245 } 246 247 /* Extract string from file */ readFile(File f)248 private static String readFile(File f) throws IOException { 249 StringBuilder sb = new StringBuilder((int) f.length()); 250 String lineSeparator = System.getProperty("line.separator"); 251 try (Scanner scanner = new Scanner(f)) { 252 while(scanner.hasNextLine()) { 253 sb.append(scanner.nextLine() + lineSeparator); 254 } 255 return sb.toString(); 256 } 257 } 258 259 /** Extract string from stream */ readStream(FileInputStream stream)260 private static String readStream(FileInputStream stream) throws IOException { 261 int irChar = -1; 262 StringBuilder builder = new StringBuilder(); 263 try (Reader ir = new BufferedReader(new InputStreamReader(stream))) { 264 while ((irChar = ir.read()) != -1) { 265 builder.append((char) irChar); 266 } 267 } 268 return builder.toString(); 269 } 270 } 271