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..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,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.LSPWebSocketConstants; +import io.cloudbeaver.server.websockets.lsp.LSPWebSocketEndpoint; 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( + LSPWebSocketEndpoint.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 1a086668b65..13c821402a5 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, @@ -19,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, @@ -33,9 +35,11 @@ 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 -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/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.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebServerSesssionProvider.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebServerSesssionProvider.java new file mode 100644 index 00000000000..c456010c4bd --- /dev/null +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebServerSesssionProvider.java @@ -0,0 +1,46 @@ +/* + * 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 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.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketConstants.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketConstants.java new file mode 100644 index 00000000000..2faf2c4b8ee --- /dev/null +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketConstants.java @@ -0,0 +1,26 @@ +/* + * 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; + +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.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketEndpoint.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketEndpoint.java new file mode 100644 index 00000000000..e7493567ff7 --- /dev/null +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketEndpoint.java @@ -0,0 +1,66 @@ +/* + * 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 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.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 class LSPWebSocketEndpoint extends CBAbstractWebSocket { + private static final Log log = Log.getLog(LSPWebSocketEndpoint.class); + + public LSPWebSocketEndpoint() {} + + @Override + public void onOpen(Session wsSession, EndpointConfig endpointConfig) { + BaseWebSession webSession = (BaseWebSession) wsSession.getUserProperties() + .get(CBWebSocketServerConfigurator.PROP_WEB_SESSION); + 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()); + } + + @Override + public void onError(Session session, Throwable thr) { + log.error("LSP WebSocket error", thr); + } +}