1 package jalview.analytics;
3 import java.io.IOException;
4 import java.io.OutputStream;
5 import java.net.HttpURLConnection;
6 import java.net.MalformedURLException;
8 import java.net.URLConnection;
9 import java.net.URLEncoder;
10 import java.nio.charset.StandardCharsets;
11 import java.util.AbstractMap;
12 import java.util.ArrayList;
13 import java.util.List;
15 import java.util.Random;
16 import java.util.UUID;
18 import jalview.bin.Console;
20 public class GoogleAnalytics4
22 private static final String JALVIEW_ID = "Jalview Desktop";
24 private static final String SESSION_ID = new Random().toString();
26 private static final String MEASUREMENT_ID = "G-6TMPHMXEQ0";
28 private static final String API_SECRET = "Qb9NSbqkRDqizG6j2BBJ2g";
30 // This will generate a different CLIENT_ID each time the application is
31 // launched. Do we want to store it in .jalview_properties?
32 private static final String CLIENT_ID = UUID.randomUUID().toString();
34 private static final String BASE_URL = "https://www.google-analytics.com/mp/collect";
36 private List<Map.Entry<String, String>> queryStringValues;
38 private List<Map.Entry<String, Object>> jsonObject;
40 private List<Event> events;
42 private List<Map.Entry<String, String>> cookieValues;
44 private static boolean ENABLED = false;
46 public GoogleAnalytics4()
51 private static void setEnabled(boolean b)
56 public void sendAnalytics()
61 public void sendAnalytics(String path)
65 Console.debug("GoogleAnalytics4 not enabled.");
70 addEvent("page_event", path);
72 addQueryStringValue("measurement_id", MEASUREMENT_ID);
73 addQueryStringValue("api_secret", API_SECRET);
74 addJsonValue("client_id", CLIENT_ID);
75 addJsonValues("events", Event.toObjectList(events));
76 // addJsonValue("events", )
77 StringBuilder urlSb = new StringBuilder();
78 urlSb.append(BASE_URL);
80 urlSb.append(buildQueryString());
83 URL url = new URL(urlSb.toString());
84 URLConnection urlConnection = url.openConnection();
85 HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;
86 httpURLConnection.setRequestMethod("POST");
87 httpURLConnection.setDoOutput(true);
89 String jsonString = buildJson();
91 Console.debug("##### HTTP Request is: '" + urlSb.toString() + "'");
92 Console.debug("##### POSTed JSON is:\n" + jsonString);
94 byte[] jsonBytes = jsonString.getBytes(StandardCharsets.UTF_8);
95 int jsonLength = jsonBytes.length;
97 httpURLConnection.setFixedLengthStreamingMode(jsonLength);
98 httpURLConnection.setRequestProperty("Content-Type",
99 "application/json; charset=UTF-8");
100 httpURLConnection.connect();
101 try (OutputStream os = httpURLConnection.getOutputStream())
105 int responseCode = httpURLConnection.getResponseCode();
106 String responseMessage = httpURLConnection.getResponseMessage();
107 if (responseCode < 200 || responseCode > 299)
109 Console.warn("GoogleAnalytics4 connection failed: '" + responseCode
110 + " " + responseMessage + "'");
114 Console.debug("GoogleAnalytics4 connection succeeded: '"
115 + responseCode + " " + responseMessage + "'");
117 } catch (MalformedURLException e)
120 "Somehow the GoogleAnalytics4 BASE_URL and queryString is malformed.",
123 } catch (IOException e)
125 Console.debug("Connection to GoogleAnalytics4 BASE_URL '" + BASE_URL
127 } catch (ClassCastException e)
130 "Couldn't cast URLConnection to HttpURLConnection in GoogleAnalytics4.",
135 public void addEvent(String name, String... paramsStrings)
137 if (paramsStrings.length % 2 != 0)
140 "Cannot addEvent with odd number of paramsStrings. Ignoring.");
143 Event event = new Event(name);
144 for (int i = 0; i < paramsStrings.length - 1; i += 2)
146 String key = paramsStrings[i];
147 String value = paramsStrings[i + 1];
148 event.addParam(key, value);
153 private void addJsonObject(String key,
154 List<Map.Entry<String, Object>> object)
156 jsonObject.add(objectEntry(key, object));
159 private void addJsonValues(String key, List<Object> values)
161 jsonObject.add(objectEntry(key, values));
164 private void addJsonValue(String key, String value)
166 jsonObject.add(objectEntry(key, value));
169 private void addJsonValue(String key, int value)
171 jsonObject.add(objectEntry(key, Integer.valueOf(value)));
174 private void addJsonValue(String key, boolean value)
176 jsonObject.add(objectEntry(key, Boolean.valueOf(value)));
179 private void addQueryStringValue(String key, String value)
181 queryStringValues.add(stringEntry(key, value));
184 private void addCookieValue(String key, String value)
186 cookieValues.add(stringEntry(key, value));
191 jsonObject = new ArrayList<>();
192 events = new ArrayList<Event>();
193 queryStringValues = new ArrayList<>();
194 cookieValues = new ArrayList<>();
197 private String buildQueryString()
199 StringBuilder sb = new StringBuilder();
200 for (Map.Entry<String, String> entry : queryStringValues)
206 sb.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8));
209 URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
211 return sb.toString();
214 private void buildCookieHeaders()
216 // TODO not needed yet
219 private String buildJson()
221 StringBuilder sb = new StringBuilder();
222 addJsonObject(sb, 0, jsonObject);
223 return sb.toString();
226 private void addJsonObject(StringBuilder sb, int indent,
227 List<Map.Entry<String, Object>> entries)
231 for (Map.Entry<String, Object> entry : entries)
233 String key = entry.getKey();
234 // TODO sensibly escape " characters in key
235 Object value = entry.getValue();
237 sb.append('"').append(key).append('"');
239 if (List.class.equals(value.getClass()))
243 addJsonValue(sb, indent + 1, value);
249 private void addJsonValue(StringBuilder sb, int indent, Object value)
257 Class<? extends Object> c = value.getClass();
258 if (Map.Entry.class.equals(c))
260 Map.Entry<String, Object> entry = (Map.Entry<String, Object>) value;
261 List<Map.Entry<String, Object>> object = new ArrayList<>();
263 addJsonObject(sb, indent + 1, object);
265 else if (List.class.equals(c))
267 // list of Map.Entries or list of values?
268 List<Object> valueList = (List<Object>) value;
269 if (valueList.size() > 0
270 && Map.Entry.class.equals(valueList.get(0).getClass()))
274 List<Map.Entry<String, Object>> entryList = (List<Map.Entry<String, Object>>) value;
275 addJsonObject(sb, indent + 1, entryList);
282 for (Object v : valueList)
284 indent(sb, indent + 1);
285 addJsonValue(sb, indent + 1, v);
292 else if (String.class.equals(c))
295 sb.append((String) value);
298 else if (Integer.class.equals(c))
300 sb.append(((Integer) value).toString());
302 else if (Boolean.class.equals(c))
304 sb.append(((Boolean) value).toString());
306 } catch (ClassCastException e)
309 "Could not deal with type of jsonObject " + value.toString(),
314 private void indent(StringBuilder sb, int indent)
316 sb.append(" ".repeat(indent));
319 protected static Map.Entry<String, Object> objectEntry(String s, Object o)
321 return new AbstractMap.SimpleEntry<String, Object>(s, o);
324 protected static Map.Entry<String, String> stringEntry(String s, String v)
326 return new AbstractMap.SimpleEntry<String, String>(s, v);
334 private List<Map.Entry<String, String>> params;
337 public Event(String name, Map.Entry<String, String>... paramEntries)
340 this.params = new ArrayList<Map.Entry<String, String>>();
341 for (Map.Entry<String, String> paramEntry : paramEntries)
343 if (paramEntry == null)
347 params.add(paramEntry);
351 public void addParam(String param, String value)
353 params.add(GoogleAnalytics4.stringEntry(param, value));
356 protected List<Map.Entry<String, Object>> toObject()
358 List<Map.Entry<String, Object>> object = new ArrayList<>();
359 object.add(GoogleAnalytics4.objectEntry("name", (Object) name));
360 if (params.size() > 0)
362 object.add(GoogleAnalytics4.objectEntry("params", (Object) params));
367 protected static List<Object> toObjectList(List<Event> events)
369 List<Object> eventObjectList = new ArrayList<>();
370 for (Event event : events)
372 eventObjectList.add((Object) event.toObject());
374 return eventObjectList;