From 5ce49faea029f2a609eeda3df08e7ef56f767c7f Mon Sep 17 00:00:00 2001 From: daelynum Date: Fri, 13 Sep 2024 17:09:37 +0200 Subject: [PATCH 01/13] dbeaver/tech-docs#607 updated images in README.md --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f4f8107f95..d57d026f26 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 which provides rich web interface. Server itself is a Java application, web part is written on 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 out [WIKI](https://github.com/dbeaver/cloudbeaver/wiki) for more details. -![](https://github.com/dbeaver/cloudbeaver/wiki/images/demo_screenshot_1.png) + + + + ## Run in Docker From 461e2517531207a21cbd429eddeeef28b90bb2e1 Mon Sep 17 00:00:00 2001 From: daelynum Date: Mon, 16 Sep 2024 14:29:02 +0200 Subject: [PATCH 02/13] dbeaver/tech-docs#607 updated images in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fde20e181d..5bcf48ae8f 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ CloudBeaver is a web server that provides a rich web interface. The server itsel 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. + + - - ## Run in Docker From 12e1fb57d7b4be429b12de6d4a5077855291c06c Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Tue, 17 Sep 2024 14:04:02 +0200 Subject: [PATCH 03/13] dbeaver/dbeaver-devops#1483 Added docker apt-get upgrade --- deploy/docker/cloudbeaver-ce/Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/deploy/docker/cloudbeaver-ce/Dockerfile b/deploy/docker/cloudbeaver-ce/Dockerfile index 361df92c0a..4679467a03 100644 --- a/deploy/docker/cloudbeaver-ce/Dockerfile +++ b/deploy/docker/cloudbeaver-ce/Dockerfile @@ -2,6 +2,10 @@ FROM dbeaver/base-java MAINTAINER DBeaver Corp, devops@dbeaver.com +RUN set -eux; \ + apt-get update; \ + apt-get upgrade -y; + COPY cloudbeaver /opt/cloudbeaver EXPOSE 8978 From ea2246af31553478a538d2396232baf1cc2836e3 Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Tue, 17 Sep 2024 14:09:01 +0200 Subject: [PATCH 04/13] dbeaver/dbeaver-devops#1483 Remove sed before apt-get --- deploy/docker/cloudbeaver-ce/Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deploy/docker/cloudbeaver-ce/Dockerfile b/deploy/docker/cloudbeaver-ce/Dockerfile index 4679467a03..95bdcc6812 100644 --- a/deploy/docker/cloudbeaver-ce/Dockerfile +++ b/deploy/docker/cloudbeaver-ce/Dockerfile @@ -2,8 +2,7 @@ FROM dbeaver/base-java MAINTAINER DBeaver Corp, devops@dbeaver.com -RUN set -eux; \ - apt-get update; \ +RUN apt-get update; \ apt-get upgrade -y; COPY cloudbeaver /opt/cloudbeaver From 30e74ce09f24ae7133238c9e126dc9f032938bd6 Mon Sep 17 00:00:00 2001 From: alex <48489896+devnaumov@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:44:44 +0200 Subject: [PATCH 05/13] CB-5450 purge cache if quota error happens (#2917) * CB-5450 purge cache if quota error happens * CB-5450 cache only images from the same origin * CB-5450 change window to self * CB-5450 revert purgeOnQuotaError --------- Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com> --- webapp/packages/core-browser/src/service-worker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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, }), ], }), From 5b527872bd7e70fd877b323004f85a281a5ca7d7 Mon Sep 17 00:00:00 2001 From: DenisSinelnikov <142215442+DenisSinelnikov@users.noreply.github.com> Date: Tue, 17 Sep 2024 20:30:19 +0400 Subject: [PATCH 06/13] CB-4095. Added env variables for configurations parameters (#2905) * CB-4095. Added env variables for configurations parameters * CB-4095. Refactor after review * CB-4095. Refactor after review, added sub group for envs * CB-4095. Remove unusable env variable --------- Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com> Co-authored-by: Alexander Skoblikov --- .../DefaultConfiguration/cloudbeaver.conf | 41 ++++++++-------- .../SQLiteConfiguration/cloudbeaver.conf | 39 ++++++++------- .../service/sql/WebSQLConstants.java | 1 - .../workspace/conf/cloudbeaver.conf | 47 +++++++++---------- 4 files changed, 62 insertions(+), 66 deletions(-) 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/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/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: [ From 4f0db5b854f634074893136bac31fcd7dc656014 Mon Sep 17 00:00:00 2001 From: Ainur <59531286+yagudin10@users.noreply.github.com> Date: Tue, 17 Sep 2024 18:50:04 +0200 Subject: [PATCH 07/13] CB-4896 migrate to jetty 12 (#2847) * CB-4896 migrate to jetty 12 * CB-4896 fix root uri * CB-4896 fix proxy handler paths * CB-4896 resolve conflicts * CB-4896 fix hidden gql errors * CB-4896 redirect welcome mode (redirect from '/' to index.html) * CB-4896 fix url 'blinking' --------- Co-authored-by: Alexander Skoblikov Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com> --- .../io.cloudbeaver.model/META-INF/MANIFEST.MF | 2 +- .../model/session/WebHttpRequestInfo.java | 57 ++++++ .../cloudbeaver/model/session/WebSession.java | 15 +- .../server/graphql/GraphQLEndpoint.java | 2 + .../server/jetty/CBJettyServer.java | 76 ++++---- .../server/jetty/CBJettyServletContext.java | 4 +- .../server/jetty/CBSessionHandler.java | 169 +++++++++++------- .../CBSymLinkContentAllowedAliasChecker.java | 38 ++++ .../server/servlets/CBStaticServlet.java | 59 +----- .../server/servlets/CBStatusServlet.java | 3 +- .../server/servlets/ProxyResourceHandler.java | 80 +++++++++ .../websockets/CBAbstractWebSocket.java | 24 +-- .../server/websockets/CBEventsWebSocket.java | 12 +- .../websockets/CBExpiredSessionWebSocket.java | 4 +- .../websockets/CBJettyWebSocketManager.java | 42 +++-- .../websockets/WebSocketPingPongCallback.java | 8 +- .../service/session/WebSessionManager.java | 41 +++-- .../service/sql/WebSQLFileLoaderServlet.java | 4 +- .../service/sql/WebSQLResultServlet.java | 30 +++- .../service/fs/model/WebFSServlet.java | 4 +- 20 files changed, 430 insertions(+), 244 deletions(-) create mode 100644 server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebHttpRequestInfo.java create mode 100644 server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBSymLinkContentAllowedAliasChecker.java create mode 100644 server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/servlets/ProxyResourceHandler.java diff --git a/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF index 6a557347eb..b1a8d173c5 100644 --- a/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF +++ b/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF @@ -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/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.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.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)) { From 2d1bde22a2ec463848fb208a627661a70cc1579f Mon Sep 17 00:00:00 2001 From: sergeyteleshev Date: Tue, 17 Sep 2024 19:14:34 +0200 Subject: [PATCH 08/13] Cb 5562 Fix bad this.setState for the new form API (#2903) * CB-5478 adds useAdministrationUserFormState with flexible id field * CB-5478 simplifies logic fork form state hooks * CB-5562 refactors form state and form parts * CB-5562 fixes disabled param for formState * CB-5562 comments cleanup * CB-5562 fixes getting of exceptions * CB-5562 baseform bad set state fix * CB-5562 removes load and configure method for forms * CB-5562 reverts some state.exception changes * CB-5562 removes bad set state from user form * CB-5562 cleanup * CB-5562 cleanup * CB-5562 removes bad set state from user form * CB-5562 fixes bad set state for user profile * CB-5562 pr fixes * CB-5562 pr fixes --------- Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com> --- .../core-ui/src/Form/Components/BaseForm.tsx | 9 +- webapp/packages/core-ui/src/Form/FormPart.ts | 22 +++- webapp/packages/core-ui/src/Form/FormState.ts | 115 +++++------------- webapp/packages/core-ui/src/Form/IFormPart.ts | 4 +- .../packages/core-ui/src/Form/IFormState.ts | 19 +-- .../Users/UserForm/AdministrationUserForm.tsx | 4 +- .../UserFormConnectionAccessPart.ts | 2 +- .../Users/UserForm/Info/UserFormInfoPart.ts | 17 +-- .../UserProfileFormAuthenticationPart.ts | 2 +- .../UserInfoPart/UserProfileFormInfoPart.ts | 2 +- .../UserProfileForm/UserProfileFormPanel.tsx | 2 +- 11 files changed, 68 insertions(+), 130 deletions(-) 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 (
- + diff --git a/webapp/packages/core-ui/src/Form/FormPart.ts b/webapp/packages/core-ui/src/Form/FormPart.ts index 2dcf2063d7..c789e0df64 100644 --- a/webapp/packages/core-ui/src/Form/FormPart.ts +++ b/webapp/packages/core-ui/src/Form/FormPart.ts @@ -5,7 +5,7 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import { action, makeObservable, observable, toJS } from 'mobx'; +import { action, computed, makeObservable, observable, toJS } from 'mobx'; import { executorHandlerFilter, ExecutorInterrupter, type IExecutionContextProvider } from '@cloudbeaver/core-executor'; import { isObjectsEqual } from '@cloudbeaver/core-utils'; @@ -16,6 +16,7 @@ import type { IFormState } from './IFormState'; export abstract class FormPart implements IFormPart { state: TPartState; initialState: TPartState; + isSaving: boolean; exception: Error | null; promise: Promise | null; @@ -29,6 +30,7 @@ export abstract class FormPart implements IFormPar ) { this.initialState = initialState; this.state = toJS(this.initialState); + this.isSaving = false; this.exception = null; this.promise = null; @@ -37,7 +39,6 @@ export abstract class FormPart implements IFormPar this.loading = false; this.formState.submitTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.save.bind(this))); - this.formState.configureTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.configure.bind(this))); this.formState.formatTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.format.bind(this))); this.formState.validationTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.validate.bind(this))); @@ -46,12 +47,19 @@ export abstract class FormPart implements IFormPar state: observable, exception: observable.ref, promise: observable.ref, + isSaving: observable.ref, loaded: observable, loading: observable, setInitialState: action, + isDisabled: computed, + isChanged: computed, }); } + get isDisabled(): boolean { + return this.isSaving || this.isLoading(); + } + isLoading(): boolean { return this.loading; } @@ -68,7 +76,7 @@ export abstract class FormPart implements IFormPar return this.exception !== null; } - isChanged(): boolean { + get isChanged(): boolean { if (!this.loaded || this.initialState === this.state) { return false; } @@ -85,10 +93,12 @@ export abstract class FormPart implements IFormPar try { await this.loader(); - if (!this.isChanged()) { + if (!this.isChanged) { return; } + this.isSaving = true; + await this.saveChanges(data, contexts); if (ExecutorInterrupter.isInterrupted(contexts)) { return; @@ -100,6 +110,7 @@ export abstract class FormPart implements IFormPar this.exception = exception; throw exception; } finally { + this.isSaving = false; this.loading = false; } } @@ -136,7 +147,7 @@ export abstract class FormPart implements IFormPar protected setInitialState(initialState: TPartState) { this.initialState = initialState; - if (this.isChanged()) { + if (this.isChanged) { return; } @@ -147,7 +158,6 @@ export abstract class FormPart implements IFormPar this.state = state; } - protected configure(data: IFormState, contexts: IExecutionContextProvider>): void | Promise {} protected format(data: IFormState, contexts: IExecutionContextProvider>): void | Promise {} protected validate(data: IFormState, contexts: IExecutionContextProvider>): void | Promise {} diff --git a/webapp/packages/core-ui/src/Form/FormState.ts b/webapp/packages/core-ui/src/Form/FormState.ts index 2b651eda81..93bd21daa2 100644 --- a/webapp/packages/core-ui/src/Form/FormState.ts +++ b/webapp/packages/core-ui/src/Form/FormState.ts @@ -11,7 +11,7 @@ import { DataContext, dataContextAddDIProvider, DataContextGetter, type IDataCon import type { IServiceProvider } from '@cloudbeaver/core-di'; import type { ENotificationType } from '@cloudbeaver/core-events'; import { Executor, ExecutorInterrupter, IExecutionContextProvider, type IExecutor } from '@cloudbeaver/core-executor'; -import { isLoadableStateHasException, MetadataMap, uuid } from '@cloudbeaver/core-utils'; +import { isArraysEqual, isNotNullDefined, MetadataMap, uuid } from '@cloudbeaver/core-utils'; import { DATA_CONTEXT_LOADABLE_STATE, loadableStateContext } from '@cloudbeaver/core-view'; import { DATA_CONTEXT_FORM_STATE } from './DATA_CONTEXT_FORM_STATE'; @@ -25,23 +25,24 @@ export class FormState implements IFormState { mode: FormMode; parts: MetadataMap>; state: TState; - isSaving: boolean; statusMessage: string | string[] | null; statusType: ENotificationType | null; - exception: Error | (Error | null)[] | null; promise: Promise | null; get isDisabled(): boolean { - return this.isSaving || this.isLoading(); + return this.partsValues.some(part => part.isSaving || part?.isLoading?.()); + } + + get isSaving(): boolean { + return this.partsValues.some(part => part.isSaving); } readonly id: string; readonly service: FormBaseService; readonly dataContext: IDataContext; - readonly configureTask: IExecutor>; readonly formStateTask: IExecutor; readonly fillDefaultConfigTask: IExecutor>; readonly submitTask: IExecutor>; @@ -56,17 +57,12 @@ export class FormState implements IFormState { this.mode = FormMode.Create; this.parts = new MetadataMap(); this.state = state; - this.isSaving = false; this.statusMessage = null; this.statusType = null; - this.exception = null; this.promise = null; - this.configureTask = new Executor(this as IFormState, () => true); - this.configureTask.addCollection(service.onConfigure); - this.formStateTask = new Executor(state, () => true); this.formStateTask.addCollection(service.onState).addPostHandler(this.updateFormState.bind(this)); @@ -90,42 +86,43 @@ export class FormState implements IFormState { mode: observable, parts: observable.ref, promise: observable.ref, - exception: observable.ref, - isSaving: observable.ref, state: observable, + isSaving: computed, + exception: computed, isDisabled: computed, setMode: action, setPartsState: action, - setException: action, setState: action, + isChanged: computed, + partsValues: computed[]>({ + equals: isArraysEqual, + }), + isError: computed, + isCancelled: computed, }); } - isLoading(): boolean { - return this.promise !== null || this.dataContext.get(DATA_CONTEXT_LOADABLE_STATE)!.loaders.some(loader => loader.isLoading()); - } - - isLoaded(): boolean { - if (this.promise) { - return false; - } - return this.dataContext.get(DATA_CONTEXT_LOADABLE_STATE)!.loaders.every(loader => loader.isLoaded()); + get partsValues() { + return Array.from(this.parts.values()); } - isError(): boolean { - return this.dataContext.get(DATA_CONTEXT_LOADABLE_STATE)!.loaders.some(loader => loader.isError()); + get exception(): Error | (Error | null)[] | null { + return this.partsValues + .map(part => part?.exception) + .flat() + .filter(isNotNullDefined); } - isOutdated(): boolean { - return this.dataContext.get(DATA_CONTEXT_LOADABLE_STATE)!.loaders.some(loader => loader.isOutdated?.() === true); + get isError(): boolean { + return this.partsValues.some(part => part.isError()); } - isCancelled(): boolean { - return this.dataContext.get(DATA_CONTEXT_LOADABLE_STATE)!.loaders.some(loader => loader.isCancelled?.() === true); + get isCancelled(): boolean { + return this.partsValues.some(part => part?.isCancelled?.()); } - isChanged(): boolean { - return Array.from(this.parts.values()).some(part => part.isChanged()); + get isChanged(): boolean { + return this.partsValues.some(part => part.isChanged); } getPart>(getter: DataContextGetter, init: (context: IDataContext, id: string) => T): T { @@ -140,50 +137,6 @@ export class FormState implements IFormState { }) as T; } - async load(refresh?: boolean): Promise { - if (this.promise !== null) { - return this.promise; - } - - if (this.isLoaded() && !this.isOutdated() && !refresh) { - return; - } - - this.promise = (async () => { - try { - await this.configureTask.execute(this); - - const loaders = this.dataContext.get(DATA_CONTEXT_LOADABLE_STATE)!.loaders; - - for (const loader of loaders) { - if (isLoadableStateHasException(loader)) { - continue; - } - - if (!loader.isLoaded() || loader.isOutdated?.() === true) { - try { - await loader.load(); - } catch { - return; - } - } - } - - await this.fillDefaultConfigTask.execute(this); - this.exception = null; - } catch (exception: any) { - this.exception = exception; - throw exception; - } finally { - this.promise = null; - } - })(); - } - - async reload(): Promise { - await this.load(true); - } - cancel(): void { const loaders = this.dataContext.get(DATA_CONTEXT_LOADABLE_STATE)!.loaders; @@ -210,11 +163,6 @@ export class FormState implements IFormState { return this; } - setException(exception: Error | (Error | null)[] | null): this { - this.exception = exception; - return this; - } - setState(state: TState): this { this.state = state; return this; @@ -222,20 +170,15 @@ export class FormState implements IFormState { async save(): Promise { try { - this.isSaving = true; const context = await this.submitTask.execute(this); if (ExecutorInterrupter.isInterrupted(context)) { return false; } - this.exception = null; return true; - } catch (exception: any) { - this.exception = exception; - } finally { - this.isSaving = false; - } + } catch (exception: any) {} + return false; } diff --git a/webapp/packages/core-ui/src/Form/IFormPart.ts b/webapp/packages/core-ui/src/Form/IFormPart.ts index faa9e63117..446c33fd97 100644 --- a/webapp/packages/core-ui/src/Form/IFormPart.ts +++ b/webapp/packages/core-ui/src/Form/IFormPart.ts @@ -10,8 +10,10 @@ import type { ILoadableState } from '@cloudbeaver/core-utils'; export interface IFormPart extends ILoadableState { readonly state: TState; readonly initialState: TState; + isSaving: boolean; + readonly isDisabled: boolean; - isChanged(): boolean; + readonly isChanged: boolean; load(): Promise; reset(): void; diff --git a/webapp/packages/core-ui/src/Form/IFormState.ts b/webapp/packages/core-ui/src/Form/IFormState.ts index 67b2029e1f..050332649b 100644 --- a/webapp/packages/core-ui/src/Form/IFormState.ts +++ b/webapp/packages/core-ui/src/Form/IFormState.ts @@ -8,13 +8,13 @@ import type { DataContextGetter, IDataContext } from '@cloudbeaver/core-data-context'; import type { ENotificationType } from '@cloudbeaver/core-events'; import type { IExecutor } from '@cloudbeaver/core-executor'; -import type { ILoadableState, MetadataMap } from '@cloudbeaver/core-utils'; +import type { MetadataMap } from '@cloudbeaver/core-utils'; import type { FormBaseService } from './FormBaseService'; import type { FormMode } from './FormMode'; import type { IFormPart } from './IFormPart'; -export interface IFormState extends ILoadableState { +export interface IFormState { readonly id: string; readonly service: FormBaseService; readonly dataContext: IDataContext; @@ -23,14 +23,13 @@ export interface IFormState extends ILoadableState { readonly parts: MetadataMap; readonly state: TState; readonly isDisabled: boolean; + readonly exception: Error | (Error | null)[] | null; readonly promise: Promise | null; - readonly exception: Error | (Error | null)[] | null; readonly statusMessage: string | string[] | null; readonly statusType: ENotificationType | null; - readonly configureTask: IExecutor>; readonly formStateTask: IExecutor; readonly fillDefaultConfigTask: IExecutor>; readonly submitTask: IExecutor>; @@ -39,20 +38,14 @@ export interface IFormState extends ILoadableState { setMode(mode: FormMode): this; setPartsState(state: MetadataMap): this; - setException(exception: Error | (Error | null)[] | null): this; setState(state: TState): this; getPart>(getter: DataContextGetter, init: (context: IDataContext, id: string) => T): T; - isLoading(): boolean; - isLoaded(): boolean; - isError(): boolean; - isOutdated(): boolean; - isCancelled(): boolean; - isChanged(): boolean; + isError: boolean; + isCancelled: boolean; + isChanged: boolean; - load(): Promise; - reload(): Promise; save(): Promise; reset(): void; cancel(): void; diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserForm.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserForm.tsx index 5c006048a7..99bd4c784e 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserForm.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserForm.tsx @@ -59,7 +59,7 @@ export const AdministrationUserForm = observer(function AdministrationUse }, }); - useAutoLoad(AdministrationUserForm, state); + useAutoLoad(AdministrationUserForm, [userFormInfoPart]); return ( @@ -69,7 +69,7 @@ export const AdministrationUserForm = observer(function AdministrationUse diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccessPart.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccessPart.ts index 5e7d6ea83b..6cc4e3cf2a 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccessPart.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccessPart.ts @@ -24,7 +24,7 @@ export class UserFormConnectionAccessPart extends FormPart [ - getCachedDataResourceLoaderState(this.serverConfigResource, () => undefined), - getCachedDataResourceLoaderState(this.authRolesResource, () => undefined), - ]); - } private async updateCredentials() { const password = this.state.password; @@ -220,7 +211,7 @@ export class UserFormInfoPart extends FormPart Date: Tue, 17 Sep 2024 20:46:54 +0200 Subject: [PATCH 09/13] 24.2.1 version bump --- server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF | 4 ++-- server/bundles/io.cloudbeaver.model/pom.xml | 2 +- server/bundles/io.cloudbeaver.product.ce/META-INF/MANIFEST.MF | 4 ++-- server/bundles/io.cloudbeaver.product.ce/pom.xml | 2 +- .../META-INF/MANIFEST.MF | 4 ++-- server/bundles/io.cloudbeaver.resources.drivers.base/pom.xml | 2 +- server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF | 4 ++-- server/bundles/io.cloudbeaver.server/pom.xml | 2 +- .../bundles/io.cloudbeaver.service.admin/META-INF/MANIFEST.MF | 4 ++-- server/bundles/io.cloudbeaver.service.admin/pom.xml | 2 +- .../bundles/io.cloudbeaver.service.auth/META-INF/MANIFEST.MF | 4 ++-- server/bundles/io.cloudbeaver.service.auth/pom.xml | 2 +- .../io.cloudbeaver.service.data.transfer/META-INF/MANIFEST.MF | 4 ++-- server/bundles/io.cloudbeaver.service.data.transfer/pom.xml | 2 +- server/bundles/io.cloudbeaver.service.fs/META-INF/MANIFEST.MF | 4 ++-- server/bundles/io.cloudbeaver.service.fs/pom.xml | 2 +- .../io.cloudbeaver.service.metadata/META-INF/MANIFEST.MF | 4 ++-- server/bundles/io.cloudbeaver.service.metadata/pom.xml | 2 +- .../io.cloudbeaver.service.rm.nio/META-INF/MANIFEST.MF | 4 ++-- server/bundles/io.cloudbeaver.service.rm.nio/pom.xml | 2 +- server/bundles/io.cloudbeaver.service.rm/META-INF/MANIFEST.MF | 4 ++-- server/bundles/io.cloudbeaver.service.rm/pom.xml | 2 +- .../io.cloudbeaver.service.security/META-INF/MANIFEST.MF | 4 ++-- server/bundles/io.cloudbeaver.service.security/pom.xml | 2 +- server/bundles/io.cloudbeaver.slf4j/META-INF/MANIFEST.MF | 4 ++-- server/bundles/io.cloudbeaver.slf4j/pom.xml | 2 +- server/features/io.cloudbeaver.ce.drivers.feature/feature.xml | 2 +- server/features/io.cloudbeaver.ce.drivers.feature/pom.xml | 2 +- server/features/io.cloudbeaver.product.ce.feature/feature.xml | 2 +- server/features/io.cloudbeaver.product.ce.feature/pom.xml | 2 +- server/features/io.cloudbeaver.server.feature/feature.xml | 2 +- server/features/io.cloudbeaver.server.feature/pom.xml | 2 +- server/features/io.cloudbeaver.ws.feature/feature.xml | 2 +- server/features/io.cloudbeaver.ws.feature/pom.xml | 2 +- server/pom.xml | 2 +- server/product/web-server/CloudbeaverServer.product | 2 +- server/product/web-server/pom.xml | 2 +- 37 files changed, 50 insertions(+), 50 deletions(-) diff --git a/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF index b1a8d173c5..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: . 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.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.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.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 From 3c5aa6b7e85371e41cf4ec96859644af1dab8a5b Mon Sep 17 00:00:00 2001 From: Alexey Date: Wed, 18 Sep 2024 21:17:59 +0800 Subject: [PATCH 10/13] CB-5658 CB-5659 fix: pagination resolving (#2922) * CB-5658 CB-5659 fix: pagination resolving * CB-5658 fix resources pages * CB-5667 fix: node rename behavior * CB-5658 chore: refactor repeated code --------- Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com> Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com> --- .../core-authentication/src/UsersResource.ts | 17 ++--- .../src/NodesManager/DBObjectResource.ts | 19 ++--- .../src/NodesManager/NavTreeResource.ts | 17 +++-- .../src/Resource/CachedResource.ts | 72 ++++++++++++------- .../src/Resource/ResourceAlias.ts | 2 +- .../src/Resource/ResourceAliases.ts | 45 +++++++++--- .../src/Resource/ResourceMetadata.ts | 8 ++- .../src/Resource/getOffsetPageKeyInfo.ts | 46 ++++++++++++ webapp/packages/core-resource/src/index.ts | 1 + 9 files changed, 153 insertions(+), 74 deletions(-) create mode 100644 webapp/packages/core-resource/src/Resource/getOffsetPageKeyInfo.ts diff --git a/webapp/packages/core-authentication/src/UsersResource.ts b/webapp/packages/core-authentication/src/UsersResource.ts index 6e790e75a3..ea81f6d1b0 100644 --- a/webapp/packages/core-authentication/src/UsersResource.ts +++ b/webapp/packages/core-authentication/src/UsersResource.ts @@ -9,12 +9,11 @@ import { runInAction } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; import { - CACHED_RESOURCE_DEFAULT_PAGE_LIMIT, - CACHED_RESOURCE_DEFAULT_PAGE_OFFSET, CachedMapAllKey, CachedMapResource, CachedResourceOffsetPageKey, CachedResourceOffsetPageListKey, + getOffsetPageKeyInfo, isResourceAlias, type ResourceKey, resourceKeyList, @@ -260,19 +259,11 @@ export class UsersResource extends CachedMapResource user.userId), users.length === limit, ]); diff --git a/webapp/packages/core-navigation-tree/src/NodesManager/DBObjectResource.ts b/webapp/packages/core-navigation-tree/src/NodesManager/DBObjectResource.ts index 17ef52750d..90f8d9c0d1 100644 --- a/webapp/packages/core-navigation-tree/src/NodesManager/DBObjectResource.ts +++ b/webapp/packages/core-navigation-tree/src/NodesManager/DBObjectResource.ts @@ -15,6 +15,7 @@ import { CachedResourceOffsetPageKey, CachedResourceOffsetPageListKey, CachedResourceOffsetPageTargetKey, + getOffsetPageKeyInfo, isResourceAlias, type ResourceKey, resourceKeyList, @@ -96,16 +97,8 @@ export class DBObjectResource extends CachedMapResource { } protected async loader(originalKey: ResourceKey): Promise> { - let limit = this.navTreeResource.childrenLimit; - let offset = CACHED_RESOURCE_DEFAULT_PAGE_OFFSET; const parentKey = this.aliases.isAlias(originalKey, DBObjectParentKey); - const pageKey = - this.aliases.isAlias(originalKey, CachedResourceOffsetPageKey) || this.aliases.isAlias(originalKey, CachedResourceOffsetPageListKey); - - if (pageKey) { - limit = pageKey.options.limit; - offset = pageKey.options.offset; - } + const { isPageListKey, offset, limit } = getOffsetPageKeyInfo(this, originalKey, undefined, this.navTreeResource.childrenLimit); if (parentKey) { const nodeId = parentKey.options.parentId; @@ -116,11 +109,11 @@ export class DBObjectResource extends CachedMapResource { this.set(resourceKeyList(keys), dbObjects); this.offsetPagination.setPage( - CachedResourceOffsetPageKey(offset, limit).setParent(CachedResourceOffsetPageTargetKey(originalKey)), + isPageListKey + ? CachedResourceOffsetPageListKey(offset, limit).setParent(parentKey || CachedResourceOffsetPageTargetKey(nodeId)) + : CachedResourceOffsetPageKey(offset, limit).setParent(parentKey || CachedResourceOffsetPageTargetKey(nodeId)), keys, - this.navTreeResource.offsetPagination.hasNextPage( - CachedResourceOffsetPageKey(offset, limit).setParent(CachedResourceOffsetPageTargetKey(nodeId)), - ), + keys.length === limit, ); }); diff --git a/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts b/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts index 4f52088d9a..5f996e2976 100644 --- a/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts +++ b/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts @@ -12,12 +12,12 @@ import { injectable } from '@cloudbeaver/core-di'; import { Executor, ExecutorInterrupter, IExecutionContext, IExecutor } from '@cloudbeaver/core-executor'; import { ProjectInfoResource } from '@cloudbeaver/core-projects'; import { - CACHED_RESOURCE_DEFAULT_PAGE_OFFSET, CachedMapAllKey, CachedMapResource, CachedResourceOffsetPageKey, CachedResourceOffsetPageListKey, CachedResourceOffsetPageTargetKey, + getOffsetPageKeyInfo, type ICachedResourceMetadata, isResourceAlias, isResourceKeyList, @@ -275,9 +275,10 @@ export class NavTreeResource extends CachedMapResource): Promise> { - const pageKey = - this.aliases.isAlias(originalKey, CachedResourceOffsetPageKey) || this.aliases.isAlias(originalKey, CachedResourceOffsetPageListKey); + const { isPageListKey, pageTargetKey, offset, limit } = getOffsetPageKeyInfo(this, originalKey, undefined, this.childrenLimit); const allKey = this.aliases.isAlias(originalKey, CachedMapAllKey); - const pageTarget = this.aliases.isAlias(originalKey, CachedResourceOffsetPageTargetKey); if (allKey) { throw new Error('Loading all nodes is prohibited'); } - const offset = pageKey?.options.offset ?? CACHED_RESOURCE_DEFAULT_PAGE_OFFSET; - const limit = pageKey?.options.limit ?? this.childrenLimit; const values: NavNodeChildrenQuery[] = []; const pages: Parameters[] = []; await ResourceKeyUtils.forEachAsync(originalKey, async key => { - const nodeId = pageTarget?.options?.target ?? key; + const nodeId = pageTargetKey ?? key; const navNodeChildren = await this.loadNodeChildren(nodeId, offset, limit); values.push(navNodeChildren); pages.push([ - CachedResourceOffsetPageKey(offset, navNodeChildren.navNodeChildren.length).setParent(CachedResourceOffsetPageTargetKey(nodeId)), + isPageListKey + ? CachedResourceOffsetPageListKey(offset, navNodeChildren.navNodeChildren.length).setParent(CachedResourceOffsetPageTargetKey(nodeId)) + : CachedResourceOffsetPageKey(offset, navNodeChildren.navNodeChildren.length).setParent(CachedResourceOffsetPageTargetKey(nodeId)), navNodeChildren.navNodeChildren.map(node => node.id), navNodeChildren.navNodeChildren.length === limit, ]); diff --git a/webapp/packages/core-resource/src/Resource/CachedResource.ts b/webapp/packages/core-resource/src/Resource/CachedResource.ts index 50f8a914c9..e37a73e0ed 100644 --- a/webapp/packages/core-resource/src/Resource/CachedResource.ts +++ b/webapp/packages/core-resource/src/Resource/CachedResource.ts @@ -34,7 +34,7 @@ import { isResourceAlias } from './ResourceAlias'; import { ResourceError } from './ResourceError'; import type { ResourceKey, ResourceKeyFlat } from './ResourceKey'; import { resourceKeyAlias } from './ResourceKeyAlias'; -import { resourceKeyList } from './ResourceKeyList'; +import { isResourceKeyList, resourceKeyList } from './ResourceKeyList'; import { resourceKeyListAlias } from './ResourceKeyListAlias'; import { ResourceOffsetPagination } from './ResourceOffsetPagination'; @@ -91,41 +91,58 @@ export abstract class CachedResource< this.aliases.add(CachedResourceParamKey, () => defaultKey); this.aliases.add(CachedResourceListEmptyKey, () => resourceKeyList([])); this.aliases.add(CachedResourceOffsetPageTargetKey, key => key.options.target); - this.aliases.add(CachedResourceOffsetPageKey, key => { - const keys = []; - const pageInfo = this.offsetPagination.getPageInfo(key); + this.aliases.add( + CachedResourceOffsetPageListKey, + key => key.parent! as any, + (param, key) => { + if (!isResourceKeyList(key)) { + return key as any; + } - if (pageInfo) { - const from = key.options.offset; - const to = key.options.offset + key.options.limit; + const keys = new Set(); + const pageInfo = this.offsetPagination.getPageInfo(param); - for (const page of pageInfo.pages) { - if (page.isHasCommonSegment(from, to)) { - keys.push(...page.get(from, to)); + if (pageInfo) { + const from = param.options.offset; + const to = param.options.offset + param.options.limit; + + for (const page of pageInfo.pages) { + if (page.isHasCommonSegment(from, to)) { + for (const pageKey of page.get(from, to)) { + keys.add(pageKey); + } + } } } - } + return resourceKeyList(key.filter(value => keys.has(value))); + }, + ); + this.aliases.add( + CachedResourceOffsetPageKey, + key => key.parent! as any, + (param, key) => { + if (!isResourceKeyList(key)) { + return key as any; + } - // todo: return single element? - return resourceKeyList([...new Set(keys)]); - }); - this.aliases.add(CachedResourceOffsetPageListKey, key => { - const keys = []; - const pageInfo = this.offsetPagination.getPageInfo(key); + const keys = new Set(); + const pageInfo = this.offsetPagination.getPageInfo(param); - if (pageInfo) { - const from = key.options.offset; - const to = key.options.offset + key.options.limit; + if (pageInfo) { + const from = param.options.offset; + const to = param.options.offset + param.options.limit; - for (const page of pageInfo.pages) { - if (page.isHasCommonSegment(from, to)) { - keys.push(...page.get(from, to)); + for (const page of pageInfo.pages) { + if (page.isHasCommonSegment(from, to)) { + for (const pageKey of page.get(from, to)) { + keys.add(pageKey); + } + } } } - } - - return resourceKeyList([...new Set(keys)]); - }); + return resourceKeyList(key.filter(value => keys.has(value))); + }, + ); // this.logger.spy(this.beforeLoad, 'beforeLoad'); // this.logger.spy(this.onDataOutdated, 'onDataOutdated'); @@ -329,6 +346,7 @@ export abstract class CachedResource< } const pageKey = this.aliases.isAlias(param, CachedResourceOffsetPageKey) || this.aliases.isAlias(param, CachedResourceOffsetPageListKey); + if (pageKey) { const pageInfo = this.offsetPagination.getPageInfo(pageKey); diff --git a/webapp/packages/core-resource/src/Resource/ResourceAlias.ts b/webapp/packages/core-resource/src/Resource/ResourceAlias.ts index ed23a86b95..ca8f17372f 100644 --- a/webapp/packages/core-resource/src/Resource/ResourceAlias.ts +++ b/webapp/packages/core-resource/src/Resource/ResourceAlias.ts @@ -42,7 +42,7 @@ export abstract class ResourceAlias return undefined; } - setParent(parent: ResourceAlias): this { + setParent(parent: ResourceAlias | undefined): 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; diff --git a/webapp/packages/core-resource/src/Resource/ResourceAliases.ts b/webapp/packages/core-resource/src/Resource/ResourceAliases.ts index fa608783c9..c58f340918 100644 --- a/webapp/packages/core-resource/src/Resource/ResourceAliases.ts +++ b/webapp/packages/core-resource/src/Resource/ResourceAliases.ts @@ -8,7 +8,7 @@ import { toJS } from 'mobx'; import { isResourceAlias, type ResourceAlias, ResourceAliasFactory, type ResourceAliasOptions } from './ResourceAlias'; -import type { ResourceKey } from './ResourceKey'; +import type { ResourceKey, ResourceKeySimple } from './ResourceKey'; import type { ResourceKeyAlias } from './ResourceKeyAlias'; import { isResourceKeyList, ResourceKeyList } from './ResourceKeyList'; import type { ResourceKeyListAlias } from './ResourceKeyListAlias'; @@ -16,14 +16,25 @@ import type { ResourceLogger } from './ResourceLogger'; export type IParamAlias = { id: string; - getAlias: (param: ResourceAlias) => ResourceKey; + getAlias: ResourceAliasResolver; + transformKey?: ResourceAliasKeyTransformer; }; +export type ResourceAliasResolver = (param: ResourceAlias) => ResourceKey; + +export type ResourceAliasKeyTransformer = >( + param: ResourceAlias, + key: T, +) => T; + export class ResourceAliases { protected paramAliases: Array>; private captureAliasGetterExecution: boolean; - constructor(private readonly logger: ResourceLogger, private readonly validateKey: (key: TKey) => boolean) { + constructor( + private readonly logger: ResourceLogger, + private readonly validateKey: (key: TKey) => boolean, + ) { this.paramAliases = []; this.captureAliasGetterExecution = false; @@ -52,21 +63,23 @@ export class ResourceAliases { add( param: ResourceAlias | ResourceAliasFactory, - getAlias: (param: ResourceAlias) => ResourceKey, + getAlias: ResourceAliasResolver, + transformKey?: ResourceAliasKeyTransformer, ): void { - this.paramAliases.push({ id: param.id, getAlias }); + this.paramAliases.push({ id: param.id, getAlias, transformKey }); } replace( param: ResourceAlias | ResourceAliasFactory, - getAlias: (param: ResourceAlias) => ResourceKey, + getAlias: ResourceAliasResolver, + transformKey?: ResourceAliasKeyTransformer, ): void { const indexOf = this.paramAliases.findIndex(aliasInfo => aliasInfo.id === param.id); if (indexOf === -1) { - this.add(param, getAlias); + this.add(param, getAlias, transformKey); } else { - this.paramAliases.splice(indexOf, 1, { id: param.id, getAlias }); + this.paramAliases.splice(indexOf, 1, { id: param.id, getAlias, transformKey }); } } @@ -111,6 +124,7 @@ export class ResourceAliases { transformToKey(param: ResourceKey): TKey | ResourceKeyList { let deep = 0; + const transforms: Array<{ key: ResourceKeyAlias | ResourceKeyListAlias; alias: IParamAlias }> = []; while (deep < 10) { if (!this.validateResourceKey(param)) { let paramString = JSON.stringify(toJS(param)); @@ -124,7 +138,15 @@ export class ResourceAliases { if (isResourceAlias(param)) { for (const alias of this.paramAliases) { if (alias.id === param.id) { - param = this.captureGetAlias(alias, param); + transforms.push({ key: param, alias }); + const nextParam = this.captureGetAlias(alias, param); + + if (isResourceAlias(nextParam)) { + param = nextParam.setParent(param); + } else { + param = nextParam; + } + deep++; break; } @@ -141,6 +163,11 @@ export class ResourceAliases { if (isResourceAlias(param)) { throw new Error(`Alias ${param.toString()} is not registered in ${this.logger.getName()}`); } + + for (const { key, alias } of transforms) { + param = alias.transformKey ? alias.transformKey(key, param) : param; + } + return param; } diff --git a/webapp/packages/core-resource/src/Resource/ResourceMetadata.ts b/webapp/packages/core-resource/src/Resource/ResourceMetadata.ts index b5c9c67039..74179e1d87 100644 --- a/webapp/packages/core-resource/src/Resource/ResourceMetadata.ts +++ b/webapp/packages/core-resource/src/Resource/ResourceMetadata.ts @@ -11,7 +11,7 @@ import { DefaultValueGetter, isPrimitive, MetadataMap } from '@cloudbeaver/core- import { CachedResourceOffsetPageKey, CachedResourceOffsetPageListKey } from './CachedResourceOffsetPageKeys'; import type { ICachedResourceMetadata } from './ICachedResourceMetadata'; -import { isResourceAlias } from './ResourceAlias'; +import { isResourceAlias, ResourceAlias } from './ResourceAlias'; import type { ResourceAliases } from './ResourceAliases'; import type { ResourceKey, ResourceKeyFlat } from './ResourceKey'; import { isResourceKeyList, ResourceKeyList } from './ResourceKeyList'; @@ -111,6 +111,8 @@ export class ResourceMetadata { if (this.some(param, predicate)) { result = true; } + } else if (predicate(this.get(param))) { + result = true; } } @@ -194,11 +196,11 @@ export class ResourceMetadata { if (isResourceAlias(key)) { key = this.aliases.transformToAlias(key); - if (this.aliases.isAlias(key, CachedResourceOffsetPageKey) || this.aliases.isAlias(key, CachedResourceOffsetPageListKey)) { + if (isResourceAlias(key, CachedResourceOffsetPageKey) || isResourceAlias(key, CachedResourceOffsetPageListKey)) { return this.getMetadataKeyRef(key.parent as any); } - return key.toString() as TKey; + return (key as ResourceAlias).toString() as TKey; } if (isPrimitive(key)) { diff --git a/webapp/packages/core-resource/src/Resource/getOffsetPageKeyInfo.ts b/webapp/packages/core-resource/src/Resource/getOffsetPageKeyInfo.ts new file mode 100644 index 0000000000..c51ceae077 --- /dev/null +++ b/webapp/packages/core-resource/src/Resource/getOffsetPageKeyInfo.ts @@ -0,0 +1,46 @@ +/* + * 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 { CachedResource } from './CachedResource'; +import { + CACHED_RESOURCE_DEFAULT_PAGE_LIMIT, + CACHED_RESOURCE_DEFAULT_PAGE_OFFSET, + CachedResourceOffsetPageKey, + CachedResourceOffsetPageListKey, + CachedResourceOffsetPageTargetKey, +} from './CachedResourceOffsetPageKeys'; +import { CachedResourceKey } from './IResource'; + +interface IOffsetPageKeyInfo { + limit: number; + offset: number; + isPageListKey: boolean; + pageTargetKey?: any; +} + +export function getOffsetPageKeyInfo( + resource: CachedResource, + originalKey: CachedResourceKey, + offset = CACHED_RESOURCE_DEFAULT_PAGE_OFFSET, + limit = CACHED_RESOURCE_DEFAULT_PAGE_LIMIT, +): IOffsetPageKeyInfo { + const pageListKey = resource.aliases.isAlias(originalKey, CachedResourceOffsetPageListKey); + const pageKey = resource.aliases.isAlias(originalKey, CachedResourceOffsetPageKey) || pageListKey; + const pageTargetKey = resource.aliases.isAlias(originalKey, CachedResourceOffsetPageTargetKey); + + if (pageKey) { + limit = pageKey.options.limit; + offset = pageKey.options.offset; + } + + return { + limit, + offset, + isPageListKey: !!pageListKey, + pageTargetKey: pageTargetKey?.options.target, + }; +} diff --git a/webapp/packages/core-resource/src/index.ts b/webapp/packages/core-resource/src/index.ts index 6ff9ce3edd..ca7ebd42d6 100644 --- a/webapp/packages/core-resource/src/index.ts +++ b/webapp/packages/core-resource/src/index.ts @@ -18,6 +18,7 @@ export { getNextPageOffset, type ICachedResourceOffsetPageOptions, } from './Resource/CachedResourceOffsetPageKeys'; +export * from './Resource/getOffsetPageKeyInfo'; export * from './Resource/CachedTreeResource/CachedTreeResource'; export * from './Resource/CachedTreeResource/ICachedTreeMoveData'; export * from './Resource/ICachedResourceMetadata'; From 12e0107617f78588341c4d88381569bc07c7c794 Mon Sep 17 00:00:00 2001 From: serge-rider Date: Wed, 18 Sep 2024 15:40:15 +0200 Subject: [PATCH 11/13] 24.2.2 version bump --- webapp/packages/product-default/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/packages/product-default/package.json b/webapp/packages/product-default/package.json index 95ad13e66a..4bd8b88606 100644 --- a/webapp/packages/product-default/package.json +++ b/webapp/packages/product-default/package.json @@ -5,7 +5,7 @@ "src/**/*.scss", "public/**/*" ], - "version": "24.2.1", + "version": "24.2.2", "description": "CloudBeaver Community", "license": "Apache-2.0", "main": "dist/index.js", From 46309e9505d060008088e934a12a7c133376df43 Mon Sep 17 00:00:00 2001 From: Anastasiya <45152336+LonwoLonwo@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:58:39 +0300 Subject: [PATCH 12/13] Update README.md link (#2923) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5bcf48ae8f..2149dc9704 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,6 @@ You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io ## Contribution As a community-driven open-source project, we warmly welcome contributions through GitHub pull requests. -[We are happy to reward](https://dbeaver.com/help-beaver/) our most active contributors every major sprint. +[We are happy to reward](https://dbeaver.com/help-dbeaver/) our most active contributors every major sprint. The most significant contribution to our code for the major release 24.2.0 was made by: 1. [matthieukhl](https://github.com/matthieukhl) From 90acaaf00e1404899d3e469c7c121a3810865a3c Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Thu, 19 Sep 2024 08:28:15 +0200 Subject: [PATCH 13/13] dbeaver/pro#3336 RCP model refactoring. Remove VFS from base model (#2913) * dbeaver/pro#3336 RCP model refactoring. Remove VFS from base model * dbeaver/pro#3336 Simplify DBNNodeWithResource * dbeaver/pro#3336 Web node info fix * dbeaver/pro#3336 Code style * dbeaver/pro#3336 Fix node project get --- .../model/rm/DBNResourceManagerProject.java | 28 +++--- .../model/rm/DBNResourceManagerResource.java | 1 + .../model/session/WebSessionWorkspace.java | 7 ++ .../navigator/WebNavigatorNodeInfo.java | 25 ++--- .../navigator/impl/WebServiceNavigator.java | 23 ++--- .../CBEmbeddedSecurityController.java | 97 ++++++++----------- 6 files changed, 89 insertions(+), 92 deletions(-) diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/DBNResourceManagerProject.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/DBNResourceManagerProject.java index cf601dae8c..7500c25460 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/DBNResourceManagerProject.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/DBNResourceManagerProject.java @@ -20,7 +20,6 @@ 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.DBIcon; import org.jkiss.dbeaver.model.DBPImage; import org.jkiss.dbeaver.model.DBPObject; @@ -37,7 +36,6 @@ import java.util.List; public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode { - private static final Log log = Log.getLog(DBNResourceManagerProject.class); private final RMProject project; @@ -123,24 +121,18 @@ public DBNNode refreshNode(DBRProgressMonitor monitor, Object source) throws DBE return this; } - @Override - public String toString() { - return getNodeDisplayName(); - } - - + @NotNull @Override public DBPProject getOwnerProject() { List globalProjects = getModel().getModelProjects(); - if (globalProjects == null) { - return null; - } - for (DBPProject modelProject : globalProjects) { - if (CommonUtils.equalObjects(modelProject.getId(), project.getId())) { - return modelProject; + if (globalProjects != null) { + for (DBPProject modelProject : globalProjects) { + if (CommonUtils.equalObjects(modelProject.getId(), project.getId())) { + return modelProject; + } } } - return null; + throw new IllegalStateException("Project '" + project.getId() + "' not found in workspace"); } @Nullable @@ -148,4 +140,10 @@ public DBPProject getOwnerProject() { public DBPObject getObjectDetails(@NotNull DBRProgressMonitor monitor, @NotNull SMSessionContext sessionContext, @NotNull Object dataSource) throws DBException { return project; } + + @Override + public String toString() { + return getNodeDisplayName(); + } + } diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/DBNResourceManagerResource.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/DBNResourceManagerResource.java index 30919d34ca..0fdc50e6e4 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/DBNResourceManagerResource.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/DBNResourceManagerResource.java @@ -203,6 +203,7 @@ public DBPObject getObjectDetails(@NotNull DBRProgressMonitor monitor, @NotNull return resource; } + @NotNull @Override public DBPProject getOwnerProject() { return getParentNode().getOwnerProject(); diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSessionWorkspace.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSessionWorkspace.java index f1a86e49c4..f24bf8c90a 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSessionWorkspace.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSessionWorkspace.java @@ -19,6 +19,8 @@ import io.cloudbeaver.WebProjectImpl; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; +import org.jkiss.dbeaver.model.DBPAdaptable; +import org.jkiss.dbeaver.model.DBPImage; import org.jkiss.dbeaver.model.app.DBPPlatform; import org.jkiss.dbeaver.model.app.DBPProject; import org.jkiss.dbeaver.model.app.DBPWorkspace; @@ -132,6 +134,11 @@ public void dispose() { clearProjects(); } + @Override + public DBPImage getResourceIcon(DBPAdaptable resourceAdapter) { + return null; + } + public void setActiveProject(DBPProject activeProject) { this.activeProject = (WebProjectImpl) activeProject; } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/WebNavigatorNodeInfo.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/WebNavigatorNodeInfo.java index 18bf967868..6e31e3113e 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/WebNavigatorNodeInfo.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/WebNavigatorNodeInfo.java @@ -111,7 +111,7 @@ public String getPlainName() { // for renaming node @Property public String getProjectId() { - DBPProject ownerProject = node.getOwnerProject(); + DBPProject ownerProject = node.getOwnerProjectOrNull(); return ownerProject == null ? null : ownerProject.getId(); } @@ -119,11 +119,11 @@ public String getProjectId() { @Deprecated public String getFullName() { String nodeName; - if (node instanceof DBNDatabaseNode && !(node instanceof DBNDataSource)) { - DBSObject object = ((DBNDatabaseNode) node).getObject(); + if (node instanceof DBNDatabaseNode dbNode && !(node instanceof DBNDataSource)) { + DBSObject object = dbNode.getObject(); nodeName = DBUtils.getObjectFullName(object, DBPEvaluationContext.UI); - } else if (node instanceof DBNDataSource) { - DBPDataSourceContainer object = ((DBNDataSource) node).getDataSourceContainer(); + } else if (node instanceof DBNDataSource dataSource) { + DBPDataSourceContainer object = dataSource.getDataSourceContainer(); nodeName = object.getName(); } else { nodeName = node.getNodeTargetName(); @@ -235,7 +235,7 @@ public String[] getFeatures() { if (objectManager != null && objectManager.canDeleteObject(object)) { features.add(NODE_FEATURE_CAN_DELETE); } - if (objectManager instanceof DBEObjectRenamer && ((DBEObjectRenamer) objectManager).canRenameObject(object)) { + if (objectManager instanceof DBEObjectRenamer renamer && renamer.canRenameObject(object)) { if (!object.getDataSource().getContainer().getNavigatorSettings().isShowOnlyEntities()) { features.add(NODE_FEATURE_CAN_RENAME); } @@ -282,9 +282,10 @@ private boolean isDistributedSpecialFolderNode() { @Property public WebPropertyInfo[] getNodeDetails() throws DBWebException { - if (node instanceof DBPObjectWithDetails) { + if (node instanceof DBPObjectWithDetails objectWithDetails) { try { - DBPObject objectDetails = ((DBPObjectWithDetails) node).getObjectDetails(session.getProgressMonitor(), session.getSessionContext(), node); + DBPObject objectDetails = objectWithDetails.getObjectDetails( + session.getProgressMonitor(), session.getSessionContext(), node); if (objectDetails != null) { return WebServiceUtils.getObjectProperties(session, objectDetails); } @@ -301,8 +302,8 @@ public WebPropertyInfo[] getNodeDetails() throws DBWebException { @Property public WebDatabaseObjectInfo getObject() { - if (node instanceof DBNDatabaseNode) { - DBSObject object = ((DBNDatabaseNode) node).getObject(); + if (node instanceof DBNDatabaseNode dbNode) { + DBSObject object = dbNode.getObject(); return object == null ? null : new WebDatabaseObjectInfo(session, object); } return null; @@ -320,10 +321,10 @@ public String getObjectId() { @Property public DBSObjectFilter getFilter() throws DBWebException { - if (!(node instanceof DBNDatabaseNode)) { + if (!(node instanceof DBNDatabaseNode dbNode)) { throw new DBWebException("Invalid navigator node type: " + node.getClass().getName()); } - DBSObjectFilter filter = ((DBNDatabaseNode) node).getNodeFilter(((DBNDatabaseNode) node).getItemsMeta(), true); + DBSObjectFilter filter = dbNode.getNodeFilter(dbNode.getItemsMeta(), true); return filter == null || filter.isEmpty() || !filter.isEnabled() ? null : filter; } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/impl/WebServiceNavigator.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/impl/WebServiceNavigator.java index 28f54520c8..96690ea4de 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/impl/WebServiceNavigator.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/impl/WebServiceNavigator.java @@ -228,11 +228,12 @@ public boolean setNavigatorNodeFilter( filter.setExclude(exclude); } filter.setEnabled(true); - ((DBNDatabaseNode) node).setNodeFilter( - ((DBNDatabaseNode) node).getItemsMeta(), filter, true); - if (hasNodeEditPermission(webSession, node, ((WebProjectImpl) node.getOwnerProject()).getRmProject())) { - // Save settings - ((DBNDatabaseNode) node).getDataSourceContainer().persistConfiguration(); + if (node instanceof DBNDatabaseNode dbNode) { + dbNode.setNodeFilter(dbNode.getItemsMeta(), filter, true); + if (hasNodeEditPermission(webSession, node, ((WebProjectImpl) node.getOwnerProject()).getRmProject())) { + // Save settings + dbNode.getDataSourceContainer().persistConfiguration(); + } } } catch (DBException e) { if (e instanceof DBWebException) { @@ -255,14 +256,14 @@ public boolean refreshNavigatorNode( if (node == null) { throw new DBWebException("Navigator node '" + nodePath + "' not found"); } - if (node instanceof DBNDataSource) { + if (node instanceof DBNDataSource dbnDataSource) { // Do not refresh entire tree - just clear child nodes // Otherwise refresh may fail if navigator settings were changed. - DBPDataSource dataSource = ((DBNDataSource) node).getDataSource(); - if (dataSource instanceof DBPRefreshableObject) { - ((DBPRefreshableObject) dataSource).refreshObject(monitor); + DBPDataSource dataSource = dbnDataSource.getDataSource(); + if (dataSource instanceof DBPRefreshableObject refreshableObject) { + refreshableObject.refreshObject(monitor); } - ((DBNDataSource) node).cleanupNode(); + dbnDataSource.cleanupNode(); } else if (node instanceof DBNLocalFolder) { // Refresh can't be applied to the local folder node return true; @@ -425,7 +426,7 @@ private String renameConnectionFolder(@NotNull WebSession session, DBNNode node, List siblings = Arrays.stream( ((DBNLocalFolder) node).getLogicalParent().getChildren(session.getProgressMonitor())) .filter(n -> n instanceof DBNLocalFolder) - .map(DBNNode::getName).collect(Collectors.toList()); + .map(DBNNode::getName).toList(); if (siblings.contains(newName)) { throw new DBWebException("Name " + newName + " is unavailable or invalid"); } diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java index 7b21ede7fb..c8887edeed 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java @@ -515,7 +515,7 @@ public SMUser[] findUsers(@NotNull SMUserFilter filter) try (PreparedStatement dbStat = dbCon.prepareStatement( database.normalizeTableNames("SELECT USER_ID,IS_ACTIVE,DEFAULT_AUTH_ROLE FROM {table_prefix}CB_USER" + buildUsersFilter(filter) + "\nORDER BY USER_ID " + getOffsetLimitPart(filter)))) { - int parameterIndex = setUsersFilterValues(dbStat, filter, 1); + setUsersFilterValues(dbStat, filter, 1); try (ResultSet dbResult = dbStat.executeQuery()) { while (dbResult.next()) { @@ -531,14 +531,11 @@ public SMUser[] findUsers(@NotNull SMUserFilter filter) } readSubjectsMetas(dbCon, SMSubjectType.user, filter.getUserIdMask(), result); - StringBuilder teamsSql = new StringBuilder() - .append("SELECT USER_ID,TEAM_ID FROM {table_prefix}CB_USER_TEAM") - .append("\n") - .append("WHERE USER_ID IN (") - .append(SQLUtils.generateParamList(result.size())) - .append(")"); + String teamsSql = + "SELECT USER_ID,TEAM_ID FROM {table_prefix}CB_USER_TEAM\n" + + "WHERE USER_ID IN (" + SQLUtils.generateParamList(result.size()) + ")"; // Read teams - try (PreparedStatement dbStat = dbCon.prepareStatement(database.normalizeTableNames(teamsSql.toString()))) { + try (PreparedStatement dbStat = dbCon.prepareStatement(database.normalizeTableNames(teamsSql))) { int parameterIndex = 1; for (String userId : result.keySet()) { dbStat.setString(parameterIndex++, userId); @@ -574,7 +571,7 @@ private String buildUsersFilter(SMUserFilter filter) { if (filter.getEnabledState() != null) { whereParts.add("IS_ACTIVE=?"); } - if (whereParts.size() > 0) { + if (!whereParts.isEmpty()) { where.append(whereParts.stream().collect(Collectors.joining(" AND ", " WHERE ", ""))); } return where.toString(); @@ -858,7 +855,7 @@ public void setUserCredentials( String encodedValue = CommonUtils.toString(cred.getValue()); encodedValue = property.getEncryption().encrypt(userId, encodedValue); return new String[]{propertyName, encodedValue}; - }).collect(Collectors.toList()); + }).toList(); } catch (Exception e) { throw new DBCException(e.getMessage(), e); } @@ -1037,7 +1034,7 @@ public String[] getUserLinkedProviders(@NotNull String userId) throws DBExceptio @NotNull @Override - public SMPropertyDescriptor[] getMetaParametersBySubjectType(SMSubjectType subjectType) throws DBException { + public SMPropertyDescriptor[] getMetaParametersBySubjectType(SMSubjectType subjectType) { // First add global metas List props = new ArrayList<>( WebMetaParametersRegistry.getInstance().getMetaParameters(subjectType)); @@ -1083,9 +1080,11 @@ public SMTeam[] readAllTeams() throws DBCException { } } } - query = database.normalizeTableNames("SELECT SUBJECT_ID,PERMISSION_ID\n" + - "FROM {table_prefix}CB_AUTH_PERMISSIONS AP, {table_prefix}CB_TEAM R\n" + - "WHERE AP.SUBJECT_ID IN (R.TEAM_ID,?)\n"); + query = database.normalizeTableNames(""" + SELECT SUBJECT_ID,PERMISSION_ID + FROM {table_prefix}CB_AUTH_PERMISSIONS AP, {table_prefix}CB_TEAM R + WHERE AP.SUBJECT_ID IN (R.TEAM_ID,?) + """); try (PreparedStatement dbPreparedStatement = dbCon.prepareStatement(query)) { dbPreparedStatement.setString(1, defaultUserTeam); try (ResultSet dbResult = dbPreparedStatement.executeQuery()) { @@ -1422,7 +1421,7 @@ private String createSmSession( @NotNull Map parameters, @NotNull SMSessionType sessionType, Connection dbCon - ) throws SQLException, DBException { + ) throws SQLException { var sessionId = UUID.randomUUID().toString(); try (PreparedStatement dbStat = dbCon.prepareStatement( database.normalizeTableNames( @@ -1754,11 +1753,10 @@ private void updateAuthStatus( SMAuthConfigurationReference providerId = entry.getKey(); String authJson = gson.toJson(entry.getValue()); boolean configIdExist = providerId.getAuthProviderConfigurationId() != null; - var sqlBuilder = new StringBuilder(); - sqlBuilder.append("UPDATE {table_prefix}CB_AUTH_ATTEMPT_INFO SET AUTH_STATE=? ") - .append("WHERE AUTH_ID=? AND AUTH_PROVIDER_ID=? AND ") - .append(configIdExist ? "AUTH_PROVIDER_CONFIGURATION_ID=?" : "AUTH_PROVIDER_CONFIGURATION_ID IS NULL"); - try (PreparedStatement dbStat = dbCon.prepareStatement(database.normalizeTableNames(sqlBuilder.toString()))) { + String sqlBuilder = "UPDATE {table_prefix}CB_AUTH_ATTEMPT_INFO SET AUTH_STATE=? " + + "WHERE AUTH_ID=? AND AUTH_PROVIDER_ID=? AND " + + (configIdExist ? "AUTH_PROVIDER_CONFIGURATION_ID=?" : "AUTH_PROVIDER_CONFIGURATION_ID IS NULL"); + try (PreparedStatement dbStat = dbCon.prepareStatement(database.normalizeTableNames(sqlBuilder))) { dbStat.setString(1, authJson); dbStat.setString(2, authId); dbStat.setString(3, providerId.getAuthProviderId()); @@ -1848,16 +1846,14 @@ private SMAuthInfo getAuthStatus(@NotNull String authId, boolean readExpiredData if (authProviderConfiguration != null) { WebAuthProviderDescriptor authProviderDescriptor = getAuthProvider(authProviderId); var authProviderInstance = authProviderDescriptor.getInstance(); - if (SMAuthProviderFederated.class.isAssignableFrom(authProviderInstance.getClass())) { - signInLink = buildRedirectLink(((SMAuthProviderFederated) authProviderInstance).getRedirectLink( + if (authProviderInstance instanceof SMAuthProviderFederated providerFederated) { + signInLink = buildRedirectLink(providerFederated.getRedirectLink( authProviderConfiguration, Map.of()), authId); - var userCustomSignOutLink = - ((SMAuthProviderFederated) authProviderInstance).getUserSignOutLink( - application.getAuthConfiguration() - .getAuthProviderConfiguration(authProviderConfiguration), - authProviderData); - signOutLink = userCustomSignOutLink; + signOutLink = providerFederated.getUserSignOutLink( + application.getAuthConfiguration() + .getAuthProviderConfiguration(authProviderConfiguration), + authProviderData); } } @@ -1867,16 +1863,13 @@ private SMAuthInfo getAuthStatus(@NotNull String authId, boolean readExpiredData } if (smAuthStatus != SMAuthStatus.SUCCESS) { - switch (smAuthStatus) { - case IN_PROGRESS: - return SMAuthInfo.inProgress(authId, signInLink, signOutLink, authData, isMainAuth, forceSessionsLogout); - case ERROR: - return SMAuthInfo.error(authId, authError, isMainAuth, errorCode); - case EXPIRED: - return SMAuthInfo.expired(authId, readExpiredData ? authData : Map.of(), isMainAuth); - default: - throw new SMException("Unknown auth status:" + smAuthStatus); - } + return switch (smAuthStatus) { + case IN_PROGRESS -> + SMAuthInfo.inProgress(authId, signInLink, signOutLink, authData, isMainAuth, forceSessionsLogout); + case ERROR -> SMAuthInfo.error(authId, authError, isMainAuth, errorCode); + case EXPIRED -> SMAuthInfo.expired(authId, readExpiredData ? authData : Map.of(), isMainAuth); + default -> throw new SMException("Unknown auth status:" + smAuthStatus); + }; } SMTokens smTokens = findTokenBySmSession(smSessionId); @@ -2260,10 +2253,9 @@ private void autoUpdateUserTeams( String userId, SMTeam[] allTeams ) throws DBCException { - if (!(authProvider.getInstance() instanceof SMAuthProviderAssigner)) { + if (!(authProvider.getInstance() instanceof SMAuthProviderAssigner authProviderAssigner)) { return; } - SMAuthProviderAssigner authProviderAssigner = (SMAuthProviderAssigner) authProvider.getInstance(); String externalTeamIdMetadataFieldName = authProviderAssigner.getExternalTeamIdMetadataFieldName(); if (!CommonUtils.isEmpty(externalTeamIdMetadataFieldName)) { @@ -2584,7 +2576,7 @@ private SMAuthPermissions getTokenPermissions(@NotNull String token) throws DBEx try (Connection dbCon = database.openConnection(); PreparedStatement dbStat = dbCon.prepareStatement( database.normalizeTableNames("SELECT USER_ID, EXPIRATION_TIME, SESSION_ID, AUTH_ROLE FROM {table_prefix}CB_AUTH_TOKEN " + - "WHERE TOKEN_ID=?")); + "WHERE TOKEN_ID=?")) ) { dbStat.setString(1, token); try (var dbResult = dbStat.executeQuery()) { @@ -2608,17 +2600,16 @@ private SMAuthPermissions getTokenPermissions(@NotNull String token) throws DBEx @Override public SMAuthProviderDescriptor[] getAvailableAuthProviders() throws DBException { - if (!(application.getAppConfiguration() instanceof WebAuthConfiguration)) { + if (!(application.getAppConfiguration() instanceof WebAuthConfiguration appConfiguration)) { throw new DBException("Web application doesn't support external authentication"); } - WebAuthConfiguration appConfiguration = (WebAuthConfiguration) application.getAppConfiguration(); Set customConfigurations = appConfiguration.getAuthCustomConfigurations(); List providers = WebAuthProviderRegistry.getInstance().getAuthProviders().stream() .filter(ap -> !ap.isTrusted() && appConfiguration.isAuthProviderEnabled(ap.getId()) && (!ap.isConfigurable() || hasProviderConfiguration(ap, customConfigurations))) - .map(WebAuthProviderDescriptor::createDescriptorBean).collect(Collectors.toList()); + .map(WebAuthProviderDescriptor::createDescriptorBean).toList(); if (!CommonUtils.isEmpty(customConfigurations)) { // Attach custom configs to providers @@ -3009,12 +3000,12 @@ public List getObjectPermissionGrants( var grantedPermissionsBySubjectId = new HashMap(); try (Connection dbCon = database.openConnection()) { try (PreparedStatement dbStat = dbCon.prepareStatement(database.normalizeTableNames( - "SELECT OP.SUBJECT_ID,S.SUBJECT_TYPE, OP.PERMISSION\n" + - "FROM {table_prefix}CB_OBJECT_PERMISSIONS OP, {table_prefix}CB_AUTH_SUBJECT S\n" + - "WHERE S.SUBJECT_ID = OP.SUBJECT_ID AND OP.OBJECT_TYPE=? AND OP.OBJECT_ID=?"))) { + """ + SELECT OP.SUBJECT_ID,S.SUBJECT_TYPE, OP.PERMISSION + FROM {table_prefix}CB_OBJECT_PERMISSIONS OP, {table_prefix}CB_AUTH_SUBJECT S + WHERE S.SUBJECT_ID = OP.SUBJECT_ID AND OP.OBJECT_TYPE=? AND OP.OBJECT_ID=?"""))) { dbStat.setString(1, smObjectType.name()); dbStat.setString(2, objectId); - List result = new ArrayList<>(); try (ResultSet dbResult = dbStat.executeQuery()) { while (dbResult.next()) { String subjectId = dbResult.getString(1); @@ -3211,11 +3202,10 @@ public void clearOldAuthAttemptInfo() throws DBException { public Set getFilteredSubjects(Set allSubjects) { try (Connection dbCon = database.openConnection()) { Set result = new HashSet<>(); - var sqlBuilder = new StringBuilder("SELECT SUBJECT_ID FROM {table_prefix}CB_AUTH_SUBJECT U ") - .append("WHERE SUBJECT_ID IN (") - .append(SQLUtils.generateParamList(allSubjects.size())) - .append(")"); - try (var dbStat = dbCon.prepareStatement(database.normalizeTableNames(sqlBuilder.toString()))) { + String sqlBuilder = + "SELECT SUBJECT_ID FROM {table_prefix}CB_AUTH_SUBJECT U " + + "WHERE SUBJECT_ID IN (" + SQLUtils.generateParamList(allSubjects.size()) + ")"; + try (var dbStat = dbCon.prepareStatement(database.normalizeTableNames(sqlBuilder))) { int parameterIndex = 1; for (String subjectId : allSubjects) { dbStat.setString(parameterIndex++, subjectId); @@ -3235,7 +3225,6 @@ public Set getFilteredSubjects(Set allSubjects) { private SMSubjectType getSubjectType(@NotNull String subjectId) { try (Connection dbCon = database.openConnection()) { - Set result = new HashSet<>(); String sqlBuilder = "SELECT SUBJECT_TYPE FROM {table_prefix}CB_AUTH_SUBJECT U WHERE SUBJECT_ID = ?"; try (var dbStat = dbCon.prepareStatement(database.normalizeTableNames(sqlBuilder))) { dbStat.setString(1, subjectId);