Skip to content

Commit

Permalink
Use configured gson instance for toString
Browse files Browse the repository at this point in the history
This is important when users have registered custom type adapters

Fixes eclipse-lsp4j#768
  • Loading branch information
henryju committed Nov 7, 2023
1 parent 3c98376 commit 66ecdab
Show file tree
Hide file tree
Showing 14 changed files with 233 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ protected MessageJsonHandler createJsonHandler() {
@Override
protected RemoteEndpoint createRemoteEndpoint(MessageJsonHandler jsonHandler) {
MessageConsumer outgoingMessageStream = new StreamMessageConsumer(output, jsonHandler);
outgoingMessageStream = wrapMessageConsumer(outgoingMessageStream);
outgoingMessageStream = wrapMessageConsumer(outgoingMessageStream, jsonHandler);
Endpoint localEndpoint = ServiceEndpoints.toEndpoint(localServices);
RemoteEndpoint remoteEndpoint;
if (exceptionHandler == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ public static class Builder<T> {
protected boolean validateMessages;
protected Consumer<GsonBuilder> configureGson;
protected ClassLoader classLoader;
protected MessageTracer messageTracer;
protected PrintWriter messageTracer;

public Builder<T> setLocalService(Object localService) {
this.localServices = Collections.singletonList(localService);
Expand Down Expand Up @@ -294,7 +294,7 @@ public Builder<T> validateMessages(boolean validate) {

public Builder<T> traceMessages(PrintWriter tracer) {
if (tracer != null) {
this.messageTracer = new MessageTracer(tracer);
this.messageTracer = tracer;
}
return this;
}
Expand Down Expand Up @@ -322,7 +322,7 @@ public Launcher<T> create() {

// Create the message processor
StreamMessageProducer reader = new StreamMessageProducer(input, jsonHandler, remoteEndpoint);
MessageConsumer messageConsumer = wrapMessageConsumer(remoteEndpoint);
MessageConsumer messageConsumer = wrapMessageConsumer(remoteEndpoint, jsonHandler);
ConcurrentMessageProcessor msgProcessor = createMessageProcessor(reader, messageConsumer, remoteProxy);
ExecutorService execService = executorService != null ? executorService : Executors.newCachedThreadPool();
return createLauncher(execService, remoteProxy, remoteEndpoint, msgProcessor);
Expand All @@ -344,7 +344,7 @@ protected MessageJsonHandler createJsonHandler() {
*/
protected RemoteEndpoint createRemoteEndpoint(MessageJsonHandler jsonHandler) {
MessageConsumer outgoingMessageStream = new StreamMessageConsumer(output, jsonHandler);
outgoingMessageStream = wrapMessageConsumer(outgoingMessageStream);
outgoingMessageStream = wrapMessageConsumer(outgoingMessageStream, jsonHandler);
Endpoint localEndpoint = ServiceEndpoints.toEndpoint(localServices);
RemoteEndpoint remoteEndpoint;
if (exceptionHandler == null)
Expand Down Expand Up @@ -380,10 +380,10 @@ protected Launcher<T> createLauncher(ExecutorService execService, T remoteProxy,
return new StandardLauncher<T>(execService, remoteProxy, remoteEndpoint, msgProcessor);
}

protected MessageConsumer wrapMessageConsumer(MessageConsumer consumer) {
protected MessageConsumer wrapMessageConsumer(MessageConsumer consumer, MessageJsonHandler jsonHandler) {
MessageConsumer result = consumer;
if (messageTracer != null) {
result = messageTracer.apply(consumer);
result = new MessageTracer(messageTracer, jsonHandler).apply(consumer);
}
if (validateMessages) {
result = new ReflectiveMessageValidator(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,26 @@
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler;

/**
* Wraps a {@link MessageConsumer} with one that logs in a way that the LSP Inspector can parse. *
* https://microsoft.github.io/language-server-protocol/inspector/
*/
public class MessageTracer implements Function<MessageConsumer, MessageConsumer> {
private final PrintWriter printWriter;
private final MessageJsonHandler jsonHandler;
private final Map<String, RequestMetadata> sentRequests = new HashMap<>();
private final Map<String, RequestMetadata> receivedRequests = new HashMap<>();

MessageTracer(PrintWriter printWriter) {
MessageTracer(PrintWriter printWriter, MessageJsonHandler jsonHandler) {
this.printWriter = Objects.requireNonNull(printWriter);
this.jsonHandler = Objects.requireNonNull(jsonHandler);
}

@Override
public MessageConsumer apply(MessageConsumer messageConsumer) {
return new TracingMessageConsumer(
messageConsumer, sentRequests, receivedRequests, printWriter, Clock.systemDefaultZone());
messageConsumer, sentRequests, receivedRequests, printWriter, jsonHandler, Clock.systemDefaultZone());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class TracingMessageConsumer implements MessageConsumer {
private final Map<String, RequestMetadata> sentRequests;
private final Map<String, RequestMetadata> receivedRequests;
private final PrintWriter printWriter;
private final MessageJsonHandler jsonHandler;
private final Clock clock;
private final DateTimeFormatter dateTimeFormatter;

Expand All @@ -55,8 +56,9 @@ public TracingMessageConsumer(
Map<String, RequestMetadata> sentRequests,
Map<String, RequestMetadata> receivedRequests,
PrintWriter printWriter,
MessageJsonHandler jsonHandler,
Clock clock) {
this(messageConsumer, sentRequests, receivedRequests, printWriter, clock, null);
this(messageConsumer, sentRequests, receivedRequests, printWriter, jsonHandler, clock, null);
}

/**
Expand All @@ -72,12 +74,14 @@ public TracingMessageConsumer(
Map<String, RequestMetadata> sentRequests,
Map<String, RequestMetadata> receivedRequests,
PrintWriter printWriter,
MessageJsonHandler jsonHandler,
Clock clock,
Locale locale) {
this.messageConsumer = Objects.requireNonNull(messageConsumer);
this.sentRequests = Objects.requireNonNull(sentRequests);
this.receivedRequests = Objects.requireNonNull(receivedRequests);
this.printWriter = Objects.requireNonNull(printWriter);
this.jsonHandler = jsonHandler;
this.clock = Objects.requireNonNull(clock);
if (locale == null) {
this.dateTimeFormatter = DateTimeFormatter.ofPattern("KK:mm:ss a").withZone(clock.getZone());
Expand Down Expand Up @@ -122,7 +126,7 @@ private String consumeMessageSending(Message message, Instant now, String date)
RequestMetadata requestMetadata = new RequestMetadata(method, now);
sentRequests.put(id, requestMetadata);
Object params = requestMessage.getParams();
String paramsJson = MessageJsonHandler.toString(params);
String paramsJson = jsonHandler.toString(params);
String format = "[Trace - %s] Sending request '%s - (%s)'\nParams: %s\n\n\n";
return String.format(format, date, method, id, paramsJson);
} else if (message instanceof ResponseMessage) {
Expand All @@ -136,15 +140,15 @@ private String consumeMessageSending(Message message, Instant now, String date)
String method = requestMetadata.method;
long latencyMillis = now.toEpochMilli() - requestMetadata.start.toEpochMilli();
Object result = responseMessage.getResult();
String resultJson = MessageJsonHandler.toString(result);
String resultJson = jsonHandler.toString(result);
String format =
"[Trace - %s] Sending response '%s - (%s)'. Processing request took %sms\nResult: %s\n\n\n";
return String.format(format, date, method, id, latencyMillis, resultJson);
} else if (message instanceof NotificationMessage) {
NotificationMessage notificationMessage = (NotificationMessage) message;
String method = notificationMessage.getMethod();
Object params = notificationMessage.getParams();
String paramsJson = MessageJsonHandler.toString(params);
String paramsJson = jsonHandler.toString(params);
String format = "[Trace - %s] Sending notification '%s'\nParams: %s\n\n\n";
return String.format(format, date, method, paramsJson);
} else {
Expand All @@ -161,30 +165,30 @@ private String consumeMessageReceiving(Message message, Instant now, String date
RequestMetadata requestMetadata = new RequestMetadata(method, now);
receivedRequests.put(id, requestMetadata);
Object params = requestMessage.getParams();
String paramsJson = MessageJsonHandler.toString(params);
String paramsJson = jsonHandler.toString(params);
String format = "[Trace - %s] Received request '%s - (%s)'\nParams: %s\n\n\n";
return String.format(format, date, method, id, paramsJson);
} else if (message instanceof ResponseMessage) {
ResponseMessage responseMessage = (ResponseMessage) message;
String id = responseMessage.getId();
RequestMetadata requestMetadata = sentRequests.remove(id);
if (requestMetadata == null) {
LOG.log(WARNING, String.format("Unmatched response message: %s", message));
LOG.log(WARNING, String.format("Unmatched response message: %s", jsonHandler.toString(message)));
return null;
}
String method = requestMetadata.method;
long latencyMillis = now.toEpochMilli() - requestMetadata.start.toEpochMilli();
Object result = responseMessage.getResult();
String resultJson = MessageJsonHandler.toString(result);
String resultJson = jsonHandler.toString(result);
Object error = responseMessage.getError();
String errorJson = MessageJsonHandler.toString(error);
String errorJson = jsonHandler.toString(error);
String format = "[Trace - %s] Received response '%s - (%s)' in %sms\nResult: %s\nError: %s\n\n\n";
return String.format(format, date, method, id, latencyMillis, resultJson, errorJson);
} else if (message instanceof NotificationMessage) {
NotificationMessage notificationMessage = (NotificationMessage) message;
String method = notificationMessage.getMethod();
Object params = notificationMessage.getParams();
String paramsJson = MessageJsonHandler.toString(params);
String paramsJson = jsonHandler.toString(params);
String format = "[Trace - %s] Received notification '%s'\nParams: %s\n\n\n";
return String.format(format, date, method, paramsJson);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
******************************************************************************/
package org.eclipse.lsp4j.jsonrpc.json;

import com.google.gson.JsonNull;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
Expand Down Expand Up @@ -48,7 +50,7 @@ public class MessageJsonHandler {
public static final JsonRpcMethod CANCEL_METHOD = JsonRpcMethod.notification("$/cancelRequest", CancelParams.class);

private final Gson gson;

private final Map<String, JsonRpcMethod> supportedMethods;

private MethodProvider methodProvider;
Expand Down Expand Up @@ -144,21 +146,27 @@ public String serialize(Message message) {
public void serialize(Message message, Writer output) throws JsonIOException {
gson.toJson(message, Message.class, output);
}


private static MessageJsonHandler toStringInstance;


/**
* Perform JSON serialization of the given object using the default configuration of JSON-RPC messages
* Perform JSON serialization of the given object using the configuration of JSON-RPC messages
* enhanced with the pretty printing option.
*/
public static String toString(Object object) {
if (toStringInstance == null) {
toStringInstance = new MessageJsonHandler(Collections.emptyMap(), gsonBuilder -> {
gsonBuilder.setPrettyPrinting();
});
public String toString(Object object) {
StringWriter writer = new StringWriter();
JsonWriter jsonWriter = null;
try {
jsonWriter = gson.newJsonWriter(writer);
// Equivalent to set pretty printing on the gson builder
jsonWriter.setIndent(" ");
} catch (IOException e) {
throw new JsonIOException(e);
}
if (object != null) {
gson.toJson(object, object.getClass(), jsonWriter);
} else {
gson.toJson(JsonNull.INSTANCE, jsonWriter);
}
return toStringInstance.gson.toJson(object);
return writer.toString();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,6 @@ public void setId(@NonNull int id) {
public void setRawId(@NonNull Either<String, Number> id) {
this.id = id;
}

@Override
public String toString() {
return MessageJsonHandler.toString(this);
}

@Override
public boolean equals(final Object obj) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,6 @@ public void setJsonrpc(@NonNull String jsonrpc) {
this.jsonrpc = jsonrpc;
}

@Override
public String toString() {
return MessageJsonHandler.toString(this);
}

@Override
public boolean equals(final Object obj) {
if (this == obj)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,6 @@ public ResponseError(int code, String message, Object data) {
this.data = data;
}

@Override
public String toString() {
return MessageJsonHandler.toString(this);
}

@Override
public boolean equals(final Object obj) {
if (this == obj)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
import java.io.ByteArrayOutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
Expand Down Expand Up @@ -83,6 +87,16 @@ public Either<String, Integer> getEither() {
public void setEither(Either<String, Integer> either) {
this.either = either;
}

private Path customAdapter;

public Path getCustomAdapter() {
return customAdapter;
}

public void setCustomAdapter(Path customAdapter) {
this.customAdapter = customAdapter;
}
}

public static interface MyServer {
Expand Down Expand Up @@ -727,6 +741,62 @@ public void testValidationIssue2() throws Exception {
out.toString());
}

@Test
public void testMessageTracingWithCustomGsonAdapter() throws Exception {
// create client side
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
PipedInputStream in2 = new PipedInputStream();
PipedOutputStream out2 = new PipedOutputStream();

in.connect(out2);
out.connect(in2);

StringWriter clientTraceOut = new StringWriter();
StringWriter serverTraceOut = new StringWriter();

MyClient client = new MyClientImpl();
Launcher<MyServer> clientSideLauncher = new Launcher.Builder<MyServer>()
.setLocalService(client)
.setRemoteInterface(MyServer.class)
.setInput(in)
.setOutput(out)
.configureGson(gsonBuilder -> gsonBuilder.registerTypeHierarchyAdapter(Path.class, new PathTypeAdapter()))
.traceMessages(new PrintWriter(clientTraceOut))
.create();

// create server side
MyServer server = new MyServerImpl();
Launcher<MyClient> serverSideLauncher = new Launcher.Builder<MyClient>()
.setLocalService(server)
.setRemoteInterface(MyClient.class)
.setInput(in2)
.setOutput(out2)
.configureGson(gsonBuilder -> gsonBuilder.registerTypeHierarchyAdapter(Path.class, new PathTypeAdapter()))
.traceMessages(new PrintWriter(serverTraceOut))
.create();

clientSideLauncher.startListening();
serverSideLauncher.startListening();

var paramWithPath = new MyParam("FOO");
paramWithPath.setCustomAdapter(Paths.get("").toAbsolutePath());
CompletableFuture<MyParam> fooFuture = clientSideLauncher.getRemoteProxy().askServer(paramWithPath);
CompletableFuture<MyParam> barFuture = serverSideLauncher.getRemoteProxy().askClient(new MyParam("BAR"));

Assert.assertEquals(Paths.get("").toAbsolutePath(), fooFuture.get(TIMEOUT, TimeUnit.MILLISECONDS).getCustomAdapter());
Assert.assertEquals("BAR", barFuture.get(TIMEOUT, TimeUnit.MILLISECONDS).value);

Assert.assertTrue(clientTraceOut.toString().contains("Sending request 'askServer - (1)'\n"
+ "Params: {\n"
+ " \"value\": \"FOO\",\n"
+ " \"customAdapter\": \""));
Assert.assertTrue(serverTraceOut.toString().contains("Received request 'askServer - (1)'\n"
+ "Params: {\n"
+ " \"value\": \"FOO\",\n"
+ " \"customAdapter\": \""));
}

protected String getHeader(int contentLength) {
StringBuilder headerBuilder = new StringBuilder();
headerBuilder.append(CONTENT_LENGTH_HEADER).append(": ").append(contentLength).append(CRLF);
Expand Down
Loading

0 comments on commit 66ecdab

Please sign in to comment.