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