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 org.json.JSONArray; 20 import org.json.JSONException; 21 import org.json.JSONObject; 22 23 import com.android.compatibility.common.util.BusinessLogic.BusinessLogicRule; 24 import com.android.compatibility.common.util.BusinessLogic.BusinessLogicRuleAction; 25 import com.android.compatibility.common.util.BusinessLogic.BusinessLogicRuleCondition; 26 import com.android.compatibility.common.util.BusinessLogic.BusinessLogicRulesList; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.text.ParseException; 31 import java.text.SimpleDateFormat; 32 import java.util.ArrayList; 33 import java.util.Date; 34 import java.util.HashMap; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.Scanner; 38 import java.util.TimeZone; 39 40 /** 41 * Factory for creating a {@link BusinessLogic} 42 */ 43 public class BusinessLogicFactory { 44 45 // Name of list object storing test-rules pairs 46 private static final String BUSINESS_LOGIC_RULES_LISTS = "businessLogicRulesLists"; 47 // Name of test name string 48 private static final String TEST_NAME = "testName"; 49 // Name of rules object (one 'rules' object to a single test) 50 private static final String BUSINESS_LOGIC_RULES = "businessLogicRules"; 51 // Name of rule conditions array 52 private static final String RULE_CONDITIONS = "ruleConditions"; 53 // Name of rule actions array 54 private static final String RULE_ACTIONS = "ruleActions"; 55 // Description of a rule list object 56 private static final String RULES_LIST_DESCRIPTION = "description"; 57 // Name of method name string 58 private static final String METHOD_NAME = "methodName"; 59 // Name of method args array of strings 60 private static final String METHOD_ARGS = "methodArgs"; 61 // Name of the field in the response object that stores that the auth status of the request. 62 private static final String AUTHENTICATION_STATUS = "authenticationStatus"; 63 public static final String CONDITIONAL_TESTS_ENABLED = "conditionalTestsEnabled"; 64 // Name of the timestamp field 65 private static final String TIMESTAMP = "timestamp"; 66 // Date and time pattern for raw timestamp string 67 private static final String TIMESTAMP_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; 68 69 /** 70 * Create a BusinessLogic instance from a file of business logic data, formatted in JSON. 71 * This format is identical to that which is received from the Android Partner business logic 72 * service. 73 */ 74 public static BusinessLogic createFromFile(File f) { 75 // Populate the map from testname to business rules for this new BusinessLogic instance 76 Map<String, List<BusinessLogicRulesList>> rulesMap = new HashMap<>(); 77 BusinessLogic bl = new BusinessLogic(); 78 try { 79 String businessLogicString = readFile(f); 80 JSONObject root = new JSONObject(businessLogicString); 81 JSONArray jsonRulesLists = null; 82 if (root.has(AUTHENTICATION_STATUS)){ 83 String authStatus = root.getString(AUTHENTICATION_STATUS); 84 bl.setAuthenticationStatus(authStatus); 85 } 86 if (root.has(CONDITIONAL_TESTS_ENABLED)){ 87 boolean enabled = root.getBoolean(CONDITIONAL_TESTS_ENABLED); 88 bl.mConditionalTestsEnabled = enabled; 89 } 90 if (root.has(TIMESTAMP)) { 91 bl.mTimestamp = parseTimestamp(root.getString(TIMESTAMP)); 92 } 93 try { 94 jsonRulesLists = root.getJSONArray(BUSINESS_LOGIC_RULES_LISTS); 95 } catch (JSONException e) { 96 bl.mRules = rulesMap; 97 return bl; // no rules defined for this suite, leave internal map empty 98 } 99 for (int i = 0; i < jsonRulesLists.length(); i++) { 100 JSONObject jsonRulesList = jsonRulesLists.getJSONObject(i); 101 String testName = jsonRulesList.getString(TEST_NAME); 102 List<BusinessLogicRulesList> testRulesLists = rulesMap.get(testName); 103 if (testRulesLists == null) { 104 testRulesLists = new ArrayList<>(); 105 } 106 testRulesLists.add(extractRulesList(jsonRulesList)); 107 rulesMap.put(testName, testRulesLists); 108 } 109 } catch (IOException | JSONException e) { 110 throw new RuntimeException("Business Logic failed", e); 111 } 112 // Return business logic 113 bl.mRules = rulesMap; 114 return bl; 115 } 116 117 /* Extract a BusinessLogicRulesList from the representative JSON object */ 118 private static BusinessLogicRulesList extractRulesList(JSONObject rulesListJSONObject) 119 throws JSONException { 120 // First, parse the description for this rule list object, if one exists 121 String description = null; 122 try { 123 description = rulesListJSONObject.getString(RULES_LIST_DESCRIPTION); 124 } catch (JSONException e) { /* no description set, leave null */} 125 126 // Next, get the list of rules 127 List<BusinessLogicRule> rules = new ArrayList<>(); 128 JSONArray rulesJSONArray = null; 129 try { 130 rulesJSONArray = rulesListJSONObject.getJSONArray(BUSINESS_LOGIC_RULES); 131 } catch (JSONException e) { 132 // no rules defined for this test case, return new, rule-less BusinessLogicRulesList 133 return new BusinessLogicRulesList(rules, description); 134 } 135 for (int j = 0; j < rulesJSONArray.length(); j++) { 136 JSONObject ruleJSONObject = rulesJSONArray.getJSONObject(j); 137 // Build conditions list 138 List<BusinessLogicRuleCondition> ruleConditions = 139 extractRuleConditionList(ruleJSONObject); 140 // Build actions list 141 List<BusinessLogicRuleAction> ruleActions = 142 extractRuleActionList(ruleJSONObject); 143 rules.add(new BusinessLogicRule(ruleConditions, ruleActions)); 144 } 145 return new BusinessLogicRulesList(rules, description); 146 } 147 148 /* Extract all BusinessLogicRuleConditions from a JSON business logic rule */ 149 private static List<BusinessLogicRuleCondition> extractRuleConditionList( 150 JSONObject ruleJSONObject) throws JSONException { 151 List<BusinessLogicRuleCondition> ruleConditions = new ArrayList<>(); 152 // Rules do not require a condition, return empty list if no condition is found 153 JSONArray ruleConditionsJSONArray = null; 154 try { 155 ruleConditionsJSONArray = ruleJSONObject.getJSONArray(RULE_CONDITIONS); 156 } catch (JSONException e) { 157 return ruleConditions; // no conditions for this rule, apply in all cases 158 } 159 for (int i = 0; i < ruleConditionsJSONArray.length(); i++) { 160 JSONObject ruleConditionJSONObject = ruleConditionsJSONArray.getJSONObject(i); 161 String methodName = ruleConditionJSONObject.getString(METHOD_NAME); 162 boolean negated = false; 163 if (methodName.startsWith("!")) { 164 methodName = methodName.substring(1); // remove negation from method name string 165 negated = true; // change "negated" property to true 166 } 167 List<String> methodArgs = new ArrayList<>(); 168 JSONArray methodArgsJSONArray = null; 169 try { 170 methodArgsJSONArray = ruleConditionJSONObject.getJSONArray(METHOD_ARGS); 171 } catch (JSONException e) { 172 // No method args for this rule condition, add rule condition with empty args list 173 ruleConditions.add(new BusinessLogicRuleCondition(methodName, methodArgs, negated)); 174 continue; 175 } 176 for (int j = 0; j < methodArgsJSONArray.length(); j++) { 177 methodArgs.add(methodArgsJSONArray.getString(j)); 178 } 179 ruleConditions.add(new BusinessLogicRuleCondition(methodName, methodArgs, negated)); 180 } 181 return ruleConditions; 182 } 183 184 /* Extract all BusinessLogicRuleActions from a JSON business logic rule */ 185 private static List<BusinessLogicRuleAction> extractRuleActionList(JSONObject ruleJSONObject) 186 throws JSONException { 187 List<BusinessLogicRuleAction> ruleActions = new ArrayList<>(); 188 // All rules require at least one action, line below throws JSONException if not 189 JSONArray ruleActionsJSONArray = ruleJSONObject.getJSONArray(RULE_ACTIONS); 190 for (int i = 0; i < ruleActionsJSONArray.length(); i++) { 191 JSONObject ruleActionJSONObject = ruleActionsJSONArray.getJSONObject(i); 192 String methodName = ruleActionJSONObject.getString(METHOD_NAME); 193 List<String> methodArgs = new ArrayList<>(); 194 JSONArray methodArgsJSONArray = null; 195 try { 196 methodArgsJSONArray = ruleActionJSONObject.getJSONArray(METHOD_ARGS); 197 } catch (JSONException e) { 198 // No method args for this rule action, add rule action with empty args list 199 ruleActions.add(new BusinessLogicRuleAction(methodName, methodArgs)); 200 continue; 201 } 202 for (int j = 0; j < methodArgsJSONArray.length(); j++) { 203 methodArgs.add(methodArgsJSONArray.getString(j)); 204 } 205 ruleActions.add(new BusinessLogicRuleAction(methodName, methodArgs)); 206 } 207 return ruleActions; 208 } 209 210 /* Pare a timestamp string with format TIMESTAMP_PATTERN to a date object */ 211 private static Date parseTimestamp(String timestamp) { 212 SimpleDateFormat format = new SimpleDateFormat(TIMESTAMP_PATTERN); 213 format.setTimeZone(TimeZone.getTimeZone("UTC")); 214 try { 215 return format.parse(timestamp); 216 } catch (ParseException e) { 217 return null; 218 } 219 } 220 221 /* Extract string from file */ 222 private static String readFile(File f) throws IOException { 223 StringBuilder sb = new StringBuilder((int) f.length()); 224 String lineSeparator = System.getProperty("line.separator"); 225 try (Scanner scanner = new Scanner(f)) { 226 while(scanner.hasNextLine()) { 227 sb.append(scanner.nextLine() + lineSeparator); 228 } 229 return sb.toString(); 230 } 231 } 232 } 233