mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b19d7532bc | ||
|
|
9576f979ad | ||
|
|
e570a05175 | ||
|
|
f82598ad66 | ||
|
|
d3b0b96bae | ||
|
|
ec9c2b895b | ||
|
|
be8744232b | ||
|
|
4088e7998e | ||
|
|
c540728fee | ||
|
|
84f0903fc7 | ||
|
|
89152a09a1 | ||
|
|
0bfdcf0886 | ||
|
|
d2d3ada58a | ||
|
|
a62478a574 | ||
|
|
980473918e | ||
|
|
26712e7bca | ||
|
|
9597e99bc9 | ||
|
|
7f92abd33e | ||
|
|
ad686faca8 | ||
|
|
c02411085e | ||
|
|
90fcbe6b7f | ||
|
|
1deffb984b | ||
|
|
bd52ee6583 | ||
|
|
013ad81581 | ||
|
|
4843002e0d | ||
|
|
9a05d0bf7f | ||
|
|
ecaa4934b1 | ||
|
|
8d89bc25ba | ||
|
|
40581e746b | ||
|
|
a9911d9297 | ||
|
|
a8d437dce6 | ||
|
|
bacd1bff91 | ||
|
|
1d9b0a2943 | ||
|
|
aa6a81428c | ||
|
|
b91ca06359 | ||
|
|
244479cd06 | ||
|
|
1c56d55462 | ||
|
|
be78d2fe92 | ||
|
|
4178826ff4 | ||
|
|
27ef1fdf73 | ||
|
|
5b0bedcf5e | ||
|
|
821805eba8 | ||
|
|
6531e45357 | ||
|
|
598fb92eaf | ||
|
|
71dda4aa45 | ||
|
|
cc1861c614 | ||
|
|
67c15af74d | ||
|
|
79194b2b50 | ||
|
|
673b824cb5 | ||
|
|
4e7555c419 | ||
|
|
9d0e75dba8 | ||
|
|
44cc1badf4 | ||
|
|
fdb4894ae4 | ||
|
|
a836412ee4 | ||
|
|
38b0246240 | ||
|
|
0bf769a662 | ||
|
|
0349bcee4c | ||
|
|
44b89296d2 | ||
|
|
b75d802883 | ||
|
|
9652520073 | ||
|
|
7e7d6634de | ||
|
|
c12561965f | ||
|
|
7a12ad4b91 | ||
|
|
c39de17df8 | ||
|
|
147edde923 | ||
|
|
aeaa864f57 | ||
|
|
bcda3e4312 | ||
|
|
72a007ceb6 | ||
|
|
e08ba6845e | ||
|
|
8f1fc1f36e | ||
|
|
b5cfcaef31 | ||
|
|
e9b1079f85 | ||
|
|
326514f914 | ||
|
|
67489a03b3 | ||
|
|
4ea0baf79b | ||
|
|
939f4d046a | ||
|
|
cd3cc2e92f | ||
|
|
7a8c4a141f | ||
|
|
5db7a0de5e | ||
|
|
15c61239ec | ||
|
|
164d155e7d | ||
|
|
821c9c8e56 | ||
|
|
b58e01794d | ||
|
|
fbc5f2d3ce | ||
|
|
ae9e87950a | ||
|
|
58db928e34 | ||
|
|
583519d9a7 | ||
|
|
a96862a444 | ||
|
|
b9d115ec33 | ||
|
|
78daa21c87 | ||
|
|
50090a3035 | ||
|
|
fc9b1d388f | ||
|
|
0c053efd68 | ||
|
|
abf07b8c5b | ||
|
|
c119c93a7d | ||
|
|
c2045091d0 | ||
|
|
9483df70ac | ||
|
|
d774540803 | ||
|
|
736442be35 | ||
|
|
8ee58e0ce8 | ||
|
|
163b96bcae | ||
|
|
6c915eedc3 | ||
|
|
b9d55bc121 | ||
|
|
cb23fda311 | ||
|
|
dc97f315ab | ||
|
|
5d2c6dc062 | ||
|
|
435aac2860 | ||
|
|
e0cdf745ad | ||
|
|
5e11a1d0b1 | ||
|
|
83125bf8b5 | ||
|
|
3b435459a2 | ||
|
|
5dad35b73d | ||
|
|
e801f85e7c | ||
|
|
a39af0fd1b | ||
|
|
9ec227ddb1 | ||
|
|
749c116743 | ||
|
|
d6221e3d33 | ||
|
|
6316ab0cbb | ||
|
|
e2e18e01e9 | ||
|
|
d042a18d46 | ||
|
|
88d7a8d6eb | ||
|
|
33bf82970f | ||
|
|
ea552d613b | ||
|
|
0ae6a648d8 | ||
|
|
a674e11960 | ||
|
|
d1065037b9 | ||
|
|
dd3ab9d5a1 | ||
|
|
7b94dc60ab | ||
|
|
25089a9a70 | ||
|
|
d645365869 | ||
|
|
eb4f11aa0a | ||
|
|
ec3ba12699 | ||
|
|
a75a7ea5a4 | ||
|
|
3ca904924b | ||
|
|
46a0adb682 | ||
|
|
b9b4dc040c | ||
|
|
5973bd708f | ||
|
|
698ee3cba1 | ||
|
|
9394667673 | ||
|
|
37877ccf66 | ||
|
|
dc28d68208 | ||
|
|
b55398a969 | ||
|
|
6584a2252e | ||
|
|
411def42f9 | ||
|
|
8b471138db | ||
|
|
e2a5e1fca2 | ||
|
|
9c2bc100b1 | ||
|
|
8b08927af8 | ||
|
|
7ea627a24f | ||
|
|
dc5f86c40c | ||
|
|
24b7b6021d | ||
|
|
2f64827ad4 | ||
|
|
e443a88ae7 | ||
|
|
f46857fe6d | ||
|
|
11f8568238 | ||
|
|
b81c7732d1 | ||
|
|
3592e77851 | ||
|
|
4b0e8b5e91 | ||
|
|
802a3e69fe | ||
|
|
d409aad739 | ||
|
|
9b01659a6f | ||
|
|
354f13d0d4 | ||
|
|
e39b1b1ee2 | ||
|
|
f818fb1c85 | ||
|
|
6253d3e543 | ||
|
|
c7a6cc967e | ||
|
|
c3ac257fdd | ||
|
|
c9b1278788 | ||
|
|
58d316f396 | ||
|
|
37e5e1865c | ||
|
|
8cfe6a98e5 | ||
|
|
20026d1d30 | ||
|
|
7c72232215 | ||
|
|
64118de61c | ||
|
|
17331e6a8f | ||
|
|
46079dea6a | ||
|
|
d8f2df0564 | ||
|
|
f64bc1b435 | ||
|
|
754927a1ae | ||
|
|
cebbe2a89b | ||
|
|
8e44be20b0 | ||
|
|
f37a753834 | ||
|
|
348fe0c9df | ||
|
|
a62b528018 | ||
|
|
e1dc245446 | ||
|
|
4bbb9ccb93 | ||
|
|
e60ea477f5 | ||
|
|
493424ce7d | ||
|
|
d59f06276c | ||
|
|
9593b7a6c3 | ||
|
|
0955c18e38 | ||
|
|
dd3f00a3a8 | ||
|
|
22c7f0d3f2 | ||
|
|
5997d4b1a2 | ||
|
|
cdae6efbd2 | ||
|
|
ced66cd5e0 | ||
|
|
4a67c5d3ff | ||
|
|
39873f20a8 | ||
|
|
c5e8d510a2 | ||
|
|
2a107f3d63 | ||
|
|
a2eab0935b | ||
|
|
cd78601245 | ||
|
|
4ab47583ad | ||
|
|
37cfecbf91 | ||
|
|
046659672c | ||
|
|
bf52e23793 | ||
|
|
4a2ce73a9c | ||
|
|
b73b707b7f | ||
|
|
57461bd8be | ||
|
|
71bebb32dd | ||
|
|
28cade86aa | ||
|
|
92186dd332 | ||
|
|
860a9f55f2 | ||
|
|
754c8d562d | ||
|
|
ed0e92cdd6 | ||
|
|
9267e65300 | ||
|
|
4e3ec7cbf4 | ||
|
|
6978ae5355 | ||
|
|
3986debe8f | ||
|
|
38cfb0a690 | ||
|
|
682a387564 | ||
|
|
de0dee29ed | ||
|
|
3a1ae15ecb | ||
|
|
8cf064e845 | ||
|
|
ec1b455f4d | ||
|
|
683d85c8d6 | ||
|
|
8066904d4d | ||
|
|
7ea46ea2c5 | ||
|
|
8aee24cf1c | ||
|
|
5d83098d38 | ||
|
|
3e855c3afd | ||
|
|
9c50589586 | ||
|
|
f99c2e3ebd | ||
|
|
e141575785 | ||
|
|
b93b85f622 | ||
|
|
267a753d87 | ||
|
|
6cea8cd64f | ||
|
|
b630cb6791 |
Vendored
+2
-2
@@ -15,11 +15,11 @@
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"streetsidesoftware.code-spell-checker-russian",
|
||||
"syler.sass-indented",
|
||||
"VisualStudioExptTeam.vscodeintellicode",
|
||||
"github.copilot-chat",
|
||||
"yzhang.markdown-all-in-one",
|
||||
"GraphQL.vscode-graphql-syntax",
|
||||
"mrmlnc.vscode-json5",
|
||||
"esbenp.prettier-vscode",
|
||||
"devnaumov.licensify"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/cloudbeaver-logo.png" align="right" width="250"/>
|
||||
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/cloudbeaver-logo.png" alt="CloudBeaver logo" align="right" width="250"/>
|
||||
|
||||
# CloudBeaver Community
|
||||
|
||||
@@ -25,42 +25,61 @@ You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io
|
||||
|
||||
## Changelog
|
||||
|
||||
### 25.2.1 2025-09-22
|
||||
- Autocomplete values in SQL Editor are ordered alphabetically to simplify object search.
|
||||
- "Show all databases" setting was added on the connection page for MySQL and MariaDB.
|
||||
- Different bug fixes and enhancements have been made.
|
||||
### 25.3.3 2026-01-19
|
||||
|
||||
### 25.2.0 2025-09-01
|
||||
### Changes since 25.1.0:
|
||||
- Administration:
|
||||
- Added support for multiple server URLs to accommodate different access policies for internal and external users, improving flexibility in network-restricted environments. To set it up, use the Allowed Server URLs field in the Server configuration within the Administration section;
|
||||
- Added the Force HTTPS mode setting in Server Configuration to enforce redirecting from HTTP to HTTPS. This setting helps avoid a potential man-in-the-middle attack. This setting is turned off by default. Please remember to configure your proxy before enabling;
|
||||
- Added the CLOUDBEAVER_BIND_SESSION_TO_IP option to improve session security by linking user sessions to their IP address. It is disabled by default. When enabled, this helps protect against certain types of session hijacking attacks, where an attacker could try to take over a user’s session. Note: Users will be logged out automatically on the IP address change (switching networks or using mobile data);
|
||||
- A password confirmation field has been added for administrators in the Easy Config section to prevent accidental misconfigurations.
|
||||
- SQL Editor:
|
||||
- Changed the default engine used for autocompletion in the SQL Editor. This Semantic engine offers improved suggestions for database objects, keywords, and functions. You can switch back to the Legacy engine in Preferences > SQL Editor;
|
||||
- A “Clear” button was added to the output panel.
|
||||
- Data Editor:
|
||||
- Column descriptions were added in the Data Editor under column names to provide more metadata context. You can disable this in Preferences > Data Viewer;
|
||||
- Added an ability to review the script before execution when users edit tables without primary keys.
|
||||
- General:
|
||||
- Added a new welcome screen for a freshly opened application. This screen contains shortcuts to create a new connection, open SQL Editor, or documentation;
|
||||
- Added a search panel for SQL Editor and Value panel: press Ctrl/Cmd + F to open a panel that allows searching and replacing by keyword or regular expression;
|
||||
- Changed the data transfer mechanism to avoid intermediate file creation. The parameter dataExportFileSizeLimit was removed from the server configuration as deprecated;
|
||||
- The CloudBeaver default theme matches the device theme by default now. You can change this behavior in user preferences under the Theme section;
|
||||
- Added the option to display tabs across multiple rows, allowing you to see all tabs without scrolling. You can enable this in Preferences > Interface;
|
||||
- Improved dark theme accessibility by enhancing color contrast across the app for better readability and compliance with accessibility standards;
|
||||
- The database navigator now automatically hides empty folders in shared projects, reducing visual clutter and speeding up the process of finding active connections.
|
||||
- Databases and drivers:
|
||||
- Clickhouse: fixed the presentation of tuples and map data types in the Data Editor;
|
||||
- Databend database support has been added (thanks to @hantmac);
|
||||
- DuckDB: driver has been updated to version 1.3;
|
||||
- MySQL: Improved performance when retrieving foreign keys metadata;
|
||||
- PostgreSQL: fixed misplaced comment for table DDL generation.
|
||||
- For security reasons, session expiration leads to a complete logout for a user and clears all data from the screen;
|
||||
- Added "Use local formatting" setting to Data Editor. Users can choose how to display numbers and dates: using the OS locale, a custom locale, or keeping values unformatted. This formatting applies only to displayed values. Data in the database remains unchanged;
|
||||
- Added the shortcut Ctrl/Cmd + . to cancel operations in Data Editor;
|
||||
- CloudBeaver now supports long polling as a fallback when WebSockets are unavailable or blocked, ensuring reliable communication for metadata updates and SQL execution;
|
||||
- Fixed a keyboard navigation issue for panels to keep the focus inside;
|
||||
- Clickhouse: Spatial data support was added.
|
||||
|
||||
### 25.3.2 2026-01-05
|
||||
|
||||
- The ability to pin columns has been added to the Data Editor, allowing users to keep key columns (e.g., IDs, names) visible while scrolling through wide tables;
|
||||
- Added a new setting in the SQL Editor to highlight spaces, tabs, and other whitespace characters to help users read, debug, and maintain their scripts. It is turned off by default and can be configured in personal preferences;
|
||||
- Redesigned the connection configuration page. Form fields and sections have been reorganized to provide more space for input and reduce visual clutter;
|
||||
- Clickhouse: driver has been updated to version 0.9.5;
|
||||
- Oracle: Added a new "Set Username to OS_USER" option in the Misc section of Oracle connection settings. When turned on, the tool automatically uses the current database username as the operating system user identifier in the session metadata.
|
||||
|
||||
### 25.3.1 2025-12-22
|
||||
|
||||
- Implemented an ability to add parameters and variables to queries in the SQL Editor. This feature allows for the reuse of queries by changing parameters at the execution stage. It is enabled by default and can be configured in personal preferences;
|
||||
- Added support for mapping users to CloudBeaver teams based on LDAP memberOf group membership;
|
||||
- Added support for previous browser versions, but not older than three years.
|
||||
|
||||
### 25.3.0 2025-12-01
|
||||
|
||||
### Changes since 25.2.0:
|
||||
|
||||
- Administration:
|
||||
- Permission configuration for connections has been simplified, providing a clearer appearance and improved performance;
|
||||
- LDAP authentication now supports case-insensitive Distinguished Names (DNs), allowing login with mixed-case DNS.
|
||||
- Security:
|
||||
- Fixed a security issue in the Spatial Viewer for the HTML content to prevent a third-party script execution;
|
||||
- Fixed the critical vulnerability (CVE-2025-61927) in the Happy DOM library. The library was updated to version 20.0.2.
|
||||
- SQL Editor:
|
||||
- A confirmation prompt has been added to the SQL Editor to prevent accidental execution of potentially dangerous queries. You can customize this behavior in Preferences > SQL Editor;
|
||||
- A search field has been added to the catalog/schema selector on the main toolbar for easy navigation;
|
||||
- Alphabetical order has been added to the autocomplete values in the SQL Editor to simplify object search;
|
||||
- Added support for temporary tables in semantic analysis.
|
||||
- Data Editor:
|
||||
- The ability to review the database query after applying filters and sorting in the Data Editor, Grouping panel, and Result sets has been added. The new Show SQL button is placed on the Filter panel;
|
||||
- The value panel in the Data Editor has been redesigned to give users more space for viewing and editing data. Tab buttons for different data type visualizations are now aligned on a single level, creating a cleaner layout;
|
||||
- Added visualization for column reordering in the Data Editor;
|
||||
- Fixed an issue in the Data Editor where resizing one column in a wide table caused previously resized columns to revert to their original width. Users can resize multiple columns as intended.
|
||||
- General:
|
||||
- A reorder tabs option has been added to the application, allowing users to set editor tabs in custom positions;
|
||||
- Keyboard navigation has been improved across the application. Additional shortcuts are available in the main info panel (top-right corner) to help discover them faster. Relevant keyboard shortcuts are also shown in the tooltips of Data Editor buttons;
|
||||
- Boolean values have been replaced with intuitive checkboxes instead of the true/false text in the metadata editor, making editing easier and faster;
|
||||
- The database navigator now automatically hides empty subfolders in shared projects, reducing visual clutter;
|
||||
- Databases and drivers:
|
||||
- ClickHouse: query canceling support has been added;
|
||||
- DuckDB: fixed an issue where catalog selection was lost upon disconnection;
|
||||
- PostgreSQL: fixed support for GIS data types;
|
||||
- SQL Server: the driver has been updated to version 13.2.1;
|
||||
- The "Show all databases" setting has been added on the connection page for MySQL and MariaDB.
|
||||
## 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-dbeaver/) our most active contributors every major sprint.
|
||||
The most significant contribution to our code for the major release 25.2.0 was made by:
|
||||
1. [hantmac](https://github.com/hantmac) - added support for Databend in CloudBeaver Community Edition.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Web Model
|
||||
Bundle-SymbolicName: io.cloudbeaver.model.cli;singleton:=true
|
||||
Bundle-Version: 1.0.3.qualifier
|
||||
Bundle-Release-Date: 20260202
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: org.jkiss.dbeaver.model.cli;visibility:=reexport
|
||||
Export-Package: io.cloudbeaver.model.cli
|
||||
Automatic-Module-Name: io.cloudbeaver.model.cli
|
||||
Import-Package: org.slf4j
|
||||
@@ -0,0 +1,5 @@
|
||||
source.. = src/
|
||||
output.. = target/classes/
|
||||
bin.includes = .,\
|
||||
META-INF/,\
|
||||
plugin.xml
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?eclipse version="3.2"?>
|
||||
|
||||
<plugin>
|
||||
|
||||
</plugin>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>io.cloudbeaver</groupId>
|
||||
<artifactId>bundles</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.model.cli</artifactId>
|
||||
<version>1.0.3-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 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.cli;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.cli.*;
|
||||
import org.jkiss.dbeaver.model.cli.model.CommandLineAuthenticator;
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class CloudBeaverCommandLine extends ApplicationCommandLine<ApplicationInstanceController> {
|
||||
@Nullable
|
||||
private final CommandLineAuthenticator authenticator;
|
||||
private final CloudBeaverMixin mixin;
|
||||
|
||||
public CloudBeaverCommandLine(@Nullable CommandLineAuthenticator authenticator) {
|
||||
super();
|
||||
this.authenticator = authenticator;
|
||||
this.mixin = new CloudBeaverMixin();
|
||||
}
|
||||
|
||||
public CloudBeaverCommandLine(
|
||||
@Nullable CommandLineAuthenticator authenticator,
|
||||
@NotNull CloudBeaverMixin mixin
|
||||
) {
|
||||
super();
|
||||
this.authenticator = authenticator;
|
||||
this.mixin = mixin;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CloudBeaverTopLevelCommand createTopLevelCommand(
|
||||
@Nullable ApplicationInstanceController applicationInstanceController,
|
||||
@NotNull CommandLineContext context,
|
||||
@NotNull CLIRunMeta runMeta
|
||||
) {
|
||||
if (authenticator != null) {
|
||||
context.setContextParameter(CLIConstants.CONTEXT_PARAM_AUTHENTICATOR, authenticator);
|
||||
}
|
||||
return new CloudBeaverTopLevelCommand(applicationInstanceController, context, runMeta);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected CommandLine initCommandLine(
|
||||
@Nullable ApplicationInstanceController applicationInstanceController,
|
||||
@NotNull CommandLineContext context,
|
||||
@NotNull CLIRunMeta runMeta
|
||||
) {
|
||||
var cmd = super.initCommandLine(applicationInstanceController, context, runMeta);
|
||||
cmd.addMixin("cloudbeaver", mixin);
|
||||
return cmd;
|
||||
}
|
||||
}
|
||||
+12
-5
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
package io.cloudbeaver.model.cli;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationInstanceServer;
|
||||
@@ -25,20 +25,27 @@ import org.jkiss.dbeaver.model.cli.CLIProcessResult;
|
||||
import java.io.IOException;
|
||||
|
||||
public class CloudBeaverInstanceServer extends ApplicationInstanceServer<ApplicationInstanceController> {
|
||||
private final CloudBeaverCommandLine commandLine;
|
||||
|
||||
public CloudBeaverInstanceServer() throws IOException {
|
||||
super(ApplicationInstanceController.class);
|
||||
this.commandLine = new CloudBeaverCommandLine(null);
|
||||
}
|
||||
|
||||
public CloudBeaverInstanceServer(@NotNull CloudBeaverCommandLine commandLine) throws IOException {
|
||||
super(ApplicationInstanceController.class);
|
||||
this.commandLine = commandLine;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CLIProcessResult handleCommandLine(@NotNull String[] args) {
|
||||
CommandLine cmd = CloudBeaverCommandLine.getInstance().getCommandLine(args);
|
||||
try {
|
||||
return CloudBeaverCommandLine.getInstance().executeCommandLineCommands(
|
||||
cmd,
|
||||
return commandLine.executeCommandLineCommands(
|
||||
this,
|
||||
false,
|
||||
false
|
||||
false,
|
||||
args
|
||||
);
|
||||
} catch (Exception e) {
|
||||
return new CLIProcessResult(CLIProcessResult.PostAction.ERROR, "Error executing command: " + e.getMessage());
|
||||
+12
-15
@@ -16,20 +16,17 @@
|
||||
*/
|
||||
package io.cloudbeaver.model.cli;
|
||||
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationCommandLine;
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class CloudBeaverCommandLine extends ApplicationCommandLine<ApplicationInstanceController> {
|
||||
private static CloudBeaverCommandLine INSTANCE = null;
|
||||
|
||||
private CloudBeaverCommandLine() {
|
||||
super();
|
||||
}
|
||||
|
||||
public synchronized static CloudBeaverCommandLine getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new CloudBeaverCommandLine();
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
/**
|
||||
* CloudBeaver specific mixin options
|
||||
*/
|
||||
public class CloudBeaverMixin {
|
||||
@CommandLine.Option(
|
||||
names = {"-web-config"},
|
||||
arity = "1",
|
||||
hidden = true,
|
||||
scope = CommandLine.ScopeType.INHERIT
|
||||
)
|
||||
private String webConfig;
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 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.cli;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
|
||||
import org.jkiss.dbeaver.model.cli.CLIRunMeta;
|
||||
import org.jkiss.dbeaver.model.cli.CommandLineContext;
|
||||
import org.jkiss.dbeaver.model.cli.command.AbstractTopLevelCommand;
|
||||
import picocli.CommandLine;
|
||||
|
||||
@CommandLine.Command(name = "cbvr", description = "CloudBeaver commands")
|
||||
public class CloudBeaverTopLevelCommand extends AbstractTopLevelCommand {
|
||||
@CommandLine.Option(
|
||||
names = {NOSPASH_OPTION},
|
||||
hidden = true,
|
||||
scope = CommandLine.ScopeType.INHERIT
|
||||
)
|
||||
private boolean noSplash;
|
||||
|
||||
protected CloudBeaverTopLevelCommand(
|
||||
@Nullable ApplicationInstanceController controller,
|
||||
@NotNull CommandLineContext context,
|
||||
@NotNull CLIRunMeta meta
|
||||
|
||||
) {
|
||||
super(controller, context, meta);
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,18 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Web Model
|
||||
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
|
||||
Bundle-Version: 1.0.86.qualifier
|
||||
Bundle-Release-Date: 20251006
|
||||
Bundle-Version: 1.0.94.qualifier
|
||||
Bundle-Release-Date: 20260202
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
|
||||
org.jkiss.dbeaver.model;visibility:=reexport,
|
||||
org.jkiss.dbeaver.model.cli;visibility:=reexport,
|
||||
org.jkiss.dbeaver.model.sm;visibility:=reexport,
|
||||
org.jkiss.dbeaver.model.event;visibility:=reexport,
|
||||
org.jkiss.dbeaver.model.nio;visibility:=reexport,
|
||||
org.jkiss.dbeaver.registry;visibility:=reexport,
|
||||
org.jkiss.dbeaver.net.ssh,
|
||||
org.jkiss.bundle.logback;visibility:=reexport,
|
||||
org.jkiss.bundle.graphql;visibility:=reexport,
|
||||
org.jkiss.bundle.apache.dbcp,
|
||||
com.google.gson;visibility:=reexport,
|
||||
@@ -26,17 +26,16 @@ Export-Package: io.cloudbeaver,
|
||||
io.cloudbeaver.auth.provider.local,
|
||||
io.cloudbeaver.auth.provisioning,
|
||||
io.cloudbeaver.websocket,
|
||||
io.cloudbeaver.websocket.event.client,
|
||||
io.cloudbeaver.websocket.event.task,
|
||||
io.cloudbeaver.model,
|
||||
io.cloudbeaver.model.app,
|
||||
io.cloudbeaver.model.config,
|
||||
io.cloudbeaver.model.apilog,
|
||||
io.cloudbeaver.model.cli,
|
||||
io.cloudbeaver.model.events,
|
||||
io.cloudbeaver.model.fs,
|
||||
io.cloudbeaver.model.log,
|
||||
io.cloudbeaver.model.rm,
|
||||
io.cloudbeaver.model.rm.local,
|
||||
io.cloudbeaver.model.rm.lock,
|
||||
io.cloudbeaver.model.session,
|
||||
io.cloudbeaver.model.user,
|
||||
io.cloudbeaver.model.utils,
|
||||
|
||||
@@ -25,4 +25,27 @@
|
||||
label="Default secret controller" description="Default void secret controller"/>
|
||||
</extension>
|
||||
|
||||
<extension point="org.jkiss.dbeaver.ws.event">
|
||||
<event topicId="cb_session_task" id="cb_session_task_info_updated"
|
||||
class="io.cloudbeaver.websocket.event.task.WSSessionTaskInfoEvent"/>
|
||||
|
||||
<event topicId="cb_session_task" id="cb_session_task_confirmation_request"
|
||||
class="io.cloudbeaver.websocket.event.task.WSSessionTaskConfirmationRequestEvent"/>
|
||||
<event topicId="cb_session_task" id="cb_session_task_query_confirmation_request"
|
||||
class="io.cloudbeaver.websocket.event.task.WSSessionTaskQueryConfirmationRequestEvent"/>
|
||||
<event topicId="cb_session_task" id="cb_session_task_query_params_confirmation_request"
|
||||
class="io.cloudbeaver.websocket.event.task.WSSessionTaskQueryParamsConfirmationEvent"/>
|
||||
|
||||
<clientEvent id="cb_client_session_ping" class="io.cloudbeaver.websocket.event.client.WSSessionPingClientEvent"/>
|
||||
<clientEvent id="cb_client_topic_subscribe" class="io.cloudbeaver.websocket.event.client.WSSubscribeOnTopicClientEvent"/>
|
||||
<clientEvent id="cb_client_topic_unsubscribe"
|
||||
class="io.cloudbeaver.websocket.event.client.WSUnsubscribeFromTopicClientEvent"/>
|
||||
<clientEvent id="cb_client_projects_active"
|
||||
class="io.cloudbeaver.websocket.event.client.WSUpdateActiveProjectsClientEvent"/>
|
||||
<clientEvent id="cb_client_session_task_confirmation"
|
||||
class="io.cloudbeaver.websocket.event.client.WSSessionTaskConfirmationEvent"/>
|
||||
<clientEvent id="cb_client_session_task_with_parameters_confirmation"
|
||||
class="io.cloudbeaver.websocket.event.client.WSSessionTaskWithParametersConfirmationEvent"/>
|
||||
</extension>
|
||||
|
||||
</plugin>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.model</artifactId>
|
||||
<version>1.0.86-SNAPSHOT</version>
|
||||
<version>1.0.94-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -32,6 +32,8 @@ public interface DBWConstants {
|
||||
String PERMISSION_SQL_RESULT_UPDATE = "sql-result-update";
|
||||
String PERMISSION_SQL_EXECUTE_QUERY = "sql-execute-query";
|
||||
|
||||
String PERMISSION_FS_RESOURCE_EDIT = "fs-resource-edit";
|
||||
|
||||
String GLOBAL_PERMISSION_SCRIPT_EXECUTE = "permission.sql.script.execution";
|
||||
String GLOBAL_PERMISSION_DATA_EDITOR_IMPORT = "permission.data-editor.import";
|
||||
String GLOBAL_PERMISSION_DATA_EDITOR_EDITING = "permission.data-editor.editing";
|
||||
|
||||
+8
-2
@@ -18,6 +18,7 @@ package io.cloudbeaver;
|
||||
|
||||
import io.cloudbeaver.model.WebConnectionConfig;
|
||||
import io.cloudbeaver.model.app.WebAppConfiguration;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import io.cloudbeaver.utils.WebDataSourceUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
@@ -32,8 +33,10 @@ public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C ex
|
||||
private static final Log log = Log.getLog(WebConnectionConfigInputHandler.class);
|
||||
protected final T input;
|
||||
protected final DBPDataSourceRegistry registry;
|
||||
protected final WebSession webSession;
|
||||
|
||||
public WebConnectionConfigInputHandler(@NotNull DBPDataSourceRegistry registry, T configInput) {
|
||||
public WebConnectionConfigInputHandler(@NotNull WebSession webSession, @NotNull DBPDataSourceRegistry registry, T configInput) {
|
||||
this.webSession = webSession;
|
||||
this.registry = registry;
|
||||
this.input = configInput;
|
||||
}
|
||||
@@ -52,6 +55,7 @@ public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C ex
|
||||
}
|
||||
|
||||
WebDataSourceUtils.saveAuthProperties(
|
||||
webSession.getProgressMonitor(),
|
||||
newDataSource,
|
||||
newDataSource.getConnectionConfiguration(),
|
||||
input.getCredentials(),
|
||||
@@ -73,7 +77,7 @@ public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C ex
|
||||
|
||||
dataSource.setFolder(input.getFolder() != null ? registry.getFolder(input.getFolder()) : null);
|
||||
if (input.isDefaultAutoCommit() != null) {
|
||||
dataSource.setDefaultAutoCommit(input.isDefaultAutoCommit());
|
||||
dataSource.getConnectionConfiguration().getBootstrap().setDefaultAutoCommit(input.isDefaultAutoCommit());
|
||||
}
|
||||
WebDataSourceUtils.setConnectionConfiguration(
|
||||
dataSource.getDriver(),
|
||||
@@ -81,12 +85,14 @@ public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C ex
|
||||
input
|
||||
);
|
||||
WebDataSourceUtils.saveAuthProperties(
|
||||
webSession.getProgressMonitor(),
|
||||
dataSource,
|
||||
dataSource.getConnectionConfiguration(),
|
||||
input.getCredentials(),
|
||||
input.isSaveCredentials(),
|
||||
input.isSharedCredentials()
|
||||
);
|
||||
dataSource.setConnectionReadOnly(input.isReadOnly());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ public class WebDataSourceConnectEventListener implements DBPEventListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDataSourceEvent(DBPEvent event) {
|
||||
public void handleDataSourceEvent(@NotNull DBPEvent event) {
|
||||
if (!(event.getObject() instanceof DBPDataSourceContainer container)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -24,53 +24,46 @@ 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.DBPAdaptable;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.DBPObjectSettingsProvider;
|
||||
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
|
||||
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistryCache;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNModel;
|
||||
import org.jkiss.dbeaver.model.rm.RMProject;
|
||||
import org.jkiss.dbeaver.model.rm.RMUtils;
|
||||
import org.jkiss.dbeaver.model.security.SMControllerUtils;
|
||||
import org.jkiss.dbeaver.model.security.SMObjectType;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
|
||||
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
|
||||
import org.jkiss.dbeaver.registry.DataSourceRegistry;
|
||||
import org.jkiss.dbeaver.registry.project.BaseProjectSettings;
|
||||
import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.*;
|
||||
|
||||
public class WebSessionProjectImpl extends WebProjectImpl {
|
||||
public class WebSessionProjectImpl extends WebProjectImpl implements DBPAdaptable {
|
||||
private static final Log log = Log.getLog(WebSessionProjectImpl.class);
|
||||
|
||||
protected final WebSession webSession;
|
||||
private final Map<String, WebConnectionInfo> connections = new HashMap<>();
|
||||
private final Map<String, Object> projectSettings = new ConcurrentHashMap<>();
|
||||
private final BaseProjectSettings projectSettings;
|
||||
private boolean registryIsLoaded = false;
|
||||
|
||||
public WebSessionProjectImpl(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull RMProject project
|
||||
) {
|
||||
super(
|
||||
webSession.getWorkspace(),
|
||||
webSession.getRmController(),
|
||||
webSession.getSessionContext(),
|
||||
project,
|
||||
webSession.getUserPreferenceStore(),
|
||||
RMUtils.getProjectPath(project)
|
||||
);
|
||||
this.webSession = webSession;
|
||||
this(webSession, project, null);
|
||||
}
|
||||
|
||||
public WebSessionProjectImpl(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull RMProject project,
|
||||
@NotNull Path path
|
||||
@Nullable Path path
|
||||
) {
|
||||
super(
|
||||
webSession.getWorkspace(),
|
||||
@@ -78,9 +71,36 @@ public class WebSessionProjectImpl extends WebProjectImpl {
|
||||
webSession.getSessionContext(),
|
||||
project,
|
||||
webSession.getUserPreferenceStore(),
|
||||
path
|
||||
path == null ? RMUtils.getProjectPath(project) : path
|
||||
);
|
||||
this.webSession = webSession;
|
||||
this.projectSettings = new BaseProjectSettings(this) {
|
||||
@NotNull
|
||||
@Override
|
||||
protected Map<SMObjectType, Map<String, Map<String, String>>> loadAllProjectSettings() throws DBException {
|
||||
if (webSession.getUser() == null) {
|
||||
return new LinkedHashMap<>();
|
||||
}
|
||||
return SMControllerUtils.getObjectSettingsMap(WebSessionProjectImpl.this, webSession.getSecurityController());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveProjectSettings(
|
||||
@NotNull SMObjectType objectType,
|
||||
@NotNull String objectId,
|
||||
@NotNull Map<String, String> settings
|
||||
) throws DBException {
|
||||
if (webSession.getUserContext().isNonAnonymousUserAuthorizedInSM()) {
|
||||
webSession.getSecurityController().setObjectSettings(getId(), objectType, objectId, settings);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public BaseProjectSettings getProjectSettings() {
|
||||
return projectSettings;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -89,6 +109,14 @@ public class WebSessionProjectImpl extends WebProjectImpl {
|
||||
return webSession.getNavigatorModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getAdapter(@NotNull Class<T> adapter) {
|
||||
if (adapter == DBPObjectSettingsProvider.class) {
|
||||
return adapter.cast(projectSettings);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected DBPDataSourceRegistry createDataSourceRegistry() {
|
||||
@@ -244,20 +272,6 @@ public class WebSessionProjectImpl extends WebProjectImpl {
|
||||
return sendDataSourceUpdatedEvent;
|
||||
}
|
||||
|
||||
public void refreshProjectSettings() throws DBException {
|
||||
if (webSession.getUser() == null) {
|
||||
projectSettings.clear();
|
||||
return;
|
||||
}
|
||||
Map<String, Object> loadedSettings = webSession.getSecurityController().getObjectSettings(
|
||||
getId(),
|
||||
SMObjectType.project,
|
||||
null
|
||||
);
|
||||
projectSettings.clear();
|
||||
projectSettings.putAll(loadedSettings);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public WebConnectionInfo createConnectionInfo(@NotNull DBPDataSourceContainer dataSourceDescriptor) {
|
||||
return new WebConnectionInfo(webSession, dataSourceDescriptor);
|
||||
@@ -268,7 +282,7 @@ public class WebSessionProjectImpl extends WebProjectImpl {
|
||||
if (CommonUtils.isEmpty(configMap)) {
|
||||
throw new DBWebException("Connection configuration parameters are missing");
|
||||
}
|
||||
DBPDataSourceContainer newDataSource = getDataSourceContainerFromInput(configMap);
|
||||
DBPDataSourceContainer newDataSource = getDataSourceContainerFromInput(getConnectionConfigInput(configMap));
|
||||
return addDataSourceToProject(newDataSource);
|
||||
}
|
||||
|
||||
@@ -302,7 +316,7 @@ public class WebSessionProjectImpl extends WebProjectImpl {
|
||||
webSession.addInfoMessage("Update connection - " + WebDataSourceUtils.getConnectionContainerInfo(dataSource));
|
||||
|
||||
DBPDataSourceRegistry registry = getDataSourceRegistry();
|
||||
getInputConfigHandler(configMap).updateDataSource(dataSource);
|
||||
getInputConfigHandler(config).updateDataSource(dataSource);
|
||||
connectionInfo.setCredentialsSavedInSession(null);
|
||||
try {
|
||||
registry.updateDataSource(dataSource);
|
||||
@@ -330,26 +344,16 @@ public class WebSessionProjectImpl extends WebProjectImpl {
|
||||
@NotNull
|
||||
private WebConnectionInfo closeAndDeleteConnection(@NotNull WebConnectionInfo connectionInfo) throws DBWebException {
|
||||
DBPDataSourceContainer dataSourceContainer = connectionInfo.getDataSourceContainer();
|
||||
boolean disconnected = WebDataSourceUtils.disconnectDataSource(webSession, dataSourceContainer);
|
||||
WebDataSourceUtils.disconnectDataSource(webSession, dataSourceContainer);
|
||||
DBPDataSourceRegistry registry = getDataSourceRegistry();
|
||||
registry.removeDataSource(dataSourceContainer);
|
||||
try {
|
||||
registry.checkForErrors();
|
||||
} catch (DBException e) {
|
||||
try {
|
||||
registry.addDataSource(dataSourceContainer);
|
||||
} catch (DBException ex) {
|
||||
log.error("Error re-adding after delete attempt", e);
|
||||
}
|
||||
throw new DBWebException("Failed to delete connection", e);
|
||||
}
|
||||
removeConnection(dataSourceContainer);
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DataSourceDescriptor getDataSourceContainerFromInput(@NotNull Map<String, Object> configMap) throws DBWebException {
|
||||
return getInputConfigHandler(configMap).createDataSourceContainer();
|
||||
public DataSourceDescriptor getDataSourceContainerFromInput(@NotNull WebConnectionConfig configInput) throws DBWebException {
|
||||
return getInputConfigHandler(configInput).createDataSourceContainer();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -358,8 +362,8 @@ public class WebSessionProjectImpl extends WebProjectImpl {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected WebConnectionConfigInputHandler getInputConfigHandler(@NotNull Map<String, Object> configMap) {
|
||||
return new WebConnectionConfigInputHandler<>(getDataSourceRegistry(), getConnectionConfigInput(configMap));
|
||||
protected WebConnectionConfigInputHandler getInputConfigHandler(@NotNull WebConnectionConfig configInput) {
|
||||
return new WebConnectionConfigInputHandler<>(webSession, getDataSourceRegistry(), configInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+4
@@ -74,4 +74,8 @@ public interface SMAuthProviderExternal<AUTH_SESSION extends SMSession> extends
|
||||
*/
|
||||
default void postAuthentication() {}
|
||||
|
||||
@Override
|
||||
default boolean supportsOpeningSessionAsChild() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 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;
|
||||
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.utils.WebEventUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.runtime.AbstractJob;
|
||||
|
||||
public abstract class AbstractCancelableJob extends AbstractJob implements CustomCancelableJob {
|
||||
protected AbstractCancelableJob(@NotNull String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelJob(@NotNull WebSession webSession, @NotNull WebAsyncTaskInfo taskInfo) {
|
||||
cancelJob(webSession, taskInfo, "Canceled by the user");
|
||||
}
|
||||
|
||||
public void cancelJob(@NotNull WebSession webSession, @NotNull WebAsyncTaskInfo taskInfo, @NotNull String errorMessage) {
|
||||
taskInfo.setRunning(false);
|
||||
taskInfo.setJobError(new DBException(errorMessage));
|
||||
WebEventUtils.sendAsyncTaskEvent(webSession, taskInfo);
|
||||
}
|
||||
}
|
||||
+18
-2
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 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;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
@@ -10,7 +26,7 @@ import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
public abstract class BaseDatasourceAccessCheckHandler implements DBPDataSourceHandler {
|
||||
@Override
|
||||
public void beforeConnect(
|
||||
DBRProgressMonitor monitor,
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull DBPDataSourceContainer dataSourceContainer
|
||||
) throws DBException {
|
||||
if (isDriverDisabled(dataSourceContainer.getDriver())) {
|
||||
@@ -20,7 +36,7 @@ public abstract class BaseDatasourceAccessCheckHandler implements DBPDataSourceH
|
||||
|
||||
@Override
|
||||
public void beforeDisconnect(
|
||||
DBRProgressMonitor monitor,
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull DBPDataSourceContainer dataSourceContainer
|
||||
) throws DBException {
|
||||
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 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;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
|
||||
public class WebBasicEnumObjectInfo {
|
||||
|
||||
@NotNull
|
||||
private String displayName;
|
||||
@NotNull
|
||||
private String value;
|
||||
|
||||
public WebBasicEnumObjectInfo(@NotNull String displayName, @NotNull String value) {
|
||||
this.displayName = displayName;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(@NotNull String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(@NotNull String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
+114
-6
@@ -42,7 +42,7 @@ public class WebConnectionConfig {
|
||||
private String databaseName;
|
||||
private String url;
|
||||
|
||||
private int keepAliveInterval;
|
||||
private Integer keepAliveInterval;
|
||||
|
||||
private String name;
|
||||
private String description;
|
||||
@@ -53,15 +53,15 @@ public class WebConnectionConfig {
|
||||
|
||||
private String authModelId;
|
||||
private Map<String, Object> credentials;
|
||||
private boolean saveCredentials;
|
||||
private boolean sharedCredentials;
|
||||
private Boolean saveCredentials;
|
||||
private Boolean sharedCredentials;
|
||||
private Map<String, Object> mainPropertyValues;
|
||||
private Map<String, Object> expertSettingsValues;
|
||||
private Map<String, Object> providerProperties;
|
||||
private List<WebNetworkHandlerConfigInput> networkHandlersConfig;
|
||||
private DBPDriverConfigurationType configurationType;
|
||||
private String selectedSecretId;
|
||||
private boolean defaultAutoCommit;
|
||||
private Boolean defaultAutoCommit;
|
||||
private String defaultCatalogName;
|
||||
private String defaultSchemaName;
|
||||
|
||||
@@ -122,102 +122,178 @@ public class WebConnectionConfig {
|
||||
return connectionId;
|
||||
}
|
||||
|
||||
public void setConnectionId(String connectionId) {
|
||||
this.connectionId = connectionId;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getDriverId() {
|
||||
return driverId;
|
||||
}
|
||||
|
||||
public void setDriverId(String driverId) {
|
||||
this.driverId = driverId;
|
||||
}
|
||||
|
||||
@Property
|
||||
public boolean isReadOnly() {
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
public void setReadOnly(boolean readOnly) {
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getFolder() {
|
||||
return folder;
|
||||
}
|
||||
|
||||
public void setFolder(String folder) {
|
||||
this.folder = folder;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(String port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getServerName() {
|
||||
return serverName;
|
||||
}
|
||||
|
||||
public void setServerName(String serverName) {
|
||||
this.serverName = serverName;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getDatabaseName() {
|
||||
return databaseName;
|
||||
}
|
||||
|
||||
public void setDatabaseName(String databaseName) {
|
||||
this.databaseName = databaseName;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Property
|
||||
public Map<String, Object> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void setProperties(Map<String, Object> properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getUserPassword() {
|
||||
return userPassword;
|
||||
}
|
||||
|
||||
public void setUserPassword(String userPassword) {
|
||||
this.userPassword = userPassword;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getAuthModelId() {
|
||||
return authModelId;
|
||||
}
|
||||
|
||||
public void setAuthModelId(String authModelId) {
|
||||
this.authModelId = authModelId;
|
||||
}
|
||||
|
||||
@Property
|
||||
public DBPDriverConfigurationType getConfigurationType() {
|
||||
return configurationType;
|
||||
}
|
||||
|
||||
public void setConfigurationType(DBPDriverConfigurationType configurationType) {
|
||||
this.configurationType = configurationType;
|
||||
}
|
||||
|
||||
@Property
|
||||
public Map<String, Object> getCredentials() {
|
||||
return credentials;
|
||||
}
|
||||
|
||||
public void setCredentials(Map<String, Object> credentials) {
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
public List<WebNetworkHandlerConfigInput> getNetworkHandlersConfig() {
|
||||
return networkHandlersConfig;
|
||||
}
|
||||
|
||||
public void setNetworkHandlersConfig(List<WebNetworkHandlerConfigInput> networkHandlersConfig) {
|
||||
this.networkHandlersConfig = networkHandlersConfig;
|
||||
}
|
||||
|
||||
@Property
|
||||
public boolean isSaveCredentials() {
|
||||
return saveCredentials;
|
||||
}
|
||||
|
||||
public void setSaveCredentials(boolean saveCredentials) {
|
||||
this.saveCredentials = saveCredentials;
|
||||
}
|
||||
|
||||
@Property
|
||||
public boolean isSharedCredentials() {
|
||||
return sharedCredentials;
|
||||
}
|
||||
|
||||
public void setSaveCredentials(boolean saveCredentials) {
|
||||
this.saveCredentials = saveCredentials;
|
||||
public void setSharedCredentials(boolean sharedCredentials) {
|
||||
this.sharedCredentials = sharedCredentials;
|
||||
}
|
||||
|
||||
@Property
|
||||
@@ -225,38 +301,70 @@ public class WebConnectionConfig {
|
||||
return mainPropertyValues;
|
||||
}
|
||||
|
||||
public void setMainPropertyValues(Map<String, Object> mainPropertyValues) {
|
||||
this.mainPropertyValues = mainPropertyValues;
|
||||
}
|
||||
|
||||
@Property
|
||||
public Map<String, Object> getExpertSettingsValues() {
|
||||
return expertSettingsValues;
|
||||
}
|
||||
|
||||
public void setExpertSettingsValues(Map<String, Object> expertSettingsValues) {
|
||||
this.expertSettingsValues = expertSettingsValues;
|
||||
}
|
||||
|
||||
@Property
|
||||
public Map<String, Object> getProviderProperties() {
|
||||
return providerProperties;
|
||||
}
|
||||
|
||||
public void setProviderProperties(Map<String, Object> providerProperties) {
|
||||
this.providerProperties = providerProperties;
|
||||
}
|
||||
|
||||
@Property
|
||||
public Integer getKeepAliveInterval() {
|
||||
return keepAliveInterval;
|
||||
}
|
||||
|
||||
public void setKeepAliveInterval(int keepAliveInterval) {
|
||||
this.keepAliveInterval = keepAliveInterval;
|
||||
}
|
||||
|
||||
@Property
|
||||
public Boolean isDefaultAutoCommit() {
|
||||
return defaultAutoCommit;
|
||||
}
|
||||
|
||||
public void setDefaultAutoCommit(boolean defaultAutoCommit) {
|
||||
this.defaultAutoCommit = defaultAutoCommit;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSelectedSecretId() {
|
||||
return selectedSecretId;
|
||||
}
|
||||
|
||||
public void setSelectedSecretId(String selectedSecretId) {
|
||||
this.selectedSecretId = selectedSecretId;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getDefaultCatalogName() {
|
||||
return defaultCatalogName;
|
||||
}
|
||||
|
||||
public void setDefaultCatalogName(String defaultCatalogName) {
|
||||
this.defaultCatalogName = defaultCatalogName;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getDefaultSchemaName() {
|
||||
return defaultSchemaName;
|
||||
}
|
||||
|
||||
public void setDefaultSchemaName(String defaultSchemaName) {
|
||||
this.defaultSchemaName = defaultSchemaName;
|
||||
}
|
||||
}
|
||||
|
||||
+34
-3
@@ -252,6 +252,7 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public String[] getFeatures() {
|
||||
List<String> features = new ArrayList<>();
|
||||
|
||||
@@ -281,11 +282,19 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public DBNBrowseSettings getNavigatorSettings() {
|
||||
return dataSourceContainer.getNavigatorSettings();
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public DBNBrowseSettings getDefaultNavigatorSettings() {
|
||||
return dataSourceContainer.getNavigatorSettings().getOriginalSettings();
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public List<WebDataFormat> getSupportedDataFormats() {
|
||||
List<WebDataFormat> formats = new ArrayList<>();
|
||||
formats.add(WebDataFormat.resultset);
|
||||
@@ -300,11 +309,13 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public WebConnectionOriginInfo getOrigin() {
|
||||
return new WebConnectionOriginInfo(session, dataSourceContainer, dataSourceContainer.getOrigin());
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public DBPDriverConfigurationType getConfigurationType() {
|
||||
DBPDriverConfigurationType configurationType = dataSourceContainer.getConnectionConfiguration().getConfigurationType();
|
||||
if (configurationType == null) {
|
||||
@@ -330,15 +341,27 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public String getAuthModel() {
|
||||
String authModelId = dataSourceContainer.getConnectionConfiguration().getAuthModelId();
|
||||
if (CommonUtils.isEmpty(authModelId)) {
|
||||
authModelId = AuthModelDatabaseNative.ID;
|
||||
}
|
||||
|
||||
// Patch auth model
|
||||
DBPAuthModelDescriptor authModel = DBWorkbench.getPlatform().getDataSourceProviderRegistry().getAuthModel(authModelId);
|
||||
if (authModel != null) {
|
||||
DBPAuthModelDescriptor amReplace = authModel.getReplacedBy(dataSourceContainer.getDriver());
|
||||
if (amReplace != null && amReplace != authModel) {
|
||||
return amReplace.getId();
|
||||
}
|
||||
}
|
||||
|
||||
return authModelId;
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public WebPropertyInfo[] getAuthProperties() {
|
||||
String authModelId = getAuthModel();
|
||||
DBPAuthModelDescriptor authModel = DBWorkbench.getPlatform().getDataSourceProviderRegistry().getAuthModel(authModelId);
|
||||
@@ -361,6 +384,7 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public List<WebNetworkHandlerConfig> getNetworkHandlersConfig() {
|
||||
var registry = NetworkHandlerRegistry.getInstance();
|
||||
return dataSourceContainer.getConnectionConfiguration()
|
||||
@@ -375,20 +399,23 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
@Nullable
|
||||
public Map<String, Object> getCredentials() {
|
||||
//dataSourceContainer.getConnectionConfiguration().getCredentialsProvider().getCredentials();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Map<String, Object> getSavedAuthProperties() {
|
||||
return savedAuthProperties;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<WebNetworkHandlerConfigInput> getSavedNetworkCredentials() {
|
||||
return savedNetworkCredentials;
|
||||
}
|
||||
|
||||
public void setSavedCredentials(Map<String, Object> authProperties, List<WebNetworkHandlerConfigInput> networkCredentials) {
|
||||
public void setSavedCredentials(@Nullable Map<String, Object> authProperties, @Nullable List<WebNetworkHandlerConfigInput> networkCredentials) {
|
||||
this.savedAuthProperties = authProperties;
|
||||
this.savedNetworkCredentials = networkCredentials;
|
||||
}
|
||||
@@ -413,6 +440,7 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public Map<String, String> getMainPropertyValues() {
|
||||
Map<String, String> mainProperties = new LinkedHashMap<>();
|
||||
mainProperties.put(DBConstants.PROP_HOST, getHost());
|
||||
@@ -468,7 +496,7 @@ public class WebConnectionInfo {
|
||||
return dataSourceContainer.getRequiredExternalAuth();
|
||||
}
|
||||
|
||||
private boolean hasProjectPermission(RMProjectPermission projectPermission) {
|
||||
private boolean hasProjectPermission(@NotNull RMProjectPermission projectPermission) {
|
||||
DBPProject project = dataSourceContainer.getProject();
|
||||
if (!(project instanceof WebProjectImpl webProject)) {
|
||||
return false;
|
||||
@@ -508,18 +536,21 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
@Nullable
|
||||
public String getDefaultCatalogName() {
|
||||
DBPConnectionConfiguration connectionConfiguration = dataSourceContainer.getConnectionConfiguration();
|
||||
return connectionConfiguration.getBootstrap().getDefaultCatalogName();
|
||||
}
|
||||
|
||||
@Property
|
||||
@Nullable
|
||||
public String getDefaultSchemaName() {
|
||||
DBPConnectionConfiguration connectionConfiguration = dataSourceContainer.getConnectionConfiguration();
|
||||
return connectionConfiguration.getBootstrap().getDefaultSchemaName();
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public List<WebSecretInfo> getSharedSecrets() throws DBException {
|
||||
return dataSourceContainer.listSharedCredentials()
|
||||
.stream()
|
||||
@@ -527,8 +558,8 @@ public class WebConnectionInfo {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
@NotNull
|
||||
public List<String> getTools() {
|
||||
if (!session.hasPermission(RMConstants.PERMISSION_DATABASE_DEVELOPER)) {
|
||||
return List.of();
|
||||
|
||||
+6
-5
@@ -1,6 +1,7 @@
|
||||
package io.cloudbeaver.model;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.DBPObject;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriver;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriverConstants;
|
||||
@@ -52,35 +53,35 @@ public class WebExpertSettingsProperties implements DBPObject {
|
||||
|
||||
public static class KeepAliveIntervalFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
|
||||
public boolean isValidValue(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(!object.driver.isEmbedded(), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AutoCommitFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
|
||||
public boolean isValidValue(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_TRANSACTIONS), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReadOnlyFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
|
||||
public boolean isValidValue(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_READ_ONLY_MODE), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DefaultCatalogFieldVisibleValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
|
||||
public boolean isValidValue(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_CATALOG_SELECTION), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DefaultSchemaFieldVisibleValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
|
||||
public boolean isValidValue(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_SCHEMA_SELECTION), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,9 +243,11 @@ public class WebPropertyInfo {
|
||||
List<Object> result = new ArrayList<>();
|
||||
int length = Array.getLength(value);
|
||||
for (int i = 0; i < length; i++) {
|
||||
result.add(Array.get(value, i));
|
||||
result.add(makePropertyValue(Array.get(value, i)));
|
||||
}
|
||||
return result;
|
||||
} else if (value instanceof Enum<?> enumValue) {
|
||||
return new WebBasicEnumObjectInfo(enumValue.toString(), enumValue.name());
|
||||
}
|
||||
Class<?> dataType = property.getDataType();
|
||||
if (dataType == Boolean.class || dataType == Boolean.TYPE) {
|
||||
|
||||
+13
-25
@@ -20,13 +20,11 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.registry.fs.FileSystemProviderRegistry;
|
||||
import org.jkiss.dbeaver.model.fs.DBFUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
@@ -65,30 +63,20 @@ public abstract class BaseServerConfigurationController<T extends ServletServerC
|
||||
if (CommonUtils.isEmpty(workspaceLocation)) {
|
||||
return defaultWorkspaceLocation;
|
||||
}
|
||||
URI workspaceUri = URI.create(workspaceLocation);
|
||||
if (workspaceUri.getScheme() == null) {
|
||||
// default filesystem
|
||||
return getHomeDirectory().resolve(workspaceLocation);
|
||||
} else {
|
||||
var externalFsProvider =
|
||||
FileSystemProviderRegistry.getInstance().getFileSystemProviderBySchema(workspaceUri.getScheme());
|
||||
if (externalFsProvider == null) {
|
||||
log.error("File system not found for scheme: " + workspaceUri.getScheme() + " default workspace " +
|
||||
"location will be used");
|
||||
try {
|
||||
Path externalFsPath = DBFUtils.getPathFromURI(workspaceLocation);
|
||||
if (externalFsPath == null) {
|
||||
log.warn("Failed to detect workspace path from URI: " + workspaceLocation +
|
||||
" default workspace location will be used");
|
||||
return defaultWorkspaceLocation;
|
||||
}
|
||||
ClassLoader fsClassloader = externalFsProvider.getInstance().getClass().getClassLoader();
|
||||
try (FileSystem externalFileSystem = FileSystems.newFileSystem(workspaceUri,
|
||||
System.getenv(),
|
||||
fsClassloader)) {
|
||||
log.info("Path from external filesystem used for workspace: " + workspaceUri);
|
||||
return externalFileSystem.provider().getPath(workspaceUri);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to initialize workspace path: " + workspaceUri + " default workspace " +
|
||||
"location will be used", e);
|
||||
}
|
||||
log.info("Path from external filesystem used for workspace: " + externalFsPath);
|
||||
return externalFsPath;
|
||||
} catch (DBException e) {
|
||||
log.error("Failed to detect workspace path from URI: " + workspaceLocation +
|
||||
" default workspace location will be used", e);
|
||||
return defaultWorkspaceLocation;
|
||||
}
|
||||
return defaultWorkspaceLocation;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
+12
-14
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
package io.cloudbeaver.model.app;
|
||||
|
||||
import io.cloudbeaver.model.cli.CloudBeaverInstanceServer;
|
||||
import io.cloudbeaver.model.log.SLF4JLogHandler;
|
||||
import io.cloudbeaver.registry.WebFeatureRegistry;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.eclipse.equinox.app.IApplicationContext;
|
||||
import org.jkiss.code.NotNull;
|
||||
@@ -28,7 +28,6 @@ import org.jkiss.dbeaver.model.DBFileController;
|
||||
import org.jkiss.dbeaver.model.app.DBPWorkspace;
|
||||
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
|
||||
import org.jkiss.dbeaver.model.auth.SMSessionContext;
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
import org.jkiss.dbeaver.model.impl.app.ApplicationRegistry;
|
||||
import org.jkiss.dbeaver.model.impl.app.BaseApplicationImpl;
|
||||
@@ -59,7 +58,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
|
||||
private static final Log log = Log.getLog(BaseServletApplication.class);
|
||||
|
||||
private String instanceId;
|
||||
private CloudBeaverInstanceServer instanceServer;
|
||||
|
||||
@Override
|
||||
public RMController createResourceController(
|
||||
@NotNull SMCredentialsProvider credentialsProvider,
|
||||
@@ -162,6 +161,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
|
||||
* Method returns VoidSecretController instance.
|
||||
* Advanced apps may implement it differently.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public DBSSecretController getSecretController(
|
||||
@NotNull SMCredentialsProvider credentialsProvider,
|
||||
@@ -203,11 +203,6 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
|
||||
public Object start(IApplicationContext context) {
|
||||
initializeApplicationServices();
|
||||
try {
|
||||
try {
|
||||
this.instanceServer = new CloudBeaverInstanceServer();
|
||||
} catch (Exception e) {
|
||||
log.error("Error initializing instance server", e);
|
||||
}
|
||||
startServer();
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
@@ -218,6 +213,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
|
||||
|
||||
protected abstract void startServer() throws DBException;
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public synchronized String getApplicationInstanceId() throws DBException {
|
||||
if (instanceId == null) {
|
||||
@@ -242,6 +238,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
|
||||
return BaseWorkspaceImpl.readWorkspaceIdProperty();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Path getWorkspaceDirectory() {
|
||||
return getServerConfigurationController().getWorkspacePath();
|
||||
@@ -256,6 +253,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WSEventController getEventController() {
|
||||
return null;
|
||||
@@ -276,14 +274,14 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ApplicationInstanceController getInstanceServer() {
|
||||
return instanceServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnonymousAccessEnabled() {
|
||||
return getAppConfiguration().isAnonymousAccessEnabled();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebFeatureRegistry getFeatureRegistry() {
|
||||
return WebFeatureRegistry.getInstance();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -17,7 +17,6 @@
|
||||
package io.cloudbeaver.model.app;
|
||||
|
||||
import io.cloudbeaver.DBWFeatureSet;
|
||||
import io.cloudbeaver.registry.WebFeatureRegistry;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.DBConstants;
|
||||
@@ -115,7 +114,7 @@ public abstract class BaseWebAppConfiguration implements ServletAppConfiguration
|
||||
public String[] getEnabledFeatures() {
|
||||
if (enabledFeatures == null) {
|
||||
// No config - enable all features (+backward compatibility)
|
||||
return WebFeatureRegistry.getInstance().getWebFeatures()
|
||||
return ServletAppUtils.getServletApplication().getFeatureRegistry().getWebFeatures()
|
||||
.stream().map(DBWFeatureSet::getId).toArray(String[]::new);
|
||||
}
|
||||
return enabledFeatures;
|
||||
|
||||
+24
-3
@@ -16,7 +16,9 @@
|
||||
*/
|
||||
package io.cloudbeaver.model.app;
|
||||
|
||||
import io.cloudbeaver.registry.WebFeatureRegistry;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.DBFileController;
|
||||
import org.jkiss.dbeaver.model.app.DBPApplication;
|
||||
@@ -42,14 +44,19 @@ public interface ServletApplication extends DBPApplication {
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
ServletAppConfiguration getAppConfiguration();
|
||||
|
||||
@NotNull
|
||||
ServletServerConfiguration getServerConfiguration();
|
||||
|
||||
@NotNull
|
||||
Path getDataDirectory(boolean create);
|
||||
|
||||
@NotNull
|
||||
Path getWorkspaceDirectory();
|
||||
|
||||
@NotNull
|
||||
Path getHomeDirectory();
|
||||
|
||||
boolean isMultiNode();
|
||||
@@ -58,30 +65,38 @@ public interface ServletApplication extends DBPApplication {
|
||||
|
||||
SMAdminController getAdminSecurityController(@NotNull SMCredentialsProvider credentialsProvider) throws DBException;
|
||||
|
||||
@NotNull
|
||||
DBSSecretController getSecretController(
|
||||
@NotNull SMCredentialsProvider credentialsProvider,
|
||||
SMSessionContext smSessionContext
|
||||
) throws DBException;
|
||||
|
||||
@NotNull
|
||||
RMController createResourceController(
|
||||
@NotNull SMCredentialsProvider credentialsProvider,
|
||||
@NotNull DBPWorkspace workspace
|
||||
) throws DBException;
|
||||
|
||||
@NotNull
|
||||
DBFileController createFileController(@NotNull SMCredentialsProvider credentialsProvider);
|
||||
|
||||
@Nullable
|
||||
String getServerURL();
|
||||
|
||||
@NotNull
|
||||
default String getServicesURI() {
|
||||
return "/";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
default String getRootURI() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
String getApplicationInstanceId() throws DBException;
|
||||
|
||||
@NotNull
|
||||
WSEventController getEventController();
|
||||
|
||||
/**
|
||||
@@ -94,12 +109,18 @@ public interface ServletApplication extends DBPApplication {
|
||||
/**
|
||||
* Collector that contains information about system.
|
||||
*/
|
||||
@NotNull
|
||||
ServletSystemInformationCollector getSystemInformationCollector();
|
||||
@Nullable
|
||||
ServletSystemInformationCollector<?> getSystemInformationCollector();
|
||||
|
||||
default void getStatusInfo(Map<String, Object> infoMap) {
|
||||
default void getStatusInfo(@NotNull Map<String, Object> infoMap) {
|
||||
|
||||
}
|
||||
|
||||
boolean isAnonymousAccessEnabled();
|
||||
|
||||
/**
|
||||
* Web feature registry
|
||||
*/
|
||||
@NotNull
|
||||
WebFeatureRegistry getFeatureRegistry();
|
||||
}
|
||||
|
||||
+10
-1
@@ -62,6 +62,7 @@ public class ServletSystemInformationCollector<T extends ServletApplication> imp
|
||||
private DBPConnectionInformation smDatabaseInfo;
|
||||
private String workspacePath;
|
||||
private final DeploymentType deploymentType;
|
||||
private boolean internalDatabaseInfoCollected = false;
|
||||
|
||||
public ServletSystemInformationCollector(@NotNull T application) {
|
||||
this.application = application;
|
||||
@@ -158,7 +159,15 @@ public class ServletSystemInformationCollector<T extends ServletApplication> imp
|
||||
/**
|
||||
* Collects info about internal databases.
|
||||
*/
|
||||
public void collectInternalDatabaseUseInformation() throws DBException {
|
||||
public synchronized void collectInternalDatabaseUseInformation() throws DBException {
|
||||
if (internalDatabaseInfoCollected) {
|
||||
return;
|
||||
}
|
||||
collectInternalDatabaseInfo();
|
||||
this.internalDatabaseInfoCollected = true;
|
||||
}
|
||||
|
||||
protected void collectInternalDatabaseInfo() throws DBException {
|
||||
this.smDatabaseInfo = application.getAdminSecurityController(new NoAuthCredentialsProvider())
|
||||
.getInternalDatabaseInformation();
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2010-2025 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.
|
||||
@@ -28,7 +28,7 @@ import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class FSUtils {
|
||||
public class WebFSUtils {
|
||||
@NotNull
|
||||
public static String makeUniqueFsId(@NotNull DBFVirtualFileSystem fileSystem) {
|
||||
return fileSystem.getType() + "://" + fileSystem.getId();
|
||||
+11
-2
@@ -60,26 +60,31 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
|
||||
return project.getId();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getNodeType() {
|
||||
return "rm.project";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getNodeDisplayName() {
|
||||
return project.getDisplayName();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getLocalizedName(String locale) {
|
||||
public String getLocalizedName(@NotNull String locale) {
|
||||
return getNodeDisplayName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getNodeDescription() {
|
||||
return project.getDescription();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBPImage getNodeIcon() {
|
||||
return DBIcon.PROJECT;
|
||||
@@ -90,6 +95,7 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBNResourceManagerResource[] getChildren(@NotNull DBRProgressMonitor monitor) throws DBException {
|
||||
if (children == null && !monitor.isForceCacheUsage()) {
|
||||
@@ -109,14 +115,16 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
|
||||
return ((DBNResourceManagerRoot) getParentNode()).getResourceController();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Deprecated
|
||||
@Override
|
||||
public String getNodeItemPath() {
|
||||
return getParentNode().getNodeItemPath() + "/" + getName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBNNode refreshNode(DBRProgressMonitor monitor, Object source) throws DBException {
|
||||
public DBNNode refreshNode(@NotNull DBRProgressMonitor monitor, @Nullable Object source) throws DBException {
|
||||
children = null;
|
||||
return this;
|
||||
}
|
||||
@@ -141,6 +149,7 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
|
||||
return project;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return getNodeDisplayName();
|
||||
|
||||
+10
-2
@@ -49,21 +49,25 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getNodeType() {
|
||||
return "rm.resource";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getNodeDisplayName() {
|
||||
return resource.getName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getNodeDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBPImage getNodeIcon() {
|
||||
if (resource.isFolder()) {
|
||||
@@ -109,6 +113,7 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
|
||||
return resource.isFolder();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBNNode[] getChildren(@NotNull DBRProgressMonitor monitor) throws DBException {
|
||||
if (children == null && !monitor.isForceCacheUsage()) {
|
||||
@@ -154,14 +159,16 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
|
||||
throw new DBException("Can't detect resource root node");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Deprecated
|
||||
@Override
|
||||
public String getNodeItemPath() {
|
||||
return getParentNode().getNodeItemPath() + "/" + getNodeDisplayName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBNNode refreshNode(DBRProgressMonitor monitor, Object source) throws DBException {
|
||||
public DBNNode refreshNode(@NotNull DBRProgressMonitor monitor, @Nullable Object source) throws DBException {
|
||||
this.children = null;
|
||||
return super.refreshNode(monitor, source);
|
||||
}
|
||||
@@ -171,7 +178,7 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rename(DBRProgressMonitor monitor, String newName) throws DBException {
|
||||
public void rename(@NotNull DBRProgressMonitor monitor, @NotNull String newName) throws DBException {
|
||||
String resourceName = resource.getName();
|
||||
try {
|
||||
if (newName.indexOf('.') == -1) {
|
||||
@@ -192,6 +199,7 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return getNodeDisplayName();
|
||||
|
||||
+9
-1
@@ -19,6 +19,7 @@ package io.cloudbeaver.model.rm;
|
||||
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
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.DBPHiddenObject;
|
||||
@@ -49,6 +50,7 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
|
||||
return resourceController;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getNodeType() {
|
||||
return "rm";
|
||||
@@ -60,16 +62,19 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
|
||||
return "rm";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getNodeDisplayName() {
|
||||
return "resources";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getNodeDescription() {
|
||||
return "Resources";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBPImage getNodeIcon() {
|
||||
return null;
|
||||
@@ -80,6 +85,7 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBNResourceManagerProject[] getChildren(@NotNull DBRProgressMonitor monitor) throws DBException {
|
||||
if (projects == null && !monitor.isForceCacheUsage()) {
|
||||
@@ -109,6 +115,7 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
|
||||
return projects;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Deprecated
|
||||
@Override
|
||||
public String getNodeItemPath() {
|
||||
@@ -116,8 +123,9 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
|
||||
return NodePathType.ext.getPrefix() + getNodeDisplayName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBNNode refreshNode(DBRProgressMonitor monitor, Object source) throws DBException {
|
||||
public DBNNode refreshNode(@NotNull DBRProgressMonitor monitor, @Nullable Object source) throws DBException {
|
||||
projects = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
+23
-47
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
package io.cloudbeaver.model.rm.local;
|
||||
|
||||
import io.cloudbeaver.BaseWebProjectImpl;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
@@ -28,7 +27,6 @@ import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
|
||||
import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
import org.jkiss.dbeaver.model.app.DBPWorkspace;
|
||||
import org.jkiss.dbeaver.model.fs.lock.FileLockController;
|
||||
import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl;
|
||||
import org.jkiss.dbeaver.model.rm.RMController;
|
||||
import org.jkiss.dbeaver.model.rm.RMEvent;
|
||||
import org.jkiss.dbeaver.model.rm.RMEventManager;
|
||||
@@ -51,8 +49,6 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
private static final Log log = Log.getLog(BaseLocalResourceController.class);
|
||||
|
||||
public static final String DEFAULT_CHANGE_ID = "0";
|
||||
private static final String FILE_REGEX = "(?U)[\\w.$()@/\\\\ -]+";
|
||||
private static final String PROJECT_REGEX = "(?U)[\\w.$()@ -]+"; // slash not allowed in project name
|
||||
|
||||
@NotNull
|
||||
protected final DBPWorkspace workspace;
|
||||
@@ -67,6 +63,7 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
this.lockController = lockController;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public RMProject getProject(@NotNull String projectId, boolean readResources, boolean readProperties)
|
||||
throws DBException {
|
||||
@@ -85,6 +82,7 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
return project;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object getProjectProperty(@NotNull String projectId, @NotNull String propName) throws DBException {
|
||||
var project = getWebProject(projectId, false);
|
||||
@@ -97,7 +95,7 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
public void setProjectProperty(
|
||||
@NotNull String projectId,
|
||||
@NotNull String propName,
|
||||
@NotNull Object propValue
|
||||
@Nullable Object propValue
|
||||
) throws DBException {
|
||||
RMLocalProject webProject = getWebProject(projectId, false);
|
||||
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
|
||||
@@ -159,7 +157,7 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
@NotNull String configuration,
|
||||
@Nullable List<String> dataSourceIds
|
||||
) throws DBException {
|
||||
try (var lock = lockController.lock(projectId, "updateProjectDataSources")) {
|
||||
try (var ignoredLock = lockController.lock(projectId, "updateProjectDataSources")) {
|
||||
DBPProject project = getWebProject(projectId, false);
|
||||
return doFileWriteOperation(
|
||||
projectId, project.getMetadataFolder(false),
|
||||
@@ -190,7 +188,7 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
@NotNull String projectId,
|
||||
@NotNull String[] dataSourceIds
|
||||
) throws DBException {
|
||||
try (var projectLock = lockController.lock(projectId, "deleteDatasources")) {
|
||||
try (var ignoredLock = lockController.lock(projectId, "deleteDataSources")) {
|
||||
DBPProject project = getWebProject(projectId, false);
|
||||
doFileWriteOperation(projectId, project.getMetadataFolder(false), () -> {
|
||||
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
|
||||
@@ -215,7 +213,7 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
@NotNull String projectId,
|
||||
@NotNull String folderPath
|
||||
) throws DBException {
|
||||
try (var projectLock = lockController.lock(projectId, "createDatasourceFolder")) {
|
||||
try (var ignoredLock = lockController.lock(projectId, "createDatasourceFolder")) {
|
||||
DBPProject project = getWebProject(projectId, false);
|
||||
log.debug("Creating data source folder '" + folderPath + "' in project '" + projectId + "'");
|
||||
doFileWriteOperation(projectId, project.getMetadataFolder(false),
|
||||
@@ -226,8 +224,10 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
GeneralUtils.validateResourceName(newName);
|
||||
var parent = result.getParent();
|
||||
var parentFolder = parent == null ? null : registry.getFolder(parent.toString().replace("\\", "/"));
|
||||
DBPDataSourceFolder newFolder = registry.addFolder(parentFolder, newName);
|
||||
((DataSourcePersistentRegistry) registry).saveDataSources();
|
||||
registry.addFolder(parentFolder, newName);
|
||||
if (registry instanceof DataSourcePersistentRegistry pr) {
|
||||
pr.saveDataSources();
|
||||
}
|
||||
registry.checkForErrors();
|
||||
return null;
|
||||
}
|
||||
@@ -241,21 +241,19 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
@NotNull String[] folderPaths,
|
||||
boolean dropContents
|
||||
) throws DBException {
|
||||
try (var projectLock = lockController.lock(projectId, "createDatasourceFolder")) {
|
||||
try (var ignoredLock = lockController.lock(projectId, "createDatasourceFolder")) {
|
||||
DBPProject project = getWebProject(projectId, false);
|
||||
doFileWriteOperation(projectId, project.getMetadataFolder(false),
|
||||
() -> {
|
||||
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
|
||||
for (String folderPath : folderPaths) {
|
||||
DBPDataSourceFolder folder = registry.getFolder(folderPath);
|
||||
if (folder != null) {
|
||||
log.debug("Deleting data source folder '" + folderPath + "' in project '" + projectId + "'");
|
||||
registry.removeFolder(folder, dropContents);
|
||||
} else {
|
||||
log.warn("Can not find folder by path [" + folderPath + "] for deletion");
|
||||
}
|
||||
log.debug("Deleting data source folder '" + folderPath + "' in project '" + projectId + "'");
|
||||
registry.removeFolder(folder, dropContents);
|
||||
}
|
||||
if (registry instanceof DataSourcePersistentRegistry pr) {
|
||||
pr.saveDataSources();
|
||||
}
|
||||
((DataSourcePersistentRegistry) registry).saveDataSources();
|
||||
registry.checkForErrors();
|
||||
return null;
|
||||
}
|
||||
@@ -269,7 +267,7 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
@NotNull String oldPath,
|
||||
@NotNull String newPath
|
||||
) throws DBException {
|
||||
try (var projectLock = lockController.lock(projectId, "createDatasourceFolder")) {
|
||||
try (var ignoredLock = lockController.lock(projectId, "createDatasourceFolder")) {
|
||||
DBPProject project = getWebProject(projectId, false);
|
||||
log.debug("Moving data source folder from '" + oldPath + "' to '" + newPath + "' in project '" + projectId + "'");
|
||||
doFileWriteOperation(projectId, project.getMetadataFolder(false),
|
||||
@@ -284,19 +282,19 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract RMLocalProject getWebProject(String projectId, boolean refresh) throws DBException;
|
||||
protected abstract RMLocalProject getWebProject(@NotNull String projectId, boolean refresh) throws DBException;
|
||||
|
||||
protected abstract <T> T doFileWriteOperation(String projectId, Path file, RMFileOperation<T> operation)
|
||||
protected abstract <T> T doFileWriteOperation(@NotNull String projectId, @NotNull Path file, @NotNull RMFileOperation<T> operation)
|
||||
throws DBException;
|
||||
|
||||
protected abstract <T> T doFileReadOperation(String projectId, Path file, RMFileOperation<T> operation)
|
||||
protected abstract <T> T doFileReadOperation(@NotNull String projectId, @NotNull Path file, @NotNull RMFileOperation<T> operation)
|
||||
throws DBException;
|
||||
|
||||
protected abstract <T> T doProjectOperation(String projectId, RMFileOperation<T> operation) throws DBException;
|
||||
protected abstract <T> T doProjectOperation(@NotNull String projectId, @NotNull RMFileOperation<T> operation) throws DBException;
|
||||
|
||||
protected abstract RMProject makeProjectFromId(String projectId, boolean loadPermissions) throws DBException;
|
||||
protected abstract RMProject makeProjectFromId(@NotNull String projectId, boolean loadPermissions) throws DBException;
|
||||
|
||||
protected void validateResourcePath(String resourcePath) throws DBException {
|
||||
protected void validateResourcePath(@NotNull String resourcePath) throws DBException {
|
||||
var fullPath = Paths.get(resourcePath);
|
||||
for (Path path : fullPath) {
|
||||
String fileName = IOUtils.getFileNameWithoutExtension(path);
|
||||
@@ -314,28 +312,6 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
}
|
||||
}
|
||||
|
||||
protected class InternalWebProjectImpl extends BaseWebProjectImpl {
|
||||
public InternalWebProjectImpl(
|
||||
@NotNull SessionContextImpl sessionContext,
|
||||
@NotNull RMProject rmProject,
|
||||
@NotNull Path projectPath
|
||||
) {
|
||||
super(
|
||||
BaseLocalResourceController.this.workspace,
|
||||
BaseLocalResourceController.this,
|
||||
sessionContext,
|
||||
rmProject,
|
||||
projectPath
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected DBPDataSourceRegistry createDataSourceRegistry() {
|
||||
return new DataSourceRegistry(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected void fireRmResourceAddEvent(@NotNull String projectId, @NotNull String resourcePath) throws DBException {
|
||||
RMEventManager.fireEvent(
|
||||
new RMEvent(RMEvent.Action.RESOURCE_ADD,
|
||||
|
||||
+86
-62
@@ -38,7 +38,7 @@ import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
|
||||
import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNLocalFolder;
|
||||
import org.jkiss.dbeaver.model.rm.*;
|
||||
import org.jkiss.dbeaver.model.security.SMController;
|
||||
import org.jkiss.dbeaver.model.security.SMAdminController;
|
||||
import org.jkiss.dbeaver.model.security.SMObjectType;
|
||||
import org.jkiss.dbeaver.model.sql.DBQuotaException;
|
||||
import org.jkiss.dbeaver.model.websocket.event.MessageType;
|
||||
@@ -78,18 +78,19 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
private final Path userProjectsPath;
|
||||
private final Path sharedProjectsPath;
|
||||
private final String globalProjectName;
|
||||
private Supplier<SMController> smControllerSupplier;
|
||||
private Supplier<SMAdminController> smControllerSupplier;
|
||||
protected final List<RMFileOperationHandler> fileHandlers;
|
||||
|
||||
private final Map<String, RMLocalProject> projectRegistries = new LinkedHashMap<>();
|
||||
private final ProjectsMetadataInfo sharedProjectsMetadataInfo;
|
||||
|
||||
public LocalResourceController(
|
||||
DBPWorkspace workspace,
|
||||
SMCredentialsProvider credentialsProvider,
|
||||
Path rootPath,
|
||||
Path userProjectsPath,
|
||||
Path sharedProjectsPath,
|
||||
Supplier<SMController> smControllerSupplier
|
||||
@NotNull DBPWorkspace workspace,
|
||||
@NotNull SMCredentialsProvider credentialsProvider,
|
||||
@NotNull Path rootPath,
|
||||
@NotNull Path userProjectsPath,
|
||||
@NotNull Path sharedProjectsPath,
|
||||
@NotNull Supplier<SMAdminController> smControllerSupplier
|
||||
) throws DBException {
|
||||
super(workspace, new FileLockController(ServletAppUtils.getServletApplication().getApplicationInstanceId()));
|
||||
this.credentialsProvider = credentialsProvider;
|
||||
@@ -100,27 +101,34 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
|
||||
this.globalProjectName = DBWorkbench.getPlatform().getApplication().getDefaultProjectName();
|
||||
this.fileHandlers = RMFileOperationHandlersRegistry.getInstance().getFileHandlers();
|
||||
this.sharedProjectsMetadataInfo = new ProjectsMetadataInfo(sharedProjectsPath);
|
||||
}
|
||||
|
||||
protected SMController getSecurityController() {
|
||||
@NotNull
|
||||
protected SMAdminController getSecurityController() {
|
||||
return smControllerSupplier.get();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Path getGlobalProjectPath() {
|
||||
return globalProjectName == null ? null : this.rootPath.resolve(this.globalProjectName);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Path getPrivateProjectPath() {
|
||||
SMCredentials activeUserCredentials = credentialsProvider.getActiveUserCredentials();
|
||||
String userId = activeUserCredentials == null ? null : activeUserCredentials.getUserId();
|
||||
return userId == null ? null : this.userProjectsPath.resolve(userId);
|
||||
}
|
||||
|
||||
protected RMLocalProject getWebProject(String projectId, boolean refresh) throws DBException {
|
||||
protected RMLocalProject getWebProject(@NotNull String projectId, boolean refresh) throws DBException {
|
||||
synchronized (projectRegistries) {
|
||||
RMLocalProject project = projectRegistries.get(projectId);
|
||||
if (project == null || refresh) {
|
||||
project = createWebProjectImpl(projectId, new SessionContextImpl(null));
|
||||
if (project.getProjectType() == RMProjectType.SHARED) {
|
||||
project.setProjectInfo(sharedProjectsMetadataInfo.getProjectInfo(projectId));
|
||||
}
|
||||
projectRegistries.put(projectId, project);
|
||||
}
|
||||
return project;
|
||||
@@ -167,11 +175,6 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
projects.add(0, userProject);
|
||||
}
|
||||
}
|
||||
if (ServletAppUtils.getServletApplication().isMultiNode()) {
|
||||
for (RMProject rmProject : projects) {
|
||||
handleProjectOpened(rmProject.getId());
|
||||
}
|
||||
}
|
||||
|
||||
projects.sort(Comparator.comparing(RMProject::getDisplayName));
|
||||
return projects.toArray(new RMProject[0]);
|
||||
@@ -289,9 +292,6 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
try {
|
||||
log.debug("Creating project '" + rmProject.getId() + "'");
|
||||
Files.createDirectories(projectPath);
|
||||
if (ServletAppUtils.getServletApplication().isMultiNode()) {
|
||||
createResourceTypeFolders(projectPath);
|
||||
}
|
||||
fireRmProjectAddEvent(rmProject);
|
||||
return rmProject;
|
||||
} catch (IOException e) {
|
||||
@@ -299,10 +299,11 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public RMProject updateProject(@NotNull String projectId, @NotNull RMProjectInfo projectInfo) throws DBException {
|
||||
validateProjectName(projectId, projectInfo.getName());
|
||||
try (var projectLock = lockController.lock(projectId, "updateProject")) {
|
||||
try (var ignoredLock = lockController.lock(projectId, "updateProject")) {
|
||||
RMLocalProject project = getWebProject(projectId, false);
|
||||
Path targetPath = getProjectPath(projectId);
|
||||
if (!Files.exists(targetPath)) {
|
||||
@@ -312,6 +313,7 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
throw new DBException("Project '" + projectId + "' is not shared");
|
||||
}
|
||||
project.updateProject(projectInfo.getName(), projectInfo.getDescription());
|
||||
sharedProjectsMetadataInfo.updateProjectInfo(projectId, projectInfo);
|
||||
return WebRMUtils.createRmProjectFromWebProject(project);
|
||||
}
|
||||
}
|
||||
@@ -328,26 +330,28 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
|
||||
@Override
|
||||
public void deleteProject(@NotNull String projectId) throws DBException {
|
||||
try (var projectLock = lockController.lock(projectId, "deleteProject")) {
|
||||
RMProject project = makeProjectFromId(projectId, false);
|
||||
try (var ignoredLock = lockController.lock(projectId, "deleteProject")) {
|
||||
Path targetPath = getProjectPath(projectId);
|
||||
if (!Files.exists(targetPath)) {
|
||||
log.debug(MessageFormat.format("Project folder ''{0}'' is not found", projectId));
|
||||
log.error(MessageFormat.format("Project folder ''{0}'' is not found", projectId));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
log.debug("Deleting project '" + projectId + "'");
|
||||
log.debug("Deleting project '" + projectId + "' in persistence storage");
|
||||
getSecurityController().deleteObject(projectId, projectId, SMObjectType.project);
|
||||
log.debug("Deleting project '" + projectId + "' folder");
|
||||
IOUtils.deleteDirectory(targetPath);
|
||||
getSecurityController().deleteAllObjectPermissions(projectId, SMObjectType.project);
|
||||
synchronized (projectRegistries) {
|
||||
projectRegistries.remove(projectId);
|
||||
}
|
||||
sharedProjectsMetadataInfo.updateProjectInfo(projectId, null);
|
||||
} catch (IOException e) {
|
||||
throw new DBException("Error deleting project '" + project.getName() + "'", e);
|
||||
throw new DBException("Error deleting project '" + projectId + "'", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public RMProject getProject(@NotNull String projectId, boolean readResources, boolean readProperties) throws DBException {
|
||||
RMProject project = makeProjectFromId(projectId, true);
|
||||
@@ -407,6 +411,15 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
@Override
|
||||
public void deleteProjectDataSources(@NotNull String projectId, @NotNull String[] dataSourceIds) throws DBException {
|
||||
super.deleteProjectDataSources(projectId, dataSourceIds);
|
||||
|
||||
log.debug("Delete datasources '" + Arrays.toString(dataSourceIds) + "' from security controller");
|
||||
for (String dsId : dataSourceIds) {
|
||||
try {
|
||||
getSecurityController().deleteObject(projectId, dsId, SMObjectType.datasource);
|
||||
} catch (DBException e) {
|
||||
log.error("Error deleting datasource '" + dsId + "' from database");
|
||||
}
|
||||
}
|
||||
if (credentialsProvider.getActiveUserCredentials() != null && dataSourceIds.length > 0) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDataSourceEvent.delete(
|
||||
@@ -614,7 +627,8 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
}
|
||||
return doProjectOperation(projectId, () -> {
|
||||
try {
|
||||
Path folderPath = CommonUtils.isEmpty(folder) ?
|
||||
boolean projectFolder = CommonUtils.isEmpty(folder);
|
||||
Path folderPath = projectFolder ?
|
||||
projectPath :
|
||||
projectPath.resolve(folder);
|
||||
folderPath = folderPath.normalize();
|
||||
@@ -623,7 +637,7 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
throw new DBException("Invalid folder path");
|
||||
}
|
||||
createFolder(folderPath);
|
||||
return readChildResources(projectId, folderPath, nameMask, readProperties, readHistory, recursive);
|
||||
return readChildResources(projectId, folderPath, nameMask, projectFolder, readProperties, readHistory, recursive);
|
||||
} catch (NoSuchFileException e) {
|
||||
throw new DBException("Invalid resource folder " + folder);
|
||||
} catch (IOException e) {
|
||||
@@ -637,22 +651,48 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
@NotNull String projectId,
|
||||
@NotNull Path folderPath,
|
||||
@Nullable String nameMask,
|
||||
boolean projectFolder,
|
||||
boolean readProperties,
|
||||
boolean readHistory,
|
||||
boolean recursive
|
||||
) throws IOException {
|
||||
try (Stream<Path> files = Files.list(folderPath)) {
|
||||
return files.filter(path -> {
|
||||
Set<RMResource> children = files.filter(path -> {
|
||||
String fileName = path.getFileName().toString();
|
||||
return (nameMask == null || nameMask.equals(fileName)) && !fileName.startsWith(".");
|
||||
}) // skip hidden files
|
||||
.sorted(Comparator.comparing(path -> path.getFileName().toString(), String.CASE_INSENSITIVE_ORDER))
|
||||
.map((Path path) -> makeResourceFromPath(projectId, path, nameMask, readProperties, readHistory, recursive))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
if (ServletAppUtils.getServletApplication().isMultiNode() && projectFolder) {
|
||||
addResourceTypeFolders(folderPath, children);
|
||||
}
|
||||
return children.stream()
|
||||
.sorted(Comparator.comparing(RMResource::getName, String.CASE_INSENSITIVE_ORDER))
|
||||
.toArray(RMResource[]::new);
|
||||
}
|
||||
}
|
||||
|
||||
private void addResourceTypeFolders(@NotNull Path projectPath, @NotNull Set<RMResource> children) {
|
||||
Set<String> folderNames = children.stream()
|
||||
.filter(RMResource::isFolder)
|
||||
.map(RMResource::getName)
|
||||
.collect(Collectors.toSet());
|
||||
// fake resources for default resource type folders
|
||||
for (ResourceTypeDescriptor type : ResourceTypeRegistry.getInstance().getResourceTypes()) {
|
||||
String defaultRoot = type.getDefaultRoot(null);
|
||||
if (CommonUtils.isEmpty(defaultRoot)) {
|
||||
continue;
|
||||
}
|
||||
Path resourceFolder = projectPath.resolve(defaultRoot);
|
||||
if (!folderNames.contains(resourceFolder.getFileName().toString())) {
|
||||
RMResource resource = new RMResource(resourceFolder.getFileName().toString());
|
||||
resource.setFolder(true);
|
||||
children.add(resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createResource(
|
||||
@NotNull String projectId,
|
||||
@@ -954,7 +994,7 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected RMProject makeProjectFromId(String projectId, boolean loadPermissions) throws DBException {
|
||||
protected RMProject makeProjectFromId(@NotNull String projectId, boolean loadPermissions) throws DBException {
|
||||
var projectName = WebRMUtils.parseProjectName(projectId);
|
||||
var projectPath = getProjectPath(projectId);
|
||||
if (!Files.exists(projectPath)) {
|
||||
@@ -993,29 +1033,13 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
.toArray(String[]::new);
|
||||
|
||||
RMLocalProject webProject = new RMLocalProject(workspace, new SessionContextImpl(null), path, type);
|
||||
if (type == RMProjectType.SHARED) {
|
||||
webProject.setProjectInfo(sharedProjectsMetadataInfo.getProjectInfo(webProject.getId()));
|
||||
}
|
||||
return createRmProjectFromWebProject(path, webProject, allProjectPermissions);
|
||||
}
|
||||
|
||||
private void createResourceTypeFolders(Path path) {
|
||||
// FIXME: do not create folders by force!!!
|
||||
var resourceTypes = ResourceTypeRegistry.getInstance().getResourceTypes();
|
||||
for (var resourceType : resourceTypes) {
|
||||
var defaultRoot = resourceType.getDefaultRoot(null);
|
||||
if (defaultRoot == null) {
|
||||
continue;
|
||||
}
|
||||
var typeFolder = path.resolve(defaultRoot);
|
||||
try {
|
||||
if (!Files.exists(typeFolder)) {
|
||||
createFolder(typeFolder);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Resource folder " + typeFolder + " is not created", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected <T> T doProjectOperation(String projectId, RMFileOperation<T> operation) throws DBException {
|
||||
protected <T> T doProjectOperation(@NotNull String projectId, @NotNull RMFileOperation<T> operation) throws DBException {
|
||||
for (RMFileOperationHandler fileHandler : fileHandlers) {
|
||||
try {
|
||||
fileHandler.projectOpened(projectId);
|
||||
@@ -1033,7 +1057,7 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
return operation.doOperation();
|
||||
}
|
||||
|
||||
protected <T> T doFileReadOperation(String projectId, Path file, RMFileOperation<T> operation) throws DBException {
|
||||
protected <T> T doFileReadOperation(@NotNull String projectId, @NotNull Path file, @NotNull RMFileOperation<T> operation) throws DBException {
|
||||
for (RMFileOperationHandler fileHandler : fileHandlers) {
|
||||
try {
|
||||
fileHandler.beforeFileRead(projectId, file);
|
||||
@@ -1052,7 +1076,11 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
return operation.doOperation();
|
||||
}
|
||||
|
||||
protected <T> T doFileWriteOperation(String projectId, Path file, RMFileOperation<T> operation) throws DBException {
|
||||
protected <T> T doFileWriteOperation(
|
||||
@NotNull String projectId,
|
||||
@NotNull Path file,
|
||||
@NotNull RMFileOperation<T> operation
|
||||
) throws DBException {
|
||||
for (RMFileOperationHandler fileHandler : fileHandlers) {
|
||||
fileHandler.beforeFileChange(projectId, file);
|
||||
}
|
||||
@@ -1071,7 +1099,7 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
return result;
|
||||
}
|
||||
|
||||
protected Path getProjectPath(String projectId) throws DBException {
|
||||
protected Path getProjectPath(@NotNull String projectId) throws DBException {
|
||||
RMProjectName project = WebRMUtils.parseProjectName(projectId);
|
||||
RMProjectType type = project.getType();
|
||||
String projectName = project.getName();
|
||||
@@ -1162,7 +1190,7 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
|
||||
if (recursive && resource.isFolder()) {
|
||||
try {
|
||||
resource.setChildren(readChildResources(projectId, path, nameMask, readProperties, readHistory, true));
|
||||
resource.setChildren(readChildResources(projectId, path, nameMask, false, readProperties, readHistory, true));
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
}
|
||||
@@ -1176,14 +1204,10 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
return getProjectPath(projectId).toAbsolutePath().relativize(path).toString().replace('\\', IPath.SEPARATOR);
|
||||
}
|
||||
|
||||
protected void handleProjectOpened(String projectId) throws DBException {
|
||||
createResourceTypeFolders(getProjectPath(projectId));
|
||||
}
|
||||
|
||||
public static Builder builder(
|
||||
SMCredentialsProvider credentialsProvider,
|
||||
DBPWorkspace workspace,
|
||||
Supplier<SMController> smControllerSupplier
|
||||
Supplier<SMAdminController> smControllerSupplier
|
||||
) {
|
||||
return new Builder(workspace, credentialsProvider, smControllerSupplier);
|
||||
}
|
||||
@@ -1195,7 +1219,7 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
|
||||
public static class Builder {
|
||||
protected final SMCredentialsProvider credentialsProvider;
|
||||
protected final Supplier<SMController> smController;
|
||||
protected final Supplier<SMAdminController> smController;
|
||||
protected final DBPWorkspace workspace;
|
||||
|
||||
protected Path rootPath;
|
||||
@@ -1204,7 +1228,7 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
|
||||
protected Builder(
|
||||
DBPWorkspace workspace, SMCredentialsProvider credentialsProvider,
|
||||
Supplier<SMController> smControllerSupplier
|
||||
Supplier<SMAdminController> smControllerSupplier
|
||||
) {
|
||||
this.workspace = workspace;
|
||||
this.credentialsProvider = credentialsProvider;
|
||||
@@ -1271,7 +1295,7 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
// Resource types
|
||||
project.setResourceTypes(ResourceTypeRegistry.getInstance().getResourceTypes()
|
||||
.stream()
|
||||
.filter(ResourceTypeDescriptor::isManagable)
|
||||
.filter(ResourceTypeDescriptor::isManageable)
|
||||
.map(RMResourceType::new)
|
||||
.toArray(RMResourceType[]::new));
|
||||
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 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.rm.local;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
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.app.DBPProject;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
import org.jkiss.dbeaver.model.fs.lock.FileLockController;
|
||||
import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
|
||||
import org.jkiss.dbeaver.model.rm.RMProjectInfo;
|
||||
import org.jkiss.dbeaver.model.rm.RMProjectType;
|
||||
import org.jkiss.dbeaver.model.rm.RMUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ProjectsMetadataInfo {
|
||||
private static final Log log = Log.getLog(ProjectsMetadataInfo.class);
|
||||
private static final String PROJECTS_INFO_FILE_NAME = "projects-info.json";
|
||||
|
||||
private final Map<String, RMProjectInfo> projectsInfo = new LinkedHashMap<>();
|
||||
private final Path projectsPath;
|
||||
private final FileLockController lockController;
|
||||
|
||||
|
||||
public ProjectsMetadataInfo(@NotNull Path projectsPath) throws DBException {
|
||||
this.projectsPath = projectsPath;
|
||||
this.lockController = new FileLockController(ServletAppUtils.getServletApplication().getApplicationInstanceId());
|
||||
readProjectInfos(projectsPath);
|
||||
}
|
||||
|
||||
public RMProjectInfo getProjectInfo(@NotNull String projectId) {
|
||||
return projectsInfo.computeIfAbsent(projectId, key -> new RMProjectInfo());
|
||||
}
|
||||
|
||||
public void updateProjectInfo(@NotNull String projectId, @Nullable RMProjectInfo projectInfo) {
|
||||
if (projectInfo != null) {
|
||||
projectsInfo.put(projectId, projectInfo);
|
||||
} else {
|
||||
projectsInfo.remove(projectId);
|
||||
}
|
||||
saveProjectsInfo();
|
||||
}
|
||||
|
||||
private void readProjectInfos(@NotNull Path path) {
|
||||
if (!Files.exists(path)) {
|
||||
return;
|
||||
}
|
||||
Path projectInfoFile = path.resolve(PROJECTS_INFO_FILE_NAME);
|
||||
if (!Files.exists(projectInfoFile)) {
|
||||
loadAndMigrateProjectDataToCommonFile(path);
|
||||
saveProjectsInfo();
|
||||
return;
|
||||
}
|
||||
readProjectInfosFromFile(projectInfoFile);
|
||||
}
|
||||
|
||||
private void readProjectInfosFromFile(@NotNull Path projectInfoFile) {
|
||||
try {
|
||||
log.info("Reading project metadata information");
|
||||
String content = Files.readString(projectInfoFile, StandardCharsets.UTF_8);
|
||||
Map<String, RMProjectInfo> loaded = JSONUtils.GSON.fromJson(
|
||||
content,
|
||||
TypeToken.getParameterized(Map.class, String.class, RMProjectInfo.class).getType()
|
||||
);
|
||||
projectsInfo.clear();
|
||||
if (loaded != null) {
|
||||
projectsInfo.putAll(loaded);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Error reading existing " + PROJECTS_INFO_FILE_NAME, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAndMigrateProjectDataToCommonFile(@NotNull Path path) {
|
||||
log.info("Migrating shared project information to the common place");
|
||||
Map<String, RMProjectInfo> infos = new LinkedHashMap<>();
|
||||
try (Stream<Path> stream = Files.list(path)) {
|
||||
stream.filter(Files::isDirectory).forEach(projectDir -> {
|
||||
RMProjectInfo info = getProjectInfoFromProjectSettings(projectDir);
|
||||
String projectId = RMUtils.makeProjectIdFromPath(projectDir, RMProjectType.SHARED);
|
||||
infos.put(projectId, info);
|
||||
});
|
||||
} catch (IOException e) {
|
||||
log.error("Error listing shared projects path", e);
|
||||
}
|
||||
log.info("Migration for project information completed");
|
||||
projectsInfo.clear();
|
||||
projectsInfo.putAll(infos);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private RMProjectInfo getProjectInfoFromProjectSettings(@NotNull Path projectPath) {
|
||||
String name = null;
|
||||
String description = null;
|
||||
|
||||
Path settings = projectPath.resolve(DBPProject.METADATA_FOLDER).resolve(BaseProjectImpl.SETTINGS_STORAGE_FILE);
|
||||
if (Files.exists(settings) && Files.isRegularFile(settings)) {
|
||||
try {
|
||||
String json = Files.readString(settings, StandardCharsets.UTF_8);
|
||||
Map<String, Object> map = JSONUtils.GSON.fromJson(json, JSONUtils.MAP_TYPE_TOKEN);
|
||||
name = JSONUtils.getString(map, BaseProjectImpl.PROP_PROJECT_NAME);
|
||||
description = JSONUtils.getString(map, BaseProjectImpl.PROP_PROJECT_DESCRIPTION);
|
||||
} catch (IOException e) {
|
||||
log.warn("Failed to read project settings for " + projectPath + ": " + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to parse project settings for " + projectPath + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
RMProjectInfo info = new RMProjectInfo();
|
||||
info.setName(CommonUtils.isEmpty(name) ? projectPath.getFileName().toString() : name);
|
||||
info.setDescription(description);
|
||||
return info;
|
||||
}
|
||||
|
||||
private void saveProjectsInfo() {
|
||||
try (var lock = lockController.lock(PROJECTS_INFO_FILE_NAME, "saveProjectsInfo")) {
|
||||
log.info("Saving project information");
|
||||
Files.writeString(projectsPath.resolve(PROJECTS_INFO_FILE_NAME), JSONUtils.GSON.toJson(projectsInfo));
|
||||
} catch (IOException e) {
|
||||
log.error("Error writing " + PROJECTS_INFO_FILE_NAME, e);
|
||||
} catch (DBException e) {
|
||||
log.error("Error locking file " + PROJECTS_INFO_FILE_NAME + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
-80
@@ -18,24 +18,21 @@ package io.cloudbeaver.model.rm.local;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.app.DBPWorkspace;
|
||||
import org.jkiss.dbeaver.model.auth.SMSessionContext;
|
||||
import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
|
||||
import org.jkiss.dbeaver.model.rm.RMProjectInfo;
|
||||
import org.jkiss.dbeaver.model.rm.RMProjectType;
|
||||
import org.jkiss.dbeaver.model.rm.RMUtils;
|
||||
import org.jkiss.dbeaver.registry.DataSourceRegistry;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
import org.jkiss.utils.Pair;
|
||||
import org.jkiss.dbeaver.registry.project.LocalProjectImpl;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
|
||||
public class RMLocalProject extends BaseProjectImpl {
|
||||
@NotNull
|
||||
private final Path projectPath;
|
||||
public class RMLocalProject extends LocalProjectImpl {
|
||||
@NotNull
|
||||
private final RMProjectType projectType;
|
||||
@Nullable
|
||||
private RMProjectInfo projectInfo;
|
||||
|
||||
public RMLocalProject(
|
||||
@NotNull DBPWorkspace workspace,
|
||||
@@ -43,12 +40,10 @@ public class RMLocalProject extends BaseProjectImpl {
|
||||
@NotNull Path projectPath,
|
||||
@NotNull RMProjectType type
|
||||
) {
|
||||
super(workspace, sessionContext);
|
||||
this.projectPath = projectPath;
|
||||
super(workspace, sessionContext, projectPath);
|
||||
this.projectType = type;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return true;
|
||||
@@ -60,88 +55,41 @@ public class RMLocalProject extends BaseProjectImpl {
|
||||
return RMUtils.makeProjectIdFromPath(projectPath, projectType);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Nullable
|
||||
@Override
|
||||
public String getName() {
|
||||
Object projectName = this.getProjectProperty(PROP_PROJECT_NAME);
|
||||
if (projectName != null) {
|
||||
return projectName.toString();
|
||||
public String getDescription() {
|
||||
if (projectInfo == null) {
|
||||
return null;
|
||||
}
|
||||
return projectPath.getFileName().toString();
|
||||
return projectInfo.getDescription();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Path getAbsolutePath() {
|
||||
return projectPath;
|
||||
public String getName() {
|
||||
if (projectInfo == null || projectInfo.getName() == null) {
|
||||
return projectPath.getFileName().toString();
|
||||
}
|
||||
return projectInfo.getName();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public RMProjectType getProjectType() {
|
||||
return projectType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return true;
|
||||
public void updateProject(@Nullable String newName, @Nullable String description) throws DBException {
|
||||
this.projectInfo = new RMProjectInfo();
|
||||
projectInfo.setName(newName);
|
||||
projectInfo.setDescription(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ensureOpen() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUseSecretStorage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Path getMetadataFilePath() {
|
||||
return getMetadataPath().resolve(METADATA_STORAGE_FILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for Bulk Update of resources properties paths
|
||||
*
|
||||
* @param oldToNewPaths collection of OldPath to NewPath pairs
|
||||
*/
|
||||
public void moveResourcePropertiesBatch(@NotNull Collection<Pair<String, String>> oldToNewPaths) {
|
||||
loadMetadata();
|
||||
synchronized (metadataSync) {
|
||||
for (var pathsPair : oldToNewPaths) {
|
||||
final var oldResourcePath = CommonUtils.normalizeResourcePath(pathsPair.getFirst());
|
||||
final var newResourcePath = CommonUtils.normalizeResourcePath(pathsPair.getSecond());
|
||||
final var resProps = resourceProperties.remove(oldResourcePath);
|
||||
if (resProps != null) {
|
||||
resourceProperties.put(newResourcePath, resProps);
|
||||
}
|
||||
}
|
||||
}
|
||||
flushMetadata();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for Bulk Remove of resources properties
|
||||
*/
|
||||
public boolean resetResourcesPropertiesBatch(@NotNull Collection<String> resourcesPaths) {
|
||||
loadMetadata();
|
||||
boolean propertiesChanged = false;
|
||||
synchronized (metadataSync) {
|
||||
for (var resourcePath : resourcesPaths) {
|
||||
var removedProperties = resourceProperties.remove(CommonUtils.normalizeResourcePath(resourcePath));
|
||||
if (removedProperties != null) {
|
||||
propertiesChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (propertiesChanged) {
|
||||
flushMetadata();
|
||||
}
|
||||
return propertiesChanged;
|
||||
public void setProjectInfo(@NotNull RMProjectInfo projectInfo) {
|
||||
this.projectInfo = projectInfo;
|
||||
}
|
||||
|
||||
public boolean canUpdateProjectName() {
|
||||
return RMProjectType.SHARED.equals(projectType);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected DBPDataSourceRegistry createDataSourceRegistry() {
|
||||
return new DataSourceRegistry<>(this);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -29,8 +29,8 @@ import org.jkiss.dbeaver.model.auth.SMAuthSpace;
|
||||
import org.jkiss.dbeaver.model.auth.SMSessionContext;
|
||||
import org.jkiss.dbeaver.model.auth.impl.AbstractSessionPersistent;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.dbeaver.model.security.user.SMTeam;
|
||||
import org.jkiss.dbeaver.model.rm.RMProjectInfo;
|
||||
import org.jkiss.dbeaver.model.security.user.SMTeam;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSEventDeleteTempFile;
|
||||
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionExpiredEvent;
|
||||
@@ -254,8 +254,12 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
@Property
|
||||
public long getRemainingTime() {
|
||||
if (application instanceof ServletAuthApplication authApplication) {
|
||||
return authApplication.getMaxSessionIdleTime() + lastAccessTime - System.currentTimeMillis();
|
||||
return getMaxSessionIdleTime(authApplication) + lastAccessTime - System.currentTimeMillis();
|
||||
}
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
protected long getMaxSessionIdleTime(@NotNull ServletAuthApplication authApplication) {
|
||||
return authApplication.getMaxSessionIdleTime();
|
||||
}
|
||||
}
|
||||
|
||||
+10
-7
@@ -41,6 +41,7 @@ public class WebAuthInfo implements SMSessionPrincipal, WebUserAuthToken {
|
||||
|
||||
private final WebSession session;
|
||||
private final WebUser user;
|
||||
@NotNull
|
||||
private final WebAuthProviderDescriptor authProvider;
|
||||
private String authProviderConfigurationId;
|
||||
@NotNull
|
||||
@@ -71,6 +72,7 @@ public class WebAuthInfo implements SMSessionPrincipal, WebUserAuthToken {
|
||||
this.loginTime = loginTime;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public OffsetDateTime getLoginTime() {
|
||||
return loginTime;
|
||||
@@ -91,16 +93,19 @@ public class WebAuthInfo implements SMSessionPrincipal, WebUserAuthToken {
|
||||
return new WebUserOriginInfo(session, user, authProvider);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String getUserId() {
|
||||
return userIdentity.getId();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String getDisplayName() {
|
||||
return userIdentity.getDisplayName();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String getAuthProvider() {
|
||||
return authProvider.getId();
|
||||
@@ -138,13 +143,11 @@ public class WebAuthInfo implements SMSessionPrincipal, WebUserAuthToken {
|
||||
}
|
||||
|
||||
void closeAuth() {
|
||||
if (authProvider != null && authSession != null) {
|
||||
try {
|
||||
SMAuthProvider authProviderInstance = this.authProvider.getInstance();
|
||||
authProviderInstance.closeSession(session, authSession);
|
||||
} catch (Exception e) {
|
||||
log.error(e);
|
||||
}
|
||||
try {
|
||||
SMAuthProvider authProviderInstance = this.authProvider.getInstance();
|
||||
authProviderInstance.closeSession(session, authSession);
|
||||
} catch (Exception e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -51,6 +51,7 @@ public class WebHttpRequestInfo {
|
||||
this.lastRemoteUserAgent = lastRemoteUserAgent;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
+104
-62
@@ -17,10 +17,7 @@
|
||||
package io.cloudbeaver.model.session;
|
||||
|
||||
import io.cloudbeaver.*;
|
||||
import io.cloudbeaver.model.CustomCancelableJob;
|
||||
import io.cloudbeaver.model.WebAsyncTaskInfo;
|
||||
import io.cloudbeaver.model.WebConnectionInfo;
|
||||
import io.cloudbeaver.model.WebServerMessage;
|
||||
import io.cloudbeaver.model.*;
|
||||
import io.cloudbeaver.model.app.ServletApplication;
|
||||
import io.cloudbeaver.model.app.ServletAuthApplication;
|
||||
import io.cloudbeaver.model.session.monitor.TaskProgressMonitor;
|
||||
@@ -30,7 +27,6 @@ import io.cloudbeaver.service.sql.WebSQLConstants;
|
||||
import io.cloudbeaver.utils.CBModelConstants;
|
||||
import io.cloudbeaver.utils.WebDataSourceUtils;
|
||||
import io.cloudbeaver.utils.WebEventUtils;
|
||||
import org.eclipse.core.runtime.IAdaptable;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.jkiss.code.NotNull;
|
||||
@@ -38,6 +34,7 @@ import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBFileController;
|
||||
import org.jkiss.dbeaver.model.DBPAdaptable;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.DBPEventListener;
|
||||
import org.jkiss.dbeaver.model.access.DBAAuthCredentials;
|
||||
@@ -45,7 +42,6 @@ import org.jkiss.dbeaver.model.access.DBACredentialsProvider;
|
||||
import org.jkiss.dbeaver.model.auth.*;
|
||||
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
|
||||
import org.jkiss.dbeaver.model.exec.DBCException;
|
||||
import org.jkiss.dbeaver.model.fs.DBFFileSystemManager;
|
||||
import org.jkiss.dbeaver.model.meta.Association;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNModel;
|
||||
@@ -61,13 +57,15 @@ import org.jkiss.dbeaver.model.websocket.event.MessageType;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSSessionLogUpdatedEvent;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
import org.jkiss.utils.function.ThrowableConsumer;
|
||||
import org.jkiss.utils.function.ThrowableFunction;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
/**
|
||||
* Web session.
|
||||
@@ -75,16 +73,16 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
//TODO: split to authenticated and non authenticated context
|
||||
public class WebSession extends BaseWebSession
|
||||
implements SMSessionWithAuth, SMCredentialsProvider, DBACredentialsProvider, IAdaptable {
|
||||
implements SMSessionWithAuth, SMCredentialsProvider, DBACredentialsProvider, DBPAdaptable {
|
||||
|
||||
private static final Log log = Log.getLog(WebSession.class);
|
||||
|
||||
public static final SMSessionType CB_SESSION_TYPE = new SMSessionType("CloudBeaver");
|
||||
private static final String WEB_SESSION_AUTH_CONTEXT_TYPE = "web-session";
|
||||
private static final String ATTR_LOCALE = "locale";
|
||||
private static final AtomicInteger TASK_ID = new AtomicInteger();
|
||||
|
||||
public static String RUNTIME_PARAM_AUTH_INFOS = "auth-infos";
|
||||
public static String RUNTIME_PARAM_CLIENT_ORIGIN = "client-origin";
|
||||
private final AtomicInteger taskCount = new AtomicInteger();
|
||||
|
||||
private final String lastRemoteAddr;
|
||||
@@ -92,27 +90,29 @@ public class WebSession extends BaseWebSession
|
||||
|
||||
private String locale;
|
||||
private boolean cacheExpired;
|
||||
private String clientOrigin;
|
||||
|
||||
protected WebSessionGlobalProjectImpl globalProject;
|
||||
private final List<WebServerMessage> sessionMessages = new ArrayList<>();
|
||||
|
||||
private final Map<String, WebAsyncTaskInfo> asyncTasks = new HashMap<>();
|
||||
private final Map<String, Function<Object, Object>> attributeDisposers = new HashMap<>();
|
||||
private final Map<String, ThrowableConsumer<Object, Exception>> attributeDisposers = new HashMap<>();
|
||||
|
||||
// Map of auth tokens. Key is authentication provider
|
||||
private final List<WebAuthInfo> authTokens = new ArrayList<>();
|
||||
|
||||
private DBNModel navigatorModel;
|
||||
private final DBRProgressMonitor progressMonitor = new SessionProgressMonitor();
|
||||
private final Map<String, DBWSessionHandler> sessionHandlers;
|
||||
private final Map<String, DBWSessionHandler<WebSession>> sessionHandlers;
|
||||
private final WebDataSourceConnectEventListener connectListener = new WebDataSourceConnectEventListener(this);
|
||||
|
||||
public WebSession(
|
||||
@NotNull WebHttpRequestInfo requestInfo,
|
||||
@NotNull ServletAuthApplication application,
|
||||
@NotNull Map<String, DBWSessionHandler> sessionHandlers
|
||||
@NotNull Map<String, DBWSessionHandler<WebSession>> sessionHandlers
|
||||
) throws DBException {
|
||||
this(requestInfo.getId(),
|
||||
this(
|
||||
Objects.requireNonNull(requestInfo.getId()),
|
||||
CommonUtils.toString(requestInfo.getLocale()),
|
||||
application,
|
||||
sessionHandlers,
|
||||
@@ -125,8 +125,8 @@ public class WebSession extends BaseWebSession
|
||||
@NotNull String id,
|
||||
@Nullable String locale,
|
||||
@NotNull ServletApplication application,
|
||||
@NotNull Map<String, DBWSessionHandler> sessionHandlers,
|
||||
@NotNull String remoteAddr
|
||||
@NotNull Map<String, DBWSessionHandler<WebSession>> sessionHandlers,
|
||||
@Nullable String remoteAddr
|
||||
) throws DBException {
|
||||
super(id, application);
|
||||
if (CommonUtils.isEmpty(remoteAddr)) {
|
||||
@@ -151,7 +151,7 @@ public class WebSession extends BaseWebSession
|
||||
if (authTokens.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return authTokens.get(0);
|
||||
return authTokens.getFirst();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,23 +254,33 @@ public class WebSession extends BaseWebSession
|
||||
}
|
||||
|
||||
// Note: for admin use only
|
||||
public synchronized void resetUserState() throws DBException {
|
||||
public synchronized void resetUserState(boolean needResetUserCache) throws DBException {
|
||||
clearAuthTokens();
|
||||
try {
|
||||
resetSessionCache();
|
||||
} catch (DBCException e) {
|
||||
addSessionError(e);
|
||||
log.error(e);
|
||||
if (needResetUserCache) {
|
||||
try {
|
||||
resetSessionCache();
|
||||
} catch (DBCException e) {
|
||||
addSessionError(e);
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
refreshUserData();
|
||||
clearSessionContext();
|
||||
}
|
||||
|
||||
public synchronized void resetUserState() throws DBException {
|
||||
resetUserState(true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DBPEventListener getDataSourceConnectListener() {
|
||||
return connectListener;
|
||||
}
|
||||
|
||||
public void updateClientOrigin(@NotNull String originFromRequest) {
|
||||
this.clientOrigin = originFromRequest;
|
||||
}
|
||||
|
||||
private void initNavigatorModel() {
|
||||
|
||||
// Cleanup current data
|
||||
@@ -309,7 +319,7 @@ public class WebSession extends BaseWebSession
|
||||
anonymousProject.setInMemory(true);
|
||||
}
|
||||
if (workspace.getActiveProject() == null && !workspace.getProjects().isEmpty()) {
|
||||
workspace.setActiveProject(workspace.getProjects().get(0));
|
||||
workspace.setActiveProject(workspace.getProjects().getFirst());
|
||||
}
|
||||
} catch (DBException e) {
|
||||
addSessionError(e);
|
||||
@@ -324,7 +334,6 @@ public class WebSession extends BaseWebSession
|
||||
} else {
|
||||
sessionProject = createSessionProject(project);
|
||||
}
|
||||
sessionProject.refreshProjectSettings();
|
||||
// do not load data sources for anonymous project
|
||||
if (project.getType() == RMProjectType.USER && userContext.getUser() == null) {
|
||||
sessionProject.setInMemory(true);
|
||||
@@ -333,10 +342,6 @@ public class WebSession extends BaseWebSession
|
||||
if (!project.isShared() || application.isConfigurationMode()) {
|
||||
getWorkspace().setActiveProject(sessionProject);
|
||||
}
|
||||
log.info(String.format(
|
||||
"Project created: [ID=%s, Name=%s, Type=%s, Creator=%s]",
|
||||
project.getId(), project.getName(), project.getType(), project.getCreator()
|
||||
));
|
||||
return sessionProject;
|
||||
}
|
||||
|
||||
@@ -358,9 +363,14 @@ public class WebSession extends BaseWebSession
|
||||
private void resetSessionCache() throws DBCException {
|
||||
// Clear attributes
|
||||
synchronized (attributes) {
|
||||
for (Map.Entry<String, Function<Object, Object>> attrDisposer : attributeDisposers.entrySet()) {
|
||||
for (Map.Entry<String, ThrowableConsumer<Object, Exception>> attrDisposer : attributeDisposers.entrySet()) {
|
||||
Object attrValue = attributes.get(attrDisposer.getKey());
|
||||
attrDisposer.getValue().apply(attrValue);
|
||||
|
||||
try {
|
||||
attrDisposer.getValue().accept(attrValue);
|
||||
} catch (Exception e) {
|
||||
log.error("Error disposing attribute '" + attrDisposer.getKey() + "'", e);
|
||||
}
|
||||
}
|
||||
attributeDisposers.clear();
|
||||
// Remove all non-persistent attributes
|
||||
@@ -440,7 +450,7 @@ public class WebSession extends BaseWebSession
|
||||
}
|
||||
|
||||
public synchronized void updateInfo(boolean isOldHttpSessionUsed) {
|
||||
log.debug("Update session lifetime " + getSessionId() + " for user " + getUserId());
|
||||
log.trace("Update session lifetime " + getSessionId() + " for user " + getUserId());
|
||||
touchSession();
|
||||
if (isOldHttpSessionUsed) {
|
||||
try {
|
||||
@@ -559,6 +569,10 @@ public class WebSession extends BaseWebSession
|
||||
if (job instanceof CustomCancelableJob cancelableJob) {
|
||||
cancelableJob.cancelJob(this, taskInfo);
|
||||
}
|
||||
CompletableFuture<?> future = getAttribute(getTaskConfirmationAttributeName(taskId));
|
||||
if (future != null) {
|
||||
future.cancel(false);
|
||||
}
|
||||
if (job != null) {
|
||||
job.cancel();
|
||||
}
|
||||
@@ -568,8 +582,7 @@ public class WebSession extends BaseWebSession
|
||||
|
||||
public WebAsyncTaskInfo createAsyncTask(@NotNull String taskName) {
|
||||
int taskId = TASK_ID.incrementAndGet();
|
||||
WebAsyncTaskInfo asyncTask = getAsyncTask(String.valueOf(taskId), taskName, true);
|
||||
return asyncTask;
|
||||
return getAsyncTask(String.valueOf(taskId), taskName, true);
|
||||
}
|
||||
|
||||
public List<WebAsyncTaskInfo> findTasksByJob(@NotNull Class<? extends AbstractJob> jobClass) {
|
||||
@@ -586,10 +599,14 @@ public class WebSession extends BaseWebSession
|
||||
|
||||
public WebAsyncTaskInfo createAndRunAsyncTask(@NotNull String taskName, @NotNull WebAsyncTaskProcessor<?> runnable) {
|
||||
WebAsyncTaskInfo asyncTask = createAsyncTask(taskName);
|
||||
return runAsyncTask(asyncTask, runnable);
|
||||
}
|
||||
|
||||
AbstractJob job = new AbstractJob(taskName) {
|
||||
public WebAsyncTaskInfo runAsyncTask(@NotNull WebAsyncTaskInfo asyncTask, @NotNull WebAsyncTaskProcessor<?> runnable) {
|
||||
AbstractJob job = new AbstractCancelableJob(asyncTask.getName()) {
|
||||
@NotNull
|
||||
@Override
|
||||
protected IStatus run(DBRProgressMonitor monitor) {
|
||||
protected IStatus run(@NotNull DBRProgressMonitor monitor) {
|
||||
int curTaskCount = taskCount.incrementAndGet();
|
||||
|
||||
DBRProgressMonitor taskMonitor = new TaskProgressMonitor(monitor, WebSession.this, asyncTask);
|
||||
@@ -684,7 +701,11 @@ public class WebSession extends BaseWebSession
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T getAttribute(String name, Function<T, T> creator, Function<T, T> disposer) {
|
||||
public <T, E extends Exception> T getAttribute(
|
||||
String name,
|
||||
ThrowableFunction<String, T, E> creator,
|
||||
ThrowableConsumer<T, E> disposer
|
||||
) throws E {
|
||||
synchronized (attributes) {
|
||||
Object value = attributes.get(name);
|
||||
if (value instanceof PersistentAttribute persistentAttribute) {
|
||||
@@ -695,7 +716,7 @@ public class WebSession extends BaseWebSession
|
||||
if (value != null) {
|
||||
attributes.put(name, value);
|
||||
if (disposer != null) {
|
||||
attributeDisposers.put(name, (Function<Object, Object>) disposer);
|
||||
attributeDisposers.put(name, (ThrowableConsumer<Object, Exception>) disposer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -720,7 +741,7 @@ public class WebSession extends BaseWebSession
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return authTokens.isEmpty() ? null : authTokens.get(0);
|
||||
return authTokens.isEmpty() ? null : authTokens.getFirst();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -763,10 +784,7 @@ public class WebSession extends BaseWebSession
|
||||
if (oldAuthInfo != null) {
|
||||
removeAuthInfo(oldAuthInfo);
|
||||
}
|
||||
SMSession authSession = authInfo.getAuthSession();
|
||||
if (authSession != null) {
|
||||
getSessionContext().addSession(authSession);
|
||||
}
|
||||
getSessionContext().addSession(authInfo.getAuthSession());
|
||||
}
|
||||
synchronized (authTokens) {
|
||||
Collections.addAll(authTokens, tokens);
|
||||
@@ -793,7 +811,7 @@ public class WebSession extends BaseWebSession
|
||||
}
|
||||
}
|
||||
|
||||
public List<WebAuthInfo> removeAuthInfo(String providerId, boolean needResetUserState) throws DBException {
|
||||
public List<WebAuthInfo> removeAuthInfo(String providerId) throws DBException {
|
||||
List<WebAuthInfo> oldInfo;
|
||||
if (providerId == null) {
|
||||
oldInfo = clearAuthTokens();
|
||||
@@ -806,16 +824,12 @@ public class WebSession extends BaseWebSession
|
||||
oldInfo = List.of();
|
||||
}
|
||||
}
|
||||
if (authTokens.isEmpty() && needResetUserState) {
|
||||
if (authTokens.isEmpty()) {
|
||||
resetUserState();
|
||||
}
|
||||
return oldInfo;
|
||||
}
|
||||
|
||||
public List<WebAuthInfo> removeAuthInfo(String providerId) throws DBException {
|
||||
return removeAuthInfo(providerId, true);
|
||||
}
|
||||
|
||||
public List<DBACredentialsProvider> getContextCredentialsProviders() {
|
||||
return getAdapters(DBACredentialsProvider.class);
|
||||
}
|
||||
@@ -834,6 +848,7 @@ public class WebSession extends BaseWebSession
|
||||
contextCredentialsProvider.provideAuthParameters(monitor, dataSourceContainer, configuration);
|
||||
}
|
||||
configuration.setRuntimeAttribute(RUNTIME_PARAM_AUTH_INFOS, getAllAuthInfo());
|
||||
configuration.setRuntimeAttribute(RUNTIME_PARAM_CLIENT_ORIGIN, this.clientOrigin);
|
||||
|
||||
WebSessionProjectImpl project = getProjectById(dataSourceContainer.getProject().getId());
|
||||
if (project != null) {
|
||||
@@ -846,7 +861,7 @@ public class WebSession extends BaseWebSession
|
||||
// uncommented because we had the problem with non-native auth models
|
||||
// (for example, can't connect to DynamoDB if credentials are not saved)
|
||||
DBAAuthCredentials credentials = configuration.getAuthModel().loadCredentials(dataSourceContainer, configuration);
|
||||
WebDataSourceUtils.updateCredentialsFromProperties(credentials, configuration.getAuthProperties());
|
||||
WebDataSourceUtils.updateCredentialsFromProperties(this.progressMonitor, credentials, configuration.getAuthProperties());
|
||||
|
||||
configuration.getAuthModel().provideCredentials(dataSourceContainer, configuration, credentials);
|
||||
} catch (DBException e) {
|
||||
@@ -864,7 +879,7 @@ public class WebSession extends BaseWebSession
|
||||
|
||||
// May be called to extract auth information from session
|
||||
@Override
|
||||
public <T> T getAdapter(Class<T> adapter) {
|
||||
public <T> T getAdapter(@NotNull Class<T> adapter) {
|
||||
synchronized (authTokens) {
|
||||
for (WebAuthInfo authInfo : authTokens) {
|
||||
if (isAuthInfoInstanceOf(authInfo, adapter)) {
|
||||
@@ -886,7 +901,7 @@ public class WebSession extends BaseWebSession
|
||||
}
|
||||
|
||||
private <T> boolean isAuthInfoInstanceOf(WebAuthInfo authInfo, Class<T> adapter) {
|
||||
if (authInfo != null && authInfo.getAuthSession() != null) {
|
||||
if (authInfo != null) {
|
||||
return adapter.isInstance(authInfo.getAuthSession());
|
||||
}
|
||||
return false;
|
||||
@@ -1004,15 +1019,6 @@ public class WebSession extends BaseWebSession
|
||||
deleteSessionProject(project);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DBFFileSystemManager getFileSystemManager(String projectId) throws DBException {
|
||||
var project = getProjectById(projectId);
|
||||
if (project == null) {
|
||||
throw new DBException("Project not found: " + projectId);
|
||||
}
|
||||
return project.getFileSystemManager();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public WebSessionPreferenceStore getUserPreferenceStore() {
|
||||
return getUserContext().getPreferenceStore();
|
||||
@@ -1023,14 +1029,50 @@ public class WebSession extends BaseWebSession
|
||||
return globalProject;
|
||||
}
|
||||
|
||||
public void handleTaskConfirmation(
|
||||
@NotNull String taskId,
|
||||
boolean confirmed,
|
||||
boolean skipConfirmations
|
||||
) {
|
||||
String attributeName = getTaskConfirmationAttributeName(taskId);
|
||||
if (confirmed && skipConfirmations) {
|
||||
setAttribute(WebSQLConstants.SKIP_TASK_CONFIRMATIONS_ATTR, Boolean.TRUE);
|
||||
}
|
||||
|
||||
CompletableFuture<Boolean> confirmationFuture = getAttribute(attributeName);
|
||||
if (confirmationFuture != null) {
|
||||
confirmationFuture.complete(confirmed);
|
||||
removeAttribute(attributeName);
|
||||
} else {
|
||||
log.error("Received unexpected confirmation event for taskId: " + taskId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void handleTaskConfirmationWithParameters(@NotNull String taskId, @NotNull Map<String, Object> parameters) {
|
||||
String attributeName = getTaskConfirmationAttributeName(taskId);
|
||||
CompletableFuture<Map<String, Object>> confirmationFuture = getAttribute(attributeName);
|
||||
if (confirmationFuture != null) {
|
||||
confirmationFuture.complete(parameters);
|
||||
removeAttribute(attributeName);
|
||||
} else {
|
||||
log.error("Received unexpected confirmation event for taskId: " + taskId);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getTaskConfirmationAttributeName(@NotNull String taskId) {
|
||||
return WebSQLConstants.TASK_CONFIRMATION_ATTR_PREFIX + taskId;
|
||||
}
|
||||
|
||||
private class SessionProgressMonitor extends BaseProgressMonitor {
|
||||
@Override
|
||||
public void beginTask(String name, int totalWork) {
|
||||
public void beginTask(@NotNull String name, int totalWork) {
|
||||
addInfoMessage(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subTask(String name) {
|
||||
public void subTask(@NotNull String name) {
|
||||
addInfoMessage(name);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ public class WebSessionAuthProcessor {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (resetUserStateOnError) {
|
||||
webSession.resetUserState();
|
||||
webSession.resetUserState(false);
|
||||
}
|
||||
throw new DBException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
+10
@@ -20,6 +20,7 @@ import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.impl.preferences.AbstractUserPreferenceStore;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
@@ -85,4 +86,13 @@ public class WebSessionPreferenceStore extends AbstractUserPreferenceStore {
|
||||
public void save() throws IOException {
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
public boolean getUserPreferenceBoolean(@NotNull String name, boolean fallbackValue) {
|
||||
String value = CommonUtils.toString(userPreferences.get(name));
|
||||
if (value.isEmpty()) {
|
||||
return fallbackValue;
|
||||
} else {
|
||||
return toBoolean(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@ public class WebSessionWorkspace implements DBPWorkspace {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public WebSessionProjectImpl getProjectById(@NotNull String projectId) {
|
||||
public WebSessionProjectImpl getProjectById(@Nullable String projectId) {
|
||||
if (projectId == null) {
|
||||
return activeProject;
|
||||
}
|
||||
|
||||
+13
-4
@@ -63,11 +63,14 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
private SMAdminController adminSecurityController;
|
||||
private DBSSecretController secretController;
|
||||
private RMController rmController;
|
||||
private DBFileController fileController;
|
||||
private Set<String> accessibleProjectIds = new HashSet<>();
|
||||
private final DBFileController fileController;
|
||||
private final Set<String> accessibleProjectIds = new HashSet<>();
|
||||
private final WebSessionPreferenceStore preferenceStore;
|
||||
|
||||
public WebUserContext(ServletApplication application, DBPWorkspace workspace) throws DBException {
|
||||
public WebUserContext(
|
||||
@NotNull ServletApplication application,
|
||||
@NotNull DBPWorkspace workspace
|
||||
) throws DBException {
|
||||
this.application = application;
|
||||
this.workspace = workspace;
|
||||
this.securityController = application.createSecurityController(this);
|
||||
@@ -84,7 +87,7 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
* @return - true if context changed
|
||||
* @throws DBException - if user already authorized and new token come from another user
|
||||
*/
|
||||
public synchronized boolean refresh(SMAuthInfo smAuthInfo) throws DBException {
|
||||
public synchronized boolean refresh(@NotNull SMAuthInfo smAuthInfo) throws DBException {
|
||||
if (smAuthInfo.getAuthPermissions() == null && !isAuthorizedInSecurityManager()) {
|
||||
throw new DBCException("Required information about session permissions is missing");
|
||||
}
|
||||
@@ -190,6 +193,10 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
return isAuthorizedInSecurityManager() && getUser() != null;
|
||||
}
|
||||
|
||||
public synchronized boolean isAnonymousUserAuthorizedInSM() {
|
||||
return isAuthorizedInSecurityManager() && getUser() == null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public synchronized WebUser getUser() {
|
||||
return user;
|
||||
@@ -238,10 +245,12 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
return smSessionId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Set<String> getDefaultPermissions() {
|
||||
return application.isAnonymousAccessEnabled() ? null : Set.of();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public RMController getRmController() {
|
||||
return rmController;
|
||||
}
|
||||
|
||||
+2
-2
@@ -40,14 +40,14 @@ public class TaskProgressMonitor extends ProxyProgressMonitor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginTask(String name, int totalWork) {
|
||||
public void beginTask(@NotNull String name, int totalWork) {
|
||||
super.beginTask(name, totalWork);
|
||||
asyncTask.setStatus(name);
|
||||
WebEventUtils.sendAsyncTaskEvent(webSession, asyncTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subTask(String name) {
|
||||
public void subTask(@NotNull String name) {
|
||||
super.subTask(name);
|
||||
asyncTask.setStatus(name);
|
||||
WebEventUtils.sendAsyncTaskEvent(webSession, asyncTask);
|
||||
|
||||
+33
-3
@@ -17,12 +17,18 @@
|
||||
package io.cloudbeaver.registry;
|
||||
|
||||
import org.eclipse.core.runtime.IConfigurationElement;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.DBPConditionalProperty;
|
||||
import org.jkiss.dbeaver.model.impl.LocalizedPropertyDescriptor;
|
||||
import org.jkiss.dbeaver.utils.RuntimeUtils;
|
||||
import org.osgi.framework.Bundle;
|
||||
|
||||
public class WebAuthProviderProperty extends LocalizedPropertyDescriptor implements DBPConditionalProperty {
|
||||
|
||||
private static final String WEB_AUTH_PROPERTY_PREFIX = "prop.auth.model.";
|
||||
|
||||
private final String[] requiredFeatures;
|
||||
@Nullable
|
||||
private final String type;
|
||||
@@ -30,6 +36,7 @@ public class WebAuthProviderProperty extends LocalizedPropertyDescriptor impleme
|
||||
private final String authProviderId;
|
||||
private final String hideExpr;
|
||||
private final String readOnlyExpr;
|
||||
private final Bundle bundle;
|
||||
|
||||
public WebAuthProviderProperty(String category, IConfigurationElement config, String authProviderId) {
|
||||
super(category, config);
|
||||
@@ -39,6 +46,7 @@ public class WebAuthProviderProperty extends LocalizedPropertyDescriptor impleme
|
||||
this.type = config.getAttribute("type");
|
||||
this.hideExpr = config.getAttribute("hideExpr");
|
||||
this.readOnlyExpr = config.getAttribute("readOnlyExpr");
|
||||
this.bundle = extractBundle(config);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -63,12 +71,34 @@ public class WebAuthProviderProperty extends LocalizedPropertyDescriptor impleme
|
||||
return readOnlyExpr;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getPropertyId() {
|
||||
public String getLocalizedName(@NotNull String locale) {
|
||||
return RuntimeUtils.getBundleLocalization(bundle, locale).getString(getPropertyId());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getLocalizedDescription(@NotNull String locale) {
|
||||
return RuntimeUtils.getBundleLocalization(bundle, locale).getString(getPropertyId() + "." + ATTR_DESCRIPTION);
|
||||
}
|
||||
|
||||
private String getPropertyId() {
|
||||
if (authProviderId != null) {
|
||||
return "prop.auth.model." + authProviderId + "." + this.getId();
|
||||
return WEB_AUTH_PROPERTY_PREFIX + authProviderId + "." + this.getId();
|
||||
} else {
|
||||
return "prop.auth.model." + this.getId();
|
||||
return WEB_AUTH_PROPERTY_PREFIX + this.getId();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Bundle extractBundle(@NotNull IConfigurationElement config) {
|
||||
final Bundle bundle;
|
||||
String bundleName = config.getContributor().getName();
|
||||
bundle = Platform.getBundle(bundleName);
|
||||
if (bundle == null) {
|
||||
throw new IllegalStateException("Bundle '" + bundleName + "' not found");
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -38,8 +38,7 @@ public class WebFeatureDescriptor extends AbstractContextDescriptor implements D
|
||||
private final DBPImage icon;
|
||||
private final boolean enabledByDefault;
|
||||
|
||||
public WebFeatureDescriptor(IConfigurationElement config)
|
||||
{
|
||||
public WebFeatureDescriptor(@NotNull IConfigurationElement config) {
|
||||
super(config);
|
||||
this.id = config.getAttribute("id");
|
||||
this.label = config.getAttribute("label");
|
||||
@@ -77,4 +76,7 @@ public class WebFeatureDescriptor extends AbstractContextDescriptor implements D
|
||||
return enabledByDefault;
|
||||
}
|
||||
|
||||
public boolean isApplicable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+15
-8
@@ -20,10 +20,12 @@ import io.cloudbeaver.DBWFeatureSet;
|
||||
import org.eclipse.core.runtime.IConfigurationElement;
|
||||
import org.eclipse.core.runtime.IExtensionRegistry;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class WebFeatureRegistry {
|
||||
|
||||
@@ -33,7 +35,7 @@ public class WebFeatureRegistry {
|
||||
|
||||
private static WebFeatureRegistry instance = null;
|
||||
|
||||
public synchronized static WebFeatureRegistry getInstance() {
|
||||
public static synchronized WebFeatureRegistry getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new WebFeatureRegistry();
|
||||
instance.loadExtensions(Platform.getExtensionRegistry());
|
||||
@@ -41,23 +43,28 @@ public class WebFeatureRegistry {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private final List<DBWFeatureSet> webFeatures = new ArrayList<>();
|
||||
private final List<WebFeatureDescriptor> webFeatures = new ArrayList<>();
|
||||
|
||||
private WebFeatureRegistry() {
|
||||
}
|
||||
|
||||
private void loadExtensions(IExtensionRegistry registry) {
|
||||
protected void loadExtensions(@NotNull IExtensionRegistry registry) {
|
||||
IConfigurationElement[] extConfigs = registry.getConfigurationElementsFor(WebFeatureDescriptor.EXTENSION_ID);
|
||||
for (IConfigurationElement ext : extConfigs) {
|
||||
if (TAG_FEATURE.equals(ext.getName())) {
|
||||
this.webFeatures.add(
|
||||
new WebFeatureDescriptor(ext));
|
||||
this.webFeatures.add(createFeatureDescriptor(ext));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected WebFeatureDescriptor createFeatureDescriptor(@NotNull IConfigurationElement ext) {
|
||||
return new WebFeatureDescriptor(ext);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<DBWFeatureSet> getWebFeatures() {
|
||||
return webFeatures;
|
||||
return webFeatures.stream()
|
||||
.filter(WebFeatureDescriptor::isApplicable)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-3
@@ -41,7 +41,7 @@ public abstract class BaseServletPlatform extends BasePlatformImpl {
|
||||
if (tempFolder == null) {
|
||||
synchronized (this) {
|
||||
if (tempFolder == null) {
|
||||
initTempFolder(monitor);
|
||||
initTempFolder();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,11 +56,11 @@ public abstract class BaseServletPlatform extends BasePlatformImpl {
|
||||
return folder;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public abstract ServletApplication getApplication();
|
||||
|
||||
private void initTempFolder(@NotNull DBRProgressMonitor monitor) {
|
||||
private void initTempFolder() {
|
||||
// Make temp folder
|
||||
monitor.subTask("Create temp folder");
|
||||
String sysTempFolder = System.getProperty(StandardConstants.ENV_TMP_DIR);
|
||||
if (CommonUtils.isNotEmpty(sysTempFolder)) {
|
||||
tempFolder = Path.of(sysTempFolder).resolve(BASE_TEMP_DIR).resolve(DBWConstants.WORK_DATA_FOLDER_NAME);
|
||||
@@ -68,9 +68,11 @@ public abstract class BaseServletPlatform extends BasePlatformImpl {
|
||||
//we do not use workspace because it can be in external file system
|
||||
tempFolder = getApplication().getHomeDirectory().resolve(DBWConstants.WORK_DATA_FOLDER_NAME);
|
||||
}
|
||||
log.debug("Temp folder: " + tempFolder);
|
||||
}
|
||||
|
||||
public synchronized void dispose() {
|
||||
super.dispose();
|
||||
// Remove temp folder
|
||||
if (tempFolder != null) {
|
||||
|
||||
|
||||
+3
@@ -45,4 +45,7 @@ public class WebSQLConstants {
|
||||
public static final String ATTR_SRID = "srid";
|
||||
public static final String ATTR_PROPERTIES = "properties";
|
||||
|
||||
public static final String TASK_CONFIRMATION_ATTR_PREFIX = "session_task_confirmation_";
|
||||
public static final String SKIP_TASK_CONFIRMATIONS_ATTR = "skip_session_task_confirmations";
|
||||
public static final int TASK_CONFIRMATION_TIMEOUT_SECONDS = 60;
|
||||
}
|
||||
|
||||
+12
@@ -21,6 +21,7 @@ import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.DBPDataSource;
|
||||
import org.jkiss.dbeaver.model.DBUtils;
|
||||
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
|
||||
import org.jkiss.dbeaver.model.data.DBDDataFilter;
|
||||
import org.jkiss.dbeaver.model.data.DBDRowIdentifier;
|
||||
import org.jkiss.dbeaver.model.exec.trace.DBCTrace;
|
||||
import org.jkiss.dbeaver.model.struct.*;
|
||||
@@ -42,6 +43,8 @@ public class WebSQLResultsInfo {
|
||||
// TODO: find a way to remove isSingleRow and use virtual keys for reading BLOB and string cell values.
|
||||
private boolean isSingleRow;
|
||||
private DBCTrace trace;
|
||||
@Nullable
|
||||
private DBDDataFilter dataFilter;
|
||||
private String queryText;
|
||||
|
||||
public WebSQLResultsInfo(@NotNull DBSDataContainer dataContainer, @NotNull String id) {
|
||||
@@ -151,4 +154,13 @@ public class WebSQLResultsInfo {
|
||||
public void setSingleRow(boolean singleRow) {
|
||||
isSingleRow = singleRow;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public DBDDataFilter getDataFilter() {
|
||||
return dataFilter;
|
||||
}
|
||||
|
||||
public void setDataFilter(@Nullable DBDDataFilter dataFilter) {
|
||||
this.dataFilter = dataFilter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,18 +18,19 @@ package io.cloudbeaver.test;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import io.cloudbeaver.server.CBConstants;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
import org.jkiss.dbeaver.utils.GeneralUtils;
|
||||
|
||||
import java.net.CookieManager;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -56,10 +57,13 @@ public class WebGQLClient {
|
||||
@NotNull
|
||||
private final HttpClient httpClient;
|
||||
@NotNull
|
||||
private final CookieManager cookieManager;
|
||||
@NotNull
|
||||
private final String apiUrl;
|
||||
|
||||
public WebGQLClient(@NotNull HttpClient httpClient, @NotNull String apiUrl) {
|
||||
public WebGQLClient(@NotNull HttpClient httpClient, @NotNull CookieManager cookieManager, @NotNull String apiUrl) {
|
||||
this.httpClient = httpClient;
|
||||
this.cookieManager = cookieManager;
|
||||
this.apiUrl = apiUrl;
|
||||
}
|
||||
|
||||
@@ -70,9 +74,18 @@ public class WebGQLClient {
|
||||
* @param variables GraphQL query variables
|
||||
* @return GraphQL response
|
||||
*/
|
||||
@NotNull
|
||||
@Nullable
|
||||
public <T> T sendQuery(@NotNull String query, @Nullable Map<String, Object> variables) throws Exception {
|
||||
return sendQueryWithHeaders(query, variables, List.of());
|
||||
return sendQueryWithHeaders(query, variables, Map.of());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<String, Object> sendQueryAndGetHeaders(
|
||||
@NotNull String query,
|
||||
@Nullable Map<String, Object> variables,
|
||||
@NotNull Map<String, String> requestHeaders
|
||||
) throws Exception {
|
||||
return executeGQLRequest(query, variables, requestHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,15 +100,26 @@ public class WebGQLClient {
|
||||
public <T> T sendQueryWithHeaders(
|
||||
@NotNull String query,
|
||||
@Nullable Map<String, Object> variables,
|
||||
@NotNull List<String> headers
|
||||
@NotNull Map<String, String> headers
|
||||
) throws Exception {
|
||||
Map<String, Object> parsed = executeGQLRequest(query, variables, headers);
|
||||
// graphql response will be in "data" key
|
||||
return (T) parsed.get("data");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Map<String, Object> executeGQLRequest(
|
||||
@NotNull String query,
|
||||
@Nullable Map<String, Object> variables,
|
||||
@NotNull Map<String, String> requestHeaders
|
||||
) throws Exception {
|
||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(apiUrl))
|
||||
.POST(HttpRequest.BodyPublishers.ofString(makeGQLRequest(query, variables)))
|
||||
.setHeader("TE-Client-Version", GeneralUtils.getMajorVersion())
|
||||
.header("Content-Type", "application/json");
|
||||
if (!headers.isEmpty()) {
|
||||
requestBuilder.headers(headers.toArray(String[]::new));
|
||||
if (!requestHeaders.isEmpty()) {
|
||||
requestHeaders.forEach(requestBuilder::header);
|
||||
}
|
||||
HttpRequest request = requestBuilder.build();
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
@@ -107,8 +131,10 @@ public class WebGQLClient {
|
||||
String message = JSONUtils.getString(JSONUtils.getObjectList(body, "errors").get(0), "message");
|
||||
throw new DBException(message);
|
||||
}
|
||||
// graphql response will be in "data" key
|
||||
return (T) JSONUtils.getObject(body, "data").get("result");
|
||||
Map<String, Object> parsed = new LinkedHashMap<>();
|
||||
parsed.put("data", JSONUtils.getObject(body, "data").get("result"));
|
||||
parsed.put("headers", response.headers().map());
|
||||
return parsed;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -121,4 +147,13 @@ public class WebGQLClient {
|
||||
|
||||
return gson.toJson(request, Map.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getSessionIdCookie() {
|
||||
return cookieManager.getCookieStore().getCookies().stream()
|
||||
.filter(cookie -> cookie.getName().equals(CBConstants.CB_SESSION_COOKIE_NAME))
|
||||
.findFirst()
|
||||
.get()
|
||||
.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
+15
-19
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2010-2026 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.
|
||||
@@ -16,10 +16,6 @@
|
||||
*/
|
||||
package io.cloudbeaver.utils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.InstanceCreator;
|
||||
import com.google.gson.Strictness;
|
||||
import io.cloudbeaver.DBWConstants;
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.WebSessionProjectImpl;
|
||||
@@ -34,7 +30,6 @@ import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBConstants;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.access.DBAAuthCredentials;
|
||||
import org.jkiss.dbeaver.model.access.DBAAuthCredentialsWithComplexProperties;
|
||||
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
|
||||
import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
|
||||
@@ -43,10 +38,12 @@ import org.jkiss.dbeaver.model.connection.DBPDriver;
|
||||
import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNativeCredentials;
|
||||
import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration;
|
||||
import org.jkiss.dbeaver.model.net.ssh.SSHConstants;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceDisconnectEvent;
|
||||
import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor;
|
||||
import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.dbeaver.utils.PropertySerializationUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.*;
|
||||
@@ -211,31 +208,30 @@ public class WebDataSourceUtils {
|
||||
return webSession.getAccessibleProjectById(projectId).getWebConnectionInfo(connectionId);
|
||||
}
|
||||
|
||||
|
||||
public static void updateCredentialsFromProperties(@NotNull DBAAuthCredentials credentials, @NotNull Map<String, ?> properties) {
|
||||
InstanceCreator<DBAAuthCredentials> credTypeAdapter = type -> credentials;
|
||||
Gson credGson = new GsonBuilder()
|
||||
.setStrictness(Strictness.LENIENT)
|
||||
.registerTypeAdapter(credentials.getClass(), credTypeAdapter)
|
||||
.create();
|
||||
|
||||
if (credentials instanceof DBAAuthCredentialsWithComplexProperties complexProperties) {
|
||||
complexProperties.updateCredentialsFromComplexProperties(properties);
|
||||
public static void updateCredentialsFromProperties(
|
||||
@NotNull DBRProgressMonitor progressMonitor,
|
||||
@NotNull DBAAuthCredentials credentials,
|
||||
@Nullable Map<String, ?> properties
|
||||
) {
|
||||
if (properties == null) {
|
||||
return;
|
||||
}
|
||||
credGson.fromJson(credGson.toJsonTree(properties), credentials.getClass());
|
||||
PropertySerializationUtils.updateCredentialsFromProperties(progressMonitor, credentials, properties);
|
||||
}
|
||||
|
||||
public static void saveAuthProperties(
|
||||
@NotNull DBRProgressMonitor progressMonitor,
|
||||
@NotNull DBPDataSourceContainer dataSourceContainer,
|
||||
@NotNull DBPConnectionConfiguration configuration,
|
||||
@Nullable Map<String, Object> authProperties,
|
||||
boolean saveCredentials,
|
||||
boolean sharedCredentials
|
||||
) {
|
||||
saveAuthProperties(dataSourceContainer, configuration, authProperties, saveCredentials, sharedCredentials, false);
|
||||
saveAuthProperties(progressMonitor, dataSourceContainer, configuration, authProperties, saveCredentials, sharedCredentials, false);
|
||||
}
|
||||
|
||||
public static void saveAuthProperties(
|
||||
@NotNull DBRProgressMonitor progressMonitor,
|
||||
@NotNull DBPDataSourceContainer dataSourceContainer,
|
||||
@NotNull DBPConnectionConfiguration configuration,
|
||||
@Nullable Map<String, Object> authProperties,
|
||||
@@ -271,7 +267,7 @@ public class WebDataSourceUtils {
|
||||
configuration.setAuthProperties(currentAuthProps);
|
||||
}
|
||||
if (!authProperties.isEmpty()) {
|
||||
updateCredentialsFromProperties(credentials, authProperties);
|
||||
updateCredentialsFromProperties(progressMonitor, credentials, authProperties);
|
||||
}
|
||||
|
||||
configuration.getAuthModel().saveCredentials(dataSourceContainer, configuration, credentials);
|
||||
|
||||
@@ -18,6 +18,7 @@ package io.cloudbeaver.utils;
|
||||
|
||||
import io.cloudbeaver.model.WebAsyncTaskInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.websocket.event.task.WSSessionTaskInfoEvent;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
@@ -30,7 +31,6 @@ import org.jkiss.dbeaver.model.websocket.event.datasource.WSDatasourceFolderEven
|
||||
import org.jkiss.dbeaver.model.websocket.event.permissions.WSSubjectPermissionEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.resource.WSResourceProperty;
|
||||
import org.jkiss.dbeaver.model.websocket.event.resource.WSResourceUpdatedEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionTaskInfoEvent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -17,8 +17,11 @@
|
||||
|
||||
package io.cloudbeaver.utils;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.utils.GeneralUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
@@ -39,4 +42,30 @@ public class WebTestUtils {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setPrivateField(
|
||||
@NotNull Object target,
|
||||
@NotNull String fieldName,
|
||||
@Nullable Object value
|
||||
) throws Exception {
|
||||
Class<?> cls = target.getClass();
|
||||
Field f = null;
|
||||
while (cls != null) {
|
||||
Field[] fields = cls.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if (field.getName().equals(fieldName)) {
|
||||
f = field;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cls = cls.getSuperclass();
|
||||
}
|
||||
if (f == null) {
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
boolean isAccessible = f.canAccess(target);
|
||||
f.setAccessible(true);
|
||||
f.set(target, value);
|
||||
f.setAccessible(isAccessible);
|
||||
}
|
||||
}
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.websocket.event.client;
|
||||
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
|
||||
|
||||
public class WSSessionPingClientEvent extends WSClientEvent {
|
||||
public static final String ID = "cb_client_session_ping";
|
||||
|
||||
public WSSessionPingClientEvent(@Nullable String topicId) {
|
||||
super(ID, topicId);
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 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.websocket.event.client;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.websocket.WSConstants;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
|
||||
|
||||
public class WSSessionTaskConfirmationEvent extends WSClientEvent {
|
||||
public static final String ID = "cb_client_session_task_confirmation";
|
||||
|
||||
private final String taskId;
|
||||
private final boolean confirmed;
|
||||
private final boolean skipConfirmations;
|
||||
|
||||
public WSSessionTaskConfirmationEvent(
|
||||
@NotNull String taskId,
|
||||
boolean confirmed,
|
||||
boolean skipConfirmations
|
||||
) {
|
||||
super(ID, WSConstants.TOPIC_SESSION_TASK);
|
||||
this.taskId = taskId;
|
||||
this.confirmed = confirmed;
|
||||
this.skipConfirmations = skipConfirmations;
|
||||
}
|
||||
|
||||
public String getTaskId() {
|
||||
return taskId;
|
||||
}
|
||||
|
||||
public boolean isConfirmed() {
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
public boolean isSkipConfirmations() {
|
||||
return skipConfirmations;
|
||||
}
|
||||
}
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 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.websocket.event.client;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.websocket.WSConstants;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class WSSessionTaskWithParametersConfirmationEvent extends WSClientEvent {
|
||||
public static final String ID = "cb_client_session_task_with_parameters_confirmation";
|
||||
@NotNull
|
||||
private final String taskId;
|
||||
@NotNull
|
||||
private final Map<String, Object> parameters;
|
||||
|
||||
protected WSSessionTaskWithParametersConfirmationEvent(@NotNull String taskId, @NotNull Map<String, Object> parameters) {
|
||||
super(ID, WSConstants.TOPIC_SESSION_TASK);
|
||||
this.taskId = taskId;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getTaskId() {
|
||||
return taskId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<String, Object> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.websocket.event.client;
|
||||
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
|
||||
|
||||
/**
|
||||
* Subscribe on event topic
|
||||
*/
|
||||
public class WSSubscribeOnTopicClientEvent extends WSClientEvent {
|
||||
public static final String ID = "cb_client_topic_subscribe";
|
||||
|
||||
public WSSubscribeOnTopicClientEvent(String topicId) {
|
||||
super(ID, topicId);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.websocket.event.client;
|
||||
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
|
||||
|
||||
/**
|
||||
* Unsubscribe from event topic
|
||||
*/
|
||||
public class WSUnsubscribeFromTopicClientEvent extends WSClientEvent {
|
||||
public static final String ID = "cb_client_topic_unsubscribe";
|
||||
|
||||
public WSUnsubscribeFromTopicClientEvent(String topicId) {
|
||||
super(ID, topicId);
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.websocket.event.client;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.websocket.WSConstants;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Subscribe on event topic
|
||||
*/
|
||||
public class WSUpdateActiveProjectsClientEvent extends WSClientEvent {
|
||||
public static final String ID = "cb_client_projects_active";
|
||||
|
||||
@NotNull
|
||||
private final Set<String> projectIds;
|
||||
|
||||
public WSUpdateActiveProjectsClientEvent(@NotNull Set<String> projectIds) {
|
||||
super(ID, WSConstants.TOPIC_PROJECTS);
|
||||
this.projectIds = projectIds;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<String> getProjectIds() {
|
||||
return projectIds;
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 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.websocket.event.task;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.websocket.WSConstants;
|
||||
import org.jkiss.dbeaver.model.websocket.event.session.WSAbstractSessionEvent;
|
||||
|
||||
public abstract class WSAbstractSessionTaskEvent extends WSAbstractSessionEvent {
|
||||
@NotNull
|
||||
private final String taskId;
|
||||
@NotNull
|
||||
private final String title;
|
||||
@NotNull
|
||||
private final String message;
|
||||
|
||||
public WSAbstractSessionTaskEvent(
|
||||
@NotNull String eventId,
|
||||
@NotNull String taskId,
|
||||
@NotNull String title,
|
||||
@NotNull String message
|
||||
) {
|
||||
super(eventId, WSConstants.TOPIC_SESSION_TASK);
|
||||
this.taskId = taskId;
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getTaskId() {
|
||||
return taskId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 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.websocket.event.task;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
|
||||
public class WSSessionTaskConfirmationRequestEvent extends WSAbstractSessionTaskEvent {
|
||||
private static final String ID = "cb_session_task_confirmation_request";
|
||||
|
||||
public WSSessionTaskConfirmationRequestEvent(
|
||||
@NotNull String taskId,
|
||||
@NotNull String title,
|
||||
@NotNull String message
|
||||
) {
|
||||
super(ID, taskId, title, message);
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 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.websocket.event.task;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.websocket.WSConstants;
|
||||
import org.jkiss.dbeaver.model.websocket.event.session.WSAbstractSessionEvent;
|
||||
|
||||
/**
|
||||
* Async task info event.
|
||||
*/
|
||||
public class WSSessionTaskInfoEvent extends WSAbstractSessionEvent {
|
||||
@NotNull
|
||||
private final String taskId;
|
||||
@Nullable
|
||||
private final String statusName;
|
||||
private final boolean running;
|
||||
|
||||
public WSSessionTaskInfoEvent(
|
||||
@NotNull String taskId,
|
||||
@Nullable String statusName,
|
||||
boolean running
|
||||
) {
|
||||
super("cb_session_task_info_updated", WSConstants.TOPIC_SESSION_TASK);
|
||||
this.taskId = taskId;
|
||||
this.statusName = statusName;
|
||||
this.running = running;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getTaskId() {
|
||||
return taskId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getStatusName() {
|
||||
return statusName;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 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.websocket.event.task;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
|
||||
public class WSSessionTaskQueryConfirmationRequestEvent extends WSAbstractSessionTaskEvent {
|
||||
private static final String ID = "cb_session_task_query_confirmation_request";
|
||||
|
||||
@NotNull
|
||||
private final String query;
|
||||
|
||||
public WSSessionTaskQueryConfirmationRequestEvent(
|
||||
@NotNull String taskId,
|
||||
@NotNull String title,
|
||||
@NotNull String message,
|
||||
@NotNull String query
|
||||
) {
|
||||
super(ID, taskId, title, message);
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 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.websocket.event.task;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class WSSessionTaskQueryParamsConfirmationEvent extends WSAbstractSessionTaskEvent {
|
||||
private static final String ID = "cb_session_task_query_params_confirmation_request";
|
||||
@NotNull
|
||||
private final String query;
|
||||
@NotNull
|
||||
private final Map<String, Object> parameters;
|
||||
|
||||
public WSSessionTaskQueryParamsConfirmationEvent(
|
||||
@NotNull String taskId,
|
||||
@NotNull String title,
|
||||
@NotNull String message,
|
||||
@NotNull String query,
|
||||
@NotNull Map<String, Object> parameters
|
||||
) {
|
||||
super(ID, taskId, title, message);
|
||||
this.query = query;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<String, Object> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
@@ -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: 25.2.2.qualifier
|
||||
Bundle-Release-Date: 20251006
|
||||
Bundle-Version: 25.3.4.qualifier
|
||||
Bundle-Release-Date: 20260202
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: io.cloudbeaver.server.ce
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.product.ce</artifactId>
|
||||
<version>25.2.2-SNAPSHOT</version>
|
||||
<version>25.3.4-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -2,8 +2,8 @@ Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: Cloudbeaver Base JDBC drivers
|
||||
Bundle-SymbolicName: io.cloudbeaver.resources.drivers.base;singleton:=true
|
||||
Bundle-Version: 1.0.131.qualifier
|
||||
Bundle-Release-Date: 20251006
|
||||
Bundle-Version: 1.0.139.qualifier
|
||||
Bundle-Release-Date: 20260202
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<driver id="sqlite:libsql_jdbc"/>
|
||||
<driver id="sqlserver:microsoft"/>
|
||||
<driver id="generic:trino_jdbc"/>
|
||||
<driver id="generic:duckdb_jdbc"/>
|
||||
<driver id="duckdb:duckdb_jdbc"/>
|
||||
<driver id="generic:kyuubi_hive"/>
|
||||
<driver id="databend:databend"/>
|
||||
</extension>
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
|
||||
<version>1.0.131-SNAPSHOT</version>
|
||||
<version>1.0.139-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
</project>
|
||||
|
||||
@@ -3,12 +3,13 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver CE Server
|
||||
Bundle-SymbolicName: io.cloudbeaver.server.ce;singleton:=true
|
||||
Bundle-Version: 25.2.2.qualifier
|
||||
Bundle-Release-Date: 20251006
|
||||
Bundle-Version: 25.3.4.qualifier
|
||||
Bundle-Release-Date: 20260202
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
|
||||
Require-Bundle: io.cloudbeaver.server;visibility:=reexport,
|
||||
io.cloudbeaver.model.cli;visibility:=reexport,
|
||||
com.google.guava
|
||||
Export-Package: io.cloudbeaver,
|
||||
io.cloudbeaver.model,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.server.ce</artifactId>
|
||||
<version>25.2.2-SNAPSHOT</version>
|
||||
<version>25.3.4-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
+2
@@ -86,10 +86,12 @@ public class CBServerConfig implements WebServerConfiguration {
|
||||
return contentRoot;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getRootURI() {
|
||||
return rootURI;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getServicesURI() {
|
||||
return serviceURI;
|
||||
}
|
||||
|
||||
+46
-24
@@ -24,15 +24,17 @@ import io.cloudbeaver.model.app.BaseServletApplication;
|
||||
import io.cloudbeaver.model.app.ServletAuthApplication;
|
||||
import io.cloudbeaver.model.app.ServletAuthConfiguration;
|
||||
import io.cloudbeaver.model.app.ServletSystemInformationCollector;
|
||||
import io.cloudbeaver.model.cli.CloudBeaverInstanceServer;
|
||||
import io.cloudbeaver.model.config.CBAppConfig;
|
||||
import io.cloudbeaver.model.config.CBServerConfig;
|
||||
import io.cloudbeaver.registry.WebDriverRegistry;
|
||||
import io.cloudbeaver.registry.WebFeatureRegistry;
|
||||
import io.cloudbeaver.registry.WebServiceRegistry;
|
||||
import io.cloudbeaver.server.jetty.CBJettyServer;
|
||||
import io.cloudbeaver.service.DBWServiceInitializer;
|
||||
import io.cloudbeaver.service.DBWServiceServerConfigurator;
|
||||
import io.cloudbeaver.service.security.CBEmbeddedSecurityController;
|
||||
import io.cloudbeaver.service.session.CBSessionManager;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import io.cloudbeaver.utils.WebDataSourceUtils;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.eclipse.osgi.service.datalocation.Location;
|
||||
@@ -48,7 +50,6 @@ import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriver;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
import org.jkiss.dbeaver.model.impl.app.BaseApplicationImpl;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.security.SMAdminController;
|
||||
import org.jkiss.dbeaver.model.security.SMConstants;
|
||||
import org.jkiss.dbeaver.model.security.SMObjectType;
|
||||
@@ -86,7 +87,6 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
*/
|
||||
private static final long CONFIGURATION_MODE_SESSION_IDLE_TIME = 60 * 60 * 1000 * 24 * 7;
|
||||
|
||||
|
||||
static {
|
||||
Log.setDefaultDebugStream(System.out);
|
||||
}
|
||||
@@ -119,6 +119,7 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
this.homeDirectory = new File(initHomeFolder());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getServerURL() {
|
||||
return getServerConfiguration().getServerURL();
|
||||
}
|
||||
@@ -138,15 +139,18 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
return getServerConfiguration().getServerName();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getRootURI() {
|
||||
return getServerConfiguration().getRootURI();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getServicesURI() {
|
||||
return getServerConfiguration().getServicesURI();
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
public Path getHomeDirectory() {
|
||||
return homeDirectory.toPath();
|
||||
}
|
||||
@@ -170,10 +174,12 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
* @return max session idle time from server configuration, may differ from {@link #getMaxSessionIdleTime()}
|
||||
*/
|
||||
|
||||
@NotNull
|
||||
public CBAppConfig getAppConfiguration() {
|
||||
return getServerConfigurationController().getAppConfiguration();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public T getServerConfiguration() {
|
||||
return getServerConfigurationController().getServerConfiguration();
|
||||
}
|
||||
@@ -199,6 +205,11 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
|
||||
@Override
|
||||
protected void startServer() {
|
||||
try {
|
||||
createInstanceServer();
|
||||
} catch (Exception e) {
|
||||
log.error("Error initializing instance server", e);
|
||||
}
|
||||
try {
|
||||
if (!loadServerConfiguration()) {
|
||||
return;
|
||||
@@ -214,23 +225,13 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
|
||||
configurationMode = CommonUtils.isEmpty(getServerConfiguration().getServerName());
|
||||
|
||||
try {
|
||||
refreshServerConfiguration();
|
||||
} catch (DBException e) {
|
||||
log.error("Error refreshing server configuration", e);
|
||||
return;
|
||||
}
|
||||
|
||||
eventController.setForceSkipEvents(isConfigurationMode()); // do not send events if configuration mode is on
|
||||
|
||||
|
||||
Location instanceLoc = Platform.getInstanceLocation();
|
||||
try {
|
||||
if (!instanceLoc.isSet()) { // always false?
|
||||
URL wsLocationURL = new URL(
|
||||
"file", //$NON-NLS-1$
|
||||
null,
|
||||
getWorkspaceDirectory().toAbsolutePath().toString());
|
||||
URL wsLocationURL = getWorkspaceDirectory().toUri().toURL();
|
||||
instanceLoc.set(wsLocationURL, true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -273,7 +274,8 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
determineLocalAddresses();
|
||||
log.debug("\tLocal host addresses:");
|
||||
for (InetAddress ia : localInetAddresses) {
|
||||
log.debug("\t\t" + ia.getHostAddress() + " (" + ia.getCanonicalHostName() + ")");
|
||||
log.debug("\t\t" + ia.getHostAddress() +
|
||||
(Objects.equals(ia.getHostAddress(), ia.getCanonicalHostName()) ? "" : (" (" + ia.getCanonicalHostName() + ")")));
|
||||
}
|
||||
}
|
||||
{
|
||||
@@ -292,7 +294,8 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
try {
|
||||
initializeServer();
|
||||
} catch (DBException e) {
|
||||
log.error("Error initializing server", e);
|
||||
log.error("Error initializing " + systemInformationCollector.getProductName(), e);
|
||||
shutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -300,6 +303,7 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
initializeSecurityController();
|
||||
} catch (Exception e) {
|
||||
log.error("Error initializing database", e);
|
||||
shutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -314,11 +318,6 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
}
|
||||
grantPermissionsToConnections();
|
||||
}
|
||||
try {
|
||||
this.systemInformationCollector.collectInternalDatabaseUseInformation();
|
||||
} catch (DBException e) {
|
||||
log.error("Error collecting system information", e);
|
||||
}
|
||||
|
||||
eventController.scheduleCheckJob();
|
||||
|
||||
@@ -339,13 +338,14 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
Set<String> enabledFeatures = new LinkedHashSet<>(Arrays.asList(getAppConfiguration().getEnabledFeatures()));
|
||||
Set<String> disabledFeatures = new LinkedHashSet<>(Arrays.asList(getAppConfiguration().getDisabledFeatures()));
|
||||
|
||||
WebFeatureRegistry.getInstance().getWebFeatures().stream()
|
||||
ServletAppUtils.getServletApplication().getFeatureRegistry().getWebFeatures().stream()
|
||||
.filter(f -> f.isEnabledByDefault() && !disabledFeatures.contains(f.getId()))
|
||||
.forEach(f -> enabledFeatures.add(f.getId()));
|
||||
|
||||
getAppConfiguration().setEnabledFeatures(enabledFeatures.toArray(new String[0]));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected ServletSystemInformationCollector<?> createSystemInformationCollector() {
|
||||
return new ServletSystemInformationCollector<>(this);
|
||||
}
|
||||
@@ -400,12 +400,16 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
getAppConfiguration(),
|
||||
null
|
||||
);
|
||||
if (!isMultiNode()) {
|
||||
grantPermissionsToConnections();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error loading server auto configuration", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void initializeServer() throws DBException {
|
||||
refreshServerConfiguration(); // update features and drivers
|
||||
for (DBWServiceServerConfigurator wsc : WebServiceRegistry.getInstance()
|
||||
.getWebServices(DBWServiceServerConfigurator.class)) {
|
||||
try {
|
||||
@@ -496,10 +500,20 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
|
||||
protected void shutdown() {
|
||||
log.debug("Cloudbeaver Server is stopping"); //$NON-NLS-1$
|
||||
|
||||
try {
|
||||
if (securityController instanceof CBEmbeddedSecurityController<?> embeddedSecurityController) {
|
||||
embeddedSecurityController.shutdown();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e);
|
||||
}
|
||||
|
||||
eventController.scheduleCheckJob();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInfoDetails(DBRProgressMonitor monitor) {
|
||||
public String getInfoDetails() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -521,6 +535,7 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
return getServerConfigurationController().getLocalHostAddress();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<InetAddress> getLocalInetAddresses() {
|
||||
return localInetAddresses;
|
||||
}
|
||||
@@ -669,6 +684,7 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CBSessionManager getSessionManager() {
|
||||
if (sessionManager == null) {
|
||||
sessionManager = createSessionManager();
|
||||
@@ -693,6 +709,7 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WSEventController getEventController() {
|
||||
return eventController;
|
||||
@@ -774,17 +791,18 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
initActions.remove(actionId);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<String, String> getInitActions() {
|
||||
return Map.copyOf(initActions);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebServerConfig getWebServerConfig() {
|
||||
return new CBWebServerConfig(this);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ServletSystemInformationCollector<?> getSystemInformationCollector() {
|
||||
return systemInformationCollector;
|
||||
}
|
||||
@@ -797,4 +815,8 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
public <T> T getApplicationContextValue(@NotNull String key) {
|
||||
return (T) applicationContext.get(key);
|
||||
}
|
||||
|
||||
protected CloudBeaverInstanceServer createInstanceServer() throws IOException {
|
||||
return new CloudBeaverInstanceServer();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-13
@@ -17,8 +17,6 @@
|
||||
package io.cloudbeaver.server;
|
||||
|
||||
import io.cloudbeaver.auth.NoAuthCredentialsProvider;
|
||||
import io.cloudbeaver.model.CBWebServerConfig;
|
||||
import io.cloudbeaver.model.WebServerConfig;
|
||||
import io.cloudbeaver.model.config.CBServerConfig;
|
||||
import io.cloudbeaver.model.rm.local.LocalResourceController;
|
||||
import io.cloudbeaver.service.security.CBEmbeddedSecurityController;
|
||||
@@ -79,6 +77,7 @@ public class CBApplicationCE extends CBApplication<CBServerConfig> {
|
||||
|
||||
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public RMController createResourceController(@NotNull SMCredentialsProvider credentialsProvider,
|
||||
@NotNull DBPWorkspace workspace) throws DBException {
|
||||
@@ -96,17 +95,6 @@ public class CBApplicationCE extends CBApplication<CBServerConfig> {
|
||||
return serverConfigController;
|
||||
}
|
||||
|
||||
protected void shutdown() {
|
||||
try {
|
||||
if (securityController instanceof CBEmbeddedSecurityController<?> embeddedSecurityController) {
|
||||
embeddedSecurityController.shutdown();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e);
|
||||
}
|
||||
super.shutdown();
|
||||
}
|
||||
|
||||
protected void finishSecurityServiceConfiguration(
|
||||
@NotNull String adminName,
|
||||
@Nullable String adminPassword,
|
||||
|
||||
@@ -20,14 +20,13 @@ package io.cloudbeaver.server;
|
||||
import io.cloudbeaver.server.jobs.SessionStateJob;
|
||||
import io.cloudbeaver.server.jobs.WebDataSourceMonitorJob;
|
||||
import io.cloudbeaver.server.jobs.WebSessionMonitorJob;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.app.DBPRegistryListener;
|
||||
import org.jkiss.dbeaver.model.impl.app.BaseApplicationImpl;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
|
||||
import org.jkiss.dbeaver.model.runtime.AbstractJob;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.runtime.LoggingProgressMonitor;
|
||||
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.utils.IOUtils;
|
||||
|
||||
@@ -38,12 +37,11 @@ import java.io.IOException;
|
||||
*/
|
||||
public class CBPlatform extends BaseWebPlatform {
|
||||
|
||||
// The plug-in ID
|
||||
public static final String PLUGIN_ID = "io.cloudbeaver.server"; //$NON-NLS-1$
|
||||
|
||||
private static final Log log = Log.getLog(CBPlatform.class);
|
||||
|
||||
private WebServerPreferenceStore preferenceStore;
|
||||
private DBPRegistryListener driverRegistryListener;
|
||||
private boolean isShutdown;
|
||||
|
||||
public static CBPlatform getInstance() {
|
||||
return (CBPlatform) DBWorkbench.getPlatform();
|
||||
@@ -55,10 +53,19 @@ public class CBPlatform extends BaseWebPlatform {
|
||||
@Override
|
||||
protected synchronized void initialize() {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.info("Initialize web platform...: ");
|
||||
log.info("Initialize web platform " + getClass().getSimpleName());
|
||||
this.preferenceStore = new WebServerPreferenceStore(WebPlatformActivator.getInstance().getPreferences());
|
||||
super.initialize();
|
||||
scheduleServerJobs();
|
||||
this.scheduleServerJobs();
|
||||
|
||||
this.driverRegistryListener = new DBPRegistryListener() {
|
||||
@Override
|
||||
public void handleRegistryReload() {
|
||||
WebAppUtils.getWebApplication().getDriverRegistry().refreshApplicableDrivers();
|
||||
}
|
||||
};
|
||||
DataSourceProviderRegistry.getInstance().addDataSourceRegistryListener(driverRegistryListener);
|
||||
|
||||
log.info("Web platform initialized (" + (System.currentTimeMillis() - startTime) + "ms)");
|
||||
}
|
||||
|
||||
@@ -73,23 +80,24 @@ public class CBPlatform extends BaseWebPlatform {
|
||||
new WebDataSourceMonitorJob(this, getApplication().getSessionManager())
|
||||
.scheduleMonitor();
|
||||
|
||||
new AbstractJob("Delete temp folder") {
|
||||
@Override
|
||||
protected IStatus run(DBRProgressMonitor monitor) {
|
||||
try {
|
||||
IOUtils.deleteDirectory(getTempFolder(monitor, TEMP_FILE_FOLDER));
|
||||
IOUtils.deleteDirectory(getTempFolder(monitor, TEMP_FILE_IMPORT_FOLDER));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return Status.OK_STATUS;
|
||||
}
|
||||
}.schedule();
|
||||
try {
|
||||
LoggingProgressMonitor monitor = new LoggingProgressMonitor(log);
|
||||
IOUtils.deleteDirectory(getTempFolder(monitor, TEMP_FILE_FOLDER));
|
||||
IOUtils.deleteDirectory(getTempFolder(monitor, TEMP_FILE_IMPORT_FOLDER));
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void dispose() {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.debug("Shutdown Core...");
|
||||
isShutdown = true;
|
||||
log.debug("Shutdown web platform");
|
||||
|
||||
if (driverRegistryListener != null) {
|
||||
DataSourceProviderRegistry.getInstance().removeDataSourceRegistryListener(driverRegistryListener);
|
||||
driverRegistryListener = null;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
|
||||
@@ -112,7 +120,7 @@ public class CBPlatform extends BaseWebPlatform {
|
||||
|
||||
@Override
|
||||
public boolean isShuttingDown() {
|
||||
return false;
|
||||
return isShutdown;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
-4
@@ -26,6 +26,7 @@ import io.cloudbeaver.server.graphql.GraphQLEndpoint;
|
||||
import io.cloudbeaver.server.servlets.CBImageServlet;
|
||||
import io.cloudbeaver.server.servlets.CBStaticServlet;
|
||||
import io.cloudbeaver.server.servlets.WebStatusServlet;
|
||||
import io.cloudbeaver.server.websockets.CBEventsLongPollingServlet;
|
||||
import io.cloudbeaver.server.websockets.CBEventsWebSocket;
|
||||
import io.cloudbeaver.server.websockets.CBWebSocketServerConfigurator;
|
||||
import io.cloudbeaver.service.DBWServiceBindingServlet;
|
||||
@@ -47,11 +48,13 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class CBJettyServer {
|
||||
|
||||
private static final Log log = Log.getLog(CBJettyServer.class);
|
||||
|
||||
static {
|
||||
// Set Jetty log level to WARN
|
||||
System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StdErrLog");
|
||||
@@ -118,6 +121,9 @@ public class CBJettyServer {
|
||||
|
||||
servletContextHandler.addServlet(new ServletHolder("status", new WebStatusServlet()), "/status");
|
||||
|
||||
ServletHolder eventsServletHolder = new ServletHolder("events", new CBEventsLongPollingServlet());
|
||||
servletContextHandler.addServlet(eventsServletHolder, serverConfiguration.getServicesURI() + "events/*");
|
||||
|
||||
GraphQLEndpoint endpoint = new GraphQLEndpoint(new ServerConfigurationTimeLimitFilter(application));
|
||||
application.addApplicationContextValue(GraphQL.class.getName(), endpoint.getGraphQL());
|
||||
String gqlServletPath = serverConfiguration.getServicesURI() + "gql/*";
|
||||
@@ -195,11 +201,15 @@ public class CBJettyServer {
|
||||
log.debug("\t" + sm.getServletName() + ": " + Arrays.toString(sm.getPathSpecs())); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
log.debug("Active websocket mappings:");
|
||||
for (String mapping : webSocketContext.getMappings()) {
|
||||
log.debug("\t" + mapping);
|
||||
List<String> wsMappings = webSocketContext.getMappings();
|
||||
if (!wsMappings.isEmpty()) {
|
||||
log.debug("Active websocket mappings:");
|
||||
for (String mapping : wsMappings) {
|
||||
log.debug("\t" + mapping);
|
||||
}
|
||||
} else {
|
||||
log.debug("No websocket mappings");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
boolean forwardProxy = application.getAppConfiguration().isEnabledForwardProxy();
|
||||
|
||||
+6
-1
@@ -219,8 +219,13 @@ public class CBStaticServlet extends DefaultServlet {
|
||||
if (pathInContext.startsWith("/")) {
|
||||
pathInContext = pathInContext.substring(1);
|
||||
}
|
||||
Path filePath = contentRoot.resolve(pathInContext).normalize();
|
||||
if (!filePath.startsWith(contentRoot)) {
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
Path filePath = contentRoot.resolve(pathInContext);
|
||||
try (InputStream fis = Files.newInputStream(filePath)) {
|
||||
IOUtils.copyStream(fis, baos);
|
||||
}
|
||||
|
||||
+2
-1
@@ -17,6 +17,7 @@
|
||||
package io.cloudbeaver.service;
|
||||
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
|
||||
/**
|
||||
@@ -24,6 +25,6 @@ import org.jkiss.dbeaver.DBException;
|
||||
*/
|
||||
public interface DBWServiceInitializer extends DBWServiceBinding {
|
||||
|
||||
void initializeService(CBApplication<?> application) throws DBException;
|
||||
void initializeService(@NotNull CBApplication<?> application) throws DBException;
|
||||
|
||||
}
|
||||
|
||||
+8
-8
@@ -26,6 +26,7 @@ import io.cloudbeaver.server.CBConstants;
|
||||
import io.cloudbeaver.server.WebAppSessionManager;
|
||||
import io.cloudbeaver.server.events.WSWebUtils;
|
||||
import io.cloudbeaver.service.DBWSessionHandler;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
@@ -49,10 +50,10 @@ public class CBSessionManager implements WebAppSessionManager {
|
||||
|
||||
private static final Log log = Log.getLog(CBSessionManager.class);
|
||||
|
||||
private final CBApplication application;
|
||||
private final CBApplication<?> application;
|
||||
private final Map<String, BaseWebSession> sessionMap = new HashMap<>();
|
||||
|
||||
public CBSessionManager(CBApplication application) {
|
||||
public CBSessionManager(CBApplication<?> application) {
|
||||
this.application = application;
|
||||
}
|
||||
|
||||
@@ -88,7 +89,7 @@ public class CBSessionManager implements WebAppSessionManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected CBApplication getApplication() {
|
||||
protected CBApplication<?> getApplication() {
|
||||
return application;
|
||||
}
|
||||
|
||||
@@ -165,6 +166,8 @@ public class CBSessionManager implements WebAppSessionManager {
|
||||
|
||||
validateSessionIp(request, webSession);
|
||||
|
||||
webSession.updateClientOrigin(ServletAppUtils.getOriginFromRequest(request));
|
||||
|
||||
return webSession;
|
||||
}
|
||||
|
||||
@@ -264,7 +267,7 @@ public class CBSessionManager implements WebAppSessionManager {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected Map<String, DBWSessionHandler> getSessionHandlers() {
|
||||
protected Map<String, DBWSessionHandler<WebSession>> getSessionHandlers() {
|
||||
return WebHandlerRegistry.getInstance().getSessionHandlers()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(WebSessionHandlerDescriptor::getId, WebSessionHandlerDescriptor::getInstance));
|
||||
@@ -304,14 +307,11 @@ public class CBSessionManager implements WebAppSessionManager {
|
||||
}
|
||||
|
||||
public void expireIdleSessions() {
|
||||
long maxSessionIdleTime = application.getMaxSessionIdleTime();
|
||||
|
||||
List<BaseWebSession> expiredList = new ArrayList<>();
|
||||
synchronized (sessionMap) {
|
||||
for (Iterator<BaseWebSession> iterator = sessionMap.values().iterator(); iterator.hasNext(); ) {
|
||||
var session = iterator.next();
|
||||
long idleMillis = System.currentTimeMillis() - session.getLastAccessTimeMillis();
|
||||
if (idleMillis >= maxSessionIdleTime) {
|
||||
if (!session.isValid()) {
|
||||
iterator.remove();
|
||||
expiredList.add(session);
|
||||
}
|
||||
|
||||
@@ -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: 25.2.2.qualifier
|
||||
Bundle-Release-Date: 20251006
|
||||
Bundle-Version: 25.3.4.qualifier
|
||||
Bundle-Release-Date: 20260202
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: org.eclipse.core.runtime;visibility:=reexport,
|
||||
@@ -18,6 +18,7 @@ Require-Bundle: org.eclipse.core.runtime;visibility:=reexport,
|
||||
org.jkiss.bundle.apache.dbcp,
|
||||
org.jkiss.dbeaver.net.ssh,
|
||||
io.cloudbeaver.model;visibility:=reexport,
|
||||
io.cloudbeaver.model.cli,
|
||||
io.cloudbeaver.service.security;visibility:=reexport
|
||||
Export-Package: io.cloudbeaver,
|
||||
io.cloudbeaver.model,
|
||||
|
||||
@@ -60,6 +60,9 @@
|
||||
<eventHandler class="io.cloudbeaver.server.events.WSEventHandlerWorkspaceConfigUpdate">
|
||||
<topic id="cb_workspace_configuration"/>
|
||||
</eventHandler>
|
||||
<eventHandler class="io.cloudbeaver.server.events.WSObjectSettingsEventHandler">
|
||||
<topic id="cb_object_settings"/>
|
||||
</eventHandler>
|
||||
</extension>
|
||||
|
||||
<extension point="org.jkiss.dbeaver.settings">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.server</artifactId>
|
||||
<version>25.2.2-SNAPSHOT</version>
|
||||
<version>25.3.4-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -548,7 +548,11 @@ type ConnectionInfo {
|
||||
|
||||
"Supported features (provided etc)"
|
||||
features: [ String! ]!
|
||||
"Navigator settings for the connection (with user settings applied)"
|
||||
navigatorSettings: NavigatorSettings!
|
||||
"Default navigator settings for the connection (without user settings applied)"
|
||||
defaultNavigatorSettings: NavigatorSettings!
|
||||
|
||||
supportedDataFormats: [ ResultDataFormat! ]!
|
||||
configurationType: DriverConfigurationType
|
||||
|
||||
@@ -595,6 +599,8 @@ type NavigatorSettings {
|
||||
hideFolders: Boolean!
|
||||
hideSchemas: Boolean!
|
||||
hideVirtualModel: Boolean!
|
||||
"Shows whether these settings are user-specific or global"
|
||||
userSettings: Boolean! @since(version: "25.3.2")
|
||||
}
|
||||
|
||||
type RMResourceType {
|
||||
@@ -637,6 +643,8 @@ input NavigatorSettingsInput {
|
||||
hideFolders: Boolean!
|
||||
hideSchemas: Boolean!
|
||||
hideVirtualModel: Boolean!
|
||||
|
||||
userSettings: Boolean @since(version: "25.3.2")
|
||||
}
|
||||
|
||||
input NetworkHandlerConfigInput {
|
||||
@@ -816,10 +824,10 @@ extend type Mutation {
|
||||
"Test connection configuration. Returns remote server version"
|
||||
testConnection( config: ConnectionConfig!, projectId: ID): ConnectionInfo!
|
||||
|
||||
"Test network handler"
|
||||
"Test network handler connectivity"
|
||||
testNetworkHandler( config: NetworkHandlerConfigInput! ): NetworkEndpointInfo!
|
||||
|
||||
"Initiate existing connection"
|
||||
"Initiate database connection. User credentials may be (optionally) override default settings."
|
||||
initConnection(
|
||||
id: ID!,
|
||||
projectId: ID,
|
||||
@@ -830,11 +838,13 @@ extend type Mutation {
|
||||
selectedSecretId:String
|
||||
): ConnectionInfo!
|
||||
|
||||
"Disconnect from database"
|
||||
"Disconnect from the database"
|
||||
closeConnection( id: ID!, projectId: ID ): ConnectionInfo!
|
||||
|
||||
"Change navigator settings for connection"
|
||||
"Change navigator settings for connection. It can change default settings or user settings depending on 'default' parameter."
|
||||
setConnectionNavigatorSettings( id: ID!, projectId: ID, settings: NavigatorSettingsInput!): ConnectionInfo!
|
||||
"Sets to default navigator settings for connection. Resets all user navigator settings for this connection."
|
||||
clearConnectionNavigatorSettings(projectId: ID!, id: ID!): ConnectionInfo! @since(version: "25.3.2")
|
||||
|
||||
#### Generic async functions
|
||||
|
||||
|
||||
@@ -69,6 +69,14 @@ enum CBServerEventId {
|
||||
cb_workspace_config_changed @since(version: "25.1.1"),
|
||||
"Server notification"
|
||||
cb_server_notification@since(version: "25.2.1")
|
||||
|
||||
"Session task confirmations"
|
||||
cb_session_task_confirmation_request @since(version: "25.2.2")
|
||||
cb_session_task_query_confirmation_request @since(version: "25.2.2")
|
||||
cb_session_task_query_params_confirmation_request @since(version: "25.2.5")
|
||||
|
||||
"Open URL request in client"
|
||||
cb_open_url @since(version: "25.3.1")
|
||||
}
|
||||
|
||||
"Events sent by client"
|
||||
@@ -76,7 +84,9 @@ enum CBClientEventId {
|
||||
cb_client_topic_subscribe,
|
||||
cb_client_topic_unsubscribe,
|
||||
cb_client_projects_active,
|
||||
cb_client_session_ping
|
||||
cb_client_session_ping,
|
||||
cb_client_session_task_confirmation @since(version: "25.2.2")
|
||||
cb_client_session_task_with_parameters_confirmation @since(version: "25.2.5")
|
||||
}
|
||||
|
||||
"Client subscribes on topic to receive only related events"
|
||||
@@ -101,6 +111,7 @@ enum CBEventTopic {
|
||||
cb_workspace_configuration @since(version: "25.1.1")
|
||||
cb_ai @since(version: "25.1.1")
|
||||
cb_notification @since(version: "25.2.1")
|
||||
cb_session_action @since(version: "25.3.1")
|
||||
}
|
||||
|
||||
"Base server event interface"
|
||||
@@ -294,6 +305,56 @@ type WSServerNotificationEvent implements CBServerEvent @since(version: "25.2.1"
|
||||
notificationType: WSServerNotificationEventType!
|
||||
}
|
||||
|
||||
"Event for opening URL in client application"
|
||||
type WSOpenUrlEvent implements CBServerEvent @since(version: "25.3.1") {
|
||||
id: CBServerEventId!
|
||||
topicId: CBEventTopic!
|
||||
timestamp: Int!
|
||||
url: String!
|
||||
}
|
||||
|
||||
type WSSessionTaskConfirmationRequestEvent implements CBServerEvent @since(version: "25.2.2") {
|
||||
id: CBServerEventId!
|
||||
topicId: CBEventTopic!
|
||||
taskId: ID!
|
||||
title: String!
|
||||
message: String!
|
||||
}
|
||||
|
||||
type WSSessionTaskQueryConfirmationRequestEvent implements CBServerEvent @since(version: "25.2.2") {
|
||||
id: CBServerEventId!
|
||||
topicId: CBEventTopic!
|
||||
taskId: ID!
|
||||
title: String!
|
||||
message: String!
|
||||
query: String!
|
||||
}
|
||||
|
||||
type WSSessionTaskConfirmationEvent implements CBClientEvent @since(version: "25.2.2") {
|
||||
id: CBClientEventId!
|
||||
topicId: CBEventTopic
|
||||
taskId: ID!
|
||||
confirmed: Boolean!
|
||||
skipConfirmations: Boolean!
|
||||
}
|
||||
|
||||
type WSSessionTaskQueryParamsConfirmationEvent implements CBServerEvent @since(version: "25.2.5") {
|
||||
id: CBServerEventId!
|
||||
topicId: CBEventTopic!
|
||||
taskId: ID!
|
||||
title: String!
|
||||
message: String!
|
||||
query: String!
|
||||
parameters: Object!
|
||||
}
|
||||
|
||||
type WSSessionTaskWithParametersConfirmationEvent implements CBClientEvent @since(version: "25.2.5") {
|
||||
id: CBClientEventId!
|
||||
topicId: CBEventTopic
|
||||
taskId: ID!
|
||||
parameters: Object!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
emptyEvent: Boolean
|
||||
}
|
||||
|
||||
@@ -155,8 +155,8 @@ extend type Mutation {
|
||||
"Reloads node and returns updated node info"
|
||||
navReloadNode( nodePath: ID! ): NavigatorNodeInfo!
|
||||
|
||||
"Renames node and returns new node name"
|
||||
navRenameNode( nodePath: ID!, newName: String! ): String
|
||||
"Renames node and returns new node id"
|
||||
navRenameNode( nodePath: ID!, newName: String! ): String!
|
||||
|
||||
"Deletes nodes with specified IDs and returns number of deleted nodes"
|
||||
navDeleteNodes( nodePaths: [ID!]! ): Int
|
||||
|
||||
@@ -126,7 +126,11 @@ type SQLResultSet {
|
||||
# server always returns hasMoreData = false
|
||||
hasMoreData: Boolean!
|
||||
"Identifies if result set has row identifier. If true then it is possible update data or load LOB files"
|
||||
hasRowIdentifier: Boolean!
|
||||
hasRowIdentifier: Boolean! @deprecated (reason: "use rowIdentifierState (25.3.4)")
|
||||
"State of a table row identifier: METADATA_NOT_FOUND, NONE, PRIMARY_KEY, VIRTUAL_KEY"
|
||||
rowIdentifierState: SQLRowIdentifierState @since(version: "25.3.4")
|
||||
"Contains info about a result set row identifier. Present if rowIdentifierState is PRIMARY_KEY"
|
||||
rowIdentifier: SQLRowIdentifier @since(version: "25.3.4")
|
||||
"Identifies if result has children collections. If true then children collections can be read from the result set"
|
||||
hasChildrenCollection: Boolean!
|
||||
"Identifies if result set supports dynamic trace. If true then dynamic trace can be read from the result set"
|
||||
@@ -139,6 +143,25 @@ type SQLResultSet {
|
||||
readOnlyStatus: String @since(version: "25.0.1")
|
||||
}
|
||||
|
||||
enum SQLRowIdentifierState {
|
||||
METADATA_NOT_FOUND,
|
||||
NONE,
|
||||
PRIMARY_KEY,
|
||||
VIRTUAL_KEY
|
||||
}
|
||||
|
||||
type SQLRowIdentifier {
|
||||
"Single or multiple attributes acting as a row identifier"
|
||||
attributes: [SQLRowIdentifierAttribute!]!
|
||||
"Type of a row identifier constraint"
|
||||
constraintType: String!
|
||||
}
|
||||
|
||||
type SQLRowIdentifierAttribute {
|
||||
name: String!
|
||||
ordinalPosition: Int!
|
||||
}
|
||||
|
||||
type SQLQueryResults {
|
||||
title: String
|
||||
updateRowCount: Float
|
||||
@@ -158,8 +181,6 @@ type SQLExecuteInfo {
|
||||
duration: Int!
|
||||
# Actual conditions applied to query
|
||||
filterText: String
|
||||
# # original sql query without SQLDataFilter
|
||||
# originalQuery: String
|
||||
# Full query that was executed, contains all used filters
|
||||
fullQuery: String
|
||||
# Results
|
||||
@@ -339,7 +360,24 @@ extend type Query {
|
||||
columnNames: [String!],
|
||||
functions: [String!],
|
||||
showDuplicatesOnly: Boolean
|
||||
): String!
|
||||
): String! @deprecated (reason: "use asyncSqlGroupingResultSet (25.3.1)")
|
||||
|
||||
"Creates async task to get grouped SQL results"
|
||||
asyncSqlGroupingResultSet(
|
||||
projectId: ID,
|
||||
contextId: ID!,
|
||||
connectionId: ID!,
|
||||
"Original results ID to group"
|
||||
originalResultsId: ID!,
|
||||
"Current results ID is needed to apply filters/sorting on top of grouping"
|
||||
currentResultsId: ID,
|
||||
columnNames: [String!],
|
||||
functions: [String!],
|
||||
showDuplicatesOnly: Boolean,
|
||||
filter: SQLDataFilter,
|
||||
dataFormat: ResultDataFormat,
|
||||
isInteractive: Boolean
|
||||
): AsyncTaskInfo! @since(version: "25.3.1")
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
@@ -361,7 +399,8 @@ extend type Mutation {
|
||||
resultId: ID,
|
||||
filter: SQLDataFilter,
|
||||
dataFormat: ResultDataFormat, # requested data format. May be ignored by server
|
||||
readLogs: Boolean # added 23.2.1
|
||||
readLogs: Boolean, # added 23.2.1
|
||||
isInteractive: Boolean # Support WS events requested by server, e.g. confirmations. Added in 23.2.3
|
||||
): AsyncTaskInfo!
|
||||
|
||||
"Creates async task for reading data from the container node"
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Marks a field or method as sensitive, so its value won't be logged.
|
||||
*/
|
||||
@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface WebParameterSecure {
|
||||
}
|
||||
@@ -39,7 +39,10 @@ import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
|
||||
import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriver;
|
||||
import org.jkiss.dbeaver.model.navigator.*;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNContainer;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNDataSource;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNNode;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNProject;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
|
||||
import org.jkiss.dbeaver.model.rm.RMProjectType;
|
||||
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings;
|
||||
@@ -68,7 +71,7 @@ public class WebServiceUtils extends WebCommonUtils {
|
||||
return WebServiceUtils.class.getClassLoader().getResourceAsStream(path);
|
||||
}
|
||||
|
||||
public static DBNBrowseSettings parseNavigatorSettings(Map<String, Object> settingsMap) {
|
||||
public static DataSourceNavigatorSettings parseNavigatorSettings(Map<String, Object> settingsMap) {
|
||||
return gson.fromJson(
|
||||
gson.toJsonTree(settingsMap), DataSourceNavigatorSettings.class);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import org.eclipse.core.runtime.IProduct;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
|
||||
import org.jkiss.dbeaver.utils.GeneralUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
@@ -75,7 +74,7 @@ public class WebProductInfo {
|
||||
@Property
|
||||
public String getLicenseInfo() {
|
||||
return provideSensitiveInformation
|
||||
? ServletAppUtils.getServletApplication().getInfoDetails(new VoidProgressMonitor())
|
||||
? ServletAppUtils.getServletApplication().getInfoDetails()
|
||||
: "";
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -17,6 +17,7 @@
|
||||
|
||||
package io.cloudbeaver.registry;
|
||||
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.service.DBWSessionHandler;
|
||||
import org.eclipse.core.runtime.IConfigurationElement;
|
||||
import org.jkiss.code.NotNull;
|
||||
@@ -31,7 +32,7 @@ public class WebSessionHandlerDescriptor extends AbstractContextDescriptor {
|
||||
public static final String EXTENSION_ID = "io.cloudbeaver.servlet.handler"; //$NON-NLS-1$
|
||||
|
||||
private final String id;
|
||||
private final DBWSessionHandler instance;
|
||||
private final DBWSessionHandler<WebSession> instance;
|
||||
|
||||
public WebSessionHandlerDescriptor(IConfigurationElement config) throws DBException {
|
||||
super(config);
|
||||
@@ -45,7 +46,7 @@ public class WebSessionHandlerDescriptor extends AbstractContextDescriptor {
|
||||
return id;
|
||||
}
|
||||
|
||||
public DBWSessionHandler getInstance() {
|
||||
public DBWSessionHandler<WebSession> getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,25 +31,32 @@ import java.util.Map;
|
||||
* Base interface for applications with web ui
|
||||
*/
|
||||
public interface WebApplication extends ServletApplication {
|
||||
@NotNull
|
||||
WebServerConfiguration getServerConfiguration();
|
||||
|
||||
@NotNull
|
||||
WebAppSessionManager getSessionManager();
|
||||
|
||||
@NotNull
|
||||
WebDriverRegistry getDriverRegistry();
|
||||
|
||||
@NotNull
|
||||
WebAppConfiguration getAppConfiguration();
|
||||
|
||||
@NotNull
|
||||
Map<String, Object> getProductConfiguration();
|
||||
|
||||
@NotNull
|
||||
List<InetAddress> getLocalInetAddresses();
|
||||
|
||||
@NotNull
|
||||
Map<String, String> getInitActions();
|
||||
|
||||
boolean isLicenseValid();
|
||||
|
||||
String getLicenseStatus();
|
||||
|
||||
@NotNull
|
||||
WebServerConfig getWebServerConfig();
|
||||
|
||||
}
|
||||
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.events;
|
||||
|
||||
import io.cloudbeaver.WebSessionProjectImpl;
|
||||
import io.cloudbeaver.model.session.BaseWebSession;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.security.SMObjectSettings;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSObjectSettingsEvent;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class WSObjectSettingsEventHandler extends WSDefaultEventHandler<WSObjectSettingsEvent> {
|
||||
private static final Log log = Log.getLog(WSObjectSettingsEventHandler.class);
|
||||
|
||||
@Override
|
||||
protected void updateSessionData(@NotNull BaseWebSession activeUserSession, @NotNull WSObjectSettingsEvent event) {
|
||||
String projectId = event.getProjectId();
|
||||
if (projectId == null) {
|
||||
// Right now we handle only project-specific object settings
|
||||
return;
|
||||
}
|
||||
if (activeUserSession instanceof WebSession webSession) {
|
||||
WebSessionProjectImpl project = webSession.getProjectById(projectId);
|
||||
if (project == null) {
|
||||
log.warn("Project with id '" + projectId + "' not found in session '" + webSession.getSessionId() + "'");
|
||||
return;
|
||||
}
|
||||
handleEventInWebSessionProject(webSession, project, event);
|
||||
}
|
||||
super.updateSessionData(activeUserSession, event);
|
||||
}
|
||||
|
||||
private void handleEventInWebSessionProject(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull WebSessionProjectImpl project,
|
||||
@NotNull WSObjectSettingsEvent event
|
||||
) {
|
||||
String objectId = event.getObjectId();
|
||||
if (WSObjectSettingsEvent.UPDATED.equals(event.getId())) {
|
||||
{
|
||||
try {
|
||||
List<SMObjectSettings> settings = webSession.getSecurityController().getObjectSettings(
|
||||
project.getId(),
|
||||
event.getSmObjectType(),
|
||||
objectId,
|
||||
event.getSettingIds().toArray(new String[0])
|
||||
);
|
||||
for (SMObjectSettings setting : settings) {
|
||||
project.getProjectSettings().updateObjectSettingsCache(
|
||||
setting.objectType(),
|
||||
setting.objectId(),
|
||||
setting.settings()
|
||||
);
|
||||
}
|
||||
} catch (DBException e) {
|
||||
log.error("Error fetching updated object settings", e);
|
||||
}
|
||||
}
|
||||
} else if (WSObjectSettingsEvent.DELETED.equals(event.getId())) {
|
||||
project.getProjectSettings().deleteObjectSettingsCache(
|
||||
event.getSmObjectType(),
|
||||
objectId,
|
||||
event.getSettingIds()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAcceptableInSession(@NotNull BaseWebSession activeUserSession, @NotNull WSObjectSettingsEvent event) {
|
||||
return super.isAcceptableInSession(activeUserSession, event) &&
|
||||
CommonUtils.equalObjects(activeUserSession.getUserContext().getUserId(), event.getUserId());
|
||||
}
|
||||
}
|
||||
+2
-7
@@ -44,6 +44,7 @@ import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
import org.jkiss.dbeaver.utils.MimeTypes;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
import org.jkiss.utils.HttpConstants;
|
||||
import org.jkiss.utils.IOUtils;
|
||||
@@ -211,6 +212,7 @@ public class GraphQLEndpoint extends HttpServlet {
|
||||
if (path.contentEquals("/schema.json") && develMode) {
|
||||
executeQuery(request, response, GraphQLConstants.SCHEMA_READ_QUERY, null, null);
|
||||
} else if (path.contentEquals("/console") && develMode) {
|
||||
response.setContentType(MimeTypes.TEXT_HTML);
|
||||
try (InputStream consolePageStream = WebServiceUtils.openStaticResource("static/graphiql/index.html")) {
|
||||
IOUtils.copyStream(consolePageStream, response.getOutputStream());
|
||||
}
|
||||
@@ -246,14 +248,7 @@ public class GraphQLEndpoint extends HttpServlet {
|
||||
if (operationName != null) {
|
||||
contextBuilder.operationName(operationName);
|
||||
}
|
||||
String sessionId = GraphQLLoggerUtil.getSmSessionId(request);
|
||||
String userId = GraphQLLoggerUtil.getUserId(request);
|
||||
String loggerMessage = GraphQLLoggerUtil.buildLoggerMessage(sessionId, userId, variables);
|
||||
if (operationName != null) {
|
||||
log.debug("API > " + operationName + loggerMessage);
|
||||
} else if (DEBUG) {
|
||||
log.debug("API > " + query + loggerMessage);
|
||||
}
|
||||
LocalDateTime startTime = LocalDateTime.now();
|
||||
ExecutionInput executionInput = contextBuilder.build();
|
||||
ExecutionResult executionResult = null;
|
||||
|
||||
+78
-34
@@ -16,35 +16,48 @@
|
||||
*/
|
||||
package io.cloudbeaver.server.graphql;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import io.cloudbeaver.WebParameterSecure;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.server.WebAppUtils;
|
||||
import io.cloudbeaver.server.WebApplication;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.qm.QMConstants;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
public class GraphQLLoggerUtil {
|
||||
|
||||
public static final String LOG_API_GRAPHQL_DEBUG_PARAMETER = "log.api.graphql.debug";
|
||||
private static final Set<String> PROHIBITED_VARIABLES =
|
||||
Set.of("password", "config", "parameters", "settings", "licenseText", "credentials", "username");
|
||||
public static final Gson GSON = new GsonBuilder().create();
|
||||
public static final String MASK_STRING = "****";
|
||||
|
||||
public static String getUserId(HttpServletRequest request) {
|
||||
@Nullable
|
||||
public static String getUserId(@NotNull HttpServletRequest request) {
|
||||
WebSession session = getWebSession(request);
|
||||
if (session == null) {
|
||||
return null;
|
||||
}
|
||||
String userId = session.getUserContext().getUserId();
|
||||
if (userId == null && session.getUserContext().isAuthorizedInSecurityManager()) {
|
||||
return "anonymous";
|
||||
if (userId == null) {
|
||||
if (session.getUserContext().isAuthorizedInSecurityManager()) {
|
||||
return QMConstants.QM_ANONYMOUS_USER;
|
||||
}
|
||||
return QMConstants.QM_UNAUTHORIZED_USER;
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
public static String getSmSessionId(HttpServletRequest request) {
|
||||
@Nullable
|
||||
public static String getSmSessionId(@NotNull HttpServletRequest request) {
|
||||
WebSession session = getWebSession(request);
|
||||
if (session == null) {
|
||||
return null;
|
||||
@@ -53,7 +66,7 @@ public class GraphQLLoggerUtil {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static WebSession getWebSession(HttpServletRequest request) {
|
||||
public static WebSession getWebSession(@NotNull HttpServletRequest request) {
|
||||
if (request.getSession() == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -63,43 +76,74 @@ public class GraphQLLoggerUtil {
|
||||
.findWebSession(request);
|
||||
}
|
||||
|
||||
public static String buildLoggerMessage(String sessionId, String userId, Map<String, Object> variables) {
|
||||
StringBuilder loggerMessage = new StringBuilder(" [user: ").append(userId)
|
||||
.append(", sessionId: ").append(sessionId).append("]");
|
||||
@NotNull
|
||||
public static String buildLoggerMessage(
|
||||
@NotNull String sessionId,
|
||||
@NotNull String userId,
|
||||
@NotNull Method method,
|
||||
@NotNull Object[] args
|
||||
) {
|
||||
StringBuilder sb = new StringBuilder(64)
|
||||
.append(" [").append(userId)
|
||||
.append(", session: ").append(sessionId)
|
||||
.append("]");
|
||||
|
||||
if (WebAppUtils.getWebPlatform().getPreferenceStore().getBoolean(LOG_API_GRAPHQL_DEBUG_PARAMETER)
|
||||
&& variables != null
|
||||
) {
|
||||
loggerMessage.append(" [variables] ");
|
||||
String parsedVariables = parseVarialbes(variables);
|
||||
if (CommonUtils.isNotEmpty(parsedVariables)) {
|
||||
loggerMessage.append(parseVarialbes(variables));
|
||||
if (WebAppUtils.getWebPlatform().getPreferenceStore().getBoolean(LOG_API_GRAPHQL_DEBUG_PARAMETER)) {
|
||||
sb.append('(');
|
||||
String text = maskArgsToString(method, args);
|
||||
if (CommonUtils.isNotEmpty(text)) {
|
||||
sb.append(text);
|
||||
}
|
||||
sb.append(')');
|
||||
}
|
||||
return loggerMessage.toString();
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String parseVarialbes(Map<String, Object> map) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
@NotNull
|
||||
public static String maskArgsToString(@NotNull Method method, @Nullable Object[] args) {
|
||||
Parameter[] params = method.getParameters();
|
||||
if (params.length == 0 || args == null || args.length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
int limit = Math.min(args.length, params.length);
|
||||
StringJoiner joiner = new StringJoiner(", ");
|
||||
|
||||
boolean isProhibited = PROHIBITED_VARIABLES.stream()
|
||||
.anyMatch(prohibitedKey -> key.toLowerCase().contains(prohibitedKey.toLowerCase()));
|
||||
|
||||
if (isProhibited) {
|
||||
result.append(key).append(": ").append("******** ");
|
||||
for (int i = 0; i < limit; i++) {
|
||||
Object value = args[i];
|
||||
if (value instanceof WebSession || value instanceof ServletRequest || value instanceof ServletResponse) {
|
||||
//we already log sessionId
|
||||
continue;
|
||||
}
|
||||
if (params[i].isAnnotationPresent(WebParameterSecure.class)) {
|
||||
joiner.add(MASK_STRING);
|
||||
continue;
|
||||
}
|
||||
if (value instanceof String sv && CommonUtils.isEmpty(sv)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value instanceof Map) {
|
||||
result.append(parseVarialbes((Map<String, Object>) value));
|
||||
if (value != null && !isSimple(value.getClass())) {
|
||||
String stringValue;
|
||||
try {
|
||||
stringValue = GSON.toJson(value);
|
||||
} catch (Exception e) {
|
||||
stringValue = value.toString();
|
||||
}
|
||||
joiner.add(stringValue);
|
||||
} else {
|
||||
result.append(key).append(": ").append(value).append(" ");
|
||||
joiner.add(String.valueOf(value));
|
||||
}
|
||||
}
|
||||
return result.toString().trim();
|
||||
return joiner.toString();
|
||||
}
|
||||
|
||||
private static boolean isSimple(Class<?> cls) {
|
||||
return cls.isPrimitive()
|
||||
|| Number.class.isAssignableFrom(cls)
|
||||
|| CharSequence.class.isAssignableFrom(cls)
|
||||
|| Boolean.class.equals(cls)
|
||||
|| Enum.class.isAssignableFrom(cls);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-1
@@ -18,12 +18,18 @@ package io.cloudbeaver.server.jetty;
|
||||
|
||||
import io.cloudbeaver.server.WebApplication;
|
||||
import org.eclipse.jetty.ee10.servlet.SessionHandler;
|
||||
import org.jkiss.code.NotNull;
|
||||
|
||||
public class CBSessionHandler extends SessionHandler {
|
||||
static final int ONE_MINUTE = 60;
|
||||
private final WebApplication application;
|
||||
|
||||
public CBSessionHandler(WebApplication application) {
|
||||
public CBSessionHandler(@NotNull WebApplication application) {
|
||||
this.application = application;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CBSessionHandler-" + super.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user