diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 1db4798379..b0cfd8d15d 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -15,7 +15,6 @@
"streetsidesoftware.code-spell-checker",
"streetsidesoftware.code-spell-checker-russian",
"syler.sass-indented",
- "VisualStudioExptTeam.intellicode-api-usage-examples",
"VisualStudioExptTeam.vscodeintellicode",
"yzhang.markdown-all-in-one",
"GraphQL.vscode-graphql-syntax",
diff --git a/README.md b/README.md
index b73090cf7e..5bcf48ae8f 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,16 @@
-# CloudBeaver Community
+
-
+# CloudBeaver Community
Cloud Database Manager - Community Edition.
CloudBeaver is a web server that provides a rich web interface. The server itself is a Java application, and the web part is written in TypeScript and React.
It is free to use and open-source (licensed under [Apache 2](https://github.com/dbeaver/cloudbeaver/blob/devel/LICENSE) license).
See our [WIKI](https://github.com/dbeaver/cloudbeaver/wiki) for more details.
-![](https://github.com/dbeaver/cloudbeaver/wiki/images/demo_screenshot_1.png)
+
+
+
+
## Run in Docker
diff --git a/config/sample-databases/DefaultConfiguration/cloudbeaver.conf b/config/sample-databases/DefaultConfiguration/cloudbeaver.conf
index 08112c854e..e063bf739b 100644
--- a/config/sample-databases/DefaultConfiguration/cloudbeaver.conf
+++ b/config/sample-databases/DefaultConfiguration/cloudbeaver.conf
@@ -1,8 +1,8 @@
{
server: {
- serverPort: 8978,
+ serverPort: "${CLOUDBEAVER_SERVICE_PORT:8978}",
- workspaceLocation: "workspace",
+ workspaceLocation: "${CLOUDBEAVER_WORKSPACE_LOCATION:workspace}",
contentRoot: "web",
driversLocation: "drivers",
@@ -27,9 +27,9 @@
sql.proposals.insert.table.alias: PLAIN
},
- expireSessionAfterPeriod: 1800000,
+ expireSessionAfterPeriod: "${CLOUDBEAVER_EXPIRE_SESSION_AFTER_PERIOD:1800000}",
- develMode: false,
+ develMode: "${CLOUDBEAVER_DEVEL_MODE:false}",
enableSecurityManager: false,
@@ -64,29 +64,28 @@
},
app: {
- anonymousAccessEnabled: true,
- anonymousUserRole: "user",
- defaultUserTeam: "user",
- grantConnectionsAccessToAnonymousTeam: false,
- supportsCustomConnections: false,
- showReadOnlyConnectionInfo: false,
+ anonymousAccessEnabled: "${CLOUDBEAVER_APP_ANONYMOUS_ACCESS_ENABLED:true}",
+ anonymousUserRole: user,
+ defaultUserTeam: "${CLOUDBEAVER_APP_DEFAULT_USER_TEAM:user}",
+ grantConnectionsAccessToAnonymousTeam: "${CLOUDBEAVER_APP_GRANT_CONNECTIONS_ACCESS_TO_ANONYMOUS_TEAM:false}",
+ supportsCustomConnections: "${CLOUDBEAVER_APP_SUPPORTS_CUSTOM_CONNECTIONS:false}",
+ showReadOnlyConnectionInfo: "${CLOUDBEAVER_APP_READ_ONLY_CONNECTION_INFO:false}",
systemVariablesResolvingEnabled: "${CLOUDBEAVER_SYSTEM_VARIABLES_RESOLVING_ENABLED:false}",
- forwardProxy: false,
+ forwardProxy: "${CLOUDBEAVER_APP_FORWARD_PROXY:false}",
- publicCredentialsSaveEnabled: true,
- adminCredentialsSaveEnabled: true,
+ publicCredentialsSaveEnabled: "${CLOUDBEAVER_APP_PUBLIC_CREDENTIALS_SAVE_ENABLED:true}",
+ adminCredentialsSaveEnabled: "${CLOUDBEAVER_APP_ADMIN_CREDENTIALS_SAVE_ENABLED:true}",
- resourceManagerEnabled: true,
+ resourceManagerEnabled: "${CLOUDBEAVER_APP_RESOURCE_MANAGER_ENABLED:true}",
resourceQuotas: {
- dataExportFileSizeLimit: 10000000,
- resourceManagerFileSizeLimit: 500000,
- sqlMaxRunningQueries: 100,
- sqlResultSetRowsLimit: 100000,
- sqlResultSetMemoryLimit: 2000000,
- sqlTextPreviewMaxLength: 4096,
- sqlBinaryPreviewMaxLength: 261120
+ dataExportFileSizeLimit: "${CLOUDBEAVER_RESOURCE_QUOTA_DATA_EXPORT_FILE_SIZE_LIMIT:10000000}",
+ resourceManagerFileSizeLimit: "${CLOUDBEAVER_RESOURCE_QUOTA_RESOURCE_MANAGER_FILE_SIZE_LIMIT:500000}",
+ sqlMaxRunningQueries: "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_MAX_RUNNING_QUERIES:100}",
+ sqlResultSetRowsLimit: "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_RESULT_SET_ROWS_LIMIT:100000}",
+ sqlTextPreviewMaxLength: "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_TEXT_PREVIEW_MAX_LENGTH:4096}",
+ sqlBinaryPreviewMaxLength: "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_BINARY_PREVIEW_MAX_LENGTH:261120}"
},
enabledAuthProviders: [
"local"
diff --git a/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf b/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf
index efc8a5732f..3c96c0389e 100644
--- a/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf
+++ b/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf
@@ -1,8 +1,8 @@
{
server: {
- serverPort: 8978,
+ serverPort: "${CLOUDBEAVER_SERVICE_PORT:8978}",
- workspaceLocation: "workspace",
+ workspaceLocation: "${CLOUDBEAVER_WORKSPACE_LOCATION:workspace}",
contentRoot: "web",
driversLocation: "drivers",
@@ -25,9 +25,9 @@
sql.proposals.insert.table.alias: PLAIN
},
- expireSessionAfterPeriod: 1800000,
+ expireSessionAfterPeriod: "${CLOUDBEAVER_EXPIRE_SESSION_AFTER_PERIOD:1800000}",
- develMode: false,
+ develMode: "${CLOUDBEAVER_DEVEL_MODE:false}",
enableSecurityManager: false,
@@ -61,28 +61,27 @@
},
app: {
- anonymousAccessEnabled: true,
- anonymousUserRole: "user",
- grantConnectionsAccessToAnonymousTeam: false,
- supportsCustomConnections: false,
- showReadOnlyConnectionInfo: false,
+ anonymousAccessEnabled: "${CLOUDBEAVER_APP_ANONYMOUS_ACCESS_ENABLED:true}",
+ anonymousUserRole: user,
+ grantConnectionsAccessToAnonymousTeam: "${CLOUDBEAVER_APP_GRANT_CONNECTIONS_ACCESS_TO_ANONYMOUS_TEAM:false}",
+ supportsCustomConnections: "${CLOUDBEAVER_APP_SUPPORTS_CUSTOM_CONNECTIONS:false}",
+ showReadOnlyConnectionInfo: "${CLOUDBEAVER_APP_READ_ONLY_CONNECTION_INFO:false}",
systemVariablesResolvingEnabled: "${CLOUDBEAVER_SYSTEM_VARIABLES_RESOLVING_ENABLED:false}",
- forwardProxy: false,
+ forwardProxy: "${CLOUDBEAVER_APP_FORWARD_PROXY:false}",
- publicCredentialsSaveEnabled: true,
- adminCredentialsSaveEnabled: true,
+ publicCredentialsSaveEnabled: "${CLOUDBEAVER_APP_PUBLIC_CREDENTIALS_SAVE_ENABLED:true}",
+ adminCredentialsSaveEnabled: "${CLOUDBEAVER_APP_ADMIN_CREDENTIALS_SAVE_ENABLED:true}",
- resourceManagerEnabled: true,
+ resourceManagerEnabled: "${CLOUDBEAVER_APP_RESOURCE_MANAGER_ENABLED:true}",
resourceQuotas: {
- dataExportFileSizeLimit: 10000000,
- resourceManagerFileSizeLimit: 500000,
- sqlMaxRunningQueries: 100,
- sqlResultSetRowsLimit: 100000,
- sqlResultSetMemoryLimit: 2000000,
- sqlTextPreviewMaxLength: 4096,
- sqlBinaryPreviewMaxLength: 261120
+ dataExportFileSizeLimit: "${CLOUDBEAVER_RESOURCE_QUOTA_DATA_EXPORT_FILE_SIZE_LIMIT:10000000}",
+ resourceManagerFileSizeLimit: "${CLOUDBEAVER_RESOURCE_QUOTA_RESOURCE_MANAGER_FILE_SIZE_LIMIT:500000}",
+ sqlMaxRunningQueries: "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_MAX_RUNNING_QUERIES:100}",
+ sqlResultSetRowsLimit: "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_RESULT_SET_ROWS_LIMIT:100000}",
+ sqlTextPreviewMaxLength: "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_TEXT_PREVIEW_MAX_LENGTH:4096}",
+ sqlBinaryPreviewMaxLength: "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_BINARY_PREVIEW_MAX_LENGTH:261120}"
},
enabledAuthProviders: [
"local"
diff --git a/deploy/docker/cloudbeaver-ce/Dockerfile b/deploy/docker/cloudbeaver-ce/Dockerfile
index 361df92c0a..95bdcc6812 100644
--- a/deploy/docker/cloudbeaver-ce/Dockerfile
+++ b/deploy/docker/cloudbeaver-ce/Dockerfile
@@ -2,6 +2,9 @@ FROM dbeaver/base-java
MAINTAINER DBeaver Corp, devops@dbeaver.com
+RUN apt-get update; \
+ apt-get upgrade -y;
+
COPY cloudbeaver /opt/cloudbeaver
EXPOSE 8978
diff --git a/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF
index 6a557347eb..226b75a8b0 100644
--- a/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF
+++ b/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
-Bundle-Version: 1.0.61.qualifier
-Bundle-Release-Date: 20240923
+Bundle-Version: 1.0.62.qualifier
+Bundle-Release-Date: 20241007
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -19,7 +19,7 @@ Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
org.jkiss.bundle.graphql.java;visibility:=reexport,
org.jkiss.bundle.apache.dbcp,
com.google.gson;visibility:=reexport,
- jakarta.servlet;visibility:=reexport
+ jakarta.servlet-api;bundle-version:="6.0.0";visibility:=reexport
Export-Package: io.cloudbeaver,
io.cloudbeaver.auth,
io.cloudbeaver.auth.provider,
diff --git a/server/bundles/io.cloudbeaver.model/pom.xml b/server/bundles/io.cloudbeaver.model/pom.xml
index feddc91ae6..421eb0e899 100644
--- a/server/bundles/io.cloudbeaver.model/pom.xml
+++ b/server/bundles/io.cloudbeaver.model/pom.xml
@@ -10,7 +10,7 @@
../
io.cloudbeaver.model
- 1.0.61-SNAPSHOT
+ 1.0.62-SNAPSHOT
eclipse-plugin
diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebHttpRequestInfo.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebHttpRequestInfo.java
new file mode 100644
index 0000000000..a6dde6469d
--- /dev/null
+++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebHttpRequestInfo.java
@@ -0,0 +1,57 @@
+/*
+ * DBeaver - Universal Database Manager
+ * Copyright (C) 2010-2024 DBeaver Corp and others
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.cloudbeaver.model.session;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+public class WebHttpRequestInfo {
+
+ private final String id;
+ private final Object locale;
+ private final String lastRemoteAddress;
+ private final String lastRemoteUserAgent;
+
+ public WebHttpRequestInfo(HttpServletRequest request) {
+ this.id = request.getSession().getId();
+ this.locale = request.getAttribute("locale");
+ this.lastRemoteAddress = request.getRemoteAddr();
+ this.lastRemoteUserAgent = request.getHeader("User-Agent");
+ }
+
+ public WebHttpRequestInfo(String id, Object locale, String lastRemoteAddress, String lastRemoteUserAgent) {
+ this.id = id;
+ this.locale = locale;
+ this.lastRemoteAddress = lastRemoteAddress;
+ this.lastRemoteUserAgent = lastRemoteUserAgent;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public Object getLocale() {
+ return locale;
+ }
+
+ public String getLastRemoteAddress() {
+ return lastRemoteAddress;
+ }
+
+ public String getLastRemoteUserAgent() {
+ return lastRemoteUserAgent;
+ }
+}
diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java
index 5b1d052433..bf0bc809c9 100644
--- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java
+++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java
@@ -33,7 +33,6 @@
import io.cloudbeaver.utils.CBModelConstants;
import io.cloudbeaver.utils.WebAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
-import jakarta.servlet.http.HttpServletRequest;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
@@ -124,13 +123,13 @@ public class WebSession extends BaseWebSession
private final Map sessionHandlers;
public WebSession(
- @NotNull HttpServletRequest request,
+ @NotNull WebHttpRequestInfo requestInfo,
@NotNull WebAuthApplication application,
@NotNull Map sessionHandlers
) throws DBException {
- super(request.getSession().getId(), application);
+ super(requestInfo.getId(), application);
this.lastAccessTime = this.createTime;
- setLocale(CommonUtils.toString(request.getSession().getAttribute(ATTR_LOCALE), this.locale));
+ setLocale(CommonUtils.toString(requestInfo.getLocale(), this.locale));
this.sessionHandlers = sessionHandlers;
//force authorization of anonymous session to avoid access error,
//because before authorization could be called by any request,
@@ -138,7 +137,7 @@ public WebSession(
//and the order of requests is not guaranteed.
//look at CB-4747
refreshSessionAuth();
- updateSessionParameters(request);
+ updateSessionParameters(requestInfo);
}
@Nullable
@@ -558,9 +557,9 @@ public synchronized void updateInfo(boolean isOldHttpSessionUsed) {
}
}
- public synchronized void updateSessionParameters(HttpServletRequest request) {
- this.lastRemoteAddr = request.getRemoteAddr();
- this.lastRemoteUserAgent = request.getHeader("User-Agent");
+ public synchronized void updateSessionParameters(WebHttpRequestInfo requestInfo) {
+ this.lastRemoteAddr = requestInfo.getLastRemoteAddress();
+ this.lastRemoteUserAgent = requestInfo.getLastRemoteUserAgent();
this.cacheExpired = false;
}
diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/service/sql/WebSQLConstants.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/service/sql/WebSQLConstants.java
index a8ff1bd845..561162394c 100644
--- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/service/sql/WebSQLConstants.java
+++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/service/sql/WebSQLConstants.java
@@ -22,7 +22,6 @@
public class WebSQLConstants {
public static final String QUOTA_PROP_ROW_LIMIT = "sqlResultSetRowsLimit";
- public static final String QUOTA_PROP_MEMORY_LIMIT = "sqlResultSetMemoryLimit";
public static final String QUOTA_PROP_QUERY_LIMIT = "sqlMaxRunningQueries";
public static final String QUOTA_PROP_SQL_QUERY_TIMEOUT = "sqlQueryTimeout";
public static final String QUOTA_PROP_TEXT_PREVIEW_MAX_LENGTH = "sqlTextPreviewMaxLength";
diff --git a/server/bundles/io.cloudbeaver.product.ce/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.product.ce/META-INF/MANIFEST.MF
index e352f3816b..f10b82c773 100644
--- a/server/bundles/io.cloudbeaver.product.ce/META-INF/MANIFEST.MF
+++ b/server/bundles/io.cloudbeaver.product.ce/META-INF/MANIFEST.MF
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Community Product
Bundle-SymbolicName: io.cloudbeaver.product.ce;singleton:=true
-Bundle-Version: 24.2.1.qualifier
-Bundle-Release-Date: 20240923
+Bundle-Version: 24.2.2.qualifier
+Bundle-Release-Date: 20241007
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
diff --git a/server/bundles/io.cloudbeaver.product.ce/pom.xml b/server/bundles/io.cloudbeaver.product.ce/pom.xml
index daafc53e12..74fb225380 100644
--- a/server/bundles/io.cloudbeaver.product.ce/pom.xml
+++ b/server/bundles/io.cloudbeaver.product.ce/pom.xml
@@ -10,7 +10,7 @@
../
io.cloudbeaver.product.ce
- 24.2.1-SNAPSHOT
+ 24.2.2-SNAPSHOT
eclipse-plugin
diff --git a/server/bundles/io.cloudbeaver.resources.drivers.base/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.resources.drivers.base/META-INF/MANIFEST.MF
index 8cbfa5fc84..1a26e7e53e 100644
--- a/server/bundles/io.cloudbeaver.resources.drivers.base/META-INF/MANIFEST.MF
+++ b/server/bundles/io.cloudbeaver.resources.drivers.base/META-INF/MANIFEST.MF
@@ -2,8 +2,8 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Base JDBC drivers
Bundle-SymbolicName: io.cloudbeaver.resources.drivers.base;singleton:=true
-Bundle-Version: 1.0.106.qualifier
-Bundle-Release-Date: 20240923
+Bundle-Version: 1.0.107.qualifier
+Bundle-Release-Date: 20241007
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
diff --git a/server/bundles/io.cloudbeaver.resources.drivers.base/pom.xml b/server/bundles/io.cloudbeaver.resources.drivers.base/pom.xml
index 6325eb04b9..8831414e5b 100644
--- a/server/bundles/io.cloudbeaver.resources.drivers.base/pom.xml
+++ b/server/bundles/io.cloudbeaver.resources.drivers.base/pom.xml
@@ -9,6 +9,6 @@
../
io.cloudbeaver.resources.drivers.base
- 1.0.106-SNAPSHOT
+ 1.0.107-SNAPSHOT
eclipse-plugin
diff --git a/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF
index 15de9b77ce..93455b8266 100644
--- a/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF
+++ b/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Server
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
-Bundle-Version: 24.2.1.qualifier
-Bundle-Release-Date: 20240923
+Bundle-Version: 24.2.2.qualifier
+Bundle-Release-Date: 20241007
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
diff --git a/server/bundles/io.cloudbeaver.server/pom.xml b/server/bundles/io.cloudbeaver.server/pom.xml
index cf53c858bd..1b33a40193 100644
--- a/server/bundles/io.cloudbeaver.server/pom.xml
+++ b/server/bundles/io.cloudbeaver.server/pom.xml
@@ -10,7 +10,7 @@
../
io.cloudbeaver.server
- 24.2.1-SNAPSHOT
+ 24.2.2-SNAPSHOT
eclipse-plugin
diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/graphql/GraphQLEndpoint.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/graphql/GraphQLEndpoint.java
index a0ee694bae..c3d9bf0f91 100644
--- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/graphql/GraphQLEndpoint.java
+++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/graphql/GraphQLEndpoint.java
@@ -23,6 +23,7 @@
import graphql.language.SourceLocation;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.GraphQLSchema;
+import graphql.schema.PropertyDataFetcherHelper;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
@@ -77,6 +78,7 @@ public class GraphQLEndpoint extends HttpServlet {
public GraphQLEndpoint() {
GraphQLSchema schema = buildSchema();
+ PropertyDataFetcherHelper.setUseLambdaFactory(false);
graphQL = GraphQL
.newGraphQL(schema)
.instrumentation(new SimplePerformantInstrumentation())
diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBJettyServer.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBJettyServer.java
index f30a4a1e46..ddf67a7d43 100644
--- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBJettyServer.java
+++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBJettyServer.java
@@ -23,18 +23,16 @@
import io.cloudbeaver.server.servlets.CBImageServlet;
import io.cloudbeaver.server.servlets.CBStaticServlet;
import io.cloudbeaver.server.servlets.CBStatusServlet;
+import io.cloudbeaver.server.servlets.ProxyResourceHandler;
import io.cloudbeaver.server.websockets.CBJettyWebSocketManager;
import io.cloudbeaver.service.DBWServiceBindingServlet;
+import org.eclipse.jetty.ee10.servlet.*;
import org.eclipse.jetty.server.*;
-import org.eclipse.jetty.server.session.DefaultSessionCache;
-import org.eclipse.jetty.server.session.DefaultSessionIdManager;
-import org.eclipse.jetty.server.session.NullSessionDataStore;
-import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.servlet.ServletMapping;
-import org.eclipse.jetty.util.resource.PathResource;
-import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
+import org.eclipse.jetty.session.DefaultSessionCache;
+import org.eclipse.jetty.session.DefaultSessionIdManager;
+import org.eclipse.jetty.session.NullSessionDataStore;
+import org.eclipse.jetty.util.resource.ResourceFactory;
+import org.eclipse.jetty.websocket.server.WebSocketUpgradeHandler;
import org.eclipse.jetty.xml.XmlConfiguration;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
@@ -66,15 +64,15 @@ public CBJettyServer(@NotNull CBApplication> application) {
public void runServer() {
try {
CBServerConfig serverConfiguration = application.getServerConfiguration();
- JettyServer server;
+ Server server;
int serverPort = serverConfiguration.getServerPort();
String serverHost = serverConfiguration.getServerHost();
Path sslPath = getSslConfigurationPath();
boolean sslConfigurationExists = sslPath != null && Files.exists(sslPath);
if (sslConfigurationExists) {
- server = new JettyServer();
- XmlConfiguration sslConfiguration = new XmlConfiguration(new PathResource(sslPath));
+ server = new Server();
+ XmlConfiguration sslConfiguration = new XmlConfiguration(ResourceFactory.of(server).newResource(sslPath));
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// method sslConfiguration.configure() does not see the context class of the Loader,
// so we have to configure it manually, then return the old classLoader.
@@ -83,23 +81,30 @@ public void runServer() {
Thread.currentThread().setContextClassLoader(classLoader);
} else {
if (CommonUtils.isEmpty(serverHost)) {
- server = new JettyServer(serverPort);
+ server = new Server(serverPort);
} else {
- server = new JettyServer(
+ server = new Server(
InetSocketAddress.createUnresolved(serverHost, serverPort));
}
}
{
// Handler configuration
+ Path contentRootPath = Path.of(serverConfiguration.getContentRoot());
ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
- servletContextHandler.setResourceBase(serverConfiguration.getContentRoot());
+ servletContextHandler.setBaseResourceAsPath(contentRootPath);
String rootURI = serverConfiguration.getRootURI();
servletContextHandler.setContextPath(rootURI);
ServletHolder staticServletHolder = new ServletHolder("static", new CBStaticServlet());
staticServletHolder.setInitParameter("dirAllowed", "false");
- servletContextHandler.addServlet(staticServletHolder, "/*");
+ staticServletHolder.setInitParameter("cacheControl", "public, max-age=" + CBStaticServlet.STATIC_CACHE_SECONDS);
+ servletContextHandler.addServlet(staticServletHolder, "/");
+ servletContextHandler.insertHandler(new ProxyResourceHandler(Path.of(serverConfiguration.getContentRoot())));
+
+ if (Files.isSymbolicLink(contentRootPath)) {
+ servletContextHandler.addAliasCheck(new CBSymLinkContentAllowedAliasChecker(contentRootPath));
+ }
ServletHolder imagesServletHolder = new ServletHolder("images", new CBImageServlet());
servletContextHandler.addServlet(imagesServletHolder, serverConfiguration.getServicesURI() + "images/*");
@@ -120,12 +125,7 @@ public void runServer() {
}
}
- initSessionManager(this.application, servletContextHandler);
-
- server.setHandler(servletContextHandler);
-
- JettyWebSocketServletContainerInitializer.configure(servletContextHandler,
- (context, wsContainer) -> {
+ WebSocketUpgradeHandler webSocketHandler = WebSocketUpgradeHandler.from(server, servletContextHandler, (wsContainer) -> {
wsContainer.setIdleTimeout(Duration.ofMinutes(5));
// Add websockets
wsContainer.addMapping(
@@ -134,6 +134,12 @@ public void runServer() {
);
}
);
+ servletContextHandler.insertHandler(webSocketHandler);
+
+ initSessionManager(this.application, server, servletContextHandler);
+
+ server.setHandler(servletContextHandler);
+
ErrorPageErrorHandler errorHandler = new ErrorPageErrorHandler();
//errorHandler.addErrorPage(404, "/missing.html");
servletContextHandler.setErrorHandler(errorHandler);
@@ -180,10 +186,11 @@ private Path getSslConfigurationPath() {
private void initSessionManager(
@NotNull CBApplication> application,
+ @NotNull Server server,
@NotNull ServletContextHandler servletContextHandler
) {
// Init sessions persistence
- CBSessionHandler sessionHandler = new CBSessionHandler(application);
+ SessionHandler sessionHandler = new SessionHandler();
var maxIdleTime = application.getMaxSessionIdleTime();
int intMaxIdleSeconds;
if (maxIdleTime > Integer.MAX_VALUE) {
@@ -198,27 +205,10 @@ private void initSessionManager(
sessionCache.setSessionDataStore(new NullSessionDataStore());
sessionHandler.setSessionCache(sessionCache);
servletContextHandler.setSessionHandler(sessionHandler);
- }
-
- public static class JettyServer extends Server {
- public JettyServer(int serverPort) {
- super(serverPort);
- }
- public JettyServer() {
- super();
- }
- public JettyServer(InetSocketAddress addr) {
- super(addr);
- }
+ DefaultSessionIdManager idMgr = new DefaultSessionIdManager(server);
+ idMgr.setWorkerName(null);
+ server.addBean(idMgr, true);
- @Override
- public void setSessionIdManager(SessionIdManager sessionIdManager) {
- if (sessionIdManager instanceof DefaultSessionIdManager) {
- // Nullify worker name to avoid dummy prefixes in session ID cookie
- ((DefaultSessionIdManager) sessionIdManager).setWorkerName(null);
- }
- super.setSessionIdManager(sessionIdManager);
- }
}
}
\ No newline at end of file
diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBJettyServletContext.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBJettyServletContext.java
index 0579ff22b0..b04374442c 100644
--- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBJettyServletContext.java
+++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBJettyServletContext.java
@@ -19,8 +19,8 @@
import io.cloudbeaver.service.DBWServletContext;
import jakarta.servlet.http.HttpServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
+import org.eclipse.jetty.ee10.servlet.ServletHolder;
public class CBJettyServletContext implements DBWServletContext {
private final ServletContextHandler contextHandler;
diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBSessionHandler.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBSessionHandler.java
index 6a091573c2..907c9e3758 100644
--- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBSessionHandler.java
+++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBSessionHandler.java
@@ -18,8 +18,13 @@
import io.cloudbeaver.server.CBApplication;
import jakarta.servlet.SessionCookieConfig;
-import org.eclipse.jetty.http.Syntax;
-import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
+import org.eclipse.jetty.ee10.servlet.SessionHandler;
+
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
public class CBSessionHandler extends SessionHandler {
private final CBCookieConfig cbCookieConfig;
@@ -37,107 +42,147 @@ public SessionCookieConfig getSessionCookieConfig() {
}
- //mostly copy of org.eclipse.jetty.server.session.CookieConfig but allows to use dynamic setSecure flag
+ //mostly copy of org.eclipse.jetty.ee10.servlet.CookieConfig but allows to use dynamic setSecure flag
public final class CBCookieConfig implements SessionCookieConfig {
- public CBCookieConfig() {
+
+ @Override
+ public boolean isSecure() {
+ var serverUrl = CBSessionHandler.this.application.getServerURL();
+ return serverUrl != null && serverUrl.startsWith("https://");
}
+ @Override
public String getComment() {
- return CBSessionHandler.this._sessionComment;
+ return getSessionComment();
}
+ @Override
public String getDomain() {
- return CBSessionHandler.this._sessionDomain;
+ return getSessionDomain();
}
+ @Override
public int getMaxAge() {
- return CBSessionHandler.this._maxCookieAge;
+ return getMaxCookieAge();
}
+ @Override
+ public void setAttribute(String name, String value) {
+ checkState();
+ String lcase = name.toLowerCase(Locale.ENGLISH);
+
+ switch (lcase) {
+ case "name" -> setName(value);
+ case "max-age" -> setMaxAge(value == null ? -1 : Integer.parseInt(value));
+ case "comment" -> setComment(value);
+ case "domain" -> setDomain(value);
+ case "httponly" -> setHttpOnly(Boolean.parseBoolean(value));
+ case "secure" -> setSecure(Boolean.parseBoolean(value));
+ case "path" -> setPath(value);
+ default -> setSessionCookieAttribute(name, value);
+ }
+ }
+
+ @Override
+ public String getAttribute(String name) {
+ String lcase = name.toLowerCase(Locale.ENGLISH);
+ return switch (lcase) {
+ case "name" -> getName();
+ case "max-age" -> Integer.toString(getMaxAge());
+ case "comment" -> getComment();
+ case "domain" -> getDomain();
+ case "httponly" -> String.valueOf(isHttpOnly());
+ case "secure" -> String.valueOf(isSecure());
+ case "path" -> getPath();
+ default -> getSessionCookieAttribute(name);
+ };
+ }
+
+ /**
+ * According to the SessionCookieConfig javadoc, the attributes must also include
+ * all values set by explicit setters.
+ *
+ * @see SessionCookieConfig
+ */
+ @Override
+ public Map getAttributes() {
+ Map specials = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ specials.put("name", getAttribute("name"));
+ specials.put("max-age", getAttribute("max-age"));
+ specials.put("comment", getAttribute("comment"));
+ specials.put("domain", getAttribute("domain"));
+ specials.put("httponly", getAttribute("httponly"));
+ specials.put("secure", getAttribute("secure"));
+ specials.put("path", getAttribute("path"));
+ specials.putAll(getSessionCookieAttributes());
+ return Collections.unmodifiableMap(specials);
+ }
+
+ @Override
public String getName() {
- return CBSessionHandler.this._sessionCookie;
+ return getSessionCookie();
}
+ @Override
public String getPath() {
- return CBSessionHandler.this._sessionPath;
+ return getSessionPath();
}
+ @Override
public boolean isHttpOnly() {
- return CBSessionHandler.this._httpOnly;
- }
-
- public boolean isSecure() {
- var serverUrl = CBSessionHandler.this.application.getServerURL();
- return serverUrl != null && serverUrl.startsWith("https://");
+ return CBSessionHandler.this.isHttpOnly();
}
+ @Override
public void setComment(String comment) {
- if (CBSessionHandler.this._context != null && CBSessionHandler.this._context.getContextHandler()
- .isAvailable()) {
- throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
- } else {
- CBSessionHandler.this._sessionComment = comment;
- }
+ checkState();
+ CBSessionHandler.this.setSessionComment(comment);
}
+ @Override
public void setDomain(String domain) {
- if (CBSessionHandler.this._context != null && CBSessionHandler.this._context.getContextHandler()
- .isAvailable()) {
- throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
- } else {
- CBSessionHandler.this._sessionDomain = domain;
- }
+ checkState();
+ CBSessionHandler.this.setSessionDomain(domain);
}
+ @Override
public void setHttpOnly(boolean httpOnly) {
- if (CBSessionHandler.this._context != null && CBSessionHandler.this._context.getContextHandler()
- .isAvailable()) {
- throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
- } else {
- CBSessionHandler.this._httpOnly = httpOnly;
- }
+ checkState();
+ CBSessionHandler.this.setHttpOnly(httpOnly);
}
+ @Override
public void setMaxAge(int maxAge) {
- if (CBSessionHandler.this._context != null && CBSessionHandler.this._context.getContextHandler()
- .isAvailable()) {
- throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
- } else {
- CBSessionHandler.this._maxCookieAge = maxAge;
- }
+ checkState();
+ CBSessionHandler.this.setMaxCookieAge(maxAge);
}
+ @Override
public void setName(String name) {
- if (CBSessionHandler.this._context != null && CBSessionHandler.this._context.getContextHandler()
- .isAvailable()) {
- throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
- } else if ("".equals(name)) {
- throw new IllegalArgumentException("Blank cookie name");
- } else {
- if (name != null) {
- Syntax.requireValidRFC2616Token(name, "Bad Session cookie name");
- }
-
- CBSessionHandler.this._sessionCookie = name;
- }
+ checkState();
+ CBSessionHandler.this.setSessionCookie(name);
}
+ @Override
public void setPath(String path) {
- if (CBSessionHandler.this._context != null && CBSessionHandler.this._context.getContextHandler()
- .isAvailable()) {
- throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
- } else {
- CBSessionHandler.this._sessionPath = path;
- }
+ checkState();
+ CBSessionHandler.this.setSessionPath(path);
}
+ @Override
public void setSecure(boolean secure) {
- if (CBSessionHandler.this._context != null && CBSessionHandler.this._context.getContextHandler()
- .isAvailable()) {
+ checkState();
+ CBSessionHandler.this.setSecureCookies(secure);
+ }
+
+ private void checkState() {
+ //It is allowable to call the CookieConfig.setXX methods after the SessionHandler has started,
+ //but before the context has fully started. Ie it is allowable for ServletContextListeners
+ //to call these methods in contextInitialized().
+ ServletContextHandler handler = ServletContextHandler.getCurrentServletContextHandler();
+ if (handler != null && handler.isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
- } else {
- CBSessionHandler.this._secureCookies = secure;
- }
+
}
}
diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBSymLinkContentAllowedAliasChecker.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBSymLinkContentAllowedAliasChecker.java
new file mode 100644
index 0000000000..ecfc108878
--- /dev/null
+++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBSymLinkContentAllowedAliasChecker.java
@@ -0,0 +1,38 @@
+/*
+ * DBeaver - Universal Database Manager
+ * Copyright (C) 2010-2024 DBeaver Corp and others
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.cloudbeaver.server.jetty;
+
+import org.eclipse.jetty.server.AliasCheck;
+import org.eclipse.jetty.util.resource.Resource;
+import org.jkiss.code.NotNull;
+
+import java.nio.file.Path;
+
+public class CBSymLinkContentAllowedAliasChecker implements AliasCheck {
+ @NotNull
+ private final Path contentRootPath;
+
+ public CBSymLinkContentAllowedAliasChecker(@NotNull Path contentRootPath) {
+ this.contentRootPath = contentRootPath;
+ }
+
+ @Override
+ public boolean checkAlias(String pathInContext, Resource resource) {
+ Path resourcePath = resource.getPath();
+ return resourcePath != null && resourcePath.startsWith(contentRootPath);
+ }
+}
diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/servlets/CBStaticServlet.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/servlets/CBStaticServlet.java
index 11ad45034e..b191027991 100644
--- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/servlets/CBStaticServlet.java
+++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/servlets/CBStaticServlet.java
@@ -29,28 +29,19 @@
import io.cloudbeaver.server.CBAppConfig;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBPlatform;
-import io.cloudbeaver.server.CBServerConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.http.HttpContent;
-import org.eclipse.jetty.http.HttpField;
-import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.server.ResourceService;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.ee10.servlet.DefaultServlet;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.auth.SMAuthInfo;
import org.jkiss.dbeaver.model.auth.SMAuthProvider;
import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration;
import org.jkiss.utils.CommonUtils;
-import org.jkiss.utils.IOUtils;
-import java.io.*;
-import java.nio.charset.StandardCharsets;
-import java.util.Enumeration;
+import java.io.IOException;
import java.util.Map;
@WebServlet(urlPatterns = "/")
@@ -62,10 +53,6 @@ public class CBStaticServlet extends DefaultServlet {
private static final Log log = Log.getLog(CBStaticServlet.class);
- public CBStaticServlet() {
- super(makeResourceService());
- }
-
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
for (WebServletHandlerDescriptor handler : WebHandlerRegistry.getInstance().getServletHandlers()) {
@@ -189,46 +176,4 @@ private boolean processSessionStart(HttpServletRequest request, HttpServletRespo
return false;
}
- private static ResourceService makeResourceService() {
- ResourceService resourceService = new ProxyResourceService();
- resourceService.setCacheControl(new HttpField(HttpHeader.CACHE_CONTROL, "public, max-age=" + STATIC_CACHE_SECONDS));
- return resourceService;
- }
-
-
- private static class ProxyResourceService extends ResourceService {
- @Override
- protected boolean sendData(HttpServletRequest request, HttpServletResponse response, boolean include, HttpContent content, Enumeration reqRanges) throws IOException {
- String resourceName = content.getResource().getName();
- if (resourceName.endsWith("index.html") || resourceName.endsWith("sso.html")) {
- return patchIndexHtml(response, content);
- }
- return super.sendData(request, response, include, content, reqRanges);
- }
-
- private boolean patchIndexHtml(HttpServletResponse response, HttpContent content) throws IOException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- Resource resource = content.getResource();
- File file = resource.getFile();
- try (InputStream fis = new FileInputStream(file)) {
- IOUtils.copyStream(fis, baos);
- }
- String indexContents = baos.toString(StandardCharsets.UTF_8);
- CBServerConfig serverConfig = CBApplication.getInstance().getServerConfiguration();
- indexContents = indexContents
- .replace("{ROOT_URI}", serverConfig.getRootURI())
- .replace("{STATIC_CONTENT}", serverConfig.getStaticContent());
- byte[] indexBytes = indexContents.getBytes(StandardCharsets.UTF_8);
-
- putHeaders(response, content, indexBytes.length);
- // Disable cache for index.html
- response.setHeader(HttpHeader.CACHE_CONTROL.toString(), "no-cache, no-store, must-revalidate");
- response.setHeader(HttpHeader.EXPIRES.toString(), "0");
-
- response.getOutputStream().write(indexBytes);
-
- return true;
- }
- }
-
}
\ No newline at end of file
diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/servlets/CBStatusServlet.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/servlets/CBStatusServlet.java
index ceb4fdf748..53979a462c 100644
--- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/servlets/CBStatusServlet.java
+++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/servlets/CBStatusServlet.java
@@ -23,8 +23,7 @@
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.jkiss.dbeaver.DBException;
+import org.eclipse.jetty.ee10.servlet.DefaultServlet;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.utils.GeneralUtils;
diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/servlets/ProxyResourceHandler.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/servlets/ProxyResourceHandler.java
new file mode 100644
index 0000000000..12b89242f5
--- /dev/null
+++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/servlets/ProxyResourceHandler.java
@@ -0,0 +1,80 @@
+/*
+ * DBeaver - Universal Database Manager
+ * Copyright (C) 2010-2024 DBeaver Corp and others
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.cloudbeaver.server.servlets;
+
+import io.cloudbeaver.server.CBApplication;
+import io.cloudbeaver.server.CBServerConfig;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.Callback;
+import org.jkiss.code.NotNull;
+import org.jkiss.utils.IOUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class ProxyResourceHandler extends Handler.Wrapper {
+ @NotNull
+ private final Path contentRoot;
+
+ public ProxyResourceHandler(@NotNull Path contentRoot) {
+ this.contentRoot = contentRoot;
+ }
+
+ public boolean handle(Request request, Response response, Callback callback) throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ String pathInContext = Request.getPathInContext(request);
+
+ if ("/".equals(pathInContext)) {
+ pathInContext = "index.html";
+ }
+
+ if (pathInContext == null || !pathInContext.endsWith("index.html")
+ && !pathInContext.endsWith("sso.html")
+ && !pathInContext.endsWith("ssoError.html")
+ ) {
+ return super.handle(request, response, callback);
+ }
+
+ if (pathInContext.startsWith("/")) {
+ pathInContext = pathInContext.substring(1);
+ }
+ var filePath = contentRoot.resolve(pathInContext);
+ try (InputStream fis = Files.newInputStream(filePath)) {
+ IOUtils.copyStream(fis, baos);
+ }
+ String indexContents = baos.toString(StandardCharsets.UTF_8);
+ CBServerConfig serverConfig = CBApplication.getInstance().getServerConfiguration();
+ indexContents = indexContents
+ .replace("{ROOT_URI}", serverConfig.getRootURI())
+ .replace("{STATIC_CONTENT}", serverConfig.getStaticContent());
+ byte[] indexBytes = indexContents.getBytes(StandardCharsets.UTF_8);
+
+ // Disable cache for index.html
+ response.getHeaders().put(HttpHeader.CACHE_CONTROL.toString(), "no-cache, no-store, must-revalidate");
+ response.getHeaders().put(HttpHeader.EXPIRES.toString(), "0");
+
+ response.write(true, ByteBuffer.wrap(indexBytes), callback);
+ return true;
+ }
+}
diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/CBAbstractWebSocket.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/CBAbstractWebSocket.java
index 807814ea19..2dac868a86 100644
--- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/CBAbstractWebSocket.java
+++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/CBAbstractWebSocket.java
@@ -17,29 +17,31 @@
package io.cloudbeaver.server.websockets;
import com.google.gson.Gson;
-import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+import org.eclipse.jetty.websocket.api.Callback;
+import org.eclipse.jetty.websocket.api.Session;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.websocket.WSUtils;
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
-import java.io.IOException;
-
-public class CBAbstractWebSocket extends WebSocketAdapter {
+public class CBAbstractWebSocket extends Session.Listener.AbstractAutoDemanding {
private static final Log log = Log.getLog(CBAbstractWebSocket.class);
protected static final Gson gson = WSUtils.gson;
public void handleEvent(WSEvent event) {
- if (isNotConnected()) {
+ if (!isOpen()) {
return;
}
- try {
- getRemote().sendString(gson.toJson(event));
- } catch (IOException e) {
- handleEventException(e);
- }
+ Session session = getSession();
+ session.sendText(gson.toJson(event), new Callback() {
+ @Override
+ public void fail(Throwable e) {
+ handleEventException(e);
+ }
+ });
+
}
- protected void handleEventException(Exception e) {
+ protected void handleEventException(Throwable e) {
log.error("Failed to send websocket message", e);
}
diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/CBEventsWebSocket.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/CBEventsWebSocket.java
index 697011c5d4..b99ff76e93 100644
--- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/CBEventsWebSocket.java
+++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/CBEventsWebSocket.java
@@ -20,8 +20,8 @@
import io.cloudbeaver.model.session.BaseWebSession;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.websocket.CBWebSessionEventHandler;
+import org.eclipse.jetty.websocket.api.Callback;
import org.eclipse.jetty.websocket.api.Session;
-import org.eclipse.jetty.websocket.api.WriteCallback;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
@@ -36,7 +36,7 @@ public class CBEventsWebSocket extends CBAbstractWebSocket implements CBWebSessi
@NotNull
private final BaseWebSession webSession;
@NotNull
- private final WriteCallback callback;
+ private final Callback callback;
public CBEventsWebSocket(@NotNull BaseWebSession webSession) {
this.webSession = webSession;
@@ -45,8 +45,8 @@ public CBEventsWebSocket(@NotNull BaseWebSession webSession) {
}
@Override
- public void onWebSocketConnect(Session session) {
- super.onWebSocketConnect(session);
+ public void onWebSocketOpen(Session session) {
+ super.onWebSocketOpen(session);
this.webSession.addEventHandler(this);
handleEvent(new WSSocketConnectedEvent(webSession.getApplication().getApplicationRunId()));
log.debug("EventWebSocket connected to the " + webSession.getSessionId() + " session");
@@ -109,7 +109,7 @@ public void handleWebSessionEvent(WSEvent event) {
super.handleEvent(event);
}
@Override
- protected void handleEventException(Exception e) {
+ protected void handleEventException(Throwable e) {
super.handleEventException(e);
webSession.addSessionError(e);
}
@@ -120,7 +120,7 @@ public BaseWebSession getWebSession() {
}
@NotNull
- public WriteCallback getCallback() {
+ public Callback getCallback() {
return callback;
}
}
diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/CBExpiredSessionWebSocket.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/CBExpiredSessionWebSocket.java
index ec294713eb..643197d7e3 100644
--- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/CBExpiredSessionWebSocket.java
+++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/CBExpiredSessionWebSocket.java
@@ -21,8 +21,8 @@
public class CBExpiredSessionWebSocket extends CBAbstractWebSocket {
@Override
- public void onWebSocketConnect(Session session) {
- super.onWebSocketConnect(session);
+ public void onWebSocketOpen(Session session) {
+ super.onWebSocketOpen(session);
handleEvent(new WSAccessTokenExpiredEvent());
close();
}
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 02c730dd10..26b958fbb2 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
@@ -18,18 +18,19 @@
import io.cloudbeaver.model.session.BaseWebSession;
import io.cloudbeaver.model.session.WebHeadlessSession;
+import io.cloudbeaver.model.session.WebHttpRequestInfo;
import io.cloudbeaver.server.CBPlatform;
import io.cloudbeaver.service.session.WebSessionManager;
-import jakarta.servlet.http.HttpServletRequest;
-import org.eclipse.jetty.websocket.server.JettyServerUpgradeRequest;
-import org.eclipse.jetty.websocket.server.JettyServerUpgradeResponse;
-import org.eclipse.jetty.websocket.server.JettyWebSocketCreator;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.websocket.server.ServerUpgradeRequest;
+import org.eclipse.jetty.websocket.server.ServerUpgradeResponse;
+import org.eclipse.jetty.websocket.server.WebSocketCreator;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.security.exception.SMAccessTokenExpiredException;
-import org.jkiss.dbeaver.runtime.DBWorkbench;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@@ -38,7 +39,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
-public class CBJettyWebSocketManager implements JettyWebSocketCreator {
+public class CBJettyWebSocketManager implements WebSocketCreator {
private static final Log log = Log.getLog(CBJettyWebSocketManager.class);
private final Map> socketBySessionId = new ConcurrentHashMap<>();
private final WebSessionManager webSessionManager;
@@ -51,17 +52,22 @@ public CBJettyWebSocketManager(@NotNull WebSessionManager webSessionManager) {
@Nullable
@Override
- public Object createWebSocket(@NotNull JettyServerUpgradeRequest request, JettyServerUpgradeResponse resp) {
- var httpRequest = request.getHttpServletRequest();
- var webSession = webSessionManager.getOrRestoreSession(httpRequest);
+ public Object createWebSocket(@NotNull ServerUpgradeRequest request, ServerUpgradeResponse resp, Callback callback) {
+ var webSession = webSessionManager.getOrRestoreSession(request);
+ var requestInfo = new WebHttpRequestInfo(
+ request.getId(),
+ request.getAttribute("locale"),
+ Request.getRemoteAddr(request),
+ request.getHeaders().get("User-Agent")
+ );
if (webSession != null) {
- webSession.updateSessionParameters(httpRequest);
+ webSession.updateSessionParameters(requestInfo);
// web client session
return createNewEventsWebSocket(webSession);
}
// possible desktop client session
try {
- var headlessSession = createHeadlessSession(httpRequest);
+ var headlessSession = createHeadlessSession(request);
if (headlessSession == null) {
log.debug("Couldn't create headless session");
return null;
@@ -86,21 +92,21 @@ private CBEventsWebSocket createNewEventsWebSocket(@NotNull BaseWebSession webSe
}
@Nullable
- private WebHeadlessSession createHeadlessSession(@NotNull HttpServletRequest request) throws DBException {
- var httpSession = request.getSession(false);
- if (httpSession == null) {
+ private WebHeadlessSession createHeadlessSession(@NotNull Request request) throws DBException {
+ var requestSession = request.getSession(false);
+ if (requestSession == null) {
log.debug("CloudBeaver web session not exist, try to create headless session");
} else {
- log.debug("CloudBeaver session not found with id " + httpSession.getId() + ", try to create headless session");
+ log.debug("CloudBeaver session not found with id " + requestSession.getId() + ", try to create headless session");
}
- return webSessionManager.getHeadlessSession(request, true);
+ return webSessionManager.getHeadlessSession(request, requestSession, true);
}
public void sendPing() {
//remove expired sessions
socketBySessionId.entrySet()
.removeIf(entry -> {
- entry.getValue().removeIf(ws -> !ws.isConnected());
+ entry.getValue().removeIf(ws -> !ws.isOpen());
return webSessionManager.getSession(entry.getKey()) == null ||
entry.getValue().isEmpty();
}
@@ -115,7 +121,7 @@ public void sendPing() {
var webSockets = entry.getValue();
for (CBEventsWebSocket webSocket : webSockets) {
try {
- webSocket.getRemote().sendPing(
+ webSocket.getSession().sendPing(
ByteBuffer.wrap("cb-ping".getBytes(StandardCharsets.UTF_8)),
webSocket.getCallback()
);
diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/WebSocketPingPongCallback.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/WebSocketPingPongCallback.java
index 8530e963c1..b19741df9a 100644
--- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/WebSocketPingPongCallback.java
+++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/WebSocketPingPongCallback.java
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
- * Copyright (C) 2010-2022 DBeaver Corp and others
+ * Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,10 +18,10 @@
import io.cloudbeaver.model.session.BaseWebSession;
import io.cloudbeaver.model.session.WebHeadlessSession;
-import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.Callback;
import org.jkiss.code.NotNull;
-public class WebSocketPingPongCallback implements WriteCallback {
+public class WebSocketPingPongCallback implements Callback {
@NotNull
private final BaseWebSession webSession;
@@ -30,7 +30,7 @@ public WebSocketPingPongCallback(@NotNull BaseWebSession webSession) {
}
@Override
- public void writeSuccess() {
+ public void succeed() {
if (webSession instanceof WebHeadlessSession) {
webSession.touchSession();
}
diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/session/WebSessionManager.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/session/WebSessionManager.java
index 40dc20ae00..3380cd4872 100644
--- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/session/WebSessionManager.java
+++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/session/WebSessionManager.java
@@ -18,18 +18,18 @@
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.auth.SMTokenCredentialProvider;
-import io.cloudbeaver.model.session.BaseWebSession;
-import io.cloudbeaver.model.session.WebHeadlessSession;
-import io.cloudbeaver.model.session.WebSession;
-import io.cloudbeaver.model.session.WebSessionAuthProcessor;
+import io.cloudbeaver.model.session.*;
import io.cloudbeaver.registry.WebHandlerRegistry;
import io.cloudbeaver.registry.WebSessionHandlerDescriptor;
import io.cloudbeaver.server.CBApplication;
+import io.cloudbeaver.server.CBConstants;
import io.cloudbeaver.server.events.WSWebUtils;
import io.cloudbeaver.service.DBWSessionHandler;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Session;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
@@ -85,7 +85,8 @@ protected CBApplication getApplication() {
public boolean touchSession(@NotNull HttpServletRequest request,
@NotNull HttpServletResponse response) throws DBWebException {
WebSession webSession = getWebSession(request, response, false);
- webSession.updateSessionParameters(request);
+ var requestInfo = new WebHttpRequestInfo(request);
+ webSession.updateSessionParameters(requestInfo);
webSession.updateInfo(!request.getSession().isNew());
return true;
}
@@ -109,14 +110,14 @@ public WebSession getWebSession(
var baseWebSession = sessionMap.get(sessionId);
if (baseWebSession == null && CBApplication.getInstance().isConfigurationMode()) {
try {
- webSession = createWebSessionImpl(request);
+ webSession = createWebSessionImpl(new WebHttpRequestInfo(request));
} catch (DBException e) {
throw new DBWebException("Failed to create web session", e);
}
sessionMap.put(sessionId, webSession);
} else if (baseWebSession == null) {
try {
- webSession = createWebSessionImpl(request);
+ webSession = createWebSessionImpl(new WebHttpRequestInfo(request));
} catch (DBException e) {
throw new DBWebException("Failed to create web session", e);
}
@@ -154,13 +155,15 @@ public WebSession getWebSession(
* @return WebSession object or null, if session expired or invalid
*/
@Nullable
- public WebSession getOrRestoreSession(@NotNull HttpServletRequest request) {
- var httpSession = request.getSession();
- if (httpSession == null) {
+ public WebSession getOrRestoreSession(@NotNull Request request) {
+ var sessionIdCookie = Request.getCookies(request).stream().filter(
+ c -> c.getName().equals(CBConstants.CB_SESSION_COOKIE_NAME)
+ ).findAny().orElse(null);
+ if (sessionIdCookie == null) {
log.debug("Http session is null. No Web Session returned");
return null;
}
- var sessionId = httpSession.getId();
+ var sessionId = sessionIdCookie.getValue();
WebSession webSession;
synchronized (sessionMap) {
if (sessionMap.containsKey(sessionId)) {
@@ -178,7 +181,12 @@ public WebSession getOrRestoreSession(@NotNull HttpServletRequest request) {
return null;
}
- webSession = createWebSessionImpl(request);
+ webSession = createWebSessionImpl(new WebHttpRequestInfo(
+ request.getId(),
+ request.getAttribute("locale"),
+ Request.getRemoteAddr(request),
+ request.getHeaders().get("User-Agent")
+ ));
restorePreviousUserSession(webSession, oldAuthInfo);
sessionMap.put(sessionId, webSession);
@@ -212,7 +220,7 @@ private void restorePreviousUserSession(
}
@NotNull
- protected WebSession createWebSessionImpl(@NotNull HttpServletRequest request) throws DBException {
+ protected WebSession createWebSessionImpl(@NotNull WebHttpRequestInfo request) throws DBException {
return new WebSession(request, application, getSessionHandlers());
}
@@ -281,16 +289,15 @@ public Collection getAllActiveSessions() {
}
@Nullable
- public WebHeadlessSession getHeadlessSession(HttpServletRequest request, boolean create) throws DBException {
- String smAccessToken = request.getHeader(WSConstants.WS_AUTH_HEADER);
+ public WebHeadlessSession getHeadlessSession(Request request, Session session, boolean create) throws DBException {
+ String smAccessToken = request.getHeaders().get(WSConstants.WS_AUTH_HEADER);
if (CommonUtils.isEmpty(smAccessToken)) {
return null;
}
synchronized (sessionMap) {
- var httpSession = request.getSession();
var tempCredProvider = new SMTokenCredentialProvider(smAccessToken);
SMAuthPermissions authPermissions = application.createSecurityController(tempCredProvider).getTokenPermissions();
- var sessionId = httpSession != null ? httpSession.getId()
+ var sessionId = session != null ? session.getId()
: authPermissions.getSessionId();
var existSession = sessionMap.get(sessionId);
diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLFileLoaderServlet.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLFileLoaderServlet.java
index 5ea8b52716..bc52ac542a 100644
--- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLFileLoaderServlet.java
+++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLFileLoaderServlet.java
@@ -29,7 +29,7 @@
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
@@ -84,7 +84,7 @@ protected void processServiceRequest(
.resolve(session.getSessionId());
MultipartConfigElement multiPartConfig = new MultipartConfigElement(tempFolder.toString());
- request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, multiPartConfig);
+ request.setAttribute(ServletContextRequest.MULTIPART_CONFIG_ELEMENT, multiPartConfig);
Map variables = gson.fromJson(request.getParameter(REQUEST_PARAM_VARIABLES), MAP_STRING_OBJECT_TYPE);
diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultServlet.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultServlet.java
index a0d231525e..31c5ff26f3 100644
--- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultServlet.java
+++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultServlet.java
@@ -1,3 +1,19 @@
+/*
+ * DBeaver - Universal Database Manager
+ * Copyright (C) 2010-2024 DBeaver Corp and others
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package io.cloudbeaver.service.sql;
import io.cloudbeaver.DBWebException;
@@ -5,18 +21,18 @@
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.servlets.CBStaticServlet;
import io.cloudbeaver.service.WebServiceServletBase;
-import org.eclipse.jetty.server.Request;
-import org.jkiss.dbeaver.DBException;
-import org.jkiss.dbeaver.Log;
-import org.jkiss.utils.CommonUtils;
-import org.jkiss.utils.IOUtils;
-
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Part;
+import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
+import org.jkiss.dbeaver.DBException;
+import org.jkiss.dbeaver.Log;
+import org.jkiss.utils.CommonUtils;
+import org.jkiss.utils.IOUtils;
+
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
@@ -42,7 +58,7 @@ public WebSQLResultServlet(CBApplication application, DBWServiceSQL sqlService)
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, MULTI_PART_CONFIG);
+ request.setAttribute(ServletContextRequest.MULTIPART_CONFIG_ELEMENT, MULTI_PART_CONFIG);
String fileName = UUID.randomUUID().toString();
for (Part part : request.getParts()) {
part.write(WebSQLDataLOBReceiver.DATA_EXPORT_FOLDER + "/" + fileName);
diff --git a/server/bundles/io.cloudbeaver.service.admin/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.service.admin/META-INF/MANIFEST.MF
index 9602219ee4..6b10865c0b 100644
--- a/server/bundles/io.cloudbeaver.service.admin/META-INF/MANIFEST.MF
+++ b/server/bundles/io.cloudbeaver.service.admin/META-INF/MANIFEST.MF
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Administration
Bundle-SymbolicName: io.cloudbeaver.service.admin;singleton:=true
-Bundle-Version: 1.0.105.qualifier
-Bundle-Release-Date: 20240923
+Bundle-Version: 1.0.106.qualifier
+Bundle-Release-Date: 20241007
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
diff --git a/server/bundles/io.cloudbeaver.service.admin/pom.xml b/server/bundles/io.cloudbeaver.service.admin/pom.xml
index f200105f1a..cc6d869b22 100644
--- a/server/bundles/io.cloudbeaver.service.admin/pom.xml
+++ b/server/bundles/io.cloudbeaver.service.admin/pom.xml
@@ -10,7 +10,7 @@
../
io.cloudbeaver.service.admin
- 1.0.105-SNAPSHOT
+ 1.0.106-SNAPSHOT
eclipse-plugin
diff --git a/server/bundles/io.cloudbeaver.service.auth/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.service.auth/META-INF/MANIFEST.MF
index 3481f6c816..fbc470b0f7 100644
--- a/server/bundles/io.cloudbeaver.service.auth/META-INF/MANIFEST.MF
+++ b/server/bundles/io.cloudbeaver.service.auth/META-INF/MANIFEST.MF
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Authentication
Bundle-SymbolicName: io.cloudbeaver.service.auth;singleton:=true
-Bundle-Version: 1.0.105.qualifier
-Bundle-Release-Date: 20240923
+Bundle-Version: 1.0.106.qualifier
+Bundle-Release-Date: 20241007
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
diff --git a/server/bundles/io.cloudbeaver.service.auth/pom.xml b/server/bundles/io.cloudbeaver.service.auth/pom.xml
index b3c214f1f2..7bff8ea860 100644
--- a/server/bundles/io.cloudbeaver.service.auth/pom.xml
+++ b/server/bundles/io.cloudbeaver.service.auth/pom.xml
@@ -10,7 +10,7 @@
../
io.cloudbeaver.service.auth
- 1.0.105-SNAPSHOT
+ 1.0.106-SNAPSHOT
eclipse-plugin
diff --git a/server/bundles/io.cloudbeaver.service.data.transfer/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.service.data.transfer/META-INF/MANIFEST.MF
index bcddeb66ac..f5250bd4cc 100644
--- a/server/bundles/io.cloudbeaver.service.data.transfer/META-INF/MANIFEST.MF
+++ b/server/bundles/io.cloudbeaver.service.data.transfer/META-INF/MANIFEST.MF
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Data Transfer
Bundle-SymbolicName: io.cloudbeaver.service.data.transfer;singleton:=true
-Bundle-Version: 1.0.106.qualifier
-Bundle-Release-Date: 20240923
+Bundle-Version: 1.0.107.qualifier
+Bundle-Release-Date: 20241007
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
diff --git a/server/bundles/io.cloudbeaver.service.data.transfer/pom.xml b/server/bundles/io.cloudbeaver.service.data.transfer/pom.xml
index aa3b788ab9..185a1d5222 100644
--- a/server/bundles/io.cloudbeaver.service.data.transfer/pom.xml
+++ b/server/bundles/io.cloudbeaver.service.data.transfer/pom.xml
@@ -10,7 +10,7 @@
../
io.cloudbeaver.service.data.transfer
- 1.0.106-SNAPSHOT
+ 1.0.107-SNAPSHOT
eclipse-plugin
diff --git a/server/bundles/io.cloudbeaver.service.fs/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.service.fs/META-INF/MANIFEST.MF
index 25acfd3be3..f87beec385 100644
--- a/server/bundles/io.cloudbeaver.service.fs/META-INF/MANIFEST.MF
+++ b/server/bundles/io.cloudbeaver.service.fs/META-INF/MANIFEST.MF
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - File System
Bundle-SymbolicName: io.cloudbeaver.service.fs;singleton:=true
-Bundle-Version: 1.0.23.qualifier
-Bundle-Release-Date: 20240923
+Bundle-Version: 1.0.24.qualifier
+Bundle-Release-Date: 20241007
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
diff --git a/server/bundles/io.cloudbeaver.service.fs/pom.xml b/server/bundles/io.cloudbeaver.service.fs/pom.xml
index 8f6b7cae02..52d9291e87 100644
--- a/server/bundles/io.cloudbeaver.service.fs/pom.xml
+++ b/server/bundles/io.cloudbeaver.service.fs/pom.xml
@@ -10,7 +10,7 @@
../
io.cloudbeaver.service.fs
- 1.0.23-SNAPSHOT
+ 1.0.24-SNAPSHOT
eclipse-plugin
diff --git a/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/model/WebFSServlet.java b/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/model/WebFSServlet.java
index 53ac1a5dad..fa2514c98f 100644
--- a/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/model/WebFSServlet.java
+++ b/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/model/WebFSServlet.java
@@ -27,7 +27,7 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Part;
-import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.navigator.fs.DBNPathBase;
@@ -79,7 +79,7 @@ private void doGet(WebSession session, HttpServletRequest request, HttpServletRe
private void doPost(WebSession session, HttpServletRequest request, HttpServletResponse response) throws DBException, IOException {
// we need to set this attribute to get parts
- request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, new MultipartConfigElement(""));
+ request.setAttribute(ServletContextRequest.MULTIPART_CONFIG_ELEMENT, new MultipartConfigElement(""));
Map variables = getVariables(request);
String parentNodePath = JSONUtils.getString(variables, "toParentNodePath");
if (CommonUtils.isEmpty(parentNodePath)) {
diff --git a/server/bundles/io.cloudbeaver.service.metadata/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.service.metadata/META-INF/MANIFEST.MF
index 26f8bbe9d5..8f2fa9480d 100644
--- a/server/bundles/io.cloudbeaver.service.metadata/META-INF/MANIFEST.MF
+++ b/server/bundles/io.cloudbeaver.service.metadata/META-INF/MANIFEST.MF
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Metadata
Bundle-SymbolicName: io.cloudbeaver.service.metadata;singleton:=true
-Bundle-Version: 1.0.109.qualifier
-Bundle-Release-Date: 20240923
+Bundle-Version: 1.0.110.qualifier
+Bundle-Release-Date: 20241007
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
diff --git a/server/bundles/io.cloudbeaver.service.metadata/pom.xml b/server/bundles/io.cloudbeaver.service.metadata/pom.xml
index 52997551fa..f97daaaaaa 100644
--- a/server/bundles/io.cloudbeaver.service.metadata/pom.xml
+++ b/server/bundles/io.cloudbeaver.service.metadata/pom.xml
@@ -10,7 +10,7 @@
../
io.cloudbeaver.service.metadata
- 1.0.109-SNAPSHOT
+ 1.0.110-SNAPSHOT
eclipse-plugin
diff --git a/server/bundles/io.cloudbeaver.service.rm.nio/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.service.rm.nio/META-INF/MANIFEST.MF
index 737a39ad87..c2fbcd9eac 100644
--- a/server/bundles/io.cloudbeaver.service.rm.nio/META-INF/MANIFEST.MF
+++ b/server/bundles/io.cloudbeaver.service.rm.nio/META-INF/MANIFEST.MF
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Resource manager NIO implementation
Bundle-SymbolicName: io.cloudbeaver.service.rm.nio;singleton:=true
-Bundle-Version: 1.0.23.qualifier
-Bundle-Release-Date: 20240923
+Bundle-Version: 1.0.24.qualifier
+Bundle-Release-Date: 20241007
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
diff --git a/server/bundles/io.cloudbeaver.service.rm.nio/pom.xml b/server/bundles/io.cloudbeaver.service.rm.nio/pom.xml
index aae7ab9f48..73a645ff48 100644
--- a/server/bundles/io.cloudbeaver.service.rm.nio/pom.xml
+++ b/server/bundles/io.cloudbeaver.service.rm.nio/pom.xml
@@ -10,7 +10,7 @@
../
io.cloudbeaver.service.rm.nio
- 1.0.23-SNAPSHOT
+ 1.0.24-SNAPSHOT
eclipse-plugin
diff --git a/server/bundles/io.cloudbeaver.service.rm/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.service.rm/META-INF/MANIFEST.MF
index c9829ed966..31ae2dab47 100644
--- a/server/bundles/io.cloudbeaver.service.rm/META-INF/MANIFEST.MF
+++ b/server/bundles/io.cloudbeaver.service.rm/META-INF/MANIFEST.MF
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Resource manager
Bundle-SymbolicName: io.cloudbeaver.service.rm;singleton:=true
-Bundle-Version: 1.0.58.qualifier
-Bundle-Release-Date: 20240923
+Bundle-Version: 1.0.59.qualifier
+Bundle-Release-Date: 20241007
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
diff --git a/server/bundles/io.cloudbeaver.service.rm/pom.xml b/server/bundles/io.cloudbeaver.service.rm/pom.xml
index 8951e0b0f0..a46196fd8e 100644
--- a/server/bundles/io.cloudbeaver.service.rm/pom.xml
+++ b/server/bundles/io.cloudbeaver.service.rm/pom.xml
@@ -10,7 +10,7 @@
../
io.cloudbeaver.service.rm
- 1.0.58-SNAPSHOT
+ 1.0.59-SNAPSHOT
eclipse-plugin
diff --git a/server/bundles/io.cloudbeaver.service.security/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.service.security/META-INF/MANIFEST.MF
index 8a8a30cb1a..514653ed5f 100644
--- a/server/bundles/io.cloudbeaver.service.security/META-INF/MANIFEST.MF
+++ b/server/bundles/io.cloudbeaver.service.security/META-INF/MANIFEST.MF
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: Cloudbeaver Web Service - Security
Bundle-Vendor: DBeaver Corp
Bundle-SymbolicName: io.cloudbeaver.service.security;singleton:=true
-Bundle-Version: 1.0.61.qualifier
-Bundle-Release-Date: 20240923
+Bundle-Version: 1.0.62.qualifier
+Bundle-Release-Date: 20241007
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
diff --git a/server/bundles/io.cloudbeaver.service.security/pom.xml b/server/bundles/io.cloudbeaver.service.security/pom.xml
index beea19d866..939a65afd6 100644
--- a/server/bundles/io.cloudbeaver.service.security/pom.xml
+++ b/server/bundles/io.cloudbeaver.service.security/pom.xml
@@ -10,7 +10,7 @@
../
io.cloudbeaver.service.security
- 1.0.61-SNAPSHOT
+ 1.0.62-SNAPSHOT
eclipse-plugin
diff --git a/server/bundles/io.cloudbeaver.slf4j/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.slf4j/META-INF/MANIFEST.MF
index cfe182e73f..ed1d71160f 100644
--- a/server/bundles/io.cloudbeaver.slf4j/META-INF/MANIFEST.MF
+++ b/server/bundles/io.cloudbeaver.slf4j/META-INF/MANIFEST.MF
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: CloudBeaver SLF4j Binding
Bundle-SymbolicName: io.cloudbeaver.slf4j;singleton:=true
-Bundle-Version: 1.0.21.qualifier
-Bundle-Release-Date: 20240923
+Bundle-Version: 1.0.22.qualifier
+Bundle-Release-Date: 20241007
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
diff --git a/server/bundles/io.cloudbeaver.slf4j/pom.xml b/server/bundles/io.cloudbeaver.slf4j/pom.xml
index ecf17e15b3..cbcfbb5d15 100644
--- a/server/bundles/io.cloudbeaver.slf4j/pom.xml
+++ b/server/bundles/io.cloudbeaver.slf4j/pom.xml
@@ -10,7 +10,7 @@
../
io.cloudbeaver.slf4j
- 1.0.21-SNAPSHOT
+ 1.0.22-SNAPSHOT
eclipse-plugin
diff --git a/server/features/io.cloudbeaver.ce.drivers.feature/feature.xml b/server/features/io.cloudbeaver.ce.drivers.feature/feature.xml
index f92eae44dc..77b67fe77c 100644
--- a/server/features/io.cloudbeaver.ce.drivers.feature/feature.xml
+++ b/server/features/io.cloudbeaver.ce.drivers.feature/feature.xml
@@ -2,7 +2,7 @@
diff --git a/server/features/io.cloudbeaver.ce.drivers.feature/pom.xml b/server/features/io.cloudbeaver.ce.drivers.feature/pom.xml
index 38579f0ce9..b2d7dbda84 100644
--- a/server/features/io.cloudbeaver.ce.drivers.feature/pom.xml
+++ b/server/features/io.cloudbeaver.ce.drivers.feature/pom.xml
@@ -9,6 +9,6 @@
../
io.cloudbeaver.ce.drivers.feature
- 1.0.129-SNAPSHOT
+ 1.0.130-SNAPSHOT
eclipse-feature
diff --git a/server/features/io.cloudbeaver.product.ce.feature/feature.xml b/server/features/io.cloudbeaver.product.ce.feature/feature.xml
index 2859ff7450..284670b63a 100644
--- a/server/features/io.cloudbeaver.product.ce.feature/feature.xml
+++ b/server/features/io.cloudbeaver.product.ce.feature/feature.xml
@@ -2,7 +2,7 @@
diff --git a/server/features/io.cloudbeaver.product.ce.feature/pom.xml b/server/features/io.cloudbeaver.product.ce.feature/pom.xml
index b711e83dba..0b891199c2 100644
--- a/server/features/io.cloudbeaver.product.ce.feature/pom.xml
+++ b/server/features/io.cloudbeaver.product.ce.feature/pom.xml
@@ -10,7 +10,7 @@
../
io.cloudbeaver.product.ce.feature
- 24.2.1-SNAPSHOT
+ 24.2.2-SNAPSHOT
eclipse-feature
diff --git a/server/features/io.cloudbeaver.server.feature/feature.xml b/server/features/io.cloudbeaver.server.feature/feature.xml
index e3873e6d98..159f75d39a 100644
--- a/server/features/io.cloudbeaver.server.feature/feature.xml
+++ b/server/features/io.cloudbeaver.server.feature/feature.xml
@@ -2,7 +2,7 @@
diff --git a/server/features/io.cloudbeaver.server.feature/pom.xml b/server/features/io.cloudbeaver.server.feature/pom.xml
index d1eda2207a..3b8fb58670 100644
--- a/server/features/io.cloudbeaver.server.feature/pom.xml
+++ b/server/features/io.cloudbeaver.server.feature/pom.xml
@@ -10,6 +10,6 @@
../
io.cloudbeaver.server.feature
- 24.2.1-SNAPSHOT
+ 24.2.2-SNAPSHOT
eclipse-feature
diff --git a/server/features/io.cloudbeaver.ws.feature/feature.xml b/server/features/io.cloudbeaver.ws.feature/feature.xml
index 7e39d57fb1..714ab2baf7 100644
--- a/server/features/io.cloudbeaver.ws.feature/feature.xml
+++ b/server/features/io.cloudbeaver.ws.feature/feature.xml
@@ -2,7 +2,7 @@
diff --git a/server/features/io.cloudbeaver.ws.feature/pom.xml b/server/features/io.cloudbeaver.ws.feature/pom.xml
index de5b10caaf..36705b3d43 100644
--- a/server/features/io.cloudbeaver.ws.feature/pom.xml
+++ b/server/features/io.cloudbeaver.ws.feature/pom.xml
@@ -10,6 +10,6 @@
../
io.cloudbeaver.ws.feature
- 1.0.59-SNAPSHOT
+ 1.0.60-SNAPSHOT
eclipse-feature
diff --git a/server/pom.xml b/server/pom.xml
index 840742f3a6..4337a2507b 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -19,7 +19,7 @@
CloudBeaver CE
- 24.2.1
+ 24.2.2
diff --git a/server/product/web-server/CloudbeaverServer.product b/server/product/web-server/CloudbeaverServer.product
index 72baeb5698..179c684197 100644
--- a/server/product/web-server/CloudbeaverServer.product
+++ b/server/product/web-server/CloudbeaverServer.product
@@ -2,7 +2,7 @@
diff --git a/server/product/web-server/pom.xml b/server/product/web-server/pom.xml
index 22f71d98a5..ef175dce7d 100644
--- a/server/product/web-server/pom.xml
+++ b/server/product/web-server/pom.xml
@@ -9,7 +9,7 @@
1.0.0-SNAPSHOT
../../
- 24.2.1-SNAPSHOT
+ 24.2.2-SNAPSHOT
web-server
eclipse-repository
Cloudbeaver Server Product
diff --git a/server/test/io.cloudbeaver.test.platform/workspace/conf/cloudbeaver.conf b/server/test/io.cloudbeaver.test.platform/workspace/conf/cloudbeaver.conf
index 6ba3e1b6ff..4511bd491c 100644
--- a/server/test/io.cloudbeaver.test.platform/workspace/conf/cloudbeaver.conf
+++ b/server/test/io.cloudbeaver.test.platform/workspace/conf/cloudbeaver.conf
@@ -1,9 +1,9 @@
{
server: {
serverPort: "${CLOUDBEAVER_TEST_PORT:18978}",
- serverName: "CloudBeaver CE Test Server",
+ serverName: "${CLOUDBEAVER_SERVER_NAME:CloudBeaver CE Test Server}",
- workspaceLocation: "workspace",
+ workspaceLocation: "${CLOUDBEAVER_WORKSPACE_LOCATION:workspace}",
contentRoot: "workspace/web",
driversLocation: "../../../deploy/",
@@ -12,37 +12,37 @@
productSettings: {},
- expireSessionAfterPeriod: 1800000,
+ expireSessionAfterPeriod: "${CLOUDBEAVER_EXPIRE_SESSION_AFTER_PERIOD:1800000}",
- develMode: false,
+ develMode: "${CLOUDBEAVER_DEVEL_MODE:false}",
sm: {
enableBruteForceProtection: "${CLOUDBEAVER_BRUTE_FORCE_PROTECTION_ENABLED:false}"
},
database: {
- driver="h2_embedded_v2",
- url: "jdbc:h2:mem:testdb",
+ driver: "${CLOUDBEAVER_DB_DRIVER:h2_embedded_v2}",
+ url: "${CLOUDBEAVER_DB_URL:jdbc:h2:mem:testdb}",
- createDatabase: true,
+ createDatabase: "${CLOUDBEAVER_CREATE_DATABASE:true}",
- initialDataConfiguration: "workspace/conf/initial-data.conf",
+ initialDataConfiguration: "${CLOUDBEAVER_DB_INITIAL_DATA:workspace/conf/initial-data.conf}",
pool: {
- minIdleConnections: 4,
- maxIdleConnections: 10,
- maxConnections: 100,
- validationQuery: "SELECT 1"
+ minIdleConnections: "${CLOUDBEAVER_DB_MIN_IDLE_CONNECTIONS:4}",
+ maxIdleConnections: "${CLOUDBEAVER_DB_MAX_IDLE_CONNECTIONS:10}",
+ maxConnections: "${CLOUDBEAVER_DB_MAX_CONNECTIONS:100}",
+ validationQuery: "${CLOUDBEAVER_DB_VALIDATION_QUERY:SELECT 1}"
}
}
},
app: {
- anonymousAccessEnabled: true,
- anonymousUserRole: "user",
- defaultUserTeam: "user",
- supportsCustomConnections: true,
- enableReverseProxyAuth: true,
+ anonymousAccessEnabled: "${CLOUDBEAVER_APP_ANONYMOUS_ACCESS_ENABLED:true}",
+ anonymousUserRole: user,
+ defaultUserTeam: "${CLOUDBEAVER_APP_DEFAULT_USER_TEAM:user}",
+ supportsCustomConnections: "${CLOUDBEAVER_APP_SUPPORTS_CUSTOM_CONNECTIONS:true}",
+ enableReverseProxyAuth: "${CLOUDBEAVER_APP_ENABLE_REVERSE_PROXY_AUTH:true}",
enabledAuthProviders: [
"local",
"reverseProxy"
@@ -52,13 +52,12 @@
],
resourceQuotas: {
- dataExportFileSizeLimit: 10000000,
- sqlMaxRunningQueries: 100,
- sqlResultSetRowsLimit: 100000,
- sqlResultSetMemoryLimit: 2000000,
- sqlTextPreviewMaxLength: 4096,
- sqlBinaryPreviewMaxLength: 261120,
- sqlQueryTimeout: 5
+ dataExportFileSizeLimit: "${CLOUDBEAVER_RESOURCE_QUOTA_DATA_EXPORT_FILE_SIZE_LIMIT:10000000}",
+ sqlMaxRunningQueries: "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_MAX_RUNNING_QUERIES:100}",
+ sqlResultSetRowsLimit: "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_RESULT_SET_ROWS_LIMIT:100000}",
+ sqlTextPreviewMaxLength: "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_TEXT_PREVIEW_MAX_LENGTH:4096}",
+ sqlBinaryPreviewMaxLength: "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_BINARY_PREVIEW_MAX_LENGTH:261120}",
+ sqlQueryTimeout: "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_QUERY_TIMEOUT:5}"
},
disabledDrivers: [
diff --git a/webapp/packages/core-authentication/src/UsersResource.ts b/webapp/packages/core-authentication/src/UsersResource.ts
index a28b2571aa..6e790e75a3 100644
--- a/webapp/packages/core-authentication/src/UsersResource.ts
+++ b/webapp/packages/core-authentication/src/UsersResource.ts
@@ -5,6 +5,8 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
+import { runInAction } from 'mobx';
+
import { injectable } from '@cloudbeaver/core-di';
import {
CACHED_RESOURCE_DEFAULT_PAGE_LIMIT,
@@ -240,6 +242,7 @@ export class UsersResource extends CachedMapResource[] = [];
await ResourceKeyUtils.forEachAsync(originalKey, async key => {
let userId: string | undefined;
@@ -290,12 +293,21 @@ export class UsersResource extends CachedMapResource user.userId),
+ users.length === limit,
+ ]);
}
});
const key = resourceKeyList(usersList.map(user => user.userId));
- this.set(key, usersList);
+ runInAction(() => {
+ this.set(key, usersList);
+ for (const pageArgs of pages) {
+ this.offsetPagination.setPage(...pageArgs);
+ }
+ });
return this.data;
}
diff --git a/webapp/packages/core-blocks/src/ResourcesHooks/useOffsetPagination.ts b/webapp/packages/core-blocks/src/ResourcesHooks/useOffsetPagination.ts
index 0d60e43648..9d1365325b 100644
--- a/webapp/packages/core-blocks/src/ResourcesHooks/useOffsetPagination.ts
+++ b/webapp/packages/core-blocks/src/ResourcesHooks/useOffsetPagination.ts
@@ -14,8 +14,10 @@ import {
CachedMapResource,
CachedResourceOffsetPageKey,
CachedResourceOffsetPageListKey,
+ CachedResourceOffsetPageTargetKey,
getNextPageOffset,
ICachedResourceOffsetPageOptions,
+ isResourceAlias,
ResourceKey,
ResourceKeyAlias,
ResourceKeyList,
@@ -30,7 +32,10 @@ interface IOptions> {
}
interface IOffsetPagination {
- key: TKey extends ResourceKeyListAlias | ResourceKeyList
+ currentPage: TKey extends ResourceKeyListAlias | ResourceKeyList
+ ? ResourceKeyListAlias>
+ : ResourceKeyAlias>;
+ allPages: TKey extends ResourceKeyListAlias | ResourceKeyList
? ResourceKeyListAlias>
: ResourceKeyAlias>;
hasNextPage: boolean;
@@ -42,6 +47,7 @@ interface IOffsetPaginationPrivate> extends IOffse
offset: number;
resource: CachedMapResource;
_key: ResourceKeyAlias> | ResourceKeyListAlias>;
+ _target: TKey | undefined;
}
export function useOffsetPagination, TKey extends ResourceKey>(
@@ -61,31 +67,37 @@ export function useOffsetPagination ({
offset,
_key: createPageKey(offset, pageSize, targetKey),
- get key() {
- const pageInfo = resource.offsetPagination.getPageInfo(createPageKey(0, 0, this._key.target));
-
- for (const page of pageInfo?.pages || []) {
- if (page.outdated && page.from < this._key.options.offset) {
- return createPageKey(page.from, this._key.options.limit, this._key.target);
+ _target: targetKey,
+ get currentPage() {
+ for (let i = 0; i < this.offset; i += this._key.options.limit) {
+ const key = createPageKey(i, this._key.options.limit, this._target);
+ if (resource.isOutdated(key)) {
+ return key;
}
}
+
return this._key as any;
},
+ get allPages(): any {
+ return createPageKey(0, this._key.options.offset + this._key.options.limit, this._target);
+ },
get hasNextPage(): boolean {
return this.resource.offsetPagination.hasNextPage(this._key);
},
loadMore() {
if (this.hasNextPage) {
- this._key = createPageKey(this._key.options.offset + this._key.options.limit, this._key.options.limit, this._key.target);
+ this._key = createPageKey(this._key.options.offset + this._key.options.limit, this._key.options.limit, this._target);
}
},
refresh() {
- this.resource.markOutdated(this._key.target);
+ this.resource.markOutdated(this._target);
},
}),
{
_key: observable.ref,
- key: computed,
+ offset: observable.ref,
+ currentPage: computed,
+ allPages: computed,
hasNextPage: computed,
loadMore: action.bound,
refresh: action.bound,
@@ -93,8 +105,9 @@ export function useOffsetPagination,
+ next: ResourceKey,
): ResourceKeyAlias> | ResourceKeyListAlias> {
- if (target instanceof ResourceKeyList || target instanceof ResourceKeyListAlias) {
- return CachedResourceOffsetPageListKey(offset, limit).setTarget(target);
+ const parent = isResourceAlias(next) ? next : CachedResourceOffsetPageTargetKey(next);
+ if (next instanceof ResourceKeyList || next instanceof ResourceKeyListAlias) {
+ return CachedResourceOffsetPageListKey(offset, limit).setParent(parent);
}
- return CachedResourceOffsetPageKey(offset, limit).setTarget(target);
+ return CachedResourceOffsetPageKey(offset, limit).setParent(parent);
}
diff --git a/webapp/packages/core-blocks/src/Table/TableState.ts b/webapp/packages/core-blocks/src/Table/TableState.ts
index e8ba799d96..2489d32f3e 100644
--- a/webapp/packages/core-blocks/src/Table/TableState.ts
+++ b/webapp/packages/core-blocks/src/Table/TableState.ts
@@ -9,30 +9,28 @@ import { action, computed, makeObservable, observable } from 'mobx';
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
-type Key = string | string[];
-
-interface IData {
- key: string;
+interface IData {
+ key: Key;
value: boolean;
}
-export class TableState {
- readonly onExpand: IExecutor;
+export class TableState {
+ readonly onExpand: IExecutor>;
- selected: Map;
- expanded: Map;
+ selected: Map;
+ expanded: Map;
get itemsSelected(): boolean {
return Array.from(this.selected.values()).some(v => v);
}
- get selectedList(): string[] {
+ get selectedList(): K[] {
return Array.from(this.selected)
.filter(([_, value]) => value)
.map(([key]) => key);
}
- get expandedList(): string[] {
+ get expandedList(): K[] {
return Array.from(this.expanded)
.filter(([_, value]) => value)
.map(([key]) => key);
@@ -41,8 +39,8 @@ export class TableState {
constructor() {
this.onExpand = new Executor();
- this.selected = new Map();
- this.expanded = new Map();
+ this.selected = new Map();
+ this.expanded = new Map();
makeObservable(this, {
selected: observable,
@@ -55,37 +53,33 @@ export class TableState {
});
}
- unselect(key?: Key): Map {
+ unselect(key?: K | K[]): Map {
if (key === undefined) {
this.selected.clear();
} else {
- if (typeof key === 'string') {
- this.selected.delete(key);
- } else {
- for (const id of key) {
- this.selected.delete(id);
- }
+ const keys = Array.isArray(key) ? key : [key];
+
+ for (const id of keys) {
+ this.selected.delete(id);
}
}
return this.selected;
}
- expand(key: string, value: boolean) {
+ expand(key: K, value: boolean) {
this.expanded.set(key, value);
this.onExpand.execute({ key, value });
}
- collapse(key?: Key): Map {
+ collapse(key?: K | K[]): Map {
if (key === undefined) {
this.expanded.clear();
} else {
- if (typeof key === 'string') {
- this.expanded.delete(key);
- } else {
- for (const id of key) {
- this.expanded.delete(id);
- }
+ const keys = Array.isArray(key) ? key : [key];
+
+ for (const id of keys) {
+ this.expanded.delete(id);
}
}
diff --git a/webapp/packages/core-blocks/src/Table/useTable.ts b/webapp/packages/core-blocks/src/Table/useTable.ts
index 987a7fd2f9..141570b584 100644
--- a/webapp/packages/core-blocks/src/Table/useTable.ts
+++ b/webapp/packages/core-blocks/src/Table/useTable.ts
@@ -9,7 +9,7 @@ import { useState } from 'react';
import { TableState } from './TableState';
-export function useTable(): TableState {
- const [table] = useState(() => new TableState());
+export function useTable(): TableState {
+ const [table] = useState(() => new TableState());
return table;
}
diff --git a/webapp/packages/core-browser/src/service-worker.ts b/webapp/packages/core-browser/src/service-worker.ts
index 4849f1b029..2d104d07df 100644
--- a/webapp/packages/core-browser/src/service-worker.ts
+++ b/webapp/packages/core-browser/src/service-worker.ts
@@ -117,7 +117,7 @@ registerRoute(
);
registerRoute(
- ({ request }) => request.destination === 'image',
+ ({ request, url }) => url.origin === self.location.origin && request.destination === 'image',
new CacheFirst({
cacheName: 'images',
plugins: [
@@ -127,6 +127,7 @@ registerRoute(
new ExpirationPlugin({
maxEntries: 1000,
maxAgeSeconds: 7 * 24 * 60 * 60,
+ purgeOnQuotaError: true,
}),
],
}),
diff --git a/webapp/packages/core-di/src/App.ts b/webapp/packages/core-di/src/App.ts
index 4d3976c026..7d8bee27a5 100644
--- a/webapp/packages/core-di/src/App.ts
+++ b/webapp/packages/core-di/src/App.ts
@@ -12,7 +12,7 @@ import { Executor, IExecutor } from '@cloudbeaver/core-executor';
import { Bootstrap } from './Bootstrap';
import { Dependency } from './Dependency';
import type { DIContainer } from './DIContainer';
-import type { IServiceCollection, IServiceConstructor, IServiceInjector } from './IApp';
+import type { IServiceCollection, IServiceConstructor } from './IApp';
import { IDiWrapper, inversifyWrapper } from './inversifyWrapper';
import { IServiceProvider } from './IServiceProvider';
import type { PluginManifest } from './PluginManifest';
@@ -92,10 +92,6 @@ export class App {
return this.diWrapper.collection;
}
- getServiceInjector(): IServiceInjector {
- return this.diWrapper.injector;
- }
-
// first phase register all dependencies
private async registerServices(preload?: boolean): Promise {
if (!this.isAppServiceBound) {
diff --git a/webapp/packages/core-di/src/index.ts b/webapp/packages/core-di/src/index.ts
index ecd20ca38e..1ae1761af5 100644
--- a/webapp/packages/core-di/src/index.ts
+++ b/webapp/packages/core-di/src/index.ts
@@ -16,7 +16,6 @@ export * from './DIService';
export * from './injectable';
export * from './PluginManifest';
export * from './useService';
-export * from './useController';
export * from './ITypedConstructor';
export * from './isConstructor';
export * from './IServiceProvider';
diff --git a/webapp/packages/core-di/src/useController.ts b/webapp/packages/core-di/src/useController.ts
deleted file mode 100644
index 76be113d79..0000000000
--- a/webapp/packages/core-di/src/useController.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 DBeaver Corp and others
- *
- * Licensed under the Apache License, Version 2.0.
- * you may not use this file except in compliance with the License.
- */
-import { useEffect, useMemo, useRef } from 'react';
-
-import { App } from './App';
-import type { ExtractInitArgs, IDestructibleController, IInitializableController, IServiceConstructor } from './IApp';
-import { useService } from './useService';
-
-/**
- * @deprecated use hooks instead
- */
-export function useController(ctor: IServiceConstructor, ...args: ExtractInitArgs): T;
-/**
- * @deprecated use hooks instead
- */
-export function useController(ctor: IServiceConstructor): T;
-/**
- * @deprecated use hooks instead
- */
-export function useController(ctor: IServiceConstructor, ...args: any[]): T {
- const appService = useService(App);
- const controllerRef = useRef();
-
- useMemo(() => {
- if (controllerRef.current && isDestructibleController(controllerRef.current)) {
- controllerRef.current.destruct();
- }
-
- const controller = appService.getServiceInjector().resolveServiceByClass(ctor);
-
- if (isInitializableController(controller)) {
- controller.init(...args);
- }
- controllerRef.current = controller;
- }, [...args, args.length]);
- /* we put dynamic array length as the dependency because of preact bug,
- otherwise useMemo will not be triggered on array change */
-
- useEffect(
- () => () => {
- if (isDestructibleController(controllerRef.current)) {
- controllerRef.current.destruct();
- }
- },
- [],
- );
-
- return controllerRef.current!;
-}
-
-function isDestructibleController(obj: any): obj is IDestructibleController {
- return obj && typeof obj.destruct === 'function';
-}
-
-function isInitializableController(obj: any): obj is IInitializableController {
- return obj && typeof obj.init === 'function';
-}
diff --git a/webapp/packages/core-navigation-tree/src/NodesManager/DBObjectResource.ts b/webapp/packages/core-navigation-tree/src/NodesManager/DBObjectResource.ts
index b118e85a26..17ef52750d 100644
--- a/webapp/packages/core-navigation-tree/src/NodesManager/DBObjectResource.ts
+++ b/webapp/packages/core-navigation-tree/src/NodesManager/DBObjectResource.ts
@@ -14,6 +14,7 @@ import {
CachedMapResource,
CachedResourceOffsetPageKey,
CachedResourceOffsetPageListKey,
+ CachedResourceOffsetPageTargetKey,
isResourceAlias,
type ResourceKey,
resourceKeyList,
@@ -47,7 +48,8 @@ export class DBObjectResource extends CachedMapResource {
const pageAlias = this.aliases.isAlias(nodeId, CachedResourceOffsetPageKey) || this.aliases.isAlias(nodeId, CachedResourceOffsetPageListKey);
if (pageAlias) {
- this.markOutdated(DBObjectParentKey(pageAlias.target));
+ const pageTarget = this.aliases.isAlias(nodeId, CachedResourceOffsetPageTargetKey);
+ this.markOutdated(DBObjectParentKey(pageTarget?.options.target));
}
if (!isResourceAlias(nodeId)) {
@@ -70,7 +72,9 @@ export class DBObjectResource extends CachedMapResource {
}
if (parentKey) {
- await this.navTreeResource.load(CachedResourceOffsetPageKey(offset, limit).setTarget(parentKey.options.parentId));
+ await this.navTreeResource.load(
+ CachedResourceOffsetPageKey(offset, limit).setParent(CachedResourceOffsetPageTargetKey(parentKey.options.parentId)),
+ );
return;
}
@@ -105,14 +109,21 @@ export class DBObjectResource extends CachedMapResource {
if (parentKey) {
const nodeId = parentKey.options.parentId;
- await this.loadFromChildren(nodeId, offset, limit);
+ const dbObjects = await this.loadFromChildren(nodeId, offset, limit);
runInAction(() => {
- this.offsetPagination.setPageEnd(
- CachedResourceOffsetPageKey(offset, limit).setTarget(originalKey),
- this.navTreeResource.offsetPagination.hasNextPage(CachedResourceOffsetPageKey(offset, limit).setTarget(nodeId)),
+ const keys = dbObjects.map(dbObject => dbObject.id);
+ this.set(resourceKeyList(keys), dbObjects);
+
+ this.offsetPagination.setPage(
+ CachedResourceOffsetPageKey(offset, limit).setParent(CachedResourceOffsetPageTargetKey(originalKey)),
+ keys,
+ this.navTreeResource.offsetPagination.hasNextPage(
+ CachedResourceOffsetPageKey(offset, limit).setParent(CachedResourceOffsetPageTargetKey(nodeId)),
+ ),
);
});
+
return this.data;
}
@@ -128,14 +139,14 @@ export class DBObjectResource extends CachedMapResource {
return this.data;
}
- private async loadFromChildren(parentId: string, offset: number, limit: number) {
+ private async loadFromChildren(parentId: string, offset: number, limit: number): Promise {
const { dbObjects } = await this.graphQLService.sdk.getChildrenDBObjectInfo({
navNodeId: parentId,
offset,
limit,
});
- this.set(resourceKeyList(dbObjects.map(dbObject => dbObject.id)), dbObjects);
+ return dbObjects;
}
private async loadDBObjectInfo(navNodeId: string): Promise {
diff --git a/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts b/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts
index b32c1283f5..4f52088d9a 100644
--- a/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts
+++ b/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts
@@ -17,6 +17,7 @@ import {
CachedMapResource,
CachedResourceOffsetPageKey,
CachedResourceOffsetPageListKey,
+ CachedResourceOffsetPageTargetKey,
type ICachedResourceMetadata,
isResourceAlias,
isResourceKeyList,
@@ -463,6 +464,7 @@ export class NavTreeResource extends CachedMapResource[] = [];
await ResourceKeyUtils.forEachAsync(originalKey, async key => {
- const nodeId = pageKey?.target ?? key;
+ const nodeId = pageTarget?.options?.target ?? key;
const navNodeChildren = await this.loadNodeChildren(nodeId, offset, limit);
values.push(navNodeChildren);
- this.offsetPagination.setPageEnd(
- CachedResourceOffsetPageKey(offset, navNodeChildren.navNodeChildren.length).setTarget(nodeId),
+ pages.push([
+ CachedResourceOffsetPageKey(offset, navNodeChildren.navNodeChildren.length).setParent(CachedResourceOffsetPageTargetKey(nodeId)),
+ navNodeChildren.navNodeChildren.map(node => node.id),
navNodeChildren.navNodeChildren.length === limit,
- );
+ ]);
});
- this.setNavObject(values, offset, limit);
+ runInAction(() => {
+ this.setNavObject(values, offset, limit);
+
+ for (const pageArgs of pages) {
+ this.offsetPagination.setPage(...pageArgs);
+ }
+ });
return this.data;
}
diff --git a/webapp/packages/core-resource/src/Resource/CachedResource.ts b/webapp/packages/core-resource/src/Resource/CachedResource.ts
index 69070eb405..50f8a914c9 100644
--- a/webapp/packages/core-resource/src/Resource/CachedResource.ts
+++ b/webapp/packages/core-resource/src/Resource/CachedResource.ts
@@ -23,7 +23,7 @@ import { getFirstException, isContainsException } from '@cloudbeaver/core-utils'
import {
CachedResourceOffsetPageKey,
CachedResourceOffsetPageListKey,
- expandOffsetPageRange,
+ CachedResourceOffsetPageTargetKey,
isOffsetPageInRange,
isOffsetPageOutdated,
} from './CachedResourceOffsetPageKeys';
@@ -75,7 +75,7 @@ export abstract class CachedResource<
constructor(defaultKey: ResourceKey, defaultValue: () => TData, defaultIncludes: TInclude = [] as any) {
super(defaultValue, defaultIncludes);
- this.offsetPagination = new ResourceOffsetPagination(this.metadata);
+ this.offsetPagination = new ResourceOffsetPagination(this.metadata, this.getKeyRef.bind(this));
this.loadingTask = this.loadingTask.bind(this);
@@ -90,8 +90,42 @@ export abstract class CachedResource<
this.aliases.add(CachedResourceParamKey, () => defaultKey);
this.aliases.add(CachedResourceListEmptyKey, () => resourceKeyList([]));
- this.aliases.add(CachedResourceOffsetPageKey, key => key.target);
- this.aliases.add(CachedResourceOffsetPageListKey, key => key.target ?? CachedResourceListEmptyKey);
+ this.aliases.add(CachedResourceOffsetPageTargetKey, key => key.options.target);
+ this.aliases.add(CachedResourceOffsetPageKey, key => {
+ const keys = [];
+ const pageInfo = this.offsetPagination.getPageInfo(key);
+
+ if (pageInfo) {
+ const from = key.options.offset;
+ const to = key.options.offset + key.options.limit;
+
+ for (const page of pageInfo.pages) {
+ if (page.isHasCommonSegment(from, to)) {
+ keys.push(...page.get(from, to));
+ }
+ }
+ }
+
+ // todo: return single element?
+ return resourceKeyList([...new Set(keys)]);
+ });
+ this.aliases.add(CachedResourceOffsetPageListKey, key => {
+ const keys = [];
+ const pageInfo = this.offsetPagination.getPageInfo(key);
+
+ if (pageInfo) {
+ const from = key.options.offset;
+ const to = key.options.offset + key.options.limit;
+
+ for (const page of pageInfo.pages) {
+ if (page.isHasCommonSegment(from, to)) {
+ keys.push(...page.get(from, to));
+ }
+ }
+ }
+
+ return resourceKeyList([...new Set(keys)]);
+ });
// this.logger.spy(this.beforeLoad, 'beforeLoad');
// this.logger.spy(this.onDataOutdated, 'onDataOutdated');
@@ -316,18 +350,9 @@ export abstract class CachedResource<
}
markLoaded(param: ResourceKey, includes?: TInclude): void {
- const pageKey = this.aliases.isAlias(param, CachedResourceOffsetPageKey) || this.aliases.isAlias(param, CachedResourceOffsetPageListKey);
-
this.metadata.update(param, metadata => {
metadata.loaded = true;
- if (pageKey) {
- metadata.offsetPage = observable({
- ...metadata.offsetPage,
- pages: expandOffsetPageRange(metadata.offsetPage?.pages || [], pageKey.options, false),
- });
- }
-
if (includes) {
this.commitIncludes(metadata, includes);
}
@@ -353,13 +378,17 @@ export abstract class CachedResource<
metadata.outdated = false;
if (pageKey) {
- metadata.offsetPage = observable({
- ...metadata.offsetPage,
- pages: expandOffsetPageRange(metadata.offsetPage?.pages || [], pageKey.options, false),
+ const from = pageKey.options.offset;
+ const to = from + pageKey.options.limit;
+
+ metadata.offsetPage?.pages.forEach(page => {
+ if (page.isInRange(from, to)) {
+ page.setOutdated(false);
+ }
});
} else {
metadata.offsetPage?.pages.forEach(page => {
- page.outdated = false;
+ page.setOutdated(false);
});
}
});
@@ -405,9 +434,13 @@ export abstract class CachedResource<
metadata.outdated = false;
if (pageKey) {
- metadata.offsetPage = observable({
- ...metadata.offsetPage,
- pages: expandOffsetPageRange(metadata.offsetPage?.pages || [], pageKey.options, false),
+ const from = pageKey.options.offset;
+ const to = from + pageKey.options.limit;
+
+ metadata.offsetPage?.pages.forEach(page => {
+ if (page.isInRange(from, to)) {
+ page.setOutdated(false);
+ }
});
}
});
@@ -541,13 +574,17 @@ export abstract class CachedResource<
metadata.outdatedIncludes = observable([...metadata.includes]);
if (pageKey) {
- metadata.offsetPage = observable({
- ...metadata.offsetPage,
- pages: expandOffsetPageRange(metadata.offsetPage?.pages || [], pageKey.options, true),
+ const from = pageKey.options.offset;
+ const to = from + pageKey.options.limit;
+
+ metadata.offsetPage?.pages.forEach(page => {
+ if (page.isHasCommonSegment(from, to)) {
+ page.setOutdated(true);
+ }
});
} else {
metadata.offsetPage?.pages.forEach(page => {
- page.outdated = true;
+ page.setOutdated(true);
});
}
});
@@ -559,7 +596,7 @@ export abstract class CachedResource<
metadata.outdated = true;
metadata.outdatedIncludes = observable([...metadata.includes]);
metadata.offsetPage?.pages.forEach(page => {
- page.outdated = true;
+ page.setOutdated(true);
});
});
}
diff --git a/webapp/packages/core-resource/src/Resource/CachedResourceOffsetPageKeys.ts b/webapp/packages/core-resource/src/Resource/CachedResourceOffsetPageKeys.ts
index 3fbc498942..28846e9e3d 100644
--- a/webapp/packages/core-resource/src/Resource/CachedResourceOffsetPageKeys.ts
+++ b/webapp/packages/core-resource/src/Resource/CachedResourceOffsetPageKeys.ts
@@ -5,6 +5,9 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
+import { IResourceOffsetPage } from './OffsetPagination/IResourceOffsetPage';
+import { ResourceOffsetPage } from './OffsetPagination/ResourceOffsetPage';
+import { ResourceKey } from './ResourceKey';
import { resourceKeyAliasFactory } from './ResourceKeyAlias';
import { resourceKeyListAliasFactory } from './ResourceKeyListAlias';
@@ -13,12 +16,6 @@ interface IOffsetPageInfo {
limit: number;
}
-interface IResourceOffsetPage {
- from: number;
- to: number;
- outdated: boolean;
-}
-
export interface ICachedResourceOffsetPage {
totalCount?: number;
end?: number;
@@ -31,6 +28,7 @@ export interface ICachedResourceOffsetPageOptions extends IOffsetPageInfo {}
export const CACHED_RESOURCE_DEFAULT_PAGE_OFFSET = 0;
export const CACHED_RESOURCE_DEFAULT_PAGE_LIMIT = 100;
+export const CachedResourceOffsetPageTargetKey = resourceKeyAliasFactory('@cached-resource/param-chain', (target: ResourceKey) => ({ target }));
export const CachedResourceOffsetPageListKey = resourceKeyListAliasFactory<
any,
[offset: number, limit: number],
@@ -66,8 +64,10 @@ export function getNextPageOffset(info: ICachedResourceOffsetPage): number {
}
export function isOffsetPageOutdated(pages: IResourceOffsetPage[], info: IOffsetPageInfo): boolean {
- for (const { from, to, outdated } of pages) {
- if (outdated && info.offset >= from && info.offset + info.limit <= to) {
+ const from = info.offset;
+ const to = info.offset + info.limit;
+ for (const page of pages) {
+ if (page.isHasCommonSegment(from, to) && page.isOutdated()) {
return true;
}
}
@@ -101,52 +101,48 @@ export function isOffsetPageInRange({ pages, end }: ICachedResourceOffsetPage, i
return false;
}
-export function limitOffsetPages(pages: IResourceOffsetPage[], limit: number): IResourceOffsetPage[] {
- const result: IResourceOffsetPage[] = [];
-
+export function expandOffsetPageRange(
+ pages: IResourceOffsetPage[],
+ info: IOffsetPageInfo,
+ items: any[],
+ outdated: boolean,
+ hasNextPage: boolean,
+): void {
+ const from = info.offset;
+ const to = info.offset + info.limit;
+
+ let pageInserted = false;
for (const page of pages) {
- if (page.from >= limit) {
- break;
+ if (page.to <= from) {
+ continue;
}
- result.push({ ...page, to: Math.min(limit, page.to) });
- }
-
- return result;
-}
-
-export function expandOffsetPageRange(pages: IResourceOffsetPage[], info: IOffsetPageInfo, outdated: boolean): IResourceOffsetPage[] {
- pages = [...pages, { from: info.offset, to: info.offset + info.limit, outdated, end: false }].sort((a, b) => a.from - b.from);
- const result: IResourceOffsetPage[] = [];
- let previous: IResourceOffsetPage | undefined;
- for (const { from, to, outdated } of pages) {
- if (!previous) {
- previous = { from, to, outdated };
- continue;
+ if (!hasNextPage) {
+ if (page.from >= to) {
+ pages.splice(pages.indexOf(page));
+ break;
+ }
}
- if (from <= previous.from + previous.to) {
- if (previous.outdated === outdated) {
- previous.to = Math.max(previous.to, to);
+ if (page.from <= from && !pageInserted) {
+ if (page.from < from) {
+ page.setSize(page.from, from);
+ pages.splice(pages.indexOf(page) + 1, 0, new ResourceOffsetPage().setSize(from, to).update(from, items).setOutdated(outdated));
} else {
- if (previous.from < from) {
- result.push({ ...previous, to: from });
- }
- if (previous.to > to) {
- result.push({ from, to, outdated });
- previous = { ...previous, from: to };
- } else {
- previous = { from, to, outdated };
- }
+ page.setSize(from, to).update(from, items).setOutdated(outdated);
}
- } else {
- result.push(previous);
- previous = { from, to, outdated };
+ pageInserted = true;
+ continue;
+ }
+
+ if (page.isInRange(from, to)) {
+ pages.splice(pages.indexOf(page), 1);
}
}
- if (previous) {
- result.push(previous);
+ const lastPage = pages[pages.length - 1];
+
+ if (!lastPage || lastPage.to <= from) {
+ pages.push(new ResourceOffsetPage().setSize(from, to).update(from, items).setOutdated(outdated));
}
- return result;
}
diff --git a/webapp/packages/core-resource/src/Resource/OffsetPagination/IResourceOffsetPage.ts b/webapp/packages/core-resource/src/Resource/OffsetPagination/IResourceOffsetPage.ts
new file mode 100644
index 0000000000..a9da410ddf
--- /dev/null
+++ b/webapp/packages/core-resource/src/Resource/OffsetPagination/IResourceOffsetPage.ts
@@ -0,0 +1,26 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 DBeaver Corp and others
+ *
+ * Licensed under the Apache License, Version 2.0.
+ * you may not use this file except in compliance with the License.
+ */
+
+export interface IResourceOffsetPage {
+ from: number;
+ to: number;
+ items: any[];
+ outdated: boolean;
+
+ get(from: number, to: number): any[];
+
+ isOutdated(): boolean;
+ isHasCommonSegment(from: number, to: number): boolean;
+ isInRange(from: number, to: number): boolean;
+
+ setSize(from: number, to: number): this;
+
+ setOutdated(outdated: boolean): this;
+
+ update(from: number, items: any[]): this;
+}
diff --git a/webapp/packages/core-resource/src/Resource/OffsetPagination/ResourceOffsetPage.ts b/webapp/packages/core-resource/src/Resource/OffsetPagination/ResourceOffsetPage.ts
new file mode 100644
index 0000000000..90d1433e5a
--- /dev/null
+++ b/webapp/packages/core-resource/src/Resource/OffsetPagination/ResourceOffsetPage.ts
@@ -0,0 +1,83 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 DBeaver Corp and others
+ *
+ * Licensed under the Apache License, Version 2.0.
+ * you may not use this file except in compliance with the License.
+ */
+import { makeObservable, observable } from 'mobx';
+
+import { IResourceOffsetPage } from './IResourceOffsetPage';
+
+export class ResourceOffsetPage implements IResourceOffsetPage {
+ from: number;
+ to: number;
+ items: any[];
+ outdated: boolean;
+
+ constructor() {
+ this.from = 0;
+ this.to = 0;
+ this.items = [];
+ this.outdated = false;
+
+ makeObservable(this, {
+ from: true,
+ to: true,
+ items: observable.shallow,
+ outdated: observable,
+ });
+ }
+
+ get(from: number, to: number): any[] {
+ return this.items.slice(from - this.from, to - this.from);
+ }
+
+ isOutdated(): boolean {
+ return this.outdated;
+ }
+
+ isHasCommonSegment(from: number, to: number): boolean {
+ return !(to < this.from || this.to <= from);
+ }
+
+ isInRange(from: number, to: number): boolean {
+ return this.from >= from && this.to <= to;
+ }
+
+ setSize(from: number, to: number): this {
+ const prevForm = this.from;
+ const prevTo = this.to;
+
+ this.from = from;
+ this.to = to;
+
+ if (from >= prevForm) {
+ this.items.splice(0, from - prevForm);
+ } else {
+ this.items.unshift(...new Array(prevForm - from));
+ this.setOutdated(true);
+ }
+
+ if (to - from <= prevTo - prevForm) {
+ this.items.splice(to - from);
+ } else {
+ this.items.push(...new Array(to - from - (prevTo - prevForm)));
+ this.setOutdated(true);
+ }
+
+ return this;
+ }
+
+ update(from: number, items: any[]): this {
+ this.items.splice(from - this.from, items.length, ...items);
+
+ return this;
+ }
+
+ setOutdated(outdated: boolean): this {
+ this.outdated = outdated;
+
+ return this;
+ }
+}
diff --git a/webapp/packages/core-resource/src/Resource/Resource.ts b/webapp/packages/core-resource/src/Resource/Resource.ts
index 0e451dac95..3804a0bf4a 100644
--- a/webapp/packages/core-resource/src/Resource/Resource.ts
+++ b/webapp/packages/core-resource/src/Resource/Resource.ts
@@ -40,7 +40,10 @@ export abstract class Resource<
protected readonly logger: ResourceLogger;
protected readonly metadata: ResourceMetadata;
- constructor(protected readonly defaultValue: () => TData, protected defaultIncludes: TInclude = [] as any) {
+ constructor(
+ protected readonly defaultValue: () => TData,
+ protected defaultIncludes: TInclude = [] as any,
+ ) {
super();
this.isKeyEqual = this.isKeyEqual.bind(this);
this.isIntersect = this.isIntersect.bind(this);
@@ -96,7 +99,7 @@ export abstract class Resource<
key = this.aliases.transformToAlias(key);
nextKey = this.aliases.transformToAlias(nextKey);
- return key.isEqual(nextKey) && this.isIntersect(key.target, nextKey.target);
+ return key.isEqual(nextKey);
} else if (isResourceAlias(key) || isResourceAlias(nextKey)) {
return true;
}
@@ -123,7 +126,7 @@ export abstract class Resource<
param = this.aliases.transformToAlias(param);
second = this.aliases.transformToAlias(second);
- return param.isEqual(second) && this.isEqual(param.target, second.target);
+ return param.isEqual(second);
}
if (isResourceAlias(param) || isResourceAlias(second)) {
diff --git a/webapp/packages/core-resource/src/Resource/ResourceAlias.ts b/webapp/packages/core-resource/src/Resource/ResourceAlias.ts
index 46358f1d00..ed23a86b95 100644
--- a/webapp/packages/core-resource/src/Resource/ResourceAlias.ts
+++ b/webapp/packages/core-resource/src/Resource/ResourceAlias.ts
@@ -7,15 +7,16 @@
*/
import { isObjectsEqual } from '@cloudbeaver/core-utils';
+import { ResourceKey } from './ResourceKey';
+
export type ResourceAliasOptionsKey = string | number;
-export type ResourceAliasOptionsValueTypes = string | number | boolean | null | undefined;
+export type ResourceAliasOptionsValueTypes = string | number | boolean | ResourceKey | null | undefined;
export type ResourceAliasOptionsValue = ResourceAliasOptionsValueTypes | Array;
export type ResourceAliasOptions = Readonly> | undefined;
export abstract class ResourceAlias {
readonly id: string;
readonly options: TOptions;
- target: any;
parent?: ResourceAlias;
private readonly typescriptHack: TKey;
abstract readonly name: string;
@@ -41,14 +42,10 @@ export abstract class ResourceAlias
return undefined;
}
- setTarget(target: any): this {
- this.target = target;
- return this;
- }
-
setParent(parent: ResourceAlias): this {
+ parent = this.parent ? this.parent.setParent(parent) : parent;
const copy = new (this.constructor as any)(this.id, this.options, parent) as this;
- return copy.setTarget(this.target);
+ return copy;
}
isEqual(key: ResourceAlias): boolean {
diff --git a/webapp/packages/core-resource/src/Resource/ResourceMetadata.ts b/webapp/packages/core-resource/src/Resource/ResourceMetadata.ts
index 55510057ca..b5c9c67039 100644
--- a/webapp/packages/core-resource/src/Resource/ResourceMetadata.ts
+++ b/webapp/packages/core-resource/src/Resource/ResourceMetadata.ts
@@ -7,8 +7,9 @@
*/
import { observable } from 'mobx';
-import { DefaultValueGetter, isNotNullDefined, isPrimitive, MetadataMap } from '@cloudbeaver/core-utils';
+import { DefaultValueGetter, isPrimitive, MetadataMap } from '@cloudbeaver/core-utils';
+import { CachedResourceOffsetPageKey, CachedResourceOffsetPageListKey } from './CachedResourceOffsetPageKeys';
import type { ICachedResourceMetadata } from './ICachedResourceMetadata';
import { isResourceAlias } from './ResourceAlias';
import type { ResourceAliases } from './ResourceAliases';
@@ -193,8 +194,8 @@ export class ResourceMetadata {
if (isResourceAlias(key)) {
key = this.aliases.transformToAlias(key);
- if (isNotNullDefined(key.target)) {
- return this.getMetadataKeyRef(key.target);
+ if (this.aliases.isAlias(key, CachedResourceOffsetPageKey) || this.aliases.isAlias(key, CachedResourceOffsetPageListKey)) {
+ return this.getMetadataKeyRef(key.parent as any);
}
return key.toString() as TKey;
diff --git a/webapp/packages/core-resource/src/Resource/ResourceOffsetPagination.ts b/webapp/packages/core-resource/src/Resource/ResourceOffsetPagination.ts
index 7744a1ac5a..d07cdad696 100644
--- a/webapp/packages/core-resource/src/Resource/ResourceOffsetPagination.ts
+++ b/webapp/packages/core-resource/src/Resource/ResourceOffsetPagination.ts
@@ -8,17 +8,20 @@
import { observable } from 'mobx';
import {
+ expandOffsetPageRange,
ICachedResourceOffsetPage,
type ICachedResourceOffsetPageOptions,
isOffsetPageInRange,
- limitOffsetPages,
} from './CachedResourceOffsetPageKeys';
import type { ICachedResourceMetadata } from './ICachedResourceMetadata';
import type { ResourceAlias } from './ResourceAlias';
import type { ResourceMetadata } from './ResourceMetadata';
export class ResourceOffsetPagination {
- constructor(protected metadata: ResourceMetadata) {
+ constructor(
+ protected metadata: ResourceMetadata,
+ private readonly getStableKey: (key: TKey) => TKey,
+ ) {
this.metadata = metadata;
}
@@ -47,29 +50,35 @@ export class ResourceOffsetPagination>, hasNextPage: boolean): void {
- const count = key.options.offset + key.options.limit;
+ setPage(key: ResourceAlias>, items: any[], hasNextPage: boolean) {
+ const offset = key.options.offset;
+ const limit = offset + key.options.limit;
this.metadata.update(key as TKey, metadata => {
let end = metadata.offsetPage?.end;
if (hasNextPage) {
- if (end !== undefined && end <= count) {
+ if (end !== undefined && end <= limit) {
end = undefined;
}
} else {
- end = count;
+ end = limit;
}
- metadata.offsetPage = observable({
- pages: [],
- ...metadata.offsetPage,
- end,
- });
+ if (!metadata.offsetPage) {
+ metadata.offsetPage = observable({
+ pages: [],
+ end,
+ });
+ }
+
+ metadata.offsetPage.end = end;
- if (!hasNextPage) {
- metadata.offsetPage.pages = limitOffsetPages(metadata.offsetPage?.pages || [], count);
+ if (!metadata.offsetPage.pages) {
+ metadata.offsetPage.pages = [];
}
+
+ expandOffsetPageRange(metadata.offsetPage.pages, key.options, items.map(this.getStableKey), false, hasNextPage);
});
}
}
diff --git a/webapp/packages/core-resource/src/index.ts b/webapp/packages/core-resource/src/index.ts
index de996d0233..6ff9ce3edd 100644
--- a/webapp/packages/core-resource/src/index.ts
+++ b/webapp/packages/core-resource/src/index.ts
@@ -1,3 +1,10 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 DBeaver Corp and others
+ *
+ * Licensed under the Apache License, Version 2.0.
+ * you may not use this file except in compliance with the License.
+ */
export * from './Resource/CachedDataResource';
export * from './Resource/CachedMapResource';
export * from './Resource/CachedResource';
@@ -7,6 +14,7 @@ export {
CACHED_RESOURCE_DEFAULT_PAGE_LIMIT,
CachedResourceOffsetPageKey,
CachedResourceOffsetPageListKey,
+ CachedResourceOffsetPageTargetKey,
getNextPageOffset,
type ICachedResourceOffsetPageOptions,
} from './Resource/CachedResourceOffsetPageKeys';
diff --git a/webapp/packages/core-ui/src/Form/Components/BaseForm.tsx b/webapp/packages/core-ui/src/Form/Components/BaseForm.tsx
index e9de4a144b..96f18cc7c6 100644
--- a/webapp/packages/core-ui/src/Form/Components/BaseForm.tsx
+++ b/webapp/packages/core-ui/src/Form/Components/BaseForm.tsx
@@ -7,7 +7,7 @@
*/
import { observer } from 'mobx-react-lite';
-import { Button, Container, Form, s, StatusMessage, useAutoLoad, useForm, useS, useTranslate } from '@cloudbeaver/core-blocks';
+import { Button, Container, Form, getComputed, s, StatusMessage, useForm, useS, useTranslate } from '@cloudbeaver/core-blocks';
import { getFirstException } from '@cloudbeaver/core-utils';
import { TabList } from '../../Tabs/TabList';
@@ -22,7 +22,8 @@ export const BaseForm = observer>(function BaseForm({ servic
const translate = useTranslate();
const editing = state.mode === FormMode.Edit;
- const changed = state.isChanged();
+ const changed = state.isChanged;
+ const error = getComputed(() => getFirstException(state.exception));
const form = useForm({
async onSubmit() {
@@ -36,15 +37,13 @@ export const BaseForm = observer>(function BaseForm({ servic
},
});
- useAutoLoad(BaseForm, state);
-
return (