1 package net.avcompris.commons3.notifier.utils;
2
3 import static com.google.common.base.Preconditions.checkArgument;
4 import static com.google.common.base.Preconditions.checkNotNull;
5 import static net.avcompris.commons3.databeans.DataBeans.instantiate;
6 import static org.apache.commons.lang3.CharEncoding.UTF_8;
7 import static org.joda.time.DateTimeZone.UTC;
8
9 import java.io.UnsupportedEncodingException;
10
11
12
13 import javax.annotation.Nullable;
14
15 import org.joda.time.DateTime;
16 import org.joda.time.format.DateTimeFormatter;
17 import org.joda.time.format.ISODateTimeFormat;
18 import org.json.simple.JSONArray;
19 import org.json.simple.JSONObject;
20 import org.json.simple.parser.JSONParser;
21 import org.json.simple.parser.ParseException;
22
23 import net.avcompris.commons3.notifier.api.ErrorNotification;
24 import net.avcompris.commons3.notifier.api.Notification;
25 import net.avcompris.commons3.notifier.api.Notification.ActionType;
26
27 public abstract class NotificationUtils {
28
29 private static final DateTimeFormatter ISO_FORMATTER = ISODateTimeFormat.dateTime();
30
31 public static byte[] serialize(final Notification notification) {
32
33 final String json = serializeToJSON(notification);
34
35 try {
36
37 return json.getBytes(UTF_8);
38
39 } catch (final UnsupportedEncodingException e) {
40
41 throw new RuntimeException(e);
42 }
43 }
44
45 private static String serializeToJSON(final Notification notification) {
46
47 checkNotNull(notification, "notification");
48
49 final ActionType actionType = notification.getActionType();
50 final String username = notification.getUsername();
51
52 final StringBuilder sb = new StringBuilder("{");
53
54 sb.append("\"actionType\": ");
55
56 sb.append("\"").append(actionType.name()).append("\"");
57
58 sb.append(", \"username\": ");
59
60 if (username == null) {
61
62 sb.append("null");
63
64 } else {
65
66 sb.append("\"").append(username).append("\"");
67 }
68
69 return sb.append("}").toString();
70 }
71
72 public static Notification deserialize(
73 final Class<? extends ActionType> actionTypeClass,
74 final byte[] bytes) {
75
76 checkNotNull(bytes, "bytes");
77 checkNotNull(actionTypeClass, "actionTypeClass");
78
79 final String s;
80
81 try {
82
83 s = new String(bytes, UTF_8);
84
85 } catch (final UnsupportedEncodingException e) {
86
87 throw new RuntimeException(e);
88 }
89
90 final JSONObject json;
91
92 try {
93
94 json = (JSONObject) new JSONParser().parse(s);
95
96 } catch (final ParseException e) {
97
98 throw new RuntimeException("JSON: " + s, e);
99 }
100
101 final Object actionTypeAsString = json.get("actionType");
102 final Object username = json.get("username");
103
104 if (actionTypeAsString == null) {
105 throw new RuntimeException("actionType should not be null in JSON: " + s);
106 }
107
108 final ActionType actionType = extractActionType(actionTypeClass, actionTypeAsString.toString());
109
110 final Notification notification = instantiate(Notification.class)
111 .setActionType(actionType);
112
113 if (username != null) {
114
115 notification.setUsername(username.toString());
116 }
117
118 return notification;
119 }
120
121 private static ActionType extractActionType(final Class<? extends ActionType> actionTypeClass, final String name) {
122
123 checkNotNull(actionTypeClass, "actionTypeClass");
124 checkNotNull(name, "name");
125
126 checkArgument(actionTypeClass.isEnum(),
127 "actionTypeClass should be Enum: %s", actionTypeClass.getName());
128
129 for (final ActionType actionType : actionTypeClass.getEnumConstants()) {
130
131 if (name.contentEquals(actionType.name())) {
132
133 return actionType;
134 }
135 }
136
137 throw new RuntimeException("actionType in JSON cannot be parsed: " + name);
138 }
139
140 public static byte[] serialize(final Throwable throwable) {
141
142 final String json = serializeToJSON(throwable);
143
144 try {
145
146 return json.getBytes(UTF_8);
147
148 } catch (final UnsupportedEncodingException e) {
149
150 throw new RuntimeException(e);
151 }
152 }
153
154 private static String escape(final String s) {
155
156 checkNotNull(s, "s");
157
158 return s.replace("\\", "\\\\").replace("\"", "\\\"");
159 }
160
161 private static String serializeToJSON(final Throwable throwable) {
162
163 checkNotNull(throwable, "throwable");
164
165 final DateTime dateTime = new DateTime().withZone(UTC);
166
167 final Class<? extends Throwable> clazz = throwable.getClass();
168
169 @Nullable
170 final String message = throwable.getMessage();
171
172 @Nullable
173 final Throwable cause = throwable.getCause();
174
175 final StringBuilder sb = new StringBuilder("{");
176
177 sb.append("\"dateTime\": ");
178
179 sb.append("\"").append(dateTime.toString(ISO_FORMATTER)).append("\"");
180
181 sb.append(", \"className\": ");
182
183 sb.append("\"").append(clazz.getName()).append("\"");
184
185 sb.append(", \"message\": ");
186
187 appendQuotedStringOrNull(sb, message);
188
189 sb.append(", \"cause\": ");
190
191 if (cause == null) {
192
193 sb.append("null");
194
195 } else {
196
197 sb.append(serializeToJSON(cause));
198 }
199
200 sb.append(", \"stackTrace\": [");
201
202 boolean start = true;
203
204 for (final StackTraceElement ste : throwable.getStackTrace()) {
205
206 if (!start) {
207 sb.append(", ");
208 } else {
209 start = false;
210 }
211
212 sb.append("{");
213
214 sb.append("\"className\": ");
215
216 appendQuotedStringOrNull(sb, ste.getClassName());
217
218 sb.append(", \"methodName\": ");
219
220 appendQuotedStringOrNull(sb, ste.getMethodName());
221
222 sb.append(", \"fileName\": ");
223
224 appendQuotedStringOrNull(sb, ste.getFileName());
225
226 sb.append(", \"lineNumber\": ");
227
228 sb.append(ste.getLineNumber());
229
230 sb.append("}");
231 }
232
233 sb.append("]");
234
235 return sb.append("}").toString();
236 }
237
238 private static void appendQuotedStringOrNull(final StringBuilder sb, @Nullable final String s) {
239
240 if (s == null) {
241
242 sb.append("null");
243
244 } else {
245
246 sb.append("\"").append(escape(s)).append("\"");
247 }
248 }
249
250 public static ErrorNotification deserializeError(final byte[] bytes) {
251
252 checkNotNull(bytes, "bytes");
253
254 final String s;
255
256 try {
257
258 s = new String(bytes, UTF_8);
259
260 } catch (final UnsupportedEncodingException e) {
261
262 throw new RuntimeException(e);
263 }
264
265 final JSONObject json;
266
267 try {
268
269 json = (JSONObject) new JSONParser().parse(s);
270
271 } catch (final ParseException e) {
272
273 throw new RuntimeException("JSON: " + s, e);
274 }
275
276 return deserializeError(json);
277 }
278
279 private static ErrorNotification deserializeError(final JSONObject json) {
280
281 final DateTime dateTime = ISO_FORMATTER.parseDateTime(json.get("dateTime").toString());
282
283 final String className = json.get("className").toString();
284
285 @Nullable
286 final Object cause = json.get("cause");
287
288 final JSONArray stackTrace = (JSONArray) json.get("stackTrace");
289
290 final ErrorNotification notification = instantiate(ErrorNotification.class)
291 .setDateTime(dateTime)
292 .setClassName(className)
293 .setMessage(toStringOrNull(json.get("message")));
294
295 if (cause != null) {
296
297 notification.setCause(deserializeError((JSONObject) cause));
298 }
299
300 for (final Object item : stackTrace) {
301
302 final JSONObject ste = (JSONObject) item;
303
304 notification.addToStackTrace(instantiate(ErrorNotification.StackTraceElement.class)
305 .setClassName(toStringOrNull(ste.get("className")))
306 .setMethodName(toStringOrNull(ste.get("methodName")))
307 .setFileName(toStringOrNull(ste.get("fileName")))
308 .setLineNumber((int) (long) ste.get("lineNumber")));
309 }
310
311 return notification;
312 }
313
314 @Nullable
315 private static String toStringOrNull(@Nullable final Object o) {
316
317 return o == null ? null : o.toString();
318 }
319
320 public static ErrorNotification toErrorNotification(final Throwable throwable) {
321
322 checkNotNull(throwable, "throwable");
323
324 final DateTime dateTime = new DateTime().withZone(UTC);
325
326 final String className = throwable.getClass().getName();
327
328 @Nullable
329 final String message = throwable.getMessage();
330
331 @Nullable
332 final Throwable cause = throwable.getCause();
333
334 final ErrorNotification notification = instantiate(ErrorNotification.class)
335 .setDateTime(dateTime)
336 .setClassName(className)
337 .setMessage(message);
338
339 if (cause != null) {
340
341 notification.setCause(toErrorNotification(cause));
342 }
343
344 for (final StackTraceElement ste : throwable.getStackTrace()) {
345
346 notification.addToStackTrace(instantiate(ErrorNotification.StackTraceElement.class)
347 .setClassName(ste.getClassName())
348 .setMethodName(ste.getMethodName())
349 .setFileName(ste.getFileName())
350 .setLineNumber(ste.getLineNumber()));
351 }
352
353 return notification;
354 }
355 }