JAL-4001 most of google analytics client
[jalview.git] / src / jalview / analytics / GoogleAnalytics4.java
1 package jalview.analytics;
2
3 import java.io.IOException;
4 import java.io.OutputStream;
5 import java.net.HttpURLConnection;
6 import java.net.MalformedURLException;
7 import java.net.URL;
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;
14 import java.util.Map;
15 import java.util.Random;
16 import java.util.UUID;
17
18 import jalview.bin.Console;
19
20 public class GoogleAnalytics4
21 {
22   private static final String JALVIEW_ID = "Jalview Desktop";
23
24   private static final String SESSION_ID = new Random().toString();
25
26   private static final String MEASUREMENT_ID = "G-6TMPHMXEQ0";
27
28   private static final String API_SECRET = "Qb9NSbqkRDqizG6j2BBJ2g";
29
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();
33
34   private static final String BASE_URL = "https://www.google-analytics.com/mp/collect";
35
36   private List<Map.Entry<String, String>> queryStringValues;
37
38   private List<Map.Entry<String, Object>> jsonValues;
39
40   private List<Map.Entry<String, String>> cookieValues;
41
42   public GoogleAnalytics4()
43   {
44     this.reset();
45   }
46
47   public void sendAnalytics()
48   {
49     StringBuilder sb = new StringBuilder();
50     sb.append(BASE_URL);
51     sb.append('?');
52     sb.append(buildQueryString());
53     try
54     {
55       URL url = new URL(sb.toString());
56       URLConnection urlConnection = url.openConnection();
57       HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;
58       httpURLConnection.setRequestMethod("POST");
59       httpURLConnection.setDoOutput(true);
60
61       byte[] jsonBytes = buildJson().getBytes(StandardCharsets.UTF_8);
62       int jsonLength = jsonBytes.length;
63
64       httpURLConnection.setFixedLengthStreamingMode(jsonLength);
65       httpURLConnection.setRequestProperty("Content-Type",
66               "application/json; charset=UTF-8");
67       httpURLConnection.connect();
68       try (OutputStream os = httpURLConnection.getOutputStream())
69       {
70         os.write(jsonBytes);
71       }
72
73     } catch (MalformedURLException e)
74     {
75       Console.debug(
76               "Somehow the GoogleAnalytics4 BASE_URL and queryString is malformed.",
77               e);
78       return;
79     } catch (IOException e)
80     {
81       Console.debug("Connection to GoogleAnalytics4 BASE_URL '" + BASE_URL
82               + "' failed.", e);
83     } catch (ClassCastException e)
84     {
85       Console.debug(
86               "Couldn't cast URLConnection to HttpURLConnection in GoogleAnalytics4.",
87               e);
88     }
89
90   }
91
92   private void addToJson(String key, List<Object> values)
93   {
94     jsonValues.add(jsonEntry(key, values));
95   }
96
97   private void addToJson(String key, String value)
98   {
99     jsonValues.add(jsonEntry(key, value));
100   }
101
102   private void addToJson(String key, int value)
103   {
104     jsonValues.add(jsonEntry(key, Integer.valueOf(value)));
105   }
106
107   private void addToJson(String key, boolean value)
108   {
109     jsonValues.add(jsonEntry(key, Boolean.valueOf(value)));
110   }
111
112   private void addQueryStringValue(String key, String value)
113   {
114     queryStringValues.add(qsEntry(key, value));
115   }
116
117   private void addCookieValue(String key, String value)
118   {
119     cookieValues.add(qsEntry(key, value));
120   }
121
122   public void reset()
123   {
124     jsonValues = new ArrayList<>();
125     queryStringValues = new ArrayList<>();
126     cookieValues = new ArrayList<>();
127   }
128
129   private String buildQueryString()
130   {
131     StringBuilder sb = new StringBuilder();
132     for (Map.Entry<String, String> entry : queryStringValues)
133     {
134       if (sb.length() > 0)
135       {
136         sb.append('&');
137       }
138       sb.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8));
139       sb.append('=');
140       sb.append(
141               URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
142     }
143     return sb.toString();
144   }
145
146   private void buildCookieHeaders()
147   {
148     // TODO not needed yet
149   }
150
151   private String buildJson()
152   {
153     StringBuilder sb = new StringBuilder();
154     int indent = 0;
155     sb.append("{");
156     for (Map.Entry<String, Object> entry : jsonValues)
157     {
158
159     }
160     sb.append("}");
161     return sb.toString();
162   }
163
164   private void addJsonObject(StringBuilder sb, int indent,
165           Map.Entry<String, Object> entry)
166   {
167     String key = entry.getKey();
168     Object value = entry.getValue();
169     indent(sb, indent);
170     sb.append('"').append(key).append('"');
171     sb.append(": ");
172     if (List.class.equals(value.getClass()))
173     {
174       sb.append('\n');
175     }
176     addJsonValue(sb, indent, value);
177     sb.append(",\n");
178   }
179
180   private void addJsonValue(StringBuilder sb, int indent, Object value)
181   {
182     if (value == null)
183     {
184       return;
185     }
186     try
187     {
188       Class<? extends Object> c = value.getClass();
189       if (Map.Entry.class.equals(c))
190       {
191         Map.Entry<String, Object> object = (Map.Entry<String, Object>) value;
192         addJsonObject(sb, indent + 1, object);
193       }
194       else if (List.class.equals(c))
195       {
196         indent(sb, indent);
197         sb.append("[\n");
198         for (Object v : (List<Object>) value)
199         {
200           indent(sb, indent + 1);
201           addJsonValue(sb, indent, v);
202           sb.append(",\n");
203         }
204         indent(sb, indent);
205         sb.append("]");
206       }
207       else if (String.class.equals(c))
208       {
209         sb.append('"');
210         sb.append((String) value);
211         sb.append('"');
212       }
213       else if (Integer.class.equals(c))
214       {
215         sb.append(((Integer) value).toString());
216       }
217       else if (Boolean.class.equals(c))
218       {
219         sb.append(((Boolean) value).toString());
220       }
221     } catch (ClassCastException e)
222     {
223       Console.debug(
224               "Could not deal with type of jsonObject " + value.toString(),
225               e);
226     }
227   }
228
229   private void indent(StringBuilder sb, int indent)
230   {
231     sb.append("  ".repeat(indent));
232   }
233
234   private static Map.Entry<String, Object> jsonEntry(String s, Object o)
235   {
236     return new AbstractMap.SimpleEntry<String, Object>(s, o);
237   }
238
239   private static Map.Entry<String, String> qsEntry(String s, String v)
240   {
241     return new AbstractMap.SimpleEntry<String, String>(s, v);
242   }
243 }