Compare commits
2 Commits
2fb4775ca7
...
ced9b0eaca
Author | SHA1 | Date | |
---|---|---|---|
ced9b0eaca | |||
4ec47b5e4b |
@ -107,6 +107,8 @@ public class Json {
|
|||||||
static {
|
static {
|
||||||
if (!hasGsonNativeRecordSupport())
|
if (!hasGsonNativeRecordSupport())
|
||||||
registerTypeAdapterFactory(RecordTypeAdapter.FACTORY);
|
registerTypeAdapterFactory(RecordTypeAdapter.FACTORY);
|
||||||
|
registerTypeAdapterFactory(StackTraceElementAdapter.FACTORY);
|
||||||
|
registerTypeAdapterFactory(ThrowableAdapter.FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
package fr.pandacube.lib.core.json;
|
||||||
|
|
||||||
|
import com.google.gson.JsonDeserializationContext;
|
||||||
|
import com.google.gson.JsonDeserializer;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
|
import com.google.gson.JsonSerializationContext;
|
||||||
|
import com.google.gson.JsonSerializer;
|
||||||
|
import com.google.gson.TypeAdapterFactory;
|
||||||
|
import com.google.gson.internal.bind.TreeTypeAdapter;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
/* package */ class StackTraceElementAdapter implements JsonSerializer<StackTraceElement>, JsonDeserializer<StackTraceElement> {
|
||||||
|
|
||||||
|
public static final TypeAdapterFactory FACTORY = TreeTypeAdapter.newTypeHierarchyFactory(StackTraceElement.class, new StackTraceElementAdapter());
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StackTraceElement deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||||
|
JsonObject obj = json.getAsJsonObject();
|
||||||
|
|
||||||
|
String classLoader = obj.has("classloader") && obj.get("classloader").isJsonPrimitive()
|
||||||
|
? obj.get("classloader").getAsString() : null;
|
||||||
|
String module = obj.has("module") && obj.get("module").isJsonPrimitive()
|
||||||
|
? obj.get("module").getAsString() : null;
|
||||||
|
String moduleVersion = obj.has("moduleversion") && obj.get("moduleversion").isJsonPrimitive()
|
||||||
|
? obj.get("moduleversion").getAsString() : null;
|
||||||
|
String clazz = obj.has("class") && obj.get("class").isJsonPrimitive()
|
||||||
|
? obj.get("class").getAsString() : null;
|
||||||
|
if (clazz == null) {
|
||||||
|
throw new JsonParseException("Missing 'class' entry");
|
||||||
|
}
|
||||||
|
String method = obj.has("method") && obj.get("method").isJsonPrimitive()
|
||||||
|
? obj.get("method").getAsString() : null;
|
||||||
|
if (method == null) {
|
||||||
|
throw new JsonParseException("Missing 'method' entry");
|
||||||
|
}
|
||||||
|
String file = obj.has("file") && obj.get("file").isJsonPrimitive()
|
||||||
|
? obj.get("file").getAsString() : null;
|
||||||
|
int line = obj.has("line") && obj.get("line").isJsonPrimitive()
|
||||||
|
? obj.get("line").getAsInt() : -1;
|
||||||
|
|
||||||
|
return new StackTraceElement(classLoader, module, moduleVersion, clazz, method, file, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(StackTraceElement src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
|
JsonObject obj = new JsonObject();
|
||||||
|
obj.addProperty("class", src.getClassName());
|
||||||
|
obj.addProperty("method", src.getMethodName());
|
||||||
|
obj.addProperty("line", src.getLineNumber());
|
||||||
|
if (src.getClassLoaderName() != null)
|
||||||
|
obj.addProperty("classloader", src.getClassLoaderName());
|
||||||
|
if (src.getModuleName() != null)
|
||||||
|
obj.addProperty("module", src.getModuleName());
|
||||||
|
if (src.getModuleVersion() != null)
|
||||||
|
obj.addProperty("moduleversion", src.getModuleVersion());
|
||||||
|
if (src.getFileName() != null)
|
||||||
|
obj.addProperty("file", src.getFileName());
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,197 @@
|
|||||||
|
package fr.pandacube.lib.core.json;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonDeserializationContext;
|
||||||
|
import com.google.gson.JsonDeserializer;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonIOException;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
|
import com.google.gson.JsonSerializationContext;
|
||||||
|
import com.google.gson.JsonSerializer;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
import com.google.gson.TypeAdapterFactory;
|
||||||
|
import com.google.gson.internal.bind.TreeTypeAdapter;
|
||||||
|
import com.google.gson.stream.MalformedJsonException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/* package */ class ThrowableAdapter implements JsonSerializer<Throwable>, JsonDeserializer<Throwable> {
|
||||||
|
|
||||||
|
public static final TypeAdapterFactory FACTORY = TreeTypeAdapter.newTypeHierarchyFactory(Throwable.class, new ThrowableAdapter());
|
||||||
|
|
||||||
|
|
||||||
|
private static final Map<Class<? extends Throwable>, ThrowableSubAdapter<?>> subAdapters = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
|
public static <T extends Throwable> void registerSubAdapter(Class<T> clazz, ThrowableSubAdapter<T> subAdapter) {
|
||||||
|
subAdapters.put(clazz, subAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Throwable deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||||
|
|
||||||
|
JsonObject obj = json.getAsJsonObject();
|
||||||
|
String message = obj.has("message") && !obj.get("message").isJsonNull()
|
||||||
|
? obj.get("message").getAsString() : null;
|
||||||
|
Throwable cause = obj.has("cause") && !obj.get("cause").isJsonNull()
|
||||||
|
? context.deserialize(obj.get("cause"), Throwable.class) : null;
|
||||||
|
|
||||||
|
// handle types
|
||||||
|
Throwable t = null;
|
||||||
|
if (obj.has("types") && obj.get("types").isJsonArray()) {
|
||||||
|
for (JsonElement clNameEl : obj.getAsJsonArray("types")) {
|
||||||
|
String clName = clNameEl.getAsString();
|
||||||
|
try {
|
||||||
|
Class<?> cl = Class.forName(clName);
|
||||||
|
synchronized (subAdapters) {
|
||||||
|
if (subAdapters.containsKey(cl)) {
|
||||||
|
t = subAdapters.get(cl).constructor.apply(message, cause);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ReflectiveOperationException ignore) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (t == null) {
|
||||||
|
t = new Throwable(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle suppressed
|
||||||
|
JsonArray suppressed = obj.has("suppressed") && !obj.get("suppressed").isJsonNull()
|
||||||
|
? obj.get("suppressed").getAsJsonArray() : null;
|
||||||
|
if (suppressed != null) {
|
||||||
|
for (JsonElement jsonel : suppressed) {
|
||||||
|
t.addSuppressed(context.deserialize(jsonel, Throwable.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle stacktrace
|
||||||
|
JsonArray stacktrace = obj.has("stacktrace") && !obj.get("stacktrace").isJsonNull()
|
||||||
|
? obj.get("stacktrace").getAsJsonArray() : null;
|
||||||
|
if (stacktrace != null) {
|
||||||
|
List<StackTraceElement> els = new ArrayList<>();
|
||||||
|
for (JsonElement jsonel : stacktrace) {
|
||||||
|
els.add(context.deserialize(jsonel, StackTraceElement.class));
|
||||||
|
}
|
||||||
|
t.setStackTrace(els.toArray(new StackTraceElement[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(Throwable src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
|
JsonObject json = new JsonObject();
|
||||||
|
|
||||||
|
// toString for easy json reading (not used for deserialization)
|
||||||
|
json.addProperty("tostring", src.toString());
|
||||||
|
|
||||||
|
// handle types
|
||||||
|
JsonArray types = new JsonArray();
|
||||||
|
Class<?> cl = src.getClass();
|
||||||
|
while (cl != Throwable.class) {
|
||||||
|
if (cl.getCanonicalName() != null)
|
||||||
|
types.add(cl.getCanonicalName());
|
||||||
|
cl = cl.getSuperclass();
|
||||||
|
}
|
||||||
|
json.add("types", types);
|
||||||
|
|
||||||
|
// general data
|
||||||
|
if (src.getMessage() != null)
|
||||||
|
json.addProperty("message", src.getMessage());
|
||||||
|
if (src.getCause() != null)
|
||||||
|
json.add("cause", context.serialize(src.getCause()));
|
||||||
|
|
||||||
|
// handle suppressed
|
||||||
|
JsonArray suppressed = new JsonArray();
|
||||||
|
for (Throwable supp : src.getSuppressed()) {
|
||||||
|
suppressed.add(context.serialize(supp));
|
||||||
|
}
|
||||||
|
json.add("suppressed", suppressed);
|
||||||
|
|
||||||
|
// handle stacktrace
|
||||||
|
JsonArray stacktrace = new JsonArray();
|
||||||
|
for (StackTraceElement stackTraceElement : src.getStackTrace()) {
|
||||||
|
stacktrace.add(context.serialize(stackTraceElement));
|
||||||
|
}
|
||||||
|
json.add("stacktrace", stacktrace);
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static class ThrowableSubAdapter<T extends Throwable> {
|
||||||
|
public final BiFunction<String, Throwable, T> constructor;
|
||||||
|
|
||||||
|
protected ThrowableSubAdapter(BiFunction<String, Throwable, T> constructor) {
|
||||||
|
this.constructor = constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends Throwable> BiFunction<String, Throwable, T> messageOnly(Function<String, T> constructorWithMessage) {
|
||||||
|
return (m, t) -> {
|
||||||
|
T inst = constructorWithMessage.apply(m);
|
||||||
|
try {
|
||||||
|
inst.initCause(t);
|
||||||
|
} catch (Exception ignore) { }
|
||||||
|
return inst;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static {
|
||||||
|
// java.lang
|
||||||
|
registerSubAdapter(Throwable.class, new ThrowableSubAdapter<>(Throwable::new));
|
||||||
|
registerSubAdapter(Error.class, new ThrowableSubAdapter<>(Error::new));
|
||||||
|
registerSubAdapter(OutOfMemoryError.class, new ThrowableSubAdapter<>(ThrowableSubAdapter.messageOnly(OutOfMemoryError::new)));
|
||||||
|
registerSubAdapter(StackOverflowError.class, new ThrowableSubAdapter<>(ThrowableSubAdapter.messageOnly(StackOverflowError::new)));
|
||||||
|
registerSubAdapter(Exception.class, new ThrowableSubAdapter<>(Exception::new));
|
||||||
|
registerSubAdapter(RuntimeException.class, new ThrowableSubAdapter<>(RuntimeException::new));
|
||||||
|
registerSubAdapter(NullPointerException.class, new ThrowableSubAdapter<>(ThrowableSubAdapter.messageOnly(NullPointerException::new)));
|
||||||
|
registerSubAdapter(IndexOutOfBoundsException.class, new ThrowableSubAdapter<>(ThrowableSubAdapter.messageOnly(IndexOutOfBoundsException::new)));
|
||||||
|
registerSubAdapter(IllegalArgumentException.class, new ThrowableSubAdapter<>(IllegalArgumentException::new));
|
||||||
|
registerSubAdapter(IllegalStateException.class, new ThrowableSubAdapter<>(IllegalStateException::new));
|
||||||
|
registerSubAdapter(SecurityException.class, new ThrowableSubAdapter<>(SecurityException::new));
|
||||||
|
registerSubAdapter(ReflectiveOperationException.class, new ThrowableSubAdapter<>(ReflectiveOperationException::new));
|
||||||
|
registerSubAdapter(UnsupportedOperationException.class, new ThrowableSubAdapter<>(UnsupportedOperationException::new));
|
||||||
|
registerSubAdapter(InterruptedException.class, new ThrowableSubAdapter<>(ThrowableSubAdapter.messageOnly(InterruptedException::new)));
|
||||||
|
|
||||||
|
// java.io
|
||||||
|
registerSubAdapter(IOException.class, new ThrowableSubAdapter<>(IOException::new));
|
||||||
|
|
||||||
|
// java.sql
|
||||||
|
registerSubAdapter(SQLException.class, new ThrowableSubAdapter<>(SQLException::new));
|
||||||
|
|
||||||
|
// java.util
|
||||||
|
registerSubAdapter(NoSuchElementException.class, new ThrowableSubAdapter<>(NoSuchElementException::new));
|
||||||
|
|
||||||
|
// java.util.concurrent
|
||||||
|
registerSubAdapter(CancellationException.class, new ThrowableSubAdapter<>(ThrowableSubAdapter.messageOnly(CancellationException::new)));
|
||||||
|
registerSubAdapter(ExecutionException.class, new ThrowableSubAdapter<>(ExecutionException::new));
|
||||||
|
registerSubAdapter(CompletionException.class, new ThrowableSubAdapter<>(CompletionException::new));
|
||||||
|
|
||||||
|
// gson
|
||||||
|
registerSubAdapter(JsonIOException.class, new ThrowableSubAdapter<>(JsonIOException::new));
|
||||||
|
registerSubAdapter(JsonParseException.class, new ThrowableSubAdapter<>(JsonParseException::new));
|
||||||
|
registerSubAdapter(JsonSyntaxException.class, new ThrowableSubAdapter<>(JsonSyntaxException::new));
|
||||||
|
registerSubAdapter(MalformedJsonException.class, new ThrowableSubAdapter<>(MalformedJsonException::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package fr.pandacube.lib.ws;
|
package fr.pandacube.lib.ws;
|
||||||
|
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
import fr.pandacube.lib.util.Log;
|
import fr.pandacube.lib.util.Log;
|
||||||
import fr.pandacube.lib.util.ThrowableUtil.RunnableException;
|
import fr.pandacube.lib.util.ThrowableUtil.RunnableException;
|
||||||
import fr.pandacube.lib.ws.payloads.ErrorPayload;
|
import fr.pandacube.lib.ws.payloads.ErrorPayload;
|
||||||
@ -104,9 +105,10 @@ public interface AbstractWS {
|
|||||||
* @param obj the object to Jsonify.
|
* @param obj the object to Jsonify.
|
||||||
* @param serializeNulls if null propreties must be included in the json object.
|
* @param serializeNulls if null propreties must be included in the json object.
|
||||||
* @throws IOException if an IO error occurs when sending the data.
|
* @throws IOException if an IO error occurs when sending the data.
|
||||||
|
* @throws JsonParseException if the json is invalid.
|
||||||
* @see PayloadRegistry#arbitraryToString(String, Object, boolean)
|
* @see PayloadRegistry#arbitraryToString(String, Object, boolean)
|
||||||
*/
|
*/
|
||||||
default void sendAsJson(String type, Object obj, boolean serializeNulls) throws IOException {
|
default void sendAsJson(String type, Object obj, boolean serializeNulls) throws IOException, JsonParseException {
|
||||||
sendString(PayloadRegistry.arbitraryToString(type, obj, serializeNulls));
|
sendString(PayloadRegistry.arbitraryToString(type, obj, serializeNulls));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,9 +129,10 @@ public interface AbstractWS {
|
|||||||
* Send the provided {@link Payload} to the remote endpoint.
|
* Send the provided {@link Payload} to the remote endpoint.
|
||||||
* @param payload the {@link Payload} to send.
|
* @param payload the {@link Payload} to send.
|
||||||
* @throws IOException if an IO error occurs when sending the data.
|
* @throws IOException if an IO error occurs when sending the data.
|
||||||
|
* @throws JsonParseException if the json is invalid.
|
||||||
* @see PayloadRegistry#toString(Payload)
|
* @see PayloadRegistry#toString(Payload)
|
||||||
*/
|
*/
|
||||||
default void sendAsJson(Payload payload) throws IOException {
|
default void sendAsJson(Payload payload) throws IOException, JsonParseException {
|
||||||
sendString(PayloadRegistry.toString(payload));
|
sendString(PayloadRegistry.toString(payload));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +211,7 @@ public interface AbstractWS {
|
|||||||
try {
|
try {
|
||||||
run.run();
|
run.run();
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException|JsonParseException e) {
|
||||||
logError(errorMessage, e);
|
logError(errorMessage, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -68,8 +68,9 @@ public class PayloadRegistry {
|
|||||||
* Serialize the provided {@link Payload}.
|
* Serialize the provided {@link Payload}.
|
||||||
* @param p the {@link Payload} to serialize. Must be of a registered type.
|
* @param p the {@link Payload} to serialize. Must be of a registered type.
|
||||||
* @return the serialized data.
|
* @return the serialized data.
|
||||||
|
* @throws JsonParseException if the json is invalid.
|
||||||
*/
|
*/
|
||||||
public static String toString(Payload p) {
|
public static String toString(Payload p) throws JsonParseException {
|
||||||
String type = payloadClasses.getKey(p.getClass());
|
String type = payloadClasses.getKey(p.getClass());
|
||||||
if (type == null)
|
if (type == null)
|
||||||
throw new IllegalArgumentException(p.getClass() + " is not a registered payload type.");
|
throw new IllegalArgumentException(p.getClass() + " is not a registered payload type.");
|
||||||
@ -84,8 +85,9 @@ public class PayloadRegistry {
|
|||||||
* @param obj the object to Jsonify
|
* @param obj the object to Jsonify
|
||||||
* @param serializeNulls if null propreties must be included in the json object.
|
* @param serializeNulls if null propreties must be included in the json object.
|
||||||
* @return the String to send through the websocket
|
* @return the String to send through the websocket
|
||||||
|
* @throws JsonParseException if the json is invalid.
|
||||||
*/
|
*/
|
||||||
public static String arbitraryToString(String type, Object obj, boolean serializeNulls) {
|
public static String arbitraryToString(String type, Object obj, boolean serializeNulls) throws JsonParseException {
|
||||||
return validateType(type) + PAYLOAD_TYPE_SEPARATOR + (serializeNulls ? Json.gsonSerializeNulls : Json.gson).toJson(obj);
|
return validateType(type) + PAYLOAD_TYPE_SEPARATOR + (serializeNulls ? Json.gsonSerializeNulls : Json.gson).toJson(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package fr.pandacube.lib.ws.payloads;
|
package fr.pandacube.lib.ws.payloads;
|
||||||
|
|
||||||
import fr.pandacube.lib.util.ThrowableUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error message payload.
|
* Error message payload.
|
||||||
*/
|
*/
|
||||||
@ -13,7 +11,7 @@ public class ErrorPayload extends Payload {
|
|||||||
/**
|
/**
|
||||||
* The error Throwable, may be null.
|
* The error Throwable, may be null.
|
||||||
*/
|
*/
|
||||||
public String exception;
|
public Throwable throwable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize an error payload with a message but not throwable.
|
* Initialize an error payload with a message but not throwable.
|
||||||
@ -30,7 +28,7 @@ public class ErrorPayload extends Payload {
|
|||||||
*/
|
*/
|
||||||
public ErrorPayload(String message, Throwable throwable) {
|
public ErrorPayload(String message, Throwable throwable) {
|
||||||
this.message = message;
|
this.message = message;
|
||||||
this.exception = ThrowableUtil.stacktraceToString(throwable);
|
this.throwable = throwable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
|
Loading…
Reference in New Issue
Block a user