Skip to content

Commit

Permalink
Provide support for Jakarta Websocket eclipse-lsp4j#471
Browse files Browse the repository at this point in the history
- will require an update of lsp4j.target.target file when orbit bundle
will be available, see
https://git.eclipse.org/r/c/orbit/orbit-recipes/+/174146
- to avoid API break, created a second bundle with the Jakarta
Websocket. it will allow also to support old version too for a moment.
the drawback is that it is making the naming a bit more complicated.

Signed-off-by: Aurélien Pupier <[email protected]>
  • Loading branch information
apupier committed Dec 31, 2020
1 parent 5cc2304 commit ee1edac
Show file tree
Hide file tree
Showing 14 changed files with 871 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

Fixed issues: https:/eclipse/lsp4j/milestone/18?closed=1

* Added new module `org.eclipse.lsp4j.websocket.jakarta` for using LSP4J over Jakarta websockets


### v0.10.0 (Nov. 2020)

Expand Down
1 change: 1 addition & 0 deletions gradle/versions.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ ext.versions = [
'gson': '2.8.2',
'gson_orbit': '2.8.2.v20180104-1110',
'websocket': '1.0',
'websocket_jakarta': '2.0.0',
'junit': '4.12'
]
27 changes: 27 additions & 0 deletions org.eclipse.lsp4j.websocket.jakarta/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.eclipse.lsp4j.websocket.jakarta</name>
<comment>WebSocket support for LSP4J</comment>
<projects/>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.xtext.ui.shared.xtextNature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments/>
</buildCommand>
<buildCommand>
<name>org.eclipse.xtext.ui.shared.xtextBuilder</name>
<arguments/>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments/>
</buildCommand>
</buildSpec>
<linkedResources/>
<filteredResources/>
</projectDescription>
24 changes: 24 additions & 0 deletions org.eclipse.lsp4j.websocket.jakarta/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 Jakarta'
description = 'Jakarta WebSocket support for LSP4J'

dependencies {
compile project(":org.eclipse.lsp4j.jsonrpc")
compile "jakarta.websocket:jakarta.websocket-api:$versions.websocket_jakarta"
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) 2021 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.jakarta;

import java.util.Collection;

import jakarta.websocket.Endpoint;
import jakarta.websocket.EndpointConfig;
import jakarta.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<>();
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}.
*/
protected abstract 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,76 @@
/******************************************************************************
* Copyright (c) 2021 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.jakarta;

import java.util.Collection;

import jakarta.websocket.Session;

import org.eclipse.lsp4j.jsonrpc.Endpoint;
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);
Endpoint localEndpoint = ServiceEndpoints.toEndpoint(localServices);
RemoteEndpoint remoteEndpoint;
if (exceptionHandler == null)
remoteEndpoint = new RemoteEndpoint(outgoingMessageStream, localEndpoint);
else
remoteEndpoint = new RemoteEndpoint(outgoingMessageStream, localEndpoint, exceptionHandler);
jsonHandler.setMethodProvider(remoteEndpoint);
return remoteEndpoint;
}

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/******************************************************************************
* Copyright (c) 2021 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.jakarta;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import jakarta.websocket.Session;

import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler;
import org.eclipse.lsp4j.jsonrpc.messages.Message;

/**
* Message consumer that sends messages via a WebSocket session.
*/
public class WebSocketMessageConsumer implements MessageConsumer {

private static final Logger LOG = Logger.getLogger(WebSocketMessageConsumer.class.getName());

private final Session session;
private final MessageJsonHandler jsonHandler;

public WebSocketMessageConsumer(Session session, MessageJsonHandler jsonHandler) {
this.session = session;
this.jsonHandler = jsonHandler;
}

public Session getSession() {
return session;
}

@Override
public void consume(Message message) {
String content = jsonHandler.serialize(message);
try {
sendMessage(content);
} catch (IOException exception) {
throw new JsonRpcException(exception);
}
}

protected void sendMessage(String message) throws IOException {
if (session.isOpen()) {
int length = message.length();
if (length <= session.getMaxTextMessageBufferSize()) {
session.getAsyncRemote().sendText(message);
} else {
int currentOffset = 0;
while (currentOffset < length) {
int currentEnd = Math.min(currentOffset + session.getMaxTextMessageBufferSize(), length);
session.getBasicRemote().sendText(message.substring(currentOffset, currentEnd), currentEnd == length);
currentOffset = currentEnd;
}
}
} else {
LOG.log(Level.INFO, "Ignoring message due to closed session: {0}", message);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/******************************************************************************
* Copyright (c) 2021 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.jakarta;

import jakarta.websocket.MessageHandler;

import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
import org.eclipse.lsp4j.jsonrpc.MessageIssueException;
import org.eclipse.lsp4j.jsonrpc.MessageIssueHandler;
import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler;
import org.eclipse.lsp4j.jsonrpc.messages.Message;

/**
* WebSocket message handler that parses JSON messages and forwards them to a {@link MessageConsumer}.
*/
public class WebSocketMessageHandler implements MessageHandler.Whole<String> {

private final MessageConsumer callback;
private final MessageJsonHandler jsonHandler;
private final MessageIssueHandler issueHandler;

public WebSocketMessageHandler(MessageConsumer callback, MessageJsonHandler jsonHandler, MessageIssueHandler issueHandler) {
this.callback = callback;
this.jsonHandler = jsonHandler;
this.issueHandler = issueHandler;
}

public void onMessage(String content) {
try {
Message message = jsonHandler.parseMessage(content);
callback.consume(message);
} catch (MessageIssueException exception) {
// An issue was found while parsing or validating the message
issueHandler.handle(exception.getRpcMessage(), exception.getIssues());
}
}

}
Loading

0 comments on commit ee1edac

Please sign in to comment.