1 /*
2  * Copyright (C) 2022 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.adservices.service.measurement;
18 
19 import android.annotation.Nullable;
20 
21 import com.android.adservices.LoggerFactory;
22 import com.android.adservices.service.Flags;
23 
24 import org.json.JSONArray;
25 import org.json.JSONException;
26 import org.json.JSONObject;
27 
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Objects;
34 
35 /** POJO for FilterMap. */
36 public class FilterMap {
37 
38     private Map<String, List<String>> mAttributionFilterMap;
39     private Map<String, FilterValue> mAttributionFilterMapWithLongValue;
40 
41     public static final String RESERVED_PREFIX = "_";
42     public static final String LOOKBACK_WINDOW = "_lookback_window";
43 
FilterMap()44     FilterMap() {
45         mAttributionFilterMap = new HashMap<>();
46         mAttributionFilterMapWithLongValue = new HashMap<>();
47     }
48 
49     @Override
equals(Object obj)50     public boolean equals(Object obj) {
51         if (!(obj instanceof FilterMap)) {
52             return false;
53         }
54         FilterMap attributionFilterMap = (FilterMap) obj;
55         return Objects.equals(mAttributionFilterMap, attributionFilterMap.mAttributionFilterMap)
56                 && Objects.equals(
57                         mAttributionFilterMapWithLongValue,
58                         attributionFilterMap.mAttributionFilterMapWithLongValue);
59     }
60 
61     @Override
hashCode()62     public int hashCode() {
63         return Objects.hash(mAttributionFilterMap, mAttributionFilterMapWithLongValue);
64     }
65 
66     /**
67      * Returns the attribution filter map.
68      *
69      * @deprecated use {@link #getAttributionFilterMapWithLongValue()} instead.
70      */
71     @Deprecated
getAttributionFilterMap()72     public Map<String, List<String>> getAttributionFilterMap() {
73         return mAttributionFilterMap;
74     }
75 
76     /** Returns the attribution filter map with lookback window included. */
getAttributionFilterMapWithLongValue()77     public Map<String, FilterValue> getAttributionFilterMapWithLongValue() {
78         return mAttributionFilterMapWithLongValue;
79     }
80 
81     /**
82      * Returns the long value given the key. {@code key} must be present and the value kind must be
83      * {@link FilterValue.Kind#LONG_VALUE}.
84      */
getLongValue(String key)85     public long getLongValue(String key) {
86         return mAttributionFilterMapWithLongValue.get(key).longValue();
87     }
88 
89     /** Returns whether the attribution filter map is empty. */
isEmpty(Flags flags)90     public boolean isEmpty(Flags flags) {
91         return flags.getMeasurementEnableLookbackWindowFilter()
92                 ? mAttributionFilterMapWithLongValue.isEmpty()
93                 : mAttributionFilterMap.isEmpty();
94     }
95 
96     /**
97      * Returns the string list value given the key. {@code key} must be present and the value kind
98      * must be {@link FilterValue.Kind#STRING_LIST_VALUE}.
99      */
getStringListValue(String key)100     public List<String> getStringListValue(String key) {
101         return mAttributionFilterMapWithLongValue.get(key).stringListValue();
102     }
103 
104     /**
105      * Serializes the object into a {@link JSONObject}.
106      *
107      * @return serialized {@link JSONObject}.
108      */
109     @Nullable
serializeAsJson(Flags flags)110     public JSONObject serializeAsJson(Flags flags) {
111         return flags.getMeasurementEnableLookbackWindowFilter()
112                 ? serializeAsJsonV2()
113                 : serializeAsJson();
114     }
115 
116     @Nullable
serializeAsJson()117     private JSONObject serializeAsJson() {
118         if (mAttributionFilterMap == null) {
119             return null;
120         }
121 
122         try {
123             JSONObject result = new JSONObject();
124             for (String key : mAttributionFilterMap.keySet()) {
125                 result.put(key, new JSONArray(mAttributionFilterMap.get(key)));
126             }
127 
128             return result;
129         } catch (JSONException e) {
130             LoggerFactory.getMeasurementLogger().d(e, "Failed to serialize filtermap.");
131             return null;
132         }
133     }
134 
135     @Nullable
serializeAsJsonV2()136     private JSONObject serializeAsJsonV2() {
137         if (mAttributionFilterMapWithLongValue == null) {
138             return null;
139         }
140 
141         try {
142             JSONObject result = new JSONObject();
143             for (String key : mAttributionFilterMapWithLongValue.keySet()) {
144                 FilterValue value = mAttributionFilterMapWithLongValue.get(key);
145                 switch (value.kind()) {
146                     case LONG_VALUE:
147                         result.put(key, value.longValue());
148                         break;
149                     case STRING_LIST_VALUE:
150                         result.put(key, new JSONArray(value.stringListValue()));
151                         break;
152                 }
153             }
154             return result;
155         } catch (JSONException e) {
156             LoggerFactory.getMeasurementLogger().d(e, "Failed to serialize filtermap.");
157             return null;
158         }
159     }
160 
161     /** Builder for {@link FilterMap}. */
162     public static final class Builder {
163         private final FilterMap mBuilding;
164 
Builder()165         public Builder() {
166             mBuilding = new FilterMap();
167         }
168 
169         /** See {@link FilterMap#getAttributionFilterMapWithLongValue()}. */
setAttributionFilterMapWithLongValue( Map<String, FilterValue> attributionFilterMap)170         public Builder setAttributionFilterMapWithLongValue(
171                 Map<String, FilterValue> attributionFilterMap) {
172             mBuilding.mAttributionFilterMapWithLongValue = attributionFilterMap;
173             return this;
174         }
175 
176         /** Adds filter with long value. */
addLongValue(String key, long value)177         public Builder addLongValue(String key, long value) {
178             mBuilding.mAttributionFilterMapWithLongValue.put(key, FilterValue.ofLong(value));
179             return this;
180         }
181 
182         /** Adds filter with string list value. */
addStringListValue(String key, List<String> value)183         public Builder addStringListValue(String key, List<String> value) {
184             mBuilding.mAttributionFilterMapWithLongValue.put(key, FilterValue.ofStringList(value));
185             return this;
186         }
187 
188         /**
189          * See {@link FilterMap#getAttributionFilterMap()}.
190          *
191          * @deprecated use {@link #setAttributionFilterMapWithLongValue} instead.
192          */
193         @Deprecated
setAttributionFilterMap(Map<String, List<String>> attributionFilterMap)194         public Builder setAttributionFilterMap(Map<String, List<String>> attributionFilterMap) {
195             mBuilding.mAttributionFilterMap = attributionFilterMap;
196             return this;
197         }
198 
199         /** Builds FilterMap from JSONObject. */
buildFilterData(JSONObject jsonObject, Flags flags)200         public Builder buildFilterData(JSONObject jsonObject, Flags flags) throws JSONException {
201             return flags.getMeasurementEnableLookbackWindowFilter()
202                     ? buildFilterDataV2(jsonObject)
203                     : buildFilterData(jsonObject);
204         }
205 
206         /**
207          * Builds FilterMap from JSONObject.
208          *
209          * @deprecated use {@link #buildFilterDataV2} instead.
210          */
211         @Deprecated
buildFilterData(JSONObject jsonObject)212         public Builder buildFilterData(JSONObject jsonObject) throws JSONException {
213             Map<String, List<String>> filterMap = new HashMap<>();
214             Iterator<String> keys = jsonObject.keys();
215             while (keys.hasNext()) {
216                 String key = keys.next();
217                 JSONArray jsonArray = jsonObject.getJSONArray(key);
218                 List<String> filterMapList = new ArrayList<>();
219                 for (int i = 0; i < jsonArray.length(); i++) {
220                     filterMapList.add(jsonArray.getString(i));
221                 }
222                 filterMap.put(key, filterMapList);
223             }
224             mBuilding.mAttributionFilterMap = filterMap;
225             return this;
226         }
227 
228         /** Builds FilterMap from JSONObject with long filter values. */
buildFilterDataV2(JSONObject jsonObject)229         public Builder buildFilterDataV2(JSONObject jsonObject) throws JSONException {
230             Map<String, FilterValue> filterMap = new HashMap<>();
231             Iterator<String> keys = jsonObject.keys();
232             while (keys.hasNext()) {
233                 String key = keys.next();
234                 if (LOOKBACK_WINDOW.equals(key)) {
235                     String value = jsonObject.getString(key);
236                     try {
237                         filterMap.put(key, FilterValue.ofLong(Long.parseLong(value)));
238                     } catch (NumberFormatException e) {
239                         throw new JSONException(
240                                 String.format(
241                                         "Failed to parse long value: %s for key: %s", value, key));
242                     }
243                 } else {
244                     JSONArray jsonArray = jsonObject.getJSONArray(key);
245                     List<String> filterMapList = new ArrayList<>();
246                     for (int i = 0; i < jsonArray.length(); i++) {
247                         filterMapList.add(jsonArray.getString(i));
248                     }
249                     filterMap.put(key, FilterValue.ofStringList(filterMapList));
250                 }
251             }
252             mBuilding.mAttributionFilterMapWithLongValue = filterMap;
253             return this;
254         }
255 
256         /** Build the {@link FilterMap}. */
build()257         public FilterMap build() {
258             return mBuilding;
259         }
260     }
261 }
262