Skip to content

Commit

Permalink
#203: Implemented WebSocket adapter for JSON-RPC
Browse files Browse the repository at this point in the history
  • Loading branch information
spoenemann committed May 28, 2019
1 parent 781e226 commit 69a2b53
Show file tree
Hide file tree
Showing 13 changed files with 890 additions and 52 deletions.
1 change: 1 addition & 0 deletions gradle/versions.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ ext.versions = [
'guava': '[14.0,22)',
'gson': '2.8.2',
'gson_orbit': '2.8.2.v20180104-1110',
'websocket': '1.0',
'junit': '4.12'
]
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
/**
* This is the entry point for applications that use LSP4J. A Launcher does all the wiring that is necessary to connect
* your endpoint via an input stream and an output stream.
*
* @param <T> remote service interface type
*/
public interface Launcher<T> {

Expand Down Expand Up @@ -217,6 +219,8 @@ static Launcher<Object> createIoLauncher(Collection<Object> localServices, Colle

/**
* The launcher builder wires up all components for JSON-RPC communication.
*
* @param <T> remote service interface type
*/
public static class Builder<T> {

Expand Down Expand Up @@ -291,7 +295,6 @@ public Builder<T> configureGson(Consumer<GsonBuilder> configureGson) {
return this;
}

@SuppressWarnings("unchecked")
public Launcher<T> create() {
// Validate input
if (input == null)
Expand All @@ -303,27 +306,63 @@ public Launcher<T> create() {
if (remoteInterfaces == null)
throw new IllegalStateException("Remote interface must be configured.");

// Create proxy and endpoints
// Create the JSON handler, remote endpoint and remote proxy
MessageJsonHandler jsonHandler = createJsonHandler();
RemoteEndpoint remoteEndpoint = createRemoteEndpoint(jsonHandler);
T remoteProxy;
if (localServices.size() == 1 && remoteInterfaces.size() == 1) {
remoteProxy = ServiceEndpoints.toServiceObject(remoteEndpoint, remoteInterfaces.iterator().next());
} else {
remoteProxy = (T) ServiceEndpoints.toServiceObject(remoteEndpoint, (Collection<Class<?>>) (Object) remoteInterfaces, classLoader);
}
T remoteProxy = createProxy(remoteEndpoint);

// create the message processor
// Create the message processor
StreamMessageProducer reader = new StreamMessageProducer(input, jsonHandler, remoteEndpoint);
MessageConsumer messageConsumer = wrapMessageConsumer(remoteEndpoint);
ExecutorService execService = executorService != null ? executorService : Executors.newCachedThreadPool();
ConcurrentMessageProcessor msgProcessor = createMessageProcessor(reader, messageConsumer, remoteProxy);

return createLauncher(reader, messageConsumer, execService, remoteProxy, remoteEndpoint, msgProcessor);
ExecutorService execService = executorService != null ? executorService : Executors.newCachedThreadPool();
return createLauncher(execService, remoteProxy, remoteEndpoint, msgProcessor);
}

protected Launcher<T> createLauncher(StreamMessageProducer reader, MessageConsumer messageConsumer,
ExecutorService execService, T remoteProxy, RemoteEndpoint remoteEndpoint, ConcurrentMessageProcessor msgProcessor) {
/**
* Create the JSON handler for messages between the local and remote services.
*/
protected MessageJsonHandler createJsonHandler() {
Map<String, JsonRpcMethod> supportedMethods = getSupportedMethods();
if (configureGson != null)
return new MessageJsonHandler(supportedMethods, configureGson);
else
return new MessageJsonHandler(supportedMethods);
}

/**
* Create the remote endpoint that communicates with the local services.
*/
protected RemoteEndpoint createRemoteEndpoint(MessageJsonHandler jsonHandler) {
MessageConsumer outgoingMessageStream = new StreamMessageConsumer(output, jsonHandler);
outgoingMessageStream = wrapMessageConsumer(outgoingMessageStream);
RemoteEndpoint remoteEndpoint = new RemoteEndpoint(outgoingMessageStream, ServiceEndpoints.toEndpoint(localServices));
jsonHandler.setMethodProvider(remoteEndpoint);
return remoteEndpoint;
}

/**
* Create the proxy for calling methods on the remote service.
*/
@SuppressWarnings("unchecked")
protected T createProxy(RemoteEndpoint remoteEndpoint) {
if (localServices.size() == 1 && remoteInterfaces.size() == 1) {
return ServiceEndpoints.toServiceObject(remoteEndpoint, remoteInterfaces.iterator().next());
} else {
return (T) ServiceEndpoints.toServiceObject(remoteEndpoint, (Collection<Class<?>>) (Object) remoteInterfaces, classLoader);
}
}

/**
* Create the message processor that listens to the input stream.
*/
protected ConcurrentMessageProcessor createMessageProcessor(MessageProducer reader,
MessageConsumer messageConsumer, T remoteProxy) {
return new ConcurrentMessageProcessor(reader, messageConsumer);
}

protected Launcher<T> createLauncher(ExecutorService execService, T remoteProxy, RemoteEndpoint remoteEndpoint,
ConcurrentMessageProcessor msgProcessor) {
return new StandardLauncher<T>(execService, remoteProxy, remoteEndpoint, msgProcessor);
}

Expand Down Expand Up @@ -368,44 +407,6 @@ protected Map<String, JsonRpcMethod> getSupportedMethods() {

return supportedMethods;
}

/**
* Create the JSON handler for messages between the local and remote services.
*/
protected MessageJsonHandler createJsonHandler() {
Map<String, JsonRpcMethod> supportedMethods = getSupportedMethods();
if (configureGson != null)
return new MessageJsonHandler(supportedMethods, configureGson);
else
return new MessageJsonHandler(supportedMethods);
}

/**
* Create a message processor given the provided parameters.
*
* Clients may override this method to create a message processor
* with an expanded feature set.
*
* @param reader - A message producer capable of receiving messages from clients
* @param messageConsumer - A message consumer capable of passing completed messages to the local service
* @param remoteProxy - The remote proxy for communicating with the connecting client
* @return A ConcurrentMessageProcessor capable of linking an incoming request to the local service's implementation
*/
protected ConcurrentMessageProcessor createMessageProcessor(MessageProducer reader,
MessageConsumer messageConsumer, T remoteProxy) {
return new ConcurrentMessageProcessor(reader, messageConsumer);
}

/**
* Create the remote endpoint that communicates with the local services.
*/
protected RemoteEndpoint createRemoteEndpoint(MessageJsonHandler jsonHandler) {
MessageConsumer outgoingMessageStream = new StreamMessageConsumer(output, jsonHandler);
outgoingMessageStream = wrapMessageConsumer(outgoingMessageStream);
RemoteEndpoint remoteEndpoint = new RemoteEndpoint(outgoingMessageStream, ServiceEndpoints.toEndpoint(localServices));
jsonHandler.setMethodProvider(remoteEndpoint);
return remoteEndpoint;
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ static <T> Launcher<T> createLauncher(Builder<T> builder, Object localService, C
*/
static <T> Builder<T> createBuilder(MessageContextStore<T> store) {
return new Builder<T>() {
@Override
protected ConcurrentMessageProcessor createMessageProcessor(MessageProducer reader,
MessageConsumer messageConsumer, T remoteProxy) {
return new CustomConcurrentMessageProcessor<T>(reader, messageConsumer, remoteProxy, store);
Expand Down
29 changes: 29 additions & 0 deletions org.eclipse.lsp4j.websocket/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.eclipse.lsp4j.websocket</name>
<comment>Project org.eclipse.lsp4j.websocket created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.xtext.ui.shared.xtextBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.xtext.ui.shared.xtextNature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
</projectDescription>
24 changes: 24 additions & 0 deletions org.eclipse.lsp4j.websocket/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/******************************************************************************
* Copyright (c) 2019 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
******************************************************************************/

ext.title = 'LSP4J WebSocket'
description = 'WebSocket support for LSP4J'

dependencies {
compile project(":org.eclipse.lsp4j.jsonrpc")
compile "javax.websocket:javax.websocket-api:${versions.websocket}"
testCompile "junit:junit:$versions.junit"
}

jar.manifest {
instruction 'Import-Package', '*'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/******************************************************************************
* Copyright (c) 2019 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
******************************************************************************/
package org.eclipse.lsp4j.websocket;

import java.util.Collection;

import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.Session;

import org.eclipse.lsp4j.jsonrpc.Launcher;

/**
* WebSocket endpoint implementation that connects to a JSON-RPC service.
*
* @param <T> remote service interface type
*/
public abstract class WebSocketEndpoint<T> extends Endpoint {

@Override
public void onOpen(Session session, EndpointConfig config) {
WebSocketLauncherBuilder<T> builder = new WebSocketLauncherBuilder<T>();
builder.setSession(session);
configure(builder);
Launcher<T> launcher = builder.create();
connect(builder.getLocalServices(), launcher.getRemoteProxy());
}

/**
* Configure the JSON-RPC launcher. Implementations should set at least the
* {@link Launcher.Builder#setLocalService(Object) local service} and the
* {@link Launcher.Builder#setRemoteInterface(Class) remote interface}.
*/
abstract protected void configure(Launcher.Builder<T> builder);

/**
* Override this in order to connect the local services to the remote service proxy.
*/
protected void connect(Collection<Object> localServices, T remoteProxy) {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/******************************************************************************
* Copyright (c) 2019 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
******************************************************************************/
package org.eclipse.lsp4j.websocket;

import java.util.Collection;

import javax.websocket.Session;

import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
import org.eclipse.lsp4j.jsonrpc.RemoteEndpoint;
import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler;
import org.eclipse.lsp4j.jsonrpc.services.ServiceEndpoints;

/**
* JSON-RPC launcher builder for use in {@link WebSocketEndpoint}.
*
* @param <T> remote service interface type
*/
public class WebSocketLauncherBuilder<T> extends Launcher.Builder<T> {

protected Session session;

public Collection<Object> getLocalServices() {
return localServices;
}

public WebSocketLauncherBuilder<T> setSession(Session session) {
this.session = session;
return this;
}

@Override
public Launcher<T> create() {
if (localServices == null)
throw new IllegalStateException("Local service must be configured.");
if (remoteInterfaces == null)
throw new IllegalStateException("Remote interface must be configured.");

MessageJsonHandler jsonHandler = createJsonHandler();
RemoteEndpoint remoteEndpoint = createRemoteEndpoint(jsonHandler);
addMessageHandlers(jsonHandler, remoteEndpoint);
T remoteProxy = createProxy(remoteEndpoint);
return createLauncher(null, remoteProxy, remoteEndpoint, null);
}

@Override
protected RemoteEndpoint createRemoteEndpoint(MessageJsonHandler jsonHandler) {
MessageConsumer outgoingMessageStream = new WebSocketMessageConsumer(session, jsonHandler);
outgoingMessageStream = wrapMessageConsumer(outgoingMessageStream);
RemoteEndpoint remoteEndpoint = new RemoteEndpoint(outgoingMessageStream, ServiceEndpoints.toEndpoint(localServices));
jsonHandler.setMethodProvider(remoteEndpoint);
return remoteEndpoint;
}

protected void addMessageHandlers(MessageJsonHandler jsonHandler, RemoteEndpoint remoteEndpoint) {
MessageConsumer messageConsumer = wrapMessageConsumer(remoteEndpoint);
session.addMessageHandler(new WebSocketMessageHandler(messageConsumer, jsonHandler, remoteEndpoint));
}

}
Loading

0 comments on commit 69a2b53

Please sign in to comment.