1 /* 2 * Copyright (C) 2015 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.tradefed.util.FileUtil; 20 21 import org.json.JSONArray; 22 import org.json.JSONException; 23 import org.json.JSONObject; 24 import org.json.JSONTokener; 25 import org.xmlpull.v1.XmlPullParserException; 26 import org.xmlpull.v1.XmlPullParserFactory; 27 import org.xmlpull.v1.XmlSerializer; 28 29 import java.io.File; 30 import java.io.FileOutputStream; 31 import java.io.IOException; 32 import java.io.OutputStream; 33 import java.util.ArrayList; 34 import java.util.Collections; 35 import java.util.HashMap; 36 import java.util.List; 37 import java.util.Map; 38 39 public class DynamicConfigHandler { 40 41 private final static String FILE_EXT = ".dynamic"; 42 private static final String NS = null; //xml constant representing null namespace 43 private static final String ENCODING = "UTF-8"; 44 getMergedDynamicConfigFile( File localConfigFile, String apbsConfigJson, String moduleName, Map<String, String> valueReplacementMap)45 public static File getMergedDynamicConfigFile( 46 File localConfigFile, 47 String apbsConfigJson, 48 String moduleName, 49 Map<String, String> valueReplacementMap) 50 throws IOException, XmlPullParserException, JSONException { 51 Map<String, List<String>> dynamicConfig = new HashMap<>(); 52 53 Map<String, List<String>> localConfig = DynamicConfig.createConfigMap(localConfigFile); 54 Map<String, List<String>> apbsConfig = parseJsonToConfigMap(apbsConfigJson); 55 localConfig.putAll(apbsConfig); 56 57 localConfig.forEach( 58 (k, v) -> { 59 dynamicConfig.put(k, updateValues(v, valueReplacementMap)); 60 }); 61 62 setRemoteConfigRetrieved(dynamicConfig, apbsConfigJson != null); 63 return storeMergedConfigFile(dynamicConfig, moduleName); 64 } 65 updateValues( List<String> values, Map<String, String> valueReplacementMap)66 private static List<String> updateValues( 67 List<String> values, Map<String, String> valueReplacementMap) { 68 List<String> updatedValues = new ArrayList<>(); 69 values.forEach( 70 v -> { 71 String updatedValue = v; 72 for (Map.Entry<String, String> entry : valueReplacementMap.entrySet()) { 73 updatedValue = updatedValue.replace(entry.getKey(), entry.getValue()); 74 } 75 updatedValues.add(updatedValue); 76 }); 77 return updatedValues; 78 } 79 setRemoteConfigRetrieved(Map<String, List<String>> config, boolean retrieved)80 private static void setRemoteConfigRetrieved(Map<String, List<String>> config, 81 boolean retrieved) { 82 List<String> val = Collections.singletonList(Boolean.toString(retrieved)); 83 config.put(DynamicConfig.REMOTE_CONFIG_RETRIEVED_KEY, val); 84 } 85 parseJsonToConfigMap(String apbsConfigJson)86 private static Map<String, List<String>> parseJsonToConfigMap(String apbsConfigJson) 87 throws JSONException { 88 89 Map<String, List<String>> configMap = new HashMap<String, List<String>>(); 90 if (apbsConfigJson == null) { 91 return configMap; 92 } 93 94 JSONObject rootObj = new JSONObject(new JSONTokener(apbsConfigJson)); 95 JSONObject configObject = null; 96 try { 97 configObject = rootObj.getJSONObject("dynamicConfigEntries"); 98 } catch (JSONException e) { 99 // no config key-value(s) pairs have been defined remotely, return empty map 100 return configMap; 101 } 102 JSONArray keys = configObject.names(); 103 for (int i = 0; i < keys.length(); i++) { 104 String key = keys.getString(i); 105 JSONArray jsonValues = configObject.getJSONObject(key).getJSONArray("configValues"); 106 List<String> values = new ArrayList<>(); 107 for (int j = 0; j < jsonValues.length(); j ++) { 108 values.add(jsonValues.getString(j)); 109 } 110 configMap.put(key, values); 111 } 112 return configMap; 113 } 114 storeMergedConfigFile(Map<String, List<String>> configMap, String moduleName)115 private static File storeMergedConfigFile(Map<String, List<String>> configMap, 116 String moduleName) throws XmlPullParserException, IOException { 117 118 File mergedConfigFile = FileUtil.createTempFile(moduleName, FILE_EXT); 119 OutputStream stream = new FileOutputStream(mergedConfigFile); 120 XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer(); 121 serializer.setOutput(stream, ENCODING); 122 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 123 serializer.startDocument(ENCODING, false); 124 125 serializer.startTag(NS, DynamicConfig.CONFIG_TAG); 126 for (String key : configMap.keySet()) { 127 serializer.startTag(NS, DynamicConfig.ENTRY_TAG); 128 serializer.attribute(NS, DynamicConfig.KEY_ATTR, key); 129 for (String value : configMap.get(key)) { 130 serializer.startTag(NS, DynamicConfig.VALUE_TAG); 131 serializer.text(value); 132 serializer.endTag(NS, DynamicConfig.VALUE_TAG); 133 } 134 serializer.endTag(NS, DynamicConfig.ENTRY_TAG); 135 } 136 serializer.endTag(NS, DynamicConfig.CONFIG_TAG); 137 serializer.endDocument(); 138 return mergedConfigFile; 139 } 140 } 141