From c96e38301872d1e25e39723645aee3edccbffc00 Mon Sep 17 00:00:00 2001 From: Dmitrii Barnukov Date: Thu, 12 Feb 2026 17:43:54 +0100 Subject: [PATCH 1/6] dbeaver/pro#7844 added lsp web socket --- .../websockets/CBJettyWebSocketManager.java | 6 +- .../META-INF/MANIFEST.MF | 17 ++++ .../build.properties | 6 ++ .../io.cloudbeaver.service.lsp/plugin.xml | 6 ++ .../io.cloudbeaver.service.lsp/pom.xml | 16 ++++ .../lsp/LSPWebServerSesssionProvider.java | 30 ++++++++ .../lsp/LSPWebSessionProvider.java | 4 + .../lsp/websocket/LSPWebSocket.java | 52 +++++++++++++ .../lsp/websocket/LSPWebSocketConstants.java | 10 +++ .../websocket/LSPWebSocketMessageHandler.java | 77 +++++++++++++++++++ .../websocket/LSPWebSocketServiceBinding.java | 44 +++++++++++ server/bundles/pom.xml | 1 + .../io.cloudbeaver.server.feature/feature.xml | 1 + 13 files changed, 267 insertions(+), 3 deletions(-) create mode 100644 server/bundles/io.cloudbeaver.service.lsp/META-INF/MANIFEST.MF create mode 100644 server/bundles/io.cloudbeaver.service.lsp/build.properties create mode 100644 server/bundles/io.cloudbeaver.service.lsp/plugin.xml create mode 100644 server/bundles/io.cloudbeaver.service.lsp/pom.xml create mode 100644 server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/LSPWebServerSesssionProvider.java create mode 100644 server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/LSPWebSessionProvider.java create mode 100644 server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocket.java create mode 100644 server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketConstants.java create mode 100644 server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketMessageHandler.java create mode 100644 server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketServiceBinding.java diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/CBJettyWebSocketManager.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/CBJettyWebSocketManager.java index bfe6712f867..7fc876fed25 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/CBJettyWebSocketManager.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/CBJettyWebSocketManager.java @@ -30,12 +30,12 @@ public class CBJettyWebSocketManager { private static final Log log = Log.getLog(CBJettyWebSocketManager.class); - private static final Map> socketBySessionId = new ConcurrentHashMap<>(); + private static final Map> socketBySessionId = new ConcurrentHashMap<>(); private CBJettyWebSocketManager() { } - public static void registerWebSocket(@NotNull String webSessionId, @NotNull CBEventsWebSocket webSocket) { + public static void registerWebSocket(@NotNull String webSessionId, @NotNull CBAbstractWebSocket webSocket) { socketBySessionId.computeIfAbsent(webSessionId, key -> new CopyOnWriteArrayList<>()).add(webSocket); } @@ -57,7 +57,7 @@ public static void sendPing() { entry -> { var sessionId = entry.getKey(); var webSockets = entry.getValue(); - for (CBEventsWebSocket webSocket : webSockets) { + for (CBAbstractWebSocket webSocket : webSockets) { try { webSocket.getSession().getBasicRemote().sendPing( ByteBuffer.wrap("cb-ping".getBytes(StandardCharsets.UTF_8)) diff --git a/server/bundles/io.cloudbeaver.service.lsp/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.service.lsp/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..0108e3aef80 --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.lsp/META-INF/MANIFEST.MF @@ -0,0 +1,17 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Vendor: DBeaver Corp +Bundle-Name: Cloudbeaver Lsp WS +Bundle-SymbolicName: io.cloudbeaver.service.lsp;singleton:=true +Bundle-Version: 1.0.0.qualifier +Bundle-Release-Date: 20260212 +Bundle-RequiredExecutionEnvironment: JavaSE-21 +Bundle-ActivationPolicy: lazy +Require-Bundle: org.jkiss.dbeaver.model, + org.jkiss.dbeaver.model.lsp, + org.eclipse.jetty.websocket.core.client, + io.cloudbeaver.model, + io.cloudbeaver.server.ce +Export-Package: io.cloudbeaver.service.lsp.ws +Automatic-Module-Name: io.cloudbeaver.service.lsp +Import-Package: org.slf4j diff --git a/server/bundles/io.cloudbeaver.service.lsp/build.properties b/server/bundles/io.cloudbeaver.service.lsp/build.properties new file mode 100644 index 00000000000..d9cc939477d --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.lsp/build.properties @@ -0,0 +1,6 @@ +source.. = src/ +output.. = target/classes/ +bin.includes = .,\ + META-INF/,\ + plugin.xml,\ + schema/ diff --git a/server/bundles/io.cloudbeaver.service.lsp/plugin.xml b/server/bundles/io.cloudbeaver.service.lsp/plugin.xml new file mode 100644 index 00000000000..ff83905d122 --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.lsp/plugin.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/server/bundles/io.cloudbeaver.service.lsp/pom.xml b/server/bundles/io.cloudbeaver.service.lsp/pom.xml new file mode 100644 index 00000000000..edb0d7cc5ff --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.lsp/pom.xml @@ -0,0 +1,16 @@ + + + 4.0.0 + + io.cloudbeaver + bundles + 1.0.0-SNAPSHOT + ../ + + io.cloudbeaver.service.lsp + 1.0.0-SNAPSHOT + eclipse-plugin + + diff --git a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/LSPWebServerSesssionProvider.java b/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/LSPWebServerSesssionProvider.java new file mode 100644 index 00000000000..f4cf9069725 --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/LSPWebServerSesssionProvider.java @@ -0,0 +1,30 @@ +package io.cloudbeaver.lsp; + +import io.cloudbeaver.model.session.BaseWebSession; +import org.jkiss.code.NotNull; +import org.jkiss.code.Nullable; +import org.jkiss.dbeaver.model.app.DBPWorkspace; +import org.jkiss.dbeaver.model.auth.impl.AbstractSessionPersistent; +import org.jkiss.dbeaver.model.lsp.DBLServerSessionProvider; + +public class LSPWebServerSesssionProvider implements DBLServerSessionProvider { + + @NotNull + private final BaseWebSession session; + + public LSPWebServerSesssionProvider(@NotNull BaseWebSession session) { + this.session = session; + } + + @Nullable + @Override + public AbstractSessionPersistent getSession() { + return session; + } + + @NotNull + @Override + public DBPWorkspace getWorkspace() { + return session.getWorkspace(); + } +} diff --git a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/LSPWebSessionProvider.java b/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/LSPWebSessionProvider.java new file mode 100644 index 00000000000..577d9557ecd --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/LSPWebSessionProvider.java @@ -0,0 +1,4 @@ +package io.cloudbeaver.lsp; + +public class LSPWebSessionProvider { +} diff --git a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocket.java b/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocket.java new file mode 100644 index 00000000000..fc3c11b8f3c --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocket.java @@ -0,0 +1,52 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2026 DBeaver Corp + * + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of DBeaver Corp and its suppliers, if any. + * The intellectual and technical concepts contained + * herein are proprietary to DBeaver Corp and its suppliers + * and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from DBeaver Corp. + */ +package io.cloudbeaver.lsp.websocket; + +import io.cloudbeaver.model.session.BaseWebSession; +import io.cloudbeaver.server.websockets.CBAbstractWebSocket; +import io.cloudbeaver.server.websockets.CBJettyWebSocketManager; +import io.cloudbeaver.server.websockets.CBWebSocketServerConfigurator; +import jakarta.websocket.CloseReason; +import jakarta.websocket.EndpointConfig; +import jakarta.websocket.Session; +import org.jkiss.dbeaver.Log; + +public abstract class LSPWebSocket extends CBAbstractWebSocket { + private static final Log log = Log.getLog(LSPWebSocket.class); + + @Override + public void onOpen(Session session, EndpointConfig endpointConfig) { + BaseWebSession webSession = (BaseWebSession) session.getUserProperties() + .get(CBWebSocketServerConfigurator.PROP_WEB_SESSION); + + session.setMaxIdleTimeout(LSPWebSocketConstants.IDLE_TIMEOUT.toMillis()); + session.setMaxTextMessageBufferSize(Integer.MAX_VALUE); + session.setMaxBinaryMessageBufferSize(Integer.MAX_VALUE); + session.addMessageHandler(new LSPWebSocketMessageHandler(webSession)); + + CBJettyWebSocketManager.registerWebSocket(webSession.getSessionId(), this); + } + + @Override + public void onClose(Session session, CloseReason closeReason) { + } + + @Override + public void onError(Session session, Throwable thr) { + log.error("LSP WebSocket error", thr); + } +} diff --git a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketConstants.java b/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketConstants.java new file mode 100644 index 00000000000..26b06701dc8 --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketConstants.java @@ -0,0 +1,10 @@ +package io.cloudbeaver.lsp.websocket; + +import java.time.Duration; + +public class LSPWebSocketConstants { + + public static final String ENDPOINT_SUFFIX = "ws/lsp"; + public static final Duration IDLE_TIMEOUT = Duration.ofMinutes(5); + +} diff --git a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketMessageHandler.java b/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketMessageHandler.java new file mode 100644 index 00000000000..abb76a7bdd8 --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketMessageHandler.java @@ -0,0 +1,77 @@ +package io.cloudbeaver.lsp.websocket; + +import io.cloudbeaver.lsp.LSPWebServerSesssionProvider; +import io.cloudbeaver.model.session.BaseWebSession; +import jakarta.websocket.MessageHandler; +import org.jkiss.code.NotNull; +import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.model.lsp.DBLFacade; +import org.jkiss.dbeaver.model.lsp.DBLServerSessionProvider; + +import java.io.*; +import java.nio.ByteBuffer; + +public class LSPWebSocketMessageHandler implements MessageHandler.Whole, AutoCloseable { + private static final Log log = Log.getLog(LSPWebSocketMessageHandler.class); + + @NotNull + private final PipedInputStream inputStream; + @NotNull + private final PipedOutputStream outputStream; + + public LSPWebSocketMessageHandler(@NotNull BaseWebSession session) { + try { + this.inputStream = new PipedInputStream(256 * 1024); + this.outputStream = new PipedOutputStream(inputStream); + } catch (IOException e) { + throw new IllegalStateException("Can't create LSP piped streams", e); + } + DBLServerSessionProvider sessionProvider = new LSPWebServerSesssionProvider(session); + Thread serverThread = new Thread(() -> runLanguageServer(sessionProvider), "LSP-WebSocket-" + session.getSessionId()); + serverThread.setDaemon(true); + serverThread.start(); + } + + @Override + public void onMessage(@NotNull ByteBuffer byteBuffer) { + try { + if (byteBuffer.hasArray()) { + int offset = byteBuffer.arrayOffset() + byteBuffer.position(); + int len = byteBuffer.remaining(); + inputStream.read(byteBuffer.array(), offset, len); + } else { + byte[] chunk = new byte[byteBuffer.remaining()]; + byteBuffer.get(chunk); + inputStream.read(chunk); + } + inputStream.reset(); + } catch (IOException e) { + close(); + } + } + + @Override + public void close() { + try { + inputStream.close(); + } catch (IOException e) { + log.debug("Error closing LSP input stream", e); + } + + try { + outputStream.close(); + } catch (IOException e) { + log.debug("Error closing LSP output stream", e); + } + } + + private void runLanguageServer(@NotNull DBLServerSessionProvider sessionProvider) { + try (InputStream in = inputStream; OutputStream out = outputStream) { + DBLFacade.runLanguageServer(in, out, sessionProvider); + } catch (Throwable e) { + log.error("Error starting LSP server", e); + } finally { + close(); + } + } +} diff --git a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketServiceBinding.java b/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketServiceBinding.java new file mode 100644 index 00000000000..43af4456ba2 --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketServiceBinding.java @@ -0,0 +1,44 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2026 DBeaver Corp + * + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of DBeaver Corp and its suppliers, if any. + * The intellectual and technical concepts contained + * herein are proprietary to DBeaver Corp and its suppliers + * and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from DBeaver Corp. + */ +package io.cloudbeaver.lsp.websocket; + +import io.cloudbeaver.model.app.ServletApplication; +import io.cloudbeaver.server.CBApplication; +import io.cloudbeaver.server.websockets.CBWebSocketServerConfigurator; +import io.cloudbeaver.service.DBWServiceBindingWebSocket; +import io.cloudbeaver.service.DBWWebSocketContext; +import jakarta.websocket.server.ServerEndpointConfig; +import org.jkiss.code.NotNull; +import org.jkiss.dbeaver.DBException; + +public class LSPWebSocketServiceBinding + implements DBWServiceBindingWebSocket> { + + @Override + public boolean isApplicable(@NotNull ServletApplication application) { + return DBWServiceBindingWebSocket.super.isApplicable(application); + } + + @Override + public void addWebSockets(@NotNull CBApplication application, @NotNull DBWWebSocketContext context) throws DBException { + String uri = application.getServicesURI() + LSPWebSocketConstants.ENDPOINT_SUFFIX; + ServerEndpointConfig endpointConfig = ServerEndpointConfig.Builder.create(LSPWebSocket.class, uri) + .configurator(new CBWebSocketServerConfigurator(application.getSessionManager())) + .build(); + context.addWebSocket(endpointConfig); + } +} diff --git a/server/bundles/pom.xml b/server/bundles/pom.xml index 7b6c56ccfaa..c8e85451e6a 100644 --- a/server/bundles/pom.xml +++ b/server/bundles/pom.xml @@ -26,6 +26,7 @@ io.cloudbeaver.service.rm io.cloudbeaver.service.rm.nio io.cloudbeaver.service.data.transfer + io.cloudbeaver.service.lsp io.cloudbeaver.service.security io.cloudbeaver.service.ldap.auth diff --git a/server/features/io.cloudbeaver.server.feature/feature.xml b/server/features/io.cloudbeaver.server.feature/feature.xml index d9ea8e86af3..c00c41f5da3 100644 --- a/server/features/io.cloudbeaver.server.feature/feature.xml +++ b/server/features/io.cloudbeaver.server.feature/feature.xml @@ -39,6 +39,7 @@ + From ec9b9de142f8072a83a1f6f7382ca33f4b00c4fa Mon Sep 17 00:00:00 2001 From: Dmitrii Barnukov Date: Mon, 16 Feb 2026 09:39:14 +0100 Subject: [PATCH 2/6] dbeaver/pro#7844 refactor deps, added socket endpoint --- .../server/jetty/CBJettyServer.java | 19 ++++++-- .../META-INF/MANIFEST.MF | 1 + .../lsp/LSPWebServerSesssionProvider.java | 2 +- .../server/websockets/lsp}/LSPWebSocket.java | 12 +++-- .../lsp}/LSPWebSocketConstants.java | 2 +- .../lsp}/LSPWebSocketMessageHandler.java | 3 +- .../META-INF/MANIFEST.MF | 17 ------- .../build.properties | 6 --- .../io.cloudbeaver.service.lsp/plugin.xml | 6 --- .../io.cloudbeaver.service.lsp/pom.xml | 16 ------- .../lsp/LSPWebSessionProvider.java | 4 -- .../websocket/LSPWebSocketServiceBinding.java | 44 ------------------- server/bundles/pom.xml | 1 - .../io.cloudbeaver.server.feature/feature.xml | 1 - 14 files changed, 28 insertions(+), 106 deletions(-) rename server/bundles/{io.cloudbeaver.service.lsp/src/io/cloudbeaver => io.cloudbeaver.server/src/io/cloudbeaver/server/websockets}/lsp/LSPWebServerSesssionProvider.java (94%) rename server/bundles/{io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket => io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp}/LSPWebSocket.java (86%) rename server/bundles/{io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket => io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp}/LSPWebSocketConstants.java (81%) rename server/bundles/{io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket => io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp}/LSPWebSocketMessageHandler.java (96%) delete mode 100644 server/bundles/io.cloudbeaver.service.lsp/META-INF/MANIFEST.MF delete mode 100644 server/bundles/io.cloudbeaver.service.lsp/build.properties delete mode 100644 server/bundles/io.cloudbeaver.service.lsp/plugin.xml delete mode 100644 server/bundles/io.cloudbeaver.service.lsp/pom.xml delete mode 100644 server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/LSPWebSessionProvider.java delete mode 100644 server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketServiceBinding.java diff --git a/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/jetty/CBJettyServer.java b/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/jetty/CBJettyServer.java index 20c09f0a721..a89128cec61 100644 --- a/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/jetty/CBJettyServer.java +++ b/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/jetty/CBJettyServer.java @@ -29,6 +29,8 @@ import io.cloudbeaver.server.websockets.CBEventsLongPollingServlet; import io.cloudbeaver.server.websockets.CBEventsWebSocket; import io.cloudbeaver.server.websockets.CBWebSocketServerConfigurator; +import io.cloudbeaver.server.websockets.lsp.LSPWebSocket; +import io.cloudbeaver.server.websockets.lsp.LSPWebSocketConstants; import io.cloudbeaver.service.DBWServiceBindingServlet; import io.cloudbeaver.service.DBWServiceBindingWebSocket; import jakarta.websocket.server.ServerEndpointConfig; @@ -174,13 +176,24 @@ public void runServer() { JakartaWebSocketServletContainerInitializer.configure(servletContextHandler, (context, container) -> { // Add echo endpoint to server container - ServerEndpointConfig eventWsEnpoint = ServerEndpointConfig.Builder + CBWebSocketServerConfigurator configurator = + new CBWebSocketServerConfigurator(application.getSessionManager()); + + ServerEndpointConfig eventWsEndpoint = ServerEndpointConfig.Builder .create( CBEventsWebSocket.class, serverConfiguration.getServicesURI() + "ws" - ).configurator(new CBWebSocketServerConfigurator(application.getSessionManager())) + ).configurator(configurator) + .build(); + container.addEndpoint(eventWsEndpoint); + + ServerEndpointConfig lspWsEndpoint = ServerEndpointConfig.Builder + .create( + LSPWebSocket.class, + serverConfiguration.getServicesURI() + LSPWebSocketConstants.ENDPOINT_SUFFIX + ).configurator(configurator) .build(); - container.addEndpoint(eventWsEnpoint); + container.addEndpoint(lspWsEndpoint); }); JettyUtils.initSessionManager( diff --git a/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF index 41815f854f5..e39ebab83c7 100644 --- a/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF +++ b/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF @@ -11,6 +11,7 @@ Require-Bundle: org.eclipse.core.runtime;visibility:=reexport, org.apache.commons.jexl, org.jkiss.utils;visibility:=reexport, org.jkiss.dbeaver.model.sql;visibility:=reexport, + org.jkiss.dbeaver.model.lsp, org.jkiss.dbeaver.data.gis, org.jkiss.bundle.jetty.server;visibility:=reexport, com.google.gson;visibility:=reexport, diff --git a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/LSPWebServerSesssionProvider.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebServerSesssionProvider.java similarity index 94% rename from server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/LSPWebServerSesssionProvider.java rename to server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebServerSesssionProvider.java index f4cf9069725..29d94104fa0 100644 --- a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/LSPWebServerSesssionProvider.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebServerSesssionProvider.java @@ -1,4 +1,4 @@ -package io.cloudbeaver.lsp; +package io.cloudbeaver.server.websockets.lsp; import io.cloudbeaver.model.session.BaseWebSession; import org.jkiss.code.NotNull; diff --git a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocket.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocket.java similarity index 86% rename from server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocket.java rename to server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocket.java index fc3c11b8f3c..9988fbf4ddd 100644 --- a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocket.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocket.java @@ -14,7 +14,7 @@ * is strictly forbidden unless prior written permission is obtained * from DBeaver Corp. */ -package io.cloudbeaver.lsp.websocket; +package io.cloudbeaver.server.websockets.lsp; import io.cloudbeaver.model.session.BaseWebSession; import io.cloudbeaver.server.websockets.CBAbstractWebSocket; @@ -28,21 +28,25 @@ public abstract class LSPWebSocket extends CBAbstractWebSocket { private static final Log log = Log.getLog(LSPWebSocket.class); + private LSPWebSocketMessageHandler handler; + @Override public void onOpen(Session session, EndpointConfig endpointConfig) { BaseWebSession webSession = (BaseWebSession) session.getUserProperties() .get(CBWebSocketServerConfigurator.PROP_WEB_SESSION); + handler = new LSPWebSocketMessageHandler(webSession); + session.addMessageHandler(handler); + CBJettyWebSocketManager.registerWebSocket(webSession.getSessionId(), this); session.setMaxIdleTimeout(LSPWebSocketConstants.IDLE_TIMEOUT.toMillis()); session.setMaxTextMessageBufferSize(Integer.MAX_VALUE); session.setMaxBinaryMessageBufferSize(Integer.MAX_VALUE); - session.addMessageHandler(new LSPWebSocketMessageHandler(webSession)); - - CBJettyWebSocketManager.registerWebSocket(webSession.getSessionId(), this); } @Override public void onClose(Session session, CloseReason closeReason) { + log.debug("Closing websocket session: " + session.getId()); + handler.close(); } @Override diff --git a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketConstants.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketConstants.java similarity index 81% rename from server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketConstants.java rename to server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketConstants.java index 26b06701dc8..2d13e274103 100644 --- a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketConstants.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketConstants.java @@ -1,4 +1,4 @@ -package io.cloudbeaver.lsp.websocket; +package io.cloudbeaver.server.websockets.lsp; import java.time.Duration; diff --git a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketMessageHandler.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketMessageHandler.java similarity index 96% rename from server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketMessageHandler.java rename to server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketMessageHandler.java index abb76a7bdd8..316ba8c6fa3 100644 --- a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketMessageHandler.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketMessageHandler.java @@ -1,6 +1,5 @@ -package io.cloudbeaver.lsp.websocket; +package io.cloudbeaver.server.websockets.lsp; -import io.cloudbeaver.lsp.LSPWebServerSesssionProvider; import io.cloudbeaver.model.session.BaseWebSession; import jakarta.websocket.MessageHandler; import org.jkiss.code.NotNull; diff --git a/server/bundles/io.cloudbeaver.service.lsp/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.service.lsp/META-INF/MANIFEST.MF deleted file mode 100644 index 0108e3aef80..00000000000 --- a/server/bundles/io.cloudbeaver.service.lsp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,17 +0,0 @@ -Manifest-Version: 1.0 -Bundle-ManifestVersion: 2 -Bundle-Vendor: DBeaver Corp -Bundle-Name: Cloudbeaver Lsp WS -Bundle-SymbolicName: io.cloudbeaver.service.lsp;singleton:=true -Bundle-Version: 1.0.0.qualifier -Bundle-Release-Date: 20260212 -Bundle-RequiredExecutionEnvironment: JavaSE-21 -Bundle-ActivationPolicy: lazy -Require-Bundle: org.jkiss.dbeaver.model, - org.jkiss.dbeaver.model.lsp, - org.eclipse.jetty.websocket.core.client, - io.cloudbeaver.model, - io.cloudbeaver.server.ce -Export-Package: io.cloudbeaver.service.lsp.ws -Automatic-Module-Name: io.cloudbeaver.service.lsp -Import-Package: org.slf4j diff --git a/server/bundles/io.cloudbeaver.service.lsp/build.properties b/server/bundles/io.cloudbeaver.service.lsp/build.properties deleted file mode 100644 index d9cc939477d..00000000000 --- a/server/bundles/io.cloudbeaver.service.lsp/build.properties +++ /dev/null @@ -1,6 +0,0 @@ -source.. = src/ -output.. = target/classes/ -bin.includes = .,\ - META-INF/,\ - plugin.xml,\ - schema/ diff --git a/server/bundles/io.cloudbeaver.service.lsp/plugin.xml b/server/bundles/io.cloudbeaver.service.lsp/plugin.xml deleted file mode 100644 index ff83905d122..00000000000 --- a/server/bundles/io.cloudbeaver.service.lsp/plugin.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/server/bundles/io.cloudbeaver.service.lsp/pom.xml b/server/bundles/io.cloudbeaver.service.lsp/pom.xml deleted file mode 100644 index edb0d7cc5ff..00000000000 --- a/server/bundles/io.cloudbeaver.service.lsp/pom.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - 4.0.0 - - io.cloudbeaver - bundles - 1.0.0-SNAPSHOT - ../ - - io.cloudbeaver.service.lsp - 1.0.0-SNAPSHOT - eclipse-plugin - - diff --git a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/LSPWebSessionProvider.java b/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/LSPWebSessionProvider.java deleted file mode 100644 index 577d9557ecd..00000000000 --- a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/LSPWebSessionProvider.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.cloudbeaver.lsp; - -public class LSPWebSessionProvider { -} diff --git a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketServiceBinding.java b/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketServiceBinding.java deleted file mode 100644 index 43af4456ba2..00000000000 --- a/server/bundles/io.cloudbeaver.service.lsp/src/io/cloudbeaver/lsp/websocket/LSPWebSocketServiceBinding.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * DBeaver - Universal Database Manager - * Copyright (C) 2010-2026 DBeaver Corp - * - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of DBeaver Corp and its suppliers, if any. - * The intellectual and technical concepts contained - * herein are proprietary to DBeaver Corp and its suppliers - * and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from DBeaver Corp. - */ -package io.cloudbeaver.lsp.websocket; - -import io.cloudbeaver.model.app.ServletApplication; -import io.cloudbeaver.server.CBApplication; -import io.cloudbeaver.server.websockets.CBWebSocketServerConfigurator; -import io.cloudbeaver.service.DBWServiceBindingWebSocket; -import io.cloudbeaver.service.DBWWebSocketContext; -import jakarta.websocket.server.ServerEndpointConfig; -import org.jkiss.code.NotNull; -import org.jkiss.dbeaver.DBException; - -public class LSPWebSocketServiceBinding - implements DBWServiceBindingWebSocket> { - - @Override - public boolean isApplicable(@NotNull ServletApplication application) { - return DBWServiceBindingWebSocket.super.isApplicable(application); - } - - @Override - public void addWebSockets(@NotNull CBApplication application, @NotNull DBWWebSocketContext context) throws DBException { - String uri = application.getServicesURI() + LSPWebSocketConstants.ENDPOINT_SUFFIX; - ServerEndpointConfig endpointConfig = ServerEndpointConfig.Builder.create(LSPWebSocket.class, uri) - .configurator(new CBWebSocketServerConfigurator(application.getSessionManager())) - .build(); - context.addWebSocket(endpointConfig); - } -} diff --git a/server/bundles/pom.xml b/server/bundles/pom.xml index c8e85451e6a..7b6c56ccfaa 100644 --- a/server/bundles/pom.xml +++ b/server/bundles/pom.xml @@ -26,7 +26,6 @@ io.cloudbeaver.service.rm io.cloudbeaver.service.rm.nio io.cloudbeaver.service.data.transfer - io.cloudbeaver.service.lsp io.cloudbeaver.service.security io.cloudbeaver.service.ldap.auth diff --git a/server/features/io.cloudbeaver.server.feature/feature.xml b/server/features/io.cloudbeaver.server.feature/feature.xml index c00c41f5da3..d9ea8e86af3 100644 --- a/server/features/io.cloudbeaver.server.feature/feature.xml +++ b/server/features/io.cloudbeaver.server.feature/feature.xml @@ -39,7 +39,6 @@ - From e7ac881fab41e49d3bf39770ab1ac7d286010646 Mon Sep 17 00:00:00 2001 From: Dmitrii Barnukov Date: Mon, 16 Feb 2026 09:40:28 +0100 Subject: [PATCH 3/6] dbeaver/pro#7844 added copy --- .../lsp/LSPWebServerSesssionProvider.java | 16 ++++++++++++++++ .../websockets/lsp/LSPWebSocketConstants.java | 16 ++++++++++++++++ .../lsp/LSPWebSocketMessageHandler.java | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebServerSesssionProvider.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebServerSesssionProvider.java index 29d94104fa0..c456010c4bd 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebServerSesssionProvider.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebServerSesssionProvider.java @@ -1,3 +1,19 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2026 DBeaver Corp + * + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of DBeaver Corp and its suppliers, if any. + * The intellectual and technical concepts contained + * herein are proprietary to DBeaver Corp and its suppliers + * and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from DBeaver Corp. + */ package io.cloudbeaver.server.websockets.lsp; import io.cloudbeaver.model.session.BaseWebSession; diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketConstants.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketConstants.java index 2d13e274103..2faf2c4b8ee 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketConstants.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketConstants.java @@ -1,3 +1,19 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2026 DBeaver Corp + * + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of DBeaver Corp and its suppliers, if any. + * The intellectual and technical concepts contained + * herein are proprietary to DBeaver Corp and its suppliers + * and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from DBeaver Corp. + */ package io.cloudbeaver.server.websockets.lsp; import java.time.Duration; diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketMessageHandler.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketMessageHandler.java index 316ba8c6fa3..149bda2ec9f 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketMessageHandler.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketMessageHandler.java @@ -1,3 +1,19 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2026 DBeaver Corp + * + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of DBeaver Corp and its suppliers, if any. + * The intellectual and technical concepts contained + * herein are proprietary to DBeaver Corp and its suppliers + * and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from DBeaver Corp. + */ package io.cloudbeaver.server.websockets.lsp; import io.cloudbeaver.model.session.BaseWebSession; From 4d0f1a1a430436e6a55740b4f557a97a1868e9a0 Mon Sep 17 00:00:00 2001 From: Dmitrii Barnukov Date: Thu, 19 Feb 2026 11:18:15 +0100 Subject: [PATCH 4/6] dbeaver/pro#7844 added missing export --- server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF | 1 + 1 file changed, 1 insertion(+) diff --git a/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF index e39ebab83c7..21b1c79cc7c 100644 --- a/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF +++ b/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF @@ -34,6 +34,7 @@ Export-Package: io.cloudbeaver, io.cloudbeaver.server.jobs, io.cloudbeaver.server.servlets, io.cloudbeaver.server.websockets, + io.cloudbeaver.server.websockets.lsp, io.cloudbeaver.service, io.cloudbeaver.service.navigator, io.cloudbeaver.service.sql From 48057948c2604f46d0c10bd5b5271eb585d3ab34 Mon Sep 17 00:00:00 2001 From: Dmitrii Barnukov Date: Wed, 11 Mar 2026 09:50:29 +0100 Subject: [PATCH 5/6] dbeaver/pro#7844 changed message handler impl --- .../server/websockets/lsp/LSPWebSocket.java | 14 +- .../lsp/LSPWebSocketMessageHandler.java | 130 ++++++++++++++---- 2 files changed, 107 insertions(+), 37 deletions(-) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocket.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocket.java index 9988fbf4ddd..b6b337f5fb1 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocket.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocket.java @@ -31,16 +31,16 @@ public abstract class LSPWebSocket extends CBAbstractWebSocket { private LSPWebSocketMessageHandler handler; @Override - public void onOpen(Session session, EndpointConfig endpointConfig) { - BaseWebSession webSession = (BaseWebSession) session.getUserProperties() + public void onOpen(Session wsSession, EndpointConfig endpointConfig) { + BaseWebSession webSession = (BaseWebSession) wsSession.getUserProperties() .get(CBWebSocketServerConfigurator.PROP_WEB_SESSION); - handler = new LSPWebSocketMessageHandler(webSession); - session.addMessageHandler(handler); + handler = new LSPWebSocketMessageHandler(wsSession, webSession); + wsSession.addMessageHandler(handler); CBJettyWebSocketManager.registerWebSocket(webSession.getSessionId(), this); - session.setMaxIdleTimeout(LSPWebSocketConstants.IDLE_TIMEOUT.toMillis()); - session.setMaxTextMessageBufferSize(Integer.MAX_VALUE); - session.setMaxBinaryMessageBufferSize(Integer.MAX_VALUE); + wsSession.setMaxIdleTimeout(LSPWebSocketConstants.IDLE_TIMEOUT.toMillis()); + wsSession.setMaxTextMessageBufferSize(Integer.MAX_VALUE); + wsSession.setMaxBinaryMessageBufferSize(Integer.MAX_VALUE); } @Override diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketMessageHandler.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketMessageHandler.java index 149bda2ec9f..53c8a34b4d5 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketMessageHandler.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketMessageHandler.java @@ -18,6 +18,7 @@ import io.cloudbeaver.model.session.BaseWebSession; import jakarta.websocket.MessageHandler; +import jakarta.websocket.Session; import org.jkiss.code.NotNull; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.lsp.DBLFacade; @@ -25,66 +26,135 @@ import java.io.*; import java.nio.ByteBuffer; +import java.util.Objects; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; public class LSPWebSocketMessageHandler implements MessageHandler.Whole, AutoCloseable { private static final Log log = Log.getLog(LSPWebSocketMessageHandler.class); - @NotNull - private final PipedInputStream inputStream; - @NotNull - private final PipedOutputStream outputStream; + private final Session wsSession; - public LSPWebSocketMessageHandler(@NotNull BaseWebSession session) { - try { - this.inputStream = new PipedInputStream(256 * 1024); - this.outputStream = new PipedOutputStream(inputStream); - } catch (IOException e) { - throw new IllegalStateException("Can't create LSP piped streams", e); - } - DBLServerSessionProvider sessionProvider = new LSPWebServerSesssionProvider(session); - Thread serverThread = new Thread(() -> runLanguageServer(sessionProvider), "LSP-WebSocket-" + session.getSessionId()); - serverThread.setDaemon(true); - serverThread.start(); + private final AtomicBoolean closed = new AtomicBoolean(false); + private final ExecutorService ioExecutor = Executors.newFixedThreadPool(2, r -> { + Thread t = new Thread(r, "lsp-ws-message-handler"); + t.setDaemon(true); + return t; + }); + + private volatile PipedOutputStream wsToServer; + private volatile PipedInputStream serverIn; + + private volatile PipedOutputStream serverOut; + private volatile PipedInputStream serverToWs; + + public LSPWebSocketMessageHandler(@NotNull Session wsSession, @NotNull BaseWebSession webSession) { + this.wsSession = Objects.requireNonNull(wsSession, "wsSession"); + + DBLServerSessionProvider sessionProvider = new LSPWebServerSesssionProvider(webSession); + runLanguageServer(sessionProvider); } @Override public void onMessage(@NotNull ByteBuffer byteBuffer) { + if (closed.get()) { + return; + } + if (wsToServer == null) { + log.debug("LSP websocket message received before bridge initialization; ignoring"); + return; + } + try { if (byteBuffer.hasArray()) { int offset = byteBuffer.arrayOffset() + byteBuffer.position(); int len = byteBuffer.remaining(); - inputStream.read(byteBuffer.array(), offset, len); + wsToServer.write(byteBuffer.array(), offset, len); + byteBuffer.position(byteBuffer.limit()); } else { - byte[] chunk = new byte[byteBuffer.remaining()]; - byteBuffer.get(chunk); - inputStream.read(chunk); + byte[] tmp = new byte[Math.min(byteBuffer.remaining(), 16 * 1024)]; + while (byteBuffer.hasRemaining()) { + int n = Math.min(byteBuffer.remaining(), tmp.length); + byteBuffer.get(tmp, 0, n); + wsToServer.write(tmp, 0, n); + } } - inputStream.reset(); + wsToServer.flush(); } catch (IOException e) { - close(); + if (!closed.get()) { + log.error("Error forwarding websocket bytes to LSP server input stream", e); + close(); + } } } @Override public void close() { + if (!closed.compareAndSet(false, true)) { + return; + } + try { - inputStream.close(); + if (wsToServer != null) { + wsToServer.close(); + } + if (serverToWs != null) { + serverToWs.close(); + } + if (serverIn != null) { + serverIn.close(); + } + if (serverOut != null) { + serverOut.close(); + } } catch (IOException e) { - log.debug("Error closing LSP input stream", e); + log.debug("Error closing lspWs pipes", e); } + ioExecutor.shutdownNow(); + } + private void runLanguageServer(@NotNull DBLServerSessionProvider sessionProvider) { try { - outputStream.close(); + wsToServer = new PipedOutputStream(); + serverIn = new PipedInputStream(wsToServer, 64 * 1024); + + serverOut = new PipedOutputStream(); + serverToWs = new PipedInputStream(serverOut, 64 * 1024); + + ioExecutor.submit(() -> pumpToWebSocket(serverToWs)); + + ioExecutor.submit(() -> { + try (InputStream in = serverIn; OutputStream out = serverOut) { + DBLFacade.runLanguageServer(in, out, sessionProvider); + } catch (Exception e) { + if (!closed.get()) { + log.error("LSP server terminated with an exception", e); + } + } finally { + close(); + } + }); } catch (IOException e) { - log.debug("Error closing LSP output stream", e); + throw new IllegalStateException("Error initializing websocket <-> stream bridge", e); } } - private void runLanguageServer(@NotNull DBLServerSessionProvider sessionProvider) { - try (InputStream in = inputStream; OutputStream out = outputStream) { - DBLFacade.runLanguageServer(in, out, sessionProvider); - } catch (Throwable e) { - log.error("Error starting LSP server", e); + private void pumpToWebSocket(@NotNull InputStream in) { + byte[] buf = new byte[16 * 1024]; + try { + int n; + while (!closed.get() && (n = in.read(buf)) >= 0) { + if (n == 0) { + continue; + } + ByteBuffer payload = ByteBuffer.wrap(buf, 0, n); + wsSession.getAsyncRemote().sendBinary(payload); + } + } catch (IOException e) { + if (!closed.get()) { + log.error("Error forwarding LSP server output stream to websocket", e); + } } finally { close(); } From d0a5c1304ae3025b37244ce38a40f504f98cd6ce Mon Sep 17 00:00:00 2001 From: Dmitrii Barnukov Date: Thu, 12 Mar 2026 15:56:02 +0100 Subject: [PATCH 6/6] dbeaver/pro#7844 switch lsp ws impl --- .../server/jetty/CBJettyServer.java | 4 +- .../META-INF/MANIFEST.MF | 6 +- ...bSocket.java => LSPWebSocketEndpoint.java} | 22 ++- .../lsp/LSPWebSocketMessageHandler.java | 162 ------------------ 4 files changed, 22 insertions(+), 172 deletions(-) rename server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/{LSPWebSocket.java => LSPWebSocketEndpoint.java} (68%) delete mode 100644 server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketMessageHandler.java diff --git a/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/jetty/CBJettyServer.java b/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/jetty/CBJettyServer.java index a89128cec61..6a471f65c93 100644 --- a/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/jetty/CBJettyServer.java +++ b/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/jetty/CBJettyServer.java @@ -29,8 +29,8 @@ import io.cloudbeaver.server.websockets.CBEventsLongPollingServlet; import io.cloudbeaver.server.websockets.CBEventsWebSocket; import io.cloudbeaver.server.websockets.CBWebSocketServerConfigurator; -import io.cloudbeaver.server.websockets.lsp.LSPWebSocket; import io.cloudbeaver.server.websockets.lsp.LSPWebSocketConstants; +import io.cloudbeaver.server.websockets.lsp.LSPWebSocketEndpoint; import io.cloudbeaver.service.DBWServiceBindingServlet; import io.cloudbeaver.service.DBWServiceBindingWebSocket; import jakarta.websocket.server.ServerEndpointConfig; @@ -189,7 +189,7 @@ public void runServer() { ServerEndpointConfig lspWsEndpoint = ServerEndpointConfig.Builder .create( - LSPWebSocket.class, + LSPWebSocketEndpoint.class, serverConfiguration.getServicesURI() + LSPWebSocketConstants.ENDPOINT_SUFFIX ).configurator(configurator) .build(); diff --git a/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF index 1dea39edd92..d9ee05cc331 100644 --- a/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF +++ b/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF @@ -20,7 +20,8 @@ Require-Bundle: org.eclipse.core.runtime;visibility:=reexport, org.jkiss.dbeaver.net.ssh, io.cloudbeaver.model;visibility:=reexport, io.cloudbeaver.model.cli, - io.cloudbeaver.service.security;visibility:=reexport + io.cloudbeaver.service.security;visibility:=reexport, + org.eclipse.lsp4j.websocket.jakarta Export-Package: io.cloudbeaver, io.cloudbeaver.model, io.cloudbeaver.model.app, @@ -38,6 +39,7 @@ Export-Package: io.cloudbeaver, io.cloudbeaver.service, io.cloudbeaver.service.navigator, io.cloudbeaver.service.sql -Import-Package: org.slf4j +Import-Package: org.slf4j, + jakarta.websocket Bundle-Localization: OSGI-INF/l10n/bundle Automatic-Module-Name: io.cloudbeaver.server diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocket.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketEndpoint.java similarity index 68% rename from server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocket.java rename to server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketEndpoint.java index b6b337f5fb1..e7493567ff7 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocket.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketEndpoint.java @@ -23,30 +23,40 @@ import jakarta.websocket.CloseReason; import jakarta.websocket.EndpointConfig; import jakarta.websocket.Session; +import org.eclipse.lsp4j.jsonrpc.Launcher; +import org.eclipse.lsp4j.services.LanguageClient; +import org.eclipse.lsp4j.websocket.jakarta.WebSocketLauncherBuilder; import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.model.lsp.DBLServer; -public abstract class LSPWebSocket extends CBAbstractWebSocket { - private static final Log log = Log.getLog(LSPWebSocket.class); +public class LSPWebSocketEndpoint extends CBAbstractWebSocket { + private static final Log log = Log.getLog(LSPWebSocketEndpoint.class); - private LSPWebSocketMessageHandler handler; + public LSPWebSocketEndpoint() {} @Override public void onOpen(Session wsSession, EndpointConfig endpointConfig) { BaseWebSession webSession = (BaseWebSession) wsSession.getUserProperties() .get(CBWebSocketServerConfigurator.PROP_WEB_SESSION); - handler = new LSPWebSocketMessageHandler(wsSession, webSession); - wsSession.addMessageHandler(handler); CBJettyWebSocketManager.registerWebSocket(webSession.getSessionId(), this); wsSession.setMaxIdleTimeout(LSPWebSocketConstants.IDLE_TIMEOUT.toMillis()); wsSession.setMaxTextMessageBufferSize(Integer.MAX_VALUE); wsSession.setMaxBinaryMessageBufferSize(Integer.MAX_VALUE); + + LSPWebServerSesssionProvider sessionProvider = new LSPWebServerSesssionProvider(webSession); + DBLServer server = new DBLServer(sessionProvider); + var builder = new WebSocketLauncherBuilder(); + builder.setSession(wsSession); + builder.setLocalService(server); + builder.setRemoteInterface(LanguageClient.class); + Launcher launcher = builder.create(); + server.connect(launcher.getRemoteProxy()); } @Override public void onClose(Session session, CloseReason closeReason) { log.debug("Closing websocket session: " + session.getId()); - handler.close(); } @Override diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketMessageHandler.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketMessageHandler.java deleted file mode 100644 index 53c8a34b4d5..00000000000 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketMessageHandler.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * DBeaver - Universal Database Manager - * Copyright (C) 2010-2026 DBeaver Corp - * - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of DBeaver Corp and its suppliers, if any. - * The intellectual and technical concepts contained - * herein are proprietary to DBeaver Corp and its suppliers - * and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from DBeaver Corp. - */ -package io.cloudbeaver.server.websockets.lsp; - -import io.cloudbeaver.model.session.BaseWebSession; -import jakarta.websocket.MessageHandler; -import jakarta.websocket.Session; -import org.jkiss.code.NotNull; -import org.jkiss.dbeaver.Log; -import org.jkiss.dbeaver.model.lsp.DBLFacade; -import org.jkiss.dbeaver.model.lsp.DBLServerSessionProvider; - -import java.io.*; -import java.nio.ByteBuffer; -import java.util.Objects; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; - -public class LSPWebSocketMessageHandler implements MessageHandler.Whole, AutoCloseable { - private static final Log log = Log.getLog(LSPWebSocketMessageHandler.class); - - private final Session wsSession; - - private final AtomicBoolean closed = new AtomicBoolean(false); - private final ExecutorService ioExecutor = Executors.newFixedThreadPool(2, r -> { - Thread t = new Thread(r, "lsp-ws-message-handler"); - t.setDaemon(true); - return t; - }); - - private volatile PipedOutputStream wsToServer; - private volatile PipedInputStream serverIn; - - private volatile PipedOutputStream serverOut; - private volatile PipedInputStream serverToWs; - - public LSPWebSocketMessageHandler(@NotNull Session wsSession, @NotNull BaseWebSession webSession) { - this.wsSession = Objects.requireNonNull(wsSession, "wsSession"); - - DBLServerSessionProvider sessionProvider = new LSPWebServerSesssionProvider(webSession); - runLanguageServer(sessionProvider); - } - - @Override - public void onMessage(@NotNull ByteBuffer byteBuffer) { - if (closed.get()) { - return; - } - if (wsToServer == null) { - log.debug("LSP websocket message received before bridge initialization; ignoring"); - return; - } - - try { - if (byteBuffer.hasArray()) { - int offset = byteBuffer.arrayOffset() + byteBuffer.position(); - int len = byteBuffer.remaining(); - wsToServer.write(byteBuffer.array(), offset, len); - byteBuffer.position(byteBuffer.limit()); - } else { - byte[] tmp = new byte[Math.min(byteBuffer.remaining(), 16 * 1024)]; - while (byteBuffer.hasRemaining()) { - int n = Math.min(byteBuffer.remaining(), tmp.length); - byteBuffer.get(tmp, 0, n); - wsToServer.write(tmp, 0, n); - } - } - wsToServer.flush(); - } catch (IOException e) { - if (!closed.get()) { - log.error("Error forwarding websocket bytes to LSP server input stream", e); - close(); - } - } - } - - @Override - public void close() { - if (!closed.compareAndSet(false, true)) { - return; - } - - try { - if (wsToServer != null) { - wsToServer.close(); - } - if (serverToWs != null) { - serverToWs.close(); - } - if (serverIn != null) { - serverIn.close(); - } - if (serverOut != null) { - serverOut.close(); - } - } catch (IOException e) { - log.debug("Error closing lspWs pipes", e); - } - ioExecutor.shutdownNow(); - } - - private void runLanguageServer(@NotNull DBLServerSessionProvider sessionProvider) { - try { - wsToServer = new PipedOutputStream(); - serverIn = new PipedInputStream(wsToServer, 64 * 1024); - - serverOut = new PipedOutputStream(); - serverToWs = new PipedInputStream(serverOut, 64 * 1024); - - ioExecutor.submit(() -> pumpToWebSocket(serverToWs)); - - ioExecutor.submit(() -> { - try (InputStream in = serverIn; OutputStream out = serverOut) { - DBLFacade.runLanguageServer(in, out, sessionProvider); - } catch (Exception e) { - if (!closed.get()) { - log.error("LSP server terminated with an exception", e); - } - } finally { - close(); - } - }); - } catch (IOException e) { - throw new IllegalStateException("Error initializing websocket <-> stream bridge", e); - } - } - - private void pumpToWebSocket(@NotNull InputStream in) { - byte[] buf = new byte[16 * 1024]; - try { - int n; - while (!closed.get() && (n = in.read(buf)) >= 0) { - if (n == 0) { - continue; - } - ByteBuffer payload = ByteBuffer.wrap(buf, 0, n); - wsSession.getAsyncRemote().sendBinary(payload); - } - } catch (IOException e) { - if (!closed.get()) { - log.error("Error forwarding LSP server output stream to websocket", e); - } - } finally { - close(); - } - } -}