Compare commits

..
Author SHA1 Message Date
Ruslan 6f5ac673cf dbeaver/pro#6976 poc log masking 2025-09-25 19:06:45 +02:00
782 changed files with 11988 additions and 15693 deletions
+2 -2
View File
@@ -15,11 +15,11 @@
"streetsidesoftware.code-spell-checker",
"streetsidesoftware.code-spell-checker-russian",
"syler.sass-indented",
"github.copilot-chat",
"VisualStudioExptTeam.vscodeintellicode",
"yzhang.markdown-all-in-one",
"GraphQL.vscode-graphql-syntax",
"mrmlnc.vscode-json5",
"esbenp.prettier-vscode",
"devnaumov.licensify"
]
}
}
+2 -2
View File
@@ -92,10 +92,10 @@
"label": "Run Backend CE",
"type": "shell",
"windows": {
"command": "./run-cloudbeaver-server.bat"
"command": "./run-server.bat"
},
"osx": {
"command": "./run-cloudbeaver-server.sh"
"command": "./run-server.sh"
},
"options": {
"cwd": "${workspaceFolder}/deploy/cloudbeaver"
+2 -22
View File
@@ -25,29 +25,9 @@ You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io
## Changelog
### 25.2.5 2025-11-17
- A "Show SQL" button has been added to the Data Editor and the grouping panel, enabling users to inspect the exact SQL query behind displayed results—helping validate data transformations, debug query logic, and ensure analytical accuracy;
- The search option has been added to the default catalog/schema selector in the SQL Editor's main toolbar;
- The SQL Server driver has been updated to version 13.2.1.
### 25.2.4 2025-11-03
- A confirmation prompt has been added for potentially dangerous queries in the SQL Editor to help prevent unintended execution. You can customize this behavior in Preferences > SQL Editor;
- The value panel in the Data Editor has been redesigned to provide users with 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;
- Permission configuration for connections was simplified, providing a clearer appearance and improved performance.
### 25.2.3 2025-10-20
- Added visualization for column reordering in the Data Editor;
- Fixed the critical issue with duplicating the execution of queries executed by a shortcut in the SQL Editor;
- Fixed the critical vulnerability (CVE-2025-61927) in the Happy DOM library. The library was updated to version 20.0.2.
### 25.2.2 2025-10-06
- Replaced true/false text with intuitive checkboxes for boolean values in the metadata editor, making editing easier and faster;
- Keyboard navigation was improved across the application. Additional shortcuts are available in the main info panel (top-right corner). To help you discover them faster, relevant keyboard shortcuts are also shown directly in the tooltips of Data Editor buttons;
- Fixed an issue in the Data Editor where resizing one column in a wide table would cause previously resized columns to revert to their original width. Users can resize multiple columns as intended.
### 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;
- 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.2.0 2025-09-01
+1 -1
View File
@@ -28,4 +28,4 @@ echo "Copy static content"
cp -rp ../webapp/packages/product-default/lib/* cloudbeaver/web
echo "Cloudbeaver is ready. Run run-cloudbeaver-server.sh in cloudbeaver folder to start the server."
echo "Cloudbeaver is ready. Run run-server.sh in cloudbeaver folder to start the server."
+1 -1
View File
@@ -72,6 +72,6 @@ echo "Copy static content"
xcopy /E /Q ..\webapp\packages\product-default\lib cloudbeaver\web >NUL
echo "Cloudbeaver is ready. Run run-cloudbeaver-server.bat in cloudbeaver folder to start the server."
echo "Cloudbeaver is ready. Run run-server.bat in cloudbeaver folder to start the server."
pause
+1 -1
View File
@@ -16,6 +16,6 @@ EXPOSE 8978
RUN find /opt/cloudbeaver -type d -exec chmod 775 {} \;
WORKDIR /opt/cloudbeaver/
RUN chmod +x "run-cloudbeaver-server.sh" "/opt/cloudbeaver/launch-product.sh"
RUN chmod +x "run-server.sh" "/opt/cloudbeaver/launch-product.sh"
ENTRYPOINT ["./launch-product.sh"]
+3 -3
View File
@@ -12,8 +12,8 @@ if [ "$(id -u)" -eq 0 ]; then
TARGET_GID=${TARGET_GID:-$DBEAVER_GID}
chown -R $DBEAVER_UID:$DBEAVER_GID $PWD/workspace
# Execute run-cloudbeaver-server.sh as the dbeaver user with the JAVA_HOME and PATH environment variables
exec su "$TARGET_USER" -c "JAVA_HOME=$JAVA_HOME PATH=$PATH ./run-cloudbeaver-server.sh"
# Execute run-server.sh as the dbeaver user with the JAVA_HOME and PATH environment variables
exec su "$TARGET_USER" -c "JAVA_HOME=$JAVA_HOME PATH=$PATH ./run-server.sh"
else
exec ./run-cloudbeaver-server.sh
exec ./run-server.sh
fi
@@ -6,4 +6,4 @@ if [[ $# -eq 0 ]] ;
fi
set -- "$@" -cli-mode
source run-cloudbeaver-server.sh "$@"
source run-server.sh "$@"
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
Bundle-Version: 1.0.90.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 1.0.86.qualifier
Bundle-Release-Date: 20251006
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model</artifactId>
<version>1.0.90-SNAPSHOT</version>
<version>1.0.86-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -17,14 +17,18 @@
package io.cloudbeaver;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
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.*;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMControllerProvider;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.Pair;
import java.nio.file.Path;
import java.util.Collection;
public abstract class BaseWebProjectImpl extends BaseProjectImpl implements RMControllerProvider {
@@ -77,12 +81,6 @@ public abstract class BaseWebProjectImpl extends BaseProjectImpl implements RMCo
return project.getId();
}
@NotNull
@Override
public String getDisplayName() {
return getName();
}
@NotNull
@Override
public Path getAbsolutePath() {
@@ -104,25 +102,52 @@ public abstract class BaseWebProjectImpl extends BaseProjectImpl implements RMCo
return false;
}
/**
* 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 Path getMetadataFilePath() {
return getMetadataPath().resolve(METADATA_STORAGE_FILE);
}
@Override
public boolean isPrivateProject() {
return RMProjectType.USER.equals(getRMProject().getType());
}
@Override
public void updateProject(@Nullable String newName, @Nullable String description) throws DBException {
RMProject rmProject = getResourceController().updateProject(this.getId(), new RMProjectInfo(newName, description));
updateProjectInfo(rmProject.getName(), rmProject.getDescription());
}
public void updateProjectInfo(@Nullable String newName, @Nullable String description) {
this.project.setName(newName);
this.project.setDescription(description);
RMEventManager.fireEvent(
new RMEvent(
RMEvent.Action.PROJECT_UPDATE,
project
)
);
}
}
@@ -32,8 +32,6 @@ 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";
@@ -1,110 +0,0 @@
/*
* 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 io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.app.WebAppConfiguration;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.utils.CommonUtils;
public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C extends DataSourceDescriptor> {
private static final Log log = Log.getLog(WebConnectionConfigInputHandler.class);
protected final T input;
protected final DBPDataSourceRegistry registry;
public WebConnectionConfigInputHandler(@NotNull DBPDataSourceRegistry registry, T configInput) {
this.registry = registry;
this.input = configInput;
}
public C createDataSourceContainer() throws DBWebException {
String driverId = input.getDriverId();
if (CommonUtils.isEmpty(driverId)) {
throw new DBWebException("Driver must be specified");
}
DBPDriver driver = WebDataSourceUtils.getDriverById(driverId);
C newDataSource = createDataSourceContainerFromInput(driver);
if (ServletAppUtils.getServletApplication().getAppConfiguration() instanceof WebAppConfiguration webAppConfiguration) {
newDataSource.setNavigatorSettings(webAppConfiguration.getDefaultNavigatorSettings());
}
WebDataSourceUtils.saveAuthProperties(
newDataSource,
newDataSource.getConnectionConfiguration(),
input.getCredentials(),
input.isSaveCredentials(),
input.isSharedCredentials()
);
return newDataSource;
}
public void updateDataSource(@NotNull C dataSource) throws DBWebException {
dataSource.setId(dataSource.getId());
if (!CommonUtils.isEmpty(input.getName())) {
dataSource.setName(input.getName());
}
if (input.getDescription() != null) {
dataSource.setDescription(input.getDescription());
}
dataSource.setFolder(input.getFolder() != null ? registry.getFolder(input.getFolder()) : null);
if (input.isDefaultAutoCommit() != null) {
dataSource.setDefaultAutoCommit(input.isDefaultAutoCommit());
}
WebDataSourceUtils.setConnectionConfiguration(
dataSource.getDriver(),
dataSource.getConnectionConfiguration(),
input
);
WebDataSourceUtils.saveAuthProperties(
dataSource,
dataSource.getConnectionConfiguration(),
input.getCredentials(),
input.isSaveCredentials(),
input.isSharedCredentials()
);
dataSource.setConnectionReadOnly(input.isReadOnly());
}
@NotNull
protected C createDataSourceContainerFromInput(@NotNull DBPDriver driver) {
DBPConnectionConfiguration dsConfig = new DBPConnectionConfiguration();
WebDataSourceUtils.setConnectionConfiguration(driver, dsConfig, input);
C newDataSource = registry.createDataSource(driver, dsConfig);
newDataSource.setSavePassword(true);
newDataSource.setName(CommonUtils.notNull(input.getName(), "NewConnection"));
newDataSource.setDescription(input.getDescription());
newDataSource.setConnectionReadOnly(input.isReadOnly());
if (input.getFolder() != null) {
newDataSource.setFolder(registry.getFolder(input.getFolder()));
}
return newDataSource;
}
}
@@ -36,7 +36,7 @@ public class WebDataSourceConnectEventListener implements DBPEventListener {
}
@Override
public void handleDataSourceEvent(@NotNull DBPEvent event) {
public void handleDataSourceEvent(DBPEvent event) {
if (!(event.getObject() instanceof DBPDataSourceContainer container)) {
return;
}
@@ -16,13 +16,11 @@
*/
package io.cloudbeaver;
import io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.utils.WebDataSourceUtils;
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.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
@@ -30,26 +28,22 @@ 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.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.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;
public class WebSessionProjectImpl extends WebProjectImpl {
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 boolean registryIsLoaded = false;
public WebSessionProjectImpl(
@@ -165,7 +159,7 @@ public class WebSessionProjectImpl extends WebProjectImpl {
*/
@NotNull
public synchronized WebConnectionInfo addConnection(@NotNull DBPDataSourceContainer dataSourceContainer) {
WebConnectionInfo connection = createConnectionInfo(dataSourceContainer);
WebConnectionInfo connection = new WebConnectionInfo(webSession, dataSourceContainer);
synchronized (connections) {
connections.put(dataSourceContainer.getId(), connection);
}
@@ -243,124 +237,4 @@ 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);
}
@NotNull
public WebConnectionInfo createConnection(@NotNull Map<String, Object> configMap) throws DBWebException {
if (CommonUtils.isEmpty(configMap)) {
throw new DBWebException("Connection configuration parameters are missing");
}
DBPDataSourceContainer newDataSource = getDataSourceContainerFromInput(getConnectionConfigInput(configMap));
return addDataSourceToProject(newDataSource);
}
@NotNull
public WebConnectionInfo addDataSourceToProject(@NotNull DBPDataSourceContainer newDataSource) throws DBWebException {
DBPDataSourceRegistry registry = getDataSourceRegistry();
try {
registry.addDataSource(newDataSource);
registry.checkForErrors();
} catch (DBException e) {
registry.removeDataSource(newDataSource);
throw new DBWebException("Failed to create connection", e);
}
WebConnectionInfo connectionInfo = addConnection(newDataSource);
webSession.addInfoMessage("New connection was created - " + WebDataSourceUtils.getConnectionContainerInfo(
newDataSource));
log.info(String.format(
"New connection was created: [info=%s, user=%s]",
WebDataSourceUtils.getConnectionContainerInfo(newDataSource),
webSession.getUserId()
));
return connectionInfo;
}
@NotNull
public WebConnectionInfo updateConnection(@Nullable Map<String, Object> configMap) throws DBWebException {
WebConnectionConfig config = getConnectionConfigInput(configMap);
WebConnectionInfo connectionInfo = getWebConnectionInfo(config.getConnectionId());
DataSourceDescriptor dataSource = (DataSourceDescriptor) connectionInfo.getDataSourceContainer();
webSession.addInfoMessage("Update connection - " + WebDataSourceUtils.getConnectionContainerInfo(dataSource));
DBPDataSourceRegistry registry = getDataSourceRegistry();
getInputConfigHandler(config).updateDataSource(dataSource);
connectionInfo.setCredentialsSavedInSession(null);
try {
registry.updateDataSource(dataSource);
registry.checkForErrors();
} catch (DBException e) {
throw new DBWebException("Failed to update connection", e);
}
return connectionInfo;
}
public boolean deleteConnection(@NotNull String connectionId) throws DBWebException {
WebConnectionInfo connectionInfo = getWebConnectionInfo(connectionId);
webSession.addInfoMessage("Delete connection - " +
WebDataSourceUtils.getConnectionContainerInfo(connectionInfo.getDataSourceContainer()));
closeAndDeleteConnection(connectionInfo);
log.info(String.format(
"Connection deleted: [info=%s, userId=%s]",
WebDataSourceUtils.getConnectionContainerInfo(connectionInfo.getDataSourceContainer()),
webSession.getUserId()
));
return true;
}
@NotNull
private WebConnectionInfo closeAndDeleteConnection(@NotNull WebConnectionInfo connectionInfo) throws DBWebException {
DBPDataSourceContainer dataSourceContainer = connectionInfo.getDataSourceContainer();
boolean disconnected = 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 WebConnectionConfig configInput) throws DBWebException {
return getInputConfigHandler(configInput).createDataSourceContainer();
}
@NotNull
public WebConnectionConfig getConnectionConfigInput(@Nullable Map<String, Object> configMap) {
return new WebConnectionConfig(configMap == null ? Map.of() : configMap);
}
@NotNull
protected WebConnectionConfigInputHandler getInputConfigHandler(@NotNull WebConnectionConfig configInput) {
return new WebConnectionConfigInputHandler<>(getDataSourceRegistry(), configInput);
}
}
@@ -74,8 +74,4 @@ public interface SMAuthProviderExternal<AUTH_SESSION extends SMSession> extends
*/
default void postAuthentication() {}
@Override
default boolean supportsOpeningSessionAsChild() {
return true;
}
}
@@ -10,7 +10,7 @@ import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
public abstract class BaseDatasourceAccessCheckHandler implements DBPDataSourceHandler {
@Override
public void beforeConnect(
@NotNull DBRProgressMonitor monitor,
DBRProgressMonitor monitor,
@NotNull DBPDataSourceContainer dataSourceContainer
) throws DBException {
if (isDriverDisabled(dataSourceContainer.getDriver())) {
@@ -20,7 +20,7 @@ public abstract class BaseDatasourceAccessCheckHandler implements DBPDataSourceH
@Override
public void beforeDisconnect(
@NotNull DBRProgressMonitor monitor,
DBRProgressMonitor monitor,
@NotNull DBPDataSourceContainer dataSourceContainer
) throws DBException {
@@ -16,11 +16,11 @@
*/
package io.cloudbeaver.model;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.connection.DBPDriverConfigurationType;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.utils.CommonUtils;
import java.util.ArrayList;
import java.util.List;
@@ -58,6 +58,7 @@ public class WebConnectionConfig {
private Map<String, Object> mainPropertyValues;
private Map<String, Object> expertSettingsValues;
private Map<String, Object> providerProperties;
private Map<String, Object> externalParameters;
private List<WebNetworkHandlerConfigInput> networkHandlersConfig;
private DBPDriverConfigurationType configurationType;
private String selectedSecretId;
@@ -68,52 +69,54 @@ public class WebConnectionConfig {
public WebConnectionConfig() {
}
public WebConnectionConfig(@NotNull Map<String, Object> params) {
connectionId = JSONUtils.getString(params, "connectionId");
driverId = JSONUtils.getString(params, "driverId");
public WebConnectionConfig(Map<String, Object> params) {
if (!CommonUtils.isEmpty(params)) {
connectionId = JSONUtils.getString(params, "connectionId");
driverId = JSONUtils.getString(params, "driverId");
host = JSONUtils.getString(params, "host");
port = JSONUtils.getString(params, "port");
serverName = JSONUtils.getString(params, "serverName");
databaseName = JSONUtils.getString(params, "databaseName");
url = JSONUtils.getString(params, "url");
host = JSONUtils.getString(params, "host");
port = JSONUtils.getString(params, "port");
serverName = JSONUtils.getString(params, "serverName");
databaseName = JSONUtils.getString(params, "databaseName");
url = JSONUtils.getString(params, "url");
name = JSONUtils.getString(params, "name");
description = JSONUtils.getString(params, "description");
folder = JSONUtils.getString(params, "folder");
name = JSONUtils.getString(params, "name");
description = JSONUtils.getString(params, "description");
folder = JSONUtils.getString(params, "folder");
properties = JSONUtils.getObjectOrNull(params, "properties");
userName = JSONUtils.getString(params, "userName");
userPassword = JSONUtils.getString(params, "userPassword");
selectedSecretId = JSONUtils.getString(params, "selectedSecretId");
properties = JSONUtils.getObjectOrNull(params, "properties");
userName = JSONUtils.getString(params, "userName");
userPassword = JSONUtils.getString(params, "userPassword");
selectedSecretId = JSONUtils.getString(params, "selectedSecretId");
authModelId = JSONUtils.getString(params, "authModelId");
credentials = JSONUtils.getObjectOrNull(params, "credentials");
saveCredentials = JSONUtils.getBoolean(params, "saveCredentials");
sharedCredentials = JSONUtils.getBoolean(params, "sharedCredentials");
authModelId = JSONUtils.getString(params, "authModelId");
credentials = JSONUtils.getObjectOrNull(params, "credentials");
saveCredentials = JSONUtils.getBoolean(params, "saveCredentials");
sharedCredentials = JSONUtils.getBoolean(params, "sharedCredentials");
mainPropertyValues = JSONUtils.getObjectOrNull(params, "mainPropertyValues");
providerProperties = JSONUtils.getObjectOrNull(params, "providerProperties");
expertSettingsValues = JSONUtils.getObjectOrNull(params, "expertSettingsValues");
keepAliveInterval = JSONUtils.getInteger(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_KEEP_ALIVE_INTERVAL, -1);
readOnly = JSONUtils.getBoolean(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_READ_ONLY);
defaultAutoCommit = JSONUtils.getBoolean(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_AUTO_COMMIT, true);
defaultCatalogName = JSONUtils.getString(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_DEFAULT_CATALOG);
defaultSchemaName = JSONUtils.getString(
expertSettingsValues != null ? expertSettingsValues : params,
WebExpertSettingsProperties.PROP_DEFAULT_SCHEMA
);
mainPropertyValues = JSONUtils.getObjectOrNull(params, "mainPropertyValues");
providerProperties = JSONUtils.getObjectOrNull(params, "providerProperties");
String configType = JSONUtils.getString(params, "configurationType");
configurationType = configType == null ? null : DBPDriverConfigurationType.valueOf(configType);
expertSettingsValues = JSONUtils.getObjectOrNull(params, "expertSettingsValues");
keepAliveInterval = JSONUtils.getInteger(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_KEEP_ALIVE_INTERVAL, -1);
readOnly = JSONUtils.getBoolean(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_READ_ONLY);
defaultAutoCommit = JSONUtils.getBoolean(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_AUTO_COMMIT, true);
defaultCatalogName = JSONUtils.getString(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_DEFAULT_CATALOG);
defaultSchemaName = JSONUtils.getString(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_DEFAULT_SCHEMA);
networkHandlersConfig = new ArrayList<>();
for (Map<String, Object> nhc : JSONUtils.getObjectList(params, "networkHandlersConfig")) {
networkHandlersConfig.add(new WebNetworkHandlerConfigInput(nhc));
String configType = JSONUtils.getString(params, "configurationType");
configurationType = configType == null ? null : DBPDriverConfigurationType.valueOf(configType);
externalParameters = JSONUtils.getObjectOrNull(params, "externalParameters");
networkHandlersConfig = new ArrayList<>();
for (Map<String, Object> nhc : JSONUtils.getObjectList(params, "networkHandlersConfig")) {
networkHandlersConfig.add(new WebNetworkHandlerConfigInput(nhc));
}
}
}
@@ -259,4 +262,8 @@ public class WebConnectionConfig {
public String getDefaultSchemaName() {
return defaultSchemaName;
}
public Map<String, Object> getExternalParameters() {
return externalParameters;
}
}
@@ -16,7 +16,6 @@
*/
package io.cloudbeaver.model;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebProjectImpl;
import io.cloudbeaver.model.app.BaseWebAppConfiguration;
import io.cloudbeaver.model.session.WebSession;
@@ -252,7 +251,6 @@ public class WebConnectionInfo {
}
@Property
@NotNull
public String[] getFeatures() {
List<String> features = new ArrayList<>();
@@ -282,13 +280,11 @@ public class WebConnectionInfo {
}
@Property
@NotNull
public DBNBrowseSettings getNavigatorSettings() {
return dataSourceContainer.getNavigatorSettings();
}
@Property
@NotNull
public List<WebDataFormat> getSupportedDataFormats() {
List<WebDataFormat> formats = new ArrayList<>();
formats.add(WebDataFormat.resultset);
@@ -303,13 +299,11 @@ 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) {
@@ -325,37 +319,21 @@ public class WebConnectionInfo {
!dataSourceContainer.getDriver().isAnonymousAccess();
}
public void validateConnection() throws DBWebException {
}
// we don't show non-secured properties in FE when connecting to DB without saved credentials
private boolean isAuthPropertiesEmpty() {
return Arrays.stream(getAuthProperties()).allMatch(f -> f.hasFeature(DBConstants.PROP_FEATURE_NON_SECURED));
}
@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);
@@ -378,7 +356,6 @@ public class WebConnectionInfo {
}
@Property
@NotNull
public List<WebNetworkHandlerConfig> getNetworkHandlersConfig() {
var registry = NetworkHandlerRegistry.getInstance();
return dataSourceContainer.getConnectionConfiguration()
@@ -393,23 +370,20 @@ 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(@Nullable Map<String, Object> authProperties, @Nullable List<WebNetworkHandlerConfigInput> networkCredentials) {
public void setSavedCredentials(Map<String, Object> authProperties, List<WebNetworkHandlerConfigInput> networkCredentials) {
this.savedAuthProperties = authProperties;
this.savedNetworkCredentials = networkCredentials;
}
@@ -434,7 +408,6 @@ public class WebConnectionInfo {
}
@Property
@NotNull
public Map<String, String> getMainPropertyValues() {
Map<String, String> mainProperties = new LinkedHashMap<>();
mainProperties.put(DBConstants.PROP_HOST, getHost());
@@ -490,7 +463,7 @@ public class WebConnectionInfo {
return dataSourceContainer.getRequiredExternalAuth();
}
private boolean hasProjectPermission(@NotNull RMProjectPermission projectPermission) {
private boolean hasProjectPermission(RMProjectPermission projectPermission) {
DBPProject project = dataSourceContainer.getProject();
if (!(project instanceof WebProjectImpl webProject)) {
return false;
@@ -530,21 +503,18 @@ 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()
@@ -552,8 +522,8 @@ public class WebConnectionInfo {
.collect(Collectors.toList());
}
@Property
@NotNull
@Property
public List<String> getTools() {
if (!session.hasPermission(RMConstants.PERMISSION_DATABASE_DEVELOPER)) {
return List.of();
@@ -1,7 +1,6 @@
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;
@@ -53,35 +52,35 @@ public class WebExpertSettingsProperties implements DBPObject {
public static class KeepAliveIntervalFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
@Override
public boolean isValidValue(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
return CommonUtils.toBoolean(!object.driver.isEmbedded(), true);
}
}
public static class AutoCommitFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
@Override
public boolean isValidValue(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
public boolean isValidValue(WebExpertSettingsProperties object, 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(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
public boolean isValidValue(WebExpertSettingsProperties object, 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(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
public boolean isValidValue(WebExpertSettingsProperties object, 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(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_SCHEMA_SELECTION), true);
}
}
@@ -41,7 +41,6 @@ import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.dbeaver.utils.RuntimeUtils;
import org.jkiss.utils.CommonUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
@@ -205,7 +204,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
initializeApplicationServices();
try {
try {
this.instanceServer = createInstanceServer();
this.instanceServer = new CloudBeaverInstanceServer();
} catch (Exception e) {
log.error("Error initializing instance server", e);
}
@@ -217,10 +216,6 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
return EXIT_OK;
}
protected CloudBeaverInstanceServer createInstanceServer() throws IOException {
return new CloudBeaverInstanceServer();
}
protected abstract void startServer() throws DBException;
@Override
@@ -286,9 +281,4 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
public ApplicationInstanceController getInstanceServer() {
return instanceServer;
}
@Override
public boolean isAnonymousAccessEnabled() {
return getAppConfiguration().isAnonymousAccessEnabled();
}
}
@@ -100,6 +100,4 @@ public interface ServletApplication extends DBPApplication {
default void getStatusInfo(Map<String, Object> infoMap) {
}
boolean isAnonymousAccessEnabled();
}
@@ -62,7 +62,6 @@ 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;
@@ -159,15 +158,7 @@ public class ServletSystemInformationCollector<T extends ServletApplication> imp
/**
* Collects info about internal databases.
*/
public synchronized void collectInternalDatabaseUseInformation() throws DBException {
if (internalDatabaseInfoCollected) {
return;
}
collectInternalDatabaseInfo();
this.internalDatabaseInfoCollected = true;
}
protected void collectInternalDatabaseInfo() throws DBException {
public void collectInternalDatabaseUseInformation() throws DBException {
this.smDatabaseInfo = application.getAdminSecurityController(new NoAuthCredentialsProvider())
.getInternalDatabaseInformation();
}
@@ -16,53 +16,20 @@
*/
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;
import org.jkiss.dbeaver.model.cli.ApplicationCommandLine;
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
public class CloudBeaverCommandLine extends ApplicationCommandLine<ApplicationInstanceController> {
@Nullable
private final CommandLineAuthenticator authenticator;
private final CloudBeaverMixin mixin;
private static CloudBeaverCommandLine INSTANCE = null;
public CloudBeaverCommandLine(@Nullable CommandLineAuthenticator authenticator) {
private CloudBeaverCommandLine() {
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);
public synchronized static CloudBeaverCommandLine getInstance() {
if (INSTANCE == null) {
INSTANCE = new CloudBeaverCommandLine();
}
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;
return INSTANCE;
}
}
@@ -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,27 +25,19 @@ 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 commandLine.executeCommandLineCommands(
return CloudBeaverCommandLine.getInstance().executeCommandLineCommands(
cmd,
this,
false,
false,
args
false
);
} catch (Exception e) {
return new CLIProcessResult(CLIProcessResult.PostAction.ERROR, "Error executing command: " + e.getMessage());
@@ -1,32 +0,0 @@
/*
* 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 picocli.CommandLine;
/**
* CloudBeaver specific mixin options
*/
public class CloudBeaverMixin {
@CommandLine.Option(
names = {"-web-config"},
arity = "1",
hidden = true,
scope = CommandLine.ScopeType.INHERIT
)
private String webConfig;
}
@@ -1,37 +0,0 @@
/*
* 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 {
protected CloudBeaverTopLevelCommand(
@Nullable ApplicationInstanceController controller,
@NotNull CommandLineContext context,
@NotNull CLIRunMeta meta
) {
super(controller, context, meta);
}
}
@@ -0,0 +1,14 @@
package io.cloudbeaver.model.log;
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})
@Retention(RetentionPolicy.RUNTIME)
public @interface Sensitive {
}
@@ -60,31 +60,26 @@ 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(@NotNull String locale) {
public String getLocalizedName(String locale) {
return getNodeDisplayName();
}
@Nullable
@Override
public String getNodeDescription() {
return project.getDescription();
}
@Nullable
@Override
public DBPImage getNodeIcon() {
return DBIcon.PROJECT;
@@ -95,7 +90,6 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
return true;
}
@Nullable
@Override
public DBNResourceManagerResource[] getChildren(@NotNull DBRProgressMonitor monitor) throws DBException {
if (children == null && !monitor.isForceCacheUsage()) {
@@ -115,16 +109,14 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
return ((DBNResourceManagerRoot) getParentNode()).getResourceController();
}
@NotNull
@Deprecated
@Override
public String getNodeItemPath() {
return getParentNode().getNodeItemPath() + "/" + getName();
}
@Nullable
@Override
public DBNNode refreshNode(@NotNull DBRProgressMonitor monitor, @Nullable Object source) throws DBException {
public DBNNode refreshNode(DBRProgressMonitor monitor, Object source) throws DBException {
children = null;
return this;
}
@@ -149,7 +141,6 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
return project;
}
@NotNull
@Override
public String toString() {
return getNodeDisplayName();
@@ -49,25 +49,21 @@ 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()) {
@@ -113,7 +109,6 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
return resource.isFolder();
}
@Nullable
@Override
public DBNNode[] getChildren(@NotNull DBRProgressMonitor monitor) throws DBException {
if (children == null && !monitor.isForceCacheUsage()) {
@@ -159,16 +154,14 @@ 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(@NotNull DBRProgressMonitor monitor, @Nullable Object source) throws DBException {
public DBNNode refreshNode(DBRProgressMonitor monitor, Object source) throws DBException {
this.children = null;
return super.refreshNode(monitor, source);
}
@@ -178,7 +171,7 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
}
@Override
public void rename(@NotNull DBRProgressMonitor monitor, @NotNull String newName) throws DBException {
public void rename(DBRProgressMonitor monitor, String newName) throws DBException {
String resourceName = resource.getName();
try {
if (newName.indexOf('.') == -1) {
@@ -199,7 +192,6 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
}
}
@NotNull
@Override
public String toString() {
return getNodeDisplayName();
@@ -50,7 +50,6 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
return resourceController;
}
@NotNull
@Override
public String getNodeType() {
return "rm";
@@ -62,19 +61,16 @@ 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;
@@ -85,7 +81,6 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
return true;
}
@Nullable
@Override
public DBNResourceManagerProject[] getChildren(@NotNull DBRProgressMonitor monitor) throws DBException {
if (projects == null && !monitor.isForceCacheUsage()) {
@@ -115,7 +110,6 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
return projects;
}
@NotNull
@Deprecated
@Override
public String getNodeItemPath() {
@@ -123,9 +117,8 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
return NodePathType.ext.getPrefix() + getNodeDisplayName();
}
@Nullable
@Override
public DBNNode refreshNode(@NotNull DBRProgressMonitor monitor, @Nullable Object source) throws DBException {
public DBNNode refreshNode(DBRProgressMonitor monitor, Object source) throws DBException {
projects = null;
return this;
}
@@ -148,9 +141,6 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
case PROJECT_ADD:
addProjectNode(event.getProject());
break;
case PROJECT_UPDATE:
updateProjectNode(event.getProject());
break;
}
}
@@ -197,14 +187,4 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
private void addProjectNode(RMProject project) {
projects = ArrayUtils.add(DBNResourceManagerProject.class, projects, new DBNResourceManagerProject(this, project));
}
private void updateProjectNode(@NotNull RMProject project) {
var projectNode = getProjectNode(project);
projectNode.ifPresent(
dbnResourceManagerProject -> {
dbnResourceManagerProject.getProject().setName(project.getName());
dbnResourceManagerProject.getProject().setDescription(project.getDescription());
}
);
}
}
@@ -99,7 +99,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String propName,
@NotNull Object propValue
) throws DBException {
RMLocalProject webProject = getWebProject(projectId, false);
BaseWebProjectImpl webProject = getWebProject(projectId, false);
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
() -> {
log.debug("Updating value for property '" + propName + "' in project '" + projectId + "'");
@@ -284,7 +284,7 @@ public abstract class BaseLocalResourceController implements RMController {
}
}
protected abstract RMLocalProject getWebProject(String projectId, boolean refresh) throws DBException;
protected abstract BaseWebProjectImpl getWebProject(String projectId, boolean refresh) throws DBException;
protected abstract <T> T doFileWriteOperation(String projectId, Path file, RMFileOperation<T> operation)
throws DBException;
@@ -16,12 +16,12 @@
*/
package io.cloudbeaver.model.rm.local;
import io.cloudbeaver.BaseWebProjectImpl;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.model.app.ServletApplication;
import io.cloudbeaver.service.security.SMUtils;
import io.cloudbeaver.service.sql.WebSQLConstants;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebRMUtils;
import io.cloudbeaver.utils.file.UniversalFileVisitor;
import org.eclipse.core.runtime.IPath;
import org.jkiss.code.NotNull;
@@ -81,7 +81,7 @@ public class LocalResourceController extends BaseLocalResourceController {
private Supplier<SMController> smControllerSupplier;
protected final List<RMFileOperationHandler> fileHandlers;
private final Map<String, RMLocalProject> projectRegistries = new LinkedHashMap<>();
private final Map<String, BaseWebProjectImpl> projectRegistries = new LinkedHashMap<>();
public LocalResourceController(
DBPWorkspace workspace,
@@ -116,11 +116,13 @@ public class LocalResourceController extends BaseLocalResourceController {
return userId == null ? null : this.userProjectsPath.resolve(userId);
}
protected RMLocalProject getWebProject(String projectId, boolean refresh) throws DBException {
protected BaseWebProjectImpl getWebProject(String projectId, boolean refresh) throws DBException {
synchronized (projectRegistries) {
RMLocalProject project = projectRegistries.get(projectId);
BaseWebProjectImpl project = projectRegistries.get(projectId);
if (project == null || refresh) {
project = createWebProjectImpl(projectId, new SessionContextImpl(null));
SessionContextImpl sessionContext = new SessionContextImpl(null);
RMProject rmProject = makeProjectFromId(projectId, false);
project = createWebProjectImpl(projectId, sessionContext, rmProject);
projectRegistries.put(projectId, project);
}
return project;
@@ -128,11 +130,12 @@ public class LocalResourceController extends BaseLocalResourceController {
}
@NotNull
protected RMLocalProject createWebProjectImpl(
@NotNull String projectId,
@NotNull SessionContextImpl sessionContext
protected InternalWebProjectImpl createWebProjectImpl(
String projectId,
SessionContextImpl sessionContext,
RMProject rmProject
) throws DBException {
return new RMLocalProject(workspace, sessionContext, getProjectPath(projectId), WebRMUtils.parseProjectName(projectId).getType());
return new InternalWebProjectImpl(sessionContext, rmProject, getProjectPath(projectId));
}
@NotNull
@@ -187,7 +190,7 @@ public class LocalResourceController extends BaseLocalResourceController {
.stream()
.filter(smObjectPermissions -> CommonUtils.isNotEmpty(smObjectPermissions.getObjectId()))
.map(projectPermission -> makeProjectFromPath(
sharedProjectsPath.resolve(WebRMUtils.parseProjectNameUnsafe(projectPermission.getObjectId()).getName()),
sharedProjectsPath.resolve(parseProjectNameUnsafe(projectPermission.getObjectId()).getName()),
Arrays.stream(projectPermission.getPermissions()).map(RMProjectPermission::fromPermission).collect(Collectors.toSet()),
RMProjectType.SHARED, true)
)
@@ -253,7 +256,7 @@ public class LocalResourceController extends BaseLocalResourceController {
var allPaths = list.toList();
for (Path path : allPaths) {
var projectPerms = getProjectPermissions(
RMUtils.makeProjectIdFromPath(path, RMProjectType.SHARED),
makeProjectIdFromPath(path, RMProjectType.SHARED),
RMProjectType.SHARED
);
var rmProject = makeProjectFromPath(path, projectPerms, RMProjectType.SHARED, false);
@@ -278,54 +281,28 @@ public class LocalResourceController extends BaseLocalResourceController {
}
}
validateResourcePath(name);
validateProjectName(null, name);
RMProject project;
var projectPath = sharedProjectsPath.resolve(name);
if (Files.exists(projectPath)) {
throw new DBException("Project '" + name + "' already exists");
}
RMLocalProject webProject = new RMLocalProject(workspace, new SessionContextImpl(null), projectPath, RMProjectType.SHARED);
webProject.updateProject(name, description);
RMProject rmProject = createRmProjectFromWebProject(projectPath, webProject, new String[0]);
project = makeProjectFromPath(projectPath, Set.of(), RMProjectType.SHARED, false);
if (project == null) {
throw new DBException("Project '" + name + "' not created");
}
try {
log.debug("Creating project '" + rmProject.getId() + "'");
log.debug("Creating project '" + project.getId() + "'");
Files.createDirectories(projectPath);
if (ServletAppUtils.getServletApplication().isMultiNode()) {
createResourceTypeFolders(projectPath);
}
fireRmProjectAddEvent(rmProject);
return rmProject;
fireRmProjectAddEvent(project);
return project;
} catch (IOException e) {
throw new DBException("Error creating project path", e);
}
}
@Override
public RMProject updateProject(@NotNull String projectId, @NotNull RMProjectInfo projectInfo) throws DBException {
validateProjectName(projectId, projectInfo.getName());
try (var projectLock = lockController.lock(projectId, "updateProject")) {
RMLocalProject project = getWebProject(projectId, false);
Path targetPath = getProjectPath(projectId);
if (!Files.exists(targetPath)) {
throw new DBException("Project folder '" + projectId + "' not found");
}
if (!project.canUpdateProjectName()) {
throw new DBException("Project '" + projectId + "' is not shared");
}
project.updateProject(projectInfo.getName(), projectInfo.getDescription());
return WebRMUtils.createRmProjectFromWebProject(project);
}
}
private void validateProjectName(@Nullable String projectId, @Nullable String name) throws DBException {
boolean duplicatedName = Arrays.stream(listAllSharedProjects())
.filter(p -> !p.getId().equals(projectId))
.map(RMProject::getName)
.noneMatch(n -> n.equalsIgnoreCase(name));
if (!duplicatedName) {
throw new DBException("Project name '" + name + "' is already used");
}
}
@Override
public void deleteProject(@NotNull String projectId) throws DBException {
try (var projectLock = lockController.lock(projectId, "deleteProject")) {
@@ -898,7 +875,7 @@ public class LocalResourceController extends BaseLocalResourceController {
) throws DBException {
try (var ignoredLock = lockController.lock(projectId, "resourcePropertyUpdate")) {
validateResourcePath(resourcePath);
RMLocalProject webProject = getWebProject(projectId, false);
BaseWebProjectImpl webProject = getWebProject(projectId, false);
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
() -> {
log.debug("Updating resource property '" + propertyName + "' in project '" + projectId + "'");
@@ -919,7 +896,7 @@ public class LocalResourceController extends BaseLocalResourceController {
) throws DBException {
try (var ignoredLock = lockController.lock(projectId, "resourcePropertyUpdate")) {
validateResourcePath(resourcePath);
RMLocalProject webProject = getWebProject(projectId, false);
BaseWebProjectImpl webProject = getWebProject(projectId, false);
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
() -> {
log.debug("Updating resource '" + resourcePath + "' properties in project '" + projectId + "'");
@@ -953,9 +930,15 @@ public class LocalResourceController extends BaseLocalResourceController {
}
}
private String makeProjectIdFromPath(Path path, RMProjectType type) {
String projectName = path.getFileName().toString();
return type.getPrefix() + "_" + projectName;
}
@Nullable
protected RMProject makeProjectFromId(String projectId, boolean loadPermissions) throws DBException {
var projectName = WebRMUtils.parseProjectName(projectId);
var projectName = parseProjectName(projectId);
var projectPath = getProjectPath(projectId);
if (!Files.exists(projectPath)) {
if (isPrivateProject(projectId) && isProjectOwner(projectId)) {
@@ -992,8 +975,28 @@ public class LocalResourceController extends BaseLocalResourceController {
.flatMap(rmProjectPermission -> rmProjectPermission.getAllPermissions().stream())
.toArray(String[]::new);
RMLocalProject webProject = new RMLocalProject(workspace, new SessionContextImpl(null), path, type);
return createRmProjectFromWebProject(path, webProject, allProjectPermissions);
RMProject project = new RMProject();
String projectName = path.getFileName().toString();
project.setName(projectName);
project.setId(makeProjectIdFromPath(path, type));
project.setType(type);
project.setProjectPermissions(allProjectPermissions);
if (Files.exists(path)) {
try {
project.setCreateTime(
OffsetDateTime.ofInstant(Files.getLastModifiedTime(path).toInstant(), ZoneId.of("UTC")).toInstant().toEpochMilli());
} catch (IOException e) {
log.error(e);
}
}
// Resource types
project.setResourceTypes(ResourceTypeRegistry.getInstance().getResourceTypes()
.stream()
.filter(ResourceTypeDescriptor::isManagable)
.map(RMResourceType::new)
.toArray(RMResourceType[]::new));
return project;
}
private void createResourceTypeFolders(Path path) {
@@ -1072,7 +1075,7 @@ public class LocalResourceController extends BaseLocalResourceController {
}
protected Path getProjectPath(String projectId) throws DBException {
RMProjectName project = WebRMUtils.parseProjectName(projectId);
RMProjectName project = parseProjectName(projectId);
RMProjectType type = project.getType();
String projectName = project.getName();
switch (type) {
@@ -1234,13 +1237,56 @@ public class LocalResourceController extends BaseLocalResourceController {
}
}
public static class RMProjectName {
String prefix;
String name;
private RMProjectName(String prefix, String name) {
this.prefix = prefix;
this.name = name;
}
public String getPrefix() {
return prefix;
}
public String getName() {
return name;
}
public RMProjectType getType() {
return RMProjectType.getByPrefix(prefix);
}
}
public static RMProjectName parseProjectName(String projectId) throws DBException {
if (CommonUtils.isEmpty(projectId)) {
throw new DBException("Project id is empty");
}
return parseProjectNameUnsafe(projectId);
}
private static RMProjectName parseProjectNameUnsafe(String projectId) {
String prefix;
String name;
int divPos = projectId.indexOf("_");
if (divPos < 0) {
prefix = RMProjectType.USER.getPrefix();
name = projectId;
} else {
prefix = projectId.substring(0, divPos);
name = projectId.substring(divPos + 1);
}
return new RMProjectName(prefix, name);
}
public static boolean isGlobalProject(String projectId) {
RMProjectName rmProjectName = WebRMUtils.parseProjectNameUnsafe(projectId);
RMProjectName rmProjectName = parseProjectNameUnsafe(projectId);
return RMProjectType.GLOBAL.getPrefix().equals(rmProjectName.getPrefix());
}
public static boolean isPrivateProject(String projectId) {
RMProjectName rmProjectName = WebRMUtils.parseProjectNameUnsafe(projectId);
RMProjectName rmProjectName = parseProjectNameUnsafe(projectId);
return RMProjectType.USER.getPrefix().equals(rmProjectName.getPrefix());
}
@@ -1251,32 +1297,9 @@ public class LocalResourceController extends BaseLocalResourceController {
}
public static boolean isProjectOwner(String projectId, String userId) {
RMProjectName rmProjectName = WebRMUtils.parseProjectNameUnsafe(projectId);
RMProjectName rmProjectName = parseProjectNameUnsafe(projectId);
return RMProjectType.USER.getPrefix().equals(rmProjectName.getPrefix()) &&
rmProjectName.name.equals(userId);
}
@NotNull
private static RMProject createRmProjectFromWebProject(Path path, RMLocalProject webProject, String[] allProjectPermissions) {
RMProject project = WebRMUtils.createRmProjectFromWebProject(webProject);
project.setProjectPermissions(allProjectPermissions);
if (Files.exists(path)) {
try {
project.setCreateTime(
OffsetDateTime.ofInstant(Files.getLastModifiedTime(path).toInstant(), ZoneId.of("UTC")).toInstant().toEpochMilli());
} catch (IOException e) {
log.error(e);
}
}
// Resource types
project.setResourceTypes(ResourceTypeRegistry.getInstance().getResourceTypes()
.stream()
.filter(ResourceTypeDescriptor::isManageable)
.map(RMResourceType::new)
.toArray(RMResourceType[]::new));
return project;
}
}
@@ -1,58 +0,0 @@
/*
* 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 org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.model.rm.RMUtils;
import org.jkiss.dbeaver.registry.project.LocalProjectImpl;
import java.nio.file.Path;
public class RMLocalProject extends LocalProjectImpl {
@NotNull
private final RMProjectType projectType;
public RMLocalProject(
@NotNull DBPWorkspace workspace,
@Nullable SMSessionContext sessionContext,
@NotNull Path projectPath,
@NotNull RMProjectType type
) {
super(workspace, sessionContext, projectPath);
this.projectType = type;
}
@Override
public boolean isVirtual() {
return true;
}
@NotNull
@Override
public String getId() {
return RMUtils.makeProjectIdFromPath(projectPath, projectType);
}
public boolean canUpdateProjectName() {
return RMProjectType.SHARED.equals(projectType);
}
}
@@ -1,41 +0,0 @@
/*
* 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 org.jkiss.dbeaver.model.rm.RMProjectType;
public class RMProjectName {
String prefix;
String name;
public RMProjectName(String prefix, String name) {
this.prefix = prefix;
this.name = name;
}
public String getPrefix() {
return prefix;
}
public String getName() {
return name;
}
public RMProjectType getType() {
return RMProjectType.getByPrefix(prefix);
}
}
@@ -29,7 +29,6 @@ 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.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;
@@ -58,7 +57,7 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
protected volatile long lastAccessTime;
private final List<CBWebSessionEventHandler> sessionEventHandlers = new CopyOnWriteArrayList<>();
private WebSessionEventsFilter eventsFilter;
private WebSessionEventsFilter eventsFilter = new WebSessionEventsFilter();
private final WebSessionWorkspace workspace;
public BaseWebSession(@NotNull String id, @NotNull ServletApplication application) throws DBException {
@@ -69,7 +68,6 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
this.workspace = createWebWorkspace();
this.workspace.getAuthContext().addSession(this);
this.userContext = createUserContext();
this.eventsFilter = new WebSessionEventsFilter(this);
}
@NotNull
@@ -235,11 +233,6 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
userContext.getAccessibleProjectIds().add(projectId);
}
public void updateSessionProject(@NotNull String projectId, @NotNull RMProjectInfo rmProjectInfo) throws DBException {
}
public void removeSessionProject(@Nullable String projectId) throws DBException {
userContext.getAccessibleProjectIds().remove(projectId);
}
@@ -254,12 +247,8 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
@Property
public long getRemainingTime() {
if (application instanceof ServletAuthApplication authApplication) {
return getMaxSessionIdleTime(authApplication) + lastAccessTime - System.currentTimeMillis();
return authApplication.getMaxSessionIdleTime() + lastAccessTime - System.currentTimeMillis();
}
return Integer.MAX_VALUE;
}
protected long getMaxSessionIdleTime(@NotNull ServletAuthApplication authApplication) {
return authApplication.getMaxSessionIdleTime();
}
}
@@ -49,7 +49,10 @@ 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;
import org.jkiss.dbeaver.model.rm.*;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.model.rm.RMUtils;
import org.jkiss.dbeaver.model.runtime.AbstractJob;
import org.jkiss.dbeaver.model.runtime.BaseProgressMonitor;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
@@ -66,7 +69,6 @@ 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;
@@ -305,7 +307,7 @@ public class WebSession extends BaseWebSession
for (RMProject project : rmProjects) {
createWebProject(project);
}
if (user == null && application.isAnonymousAccessEnabled()) {
if (user == null && application.getAppConfiguration().isAnonymousAccessEnabled()) {
WebProjectImpl anonymousProject = createWebProject(RMUtils.createAnonymousProject());
anonymousProject.setInMemory(true);
}
@@ -325,7 +327,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);
@@ -398,10 +399,9 @@ public class WebSession extends BaseWebSession
private synchronized void authAsAnonymousUser() throws DBException {
if (!application.isAnonymousAccessEnabled()) {
if (!application.getAppConfiguration().isAnonymousAccessEnabled()) {
return;
}
SMAuthInfo authInfo = getSecurityController().authenticateAnonymousUser(this.id, getSessionParameters(), CB_SESSION_TYPE);
updateSMSession(authInfo);
notifySessionAuthChange();
@@ -587,11 +587,8 @@ public class WebSession extends BaseWebSession
public WebAsyncTaskInfo createAndRunAsyncTask(@NotNull String taskName, @NotNull WebAsyncTaskProcessor<?> runnable) {
WebAsyncTaskInfo asyncTask = createAsyncTask(taskName);
return runAsyncTask(asyncTask, runnable);
}
public WebAsyncTaskInfo runAsyncTask(@NotNull WebAsyncTaskInfo asyncTask, @NotNull WebAsyncTaskProcessor<?> runnable) {
AbstractJob job = new AbstractJob(asyncTask.getName()) {
AbstractJob job = new AbstractJob(taskName) {
@Override
protected IStatus run(DBRProgressMonitor monitor) {
int curTaskCount = taskCount.incrementAndGet();
@@ -984,16 +981,6 @@ public class WebSession extends BaseWebSession
createWebProject(rmProject);
}
@Override
public void updateSessionProject(@NotNull String projectId, @NotNull RMProjectInfo rmProjectInfo) throws DBException {
super.updateSessionProject(projectId, rmProjectInfo);
var project = getProjectById(projectId);
if (project == null) {
return;
}
project.updateProjectInfo(rmProjectInfo.getName(), rmProjectInfo.getDescription());
}
@Override
public void removeSessionProject(@Nullable String projectId) throws DBException {
super.removeSessionProject(projectId);
@@ -1023,25 +1010,6 @@ public class WebSession extends BaseWebSession
return globalProject;
}
public void handleTaskConfirmation(
@NotNull String taskId,
boolean confirmed,
boolean skipConfirmations
) {
String attributeName = WebSQLConstants.TASK_CONFIRMATION_ATTR_PREFIX + 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);
}
}
private class SessionProgressMonitor extends BaseProgressMonitor {
@Override
public void beginTask(String name, int totalWork) {
@@ -21,37 +21,18 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
import org.jkiss.dbeaver.model.websocket.event.WSProjectResourceEvent;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
public class WebSessionEventsFilter {
private final BaseWebSession session;
private final Set<String> subscribedEventTopics = new CopyOnWriteArraySet<>();
private final Set<String> subscribedProjectIds = new CopyOnWriteArraySet<>();
private final Map<String, List<WebTopicActivityListener>> topicActivityListeners = new ConcurrentHashMap<>();
private final Set<String> handledStartTopics = new CopyOnWriteArraySet<>();
public WebSessionEventsFilter(BaseWebSession session) {
this.session = session;
}
public void subscribeOnEventTopic(@Nullable String topic) {
if (topic == null) {
return;
}
subscribedEventTopics.add(topic);
topicActivityListeners
.getOrDefault(topic, List.of()).stream()
.filter(listener -> !handledStartTopics.contains(listener.getTopicName()))
.forEach(listener -> {
listener.handelStartSubscriptionTopic(session);
handledStartTopics.add(topic);
});
}
public void unsubscribeFromEventTopic(@Nullable String topic) {
@@ -86,10 +67,4 @@ public class WebSessionEventsFilter {
return true;
}
public void addTopicSubscribeListener(WebTopicActivityListener listener) {
topicActivityListeners
.computeIfAbsent(listener.getTopicName(), k -> new CopyOnWriteArrayList<>())
.add(listener);
}
}
@@ -20,7 +20,6 @@ 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;
@@ -86,13 +85,4 @@ 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,24 +0,0 @@
/*
* 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.session;
public interface WebTopicActivityListener {
void handelStartSubscriptionTopic(BaseWebSession session);
String getTopicName();
}
@@ -190,10 +190,6 @@ public class WebUserContext implements SMCredentialsProvider {
return isAuthorizedInSecurityManager() && getUser() != null;
}
public synchronized boolean isAnonymousUserAuthorizedInSM() {
return isAuthorizedInSecurityManager() && getUser() == null;
}
@Nullable
public synchronized WebUser getUser() {
return user;
@@ -243,7 +239,7 @@ public class WebUserContext implements SMCredentialsProvider {
}
private Set<String> getDefaultPermissions() {
return application.isAnonymousAccessEnabled() ? null : Set.of();
return application.getAppConfiguration().isAnonymousAccessEnabled() ? null : Set.of();
}
public RMController getRmController() {
@@ -17,18 +17,12 @@
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;
@@ -36,7 +30,6 @@ 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);
@@ -46,7 +39,6 @@ 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
@@ -71,34 +63,12 @@ public class WebAuthProviderProperty extends LocalizedPropertyDescriptor impleme
return readOnlyExpr;
}
@NotNull
@Override
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() {
public String getPropertyId() {
if (authProviderId != null) {
return WEB_AUTH_PROPERTY_PREFIX + authProviderId + "." + this.getId();
return "prop.auth.model." + authProviderId + "." + this.getId();
} else {
return WEB_AUTH_PROPERTY_PREFIX + this.getId();
return "prop.auth.model." + 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;
}
}
@@ -56,7 +56,6 @@ public abstract class BaseServletPlatform extends BasePlatformImpl {
return folder;
}
@NotNull
public abstract ServletApplication getApplication();
private void initTempFolder(@NotNull DBRProgressMonitor monitor) {
@@ -45,7 +45,4 @@ 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;
}
@@ -21,7 +21,6 @@ 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.*;
@@ -43,8 +42,6 @@ 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) {
@@ -154,13 +151,4 @@ 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;
}
}
@@ -23,7 +23,6 @@ import com.google.gson.Strictness;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebSessionProjectImpl;
import io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.WebNetworkHandlerConfigInput;
import io.cloudbeaver.model.session.WebSession;
@@ -31,25 +30,21 @@ 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.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;
import org.jkiss.dbeaver.model.connection.DBPDataSourceProviderDescriptor;
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.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.utils.CommonUtils;
import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class WebDataSourceUtils {
@@ -224,181 +219,4 @@ public class WebDataSourceUtils {
}
credGson.fromJson(credGson.toJsonTree(properties), credentials.getClass());
}
public static void saveAuthProperties(
@NotNull DBPDataSourceContainer dataSourceContainer,
@NotNull DBPConnectionConfiguration configuration,
@Nullable Map<String, Object> authProperties,
boolean saveCredentials,
boolean sharedCredentials
) {
saveAuthProperties(dataSourceContainer, configuration, authProperties, saveCredentials, sharedCredentials, false);
}
public static void saveAuthProperties(
@NotNull DBPDataSourceContainer dataSourceContainer,
@NotNull DBPConnectionConfiguration configuration,
@Nullable Map<String, Object> authProperties,
boolean saveCredentials,
boolean sharedCredentials,
boolean isTest
) {
dataSourceContainer.setSavePassword(saveCredentials);
dataSourceContainer.setSharedCredentials(sharedCredentials);
if (!saveCredentials) {
// Reset credentials
if (authProperties == null) {
authProperties = new LinkedHashMap<>();
}
authProperties.replace(AuthModelDatabaseNativeCredentials.PROP_USER_PASSWORD, null);
dataSourceContainer.resetPassword();
} else {
if (authProperties == null) {
// No changes
return;
}
}
{
// Read save credentials
DBAAuthCredentials credentials = configuration.getAuthModel().loadCredentials(dataSourceContainer, configuration);
if (isTest) {
var currentAuthProps = new HashMap<String, String>();
for (Map.Entry<String, Object> stringObjectEntry : authProperties.entrySet()) {
var value = stringObjectEntry.getValue() == null ? null : stringObjectEntry.getValue().toString();
currentAuthProps.put(stringObjectEntry.getKey(), value);
}
configuration.setAuthProperties(currentAuthProps);
}
if (!authProperties.isEmpty()) {
updateCredentialsFromProperties(credentials, authProperties);
}
configuration.getAuthModel().saveCredentials(dataSourceContainer, configuration, credentials);
}
}
public static void setConnectionConfiguration(
@NotNull DBPDriver driver,
@NotNull DBPConnectionConfiguration dsConfig,
@NotNull WebConnectionConfig config
) {
setMainProperties(dsConfig, config);
if (config.getProperties() != null) {
Map<String, String> newProps = new LinkedHashMap<>();
for (Map.Entry<String, Object> pe : config.getProperties().entrySet()) {
newProps.put(pe.getKey(), CommonUtils.toString(pe.getValue()));
}
dsConfig.setProperties(newProps);
}
if (config.getUserName() != null) {
dsConfig.setUserName(config.getUserName());
}
if (config.getUserPassword() != null) {
dsConfig.setUserPassword(config.getUserPassword());
}
if (config.getAuthModelId() != null) {
dsConfig.setAuthModelId(config.getAuthModelId());
}
if (config.getKeepAliveInterval() >= 0) {
dsConfig.setKeepAliveInterval(config.getKeepAliveInterval());
}
if (config.isDefaultAutoCommit() != null) {
dsConfig.getBootstrap().setDefaultAutoCommit(config.isDefaultAutoCommit());
}
dsConfig.getBootstrap().setDefaultCatalogName(config.getDefaultCatalogName());
dsConfig.getBootstrap().setDefaultSchemaName(config.getDefaultSchemaName());
// Save provider props
if (config.getProviderProperties() != null) {
dsConfig.setProviderProperties(new LinkedHashMap<>());
for (Map.Entry<String, Object> e : config.getProviderProperties().entrySet()) {
dsConfig.setProviderProperty(e.getKey(), CommonUtils.toString(e.getValue()));
}
}
if (config.getConfigurationType() != null) {
dsConfig.setConfigurationType(config.getConfigurationType());
}
if (CommonUtils.isEmpty(config.getUrl())) {
dsConfig.setUrl(driver.getConnectionURL(dsConfig));
}
// Save network handlers
if (config.getNetworkHandlersConfig() != null) {
for (WebNetworkHandlerConfigInput nhc : config.getNetworkHandlersConfig()) {
DBWHandlerConfiguration handlerConfig = dsConfig.getHandler(nhc.getId());
if (handlerConfig == null) {
NetworkHandlerDescriptor handlerDescriptor = NetworkHandlerRegistry.getInstance().getDescriptor(nhc.getId());
if (handlerDescriptor == null) {
log.warn("Can't find network handler '" + nhc.getId() + "'");
continue;
} else {
handlerConfig = new DBWHandlerConfiguration(handlerDescriptor, null);
WebDataSourceUtils.updateHandlerConfig(handlerConfig, nhc);
}
} else {
WebDataSourceUtils.updateHandlerConfig(handlerConfig, nhc);
}
dsConfig.updateHandler(handlerConfig);
}
}
}
public static void setMainProperties(@NotNull DBPConnectionConfiguration dsConfig, @NotNull WebConnectionConfig config) {
if (CommonUtils.isNotEmpty(config.getUrl())) {
dsConfig.setUrl(config.getUrl());
return;
}
if (config.getMainPropertyValues() != null) {
for (Map.Entry<String, Object> e : config.getMainPropertyValues().entrySet()) {
if (e.getValue() == null) {
continue;
}
switch (e.getKey()) {
case DBConstants.PROP_HOST -> dsConfig.setHostName(CommonUtils.toString(e.getValue()));
case DBConstants.PROP_PORT -> dsConfig.setHostPort(CommonUtils.toString(e.getValue()));
case DBConstants.PROP_DATABASE -> dsConfig.setDatabaseName(CommonUtils.toString(e.getValue()));
case DBConstants.PROP_SERVER -> dsConfig.setServerName(CommonUtils.toString(e.getValue()));
default -> throw new IllegalStateException("Unexpected value: " + e.getKey());
}
}
return;
}
if (config.getHost() != null) {
dsConfig.setHostName(config.getHost());
}
if (config.getPort() != null) {
dsConfig.setHostPort(config.getPort());
}
if (config.getDatabaseName() != null) {
dsConfig.setDatabaseName(config.getDatabaseName());
}
if (config.getServerName() != null) {
dsConfig.setServerName(config.getServerName());
}
}
@NotNull
public static DBPDriver getDriverById(String id) throws DBWebException {
int divPos = id.indexOf(':');
if (divPos < 0) {
throw new DBWebException("Bad driver id [" + id + "]");
}
String dsId = id.substring(0, divPos);
String driverId = id.substring(divPos + 1);
DBPDataSourceProviderDescriptor dsProvider = DBWorkbench.getPlatform().getDataSourceProviderRegistry().getDataSourceProvider(dsId);
if (dsProvider == null) {
throw new DBWebException("Data source provider '" + dsId + "' not found");
}
DBPDriver driver = dsProvider.getDriver(driverId);
if (driver == null) {
throw new DBWebException("Driver '" + driverId + "' not found in provider '" + dsId + "'");
}
return driver;
}
public static String getConnectionContainerInfo(@Nullable DBPDataSourceContainer container) {
if (container == null) {
return null;
}
return container.getName() + " [" + container.getId() + "]";
}
}
@@ -1,42 +0,0 @@
package io.cloudbeaver.utils;
import io.cloudbeaver.model.rm.local.RMProjectName;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.utils.CommonUtils;
public class WebRMUtils {
public static RMProject createRmProjectFromWebProject(@NotNull DBPProject project) {
RMProject rmProject = new RMProject();
rmProject.setId(project.getId());
rmProject.setName(project.getName());
rmProject.setDescription(project.getDescription());
rmProject.setType(parseProjectNameUnsafe(rmProject.getId()).getType());
return rmProject;
}
public static RMProjectName parseProjectName(@NotNull String projectId) throws DBException {
if (CommonUtils.isEmpty(projectId)) {
throw new DBException("Project id is empty");
}
return parseProjectNameUnsafe(projectId);
}
public static RMProjectName parseProjectNameUnsafe(String projectId) {
String prefix;
String name;
int divPos = projectId.indexOf("_");
if (divPos < 0) {
prefix = RMProjectType.USER.getPrefix();
name = projectId;
} else {
prefix = projectId.substring(0, divPos);
name = projectId.substring(divPos + 1);
}
return new RMProjectName(prefix, name);
}
}
@@ -1,53 +0,0 @@
/*
* 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;
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;
public class WSServerNotificationEvent extends WSAbstractSessionEvent {
public static final String EVENT_ID = "cb_server_notification";
private String title;
private String message;
private WSServerNotificationEventType notificationType;
public WSServerNotificationEvent(
@Nullable String title,
@Nullable String message,
@NotNull WSServerNotificationEventType notificationType
) {
super(EVENT_ID, WSConstants.TOPIC_USER_NOTIFICATION);
this.title = title;
this.message = message;
this.notificationType = notificationType;
}
public String getTitle() {
return title;
}
public String getMessage() {
return message;
}
public WSServerNotificationEventType getNotificationType() {
return notificationType;
}
}
@@ -1,24 +0,0 @@
/*
* 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;
public enum WSServerNotificationEventType {
INFO,
ERROR,
LOADING,
CUSTOM
}
@@ -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.3.0.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 25.2.2.qualifier
Bundle-Release-Date: 20251006
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.3.0-SNAPSHOT</version>
<version>25.2.2-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.135.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 1.0.131.qualifier
Bundle-Release-Date: 20251006
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
<version>1.0.135-SNAPSHOT</version>
<version>1.0.131-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver CE Server
Bundle-SymbolicName: io.cloudbeaver.server.ce;singleton:=true
Bundle-Version: 25.3.0.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 25.2.2.qualifier
Bundle-Release-Date: 20251006
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server.ce</artifactId>
<version>25.3.0-SNAPSHOT</version>
<version>25.2.2-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -30,6 +30,8 @@ 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.ConnectionController;
import io.cloudbeaver.service.ConnectionControllerCE;
import io.cloudbeaver.service.DBWServiceInitializer;
import io.cloudbeaver.service.DBWServiceServerConfigurator;
import io.cloudbeaver.service.session.CBSessionManager;
@@ -314,6 +316,11 @@ public abstract class CBApplication<T extends CBServerConfig>
}
grantPermissionsToConnections();
}
try {
this.systemInformationCollector.collectInternalDatabaseUseInformation();
} catch (DBException e) {
log.error("Error collecting system information", e);
}
eventController.scheduleCheckJob();
@@ -395,9 +402,6 @@ public abstract class CBApplication<T extends CBServerConfig>
getAppConfiguration(),
null
);
if (!isMultiNode()) {
grantPermissionsToConnections();
}
} catch (Exception e) {
log.error("Error loading server auto configuration", e);
}
@@ -781,6 +785,11 @@ public abstract class CBApplication<T extends CBServerConfig>
return new CBWebServerConfig(this);
}
@Override
public ConnectionController getConnectionController() {
return new ConnectionControllerCE();
}
@NotNull
@Override
public ServletSystemInformationCollector<?> getSystemInformationCollector() {
@@ -17,6 +17,8 @@
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;
@@ -219,13 +219,8 @@ 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);
}
@@ -36,6 +36,8 @@ import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.auth.SMAuthInfo;
import org.jkiss.dbeaver.model.security.user.SMAuthPermissions;
import org.jkiss.dbeaver.model.websocket.event.WSAbstractEvent;
import org.jkiss.dbeaver.model.websocket.event.WSUserDeletedEvent;
import org.jkiss.dbeaver.model.websocket.event.WSUserDisabledEvent;
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionStateEvent;
import org.jkiss.utils.CommonUtils;
@@ -70,19 +72,13 @@ public class CBSessionManager implements WebAppSessionManager {
@Override
public BaseWebSession closeSession(@NotNull String sessionId) {
return closeSession(sessionId, true);
}
@Override
public BaseWebSession closeSession(@NotNull String sessionId, boolean sendSessionExpiredEvent) {
BaseWebSession webSession;
synchronized (sessionMap) {
webSession = sessionMap.remove(sessionId);
}
if (webSession != null) {
log.debug("> Close session '" + sessionId + "'");
webSession.close(true, sendSessionExpiredEvent);
webSession.close();
return webSession;
}
return null;
@@ -304,11 +300,14 @@ 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();
if (!session.isValid()) {
long idleMillis = System.currentTimeMillis() - session.getLastAccessTimeMillis();
if (idleMillis >= maxSessionIdleTime) {
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.3.0.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 25.2.2.qualifier
Bundle-Release-Date: 20251006
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.eclipse.core.runtime;visibility:=reexport,
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>25.3.0-SNAPSHOT</version>
<version>25.2.2-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -45,8 +45,6 @@ enum CBServerEventId {
"Resource manager project added"
cb_rm_project_added,
"Resource manager project added"
cb_rm_project_updated,
"Resource manager project removed"
cb_rm_project_removed,
@@ -66,13 +64,7 @@ enum CBServerEventId {
"Session async task info updated"
cb_session_task_info_updated @since(version: "24.3.1"),
"Workspace configuration updated"
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_workspace_config_changed @since(version: "25.1.1")
}
"Events sent by client"
@@ -80,8 +72,7 @@ enum CBClientEventId {
cb_client_topic_subscribe,
cb_client_topic_unsubscribe,
cb_client_projects_active,
cb_client_session_ping,
cb_client_session_task_confirmation@since(version: "25.2.2")
cb_client_session_ping
}
"Client subscribes on topic to receive only related events"
@@ -105,7 +96,6 @@ enum CBEventTopic {
cb_transaction @since(version: "24.3.3"),
cb_workspace_configuration @since(version: "25.1.1")
cb_ai @since(version: "25.1.1")
cb_notification @since(version: "25.2.1")
}
"Base server event interface"
@@ -284,46 +274,6 @@ type WSAIChatMessageErrorEvent implements CBServerEvent @since(version: "25.1.1"
errorMessage: String
}
enum WSServerNotificationEventType {
INFO
ERROR
LOADING
CUSTOM
}
"General event for showing user notification"
type WSServerNotificationEvent implements CBServerEvent @since(version: "25.2.1") {
id: CBServerEventId!
topicId: CBEventTopic!
title: String
message: String
notificationType: WSServerNotificationEventType!
}
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!
}
extend type Query {
emptyEvent: Boolean
}
@@ -119,8 +119,6 @@ type DatabaseStructContainers {
schemaList: [ NavigatorNodeInfo! ]!
supportsCatalogChange: Boolean!
supportsSchemaChange: Boolean!
defaultCatalog: String @since(version: "25.2.2")
defaultSchema: String @since(version: "25.2.2")
}
####################################################
@@ -158,6 +158,8 @@ 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
@@ -360,7 +362,6 @@ extend type Mutation {
filter: SQLDataFilter,
dataFormat: ResultDataFormat, # requested data format. May be ignored by server
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"
@@ -1,31 +0,0 @@
/*
* 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 {
}
@@ -18,7 +18,10 @@ package io.cloudbeaver;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.WebNetworkHandlerConfigInput;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.app.ServletApplication;
import io.cloudbeaver.model.app.WebAppConfiguration;
import io.cloudbeaver.model.rm.DBNResourceManagerResource;
import io.cloudbeaver.model.session.WebActionParameters;
@@ -27,6 +30,7 @@ import io.cloudbeaver.model.utils.ConfigurationUtils;
import io.cloudbeaver.registry.WebAuthProviderDescriptor;
import io.cloudbeaver.registry.WebAuthProviderRegistry;
import io.cloudbeaver.server.WebAppUtils;
import io.cloudbeaver.server.WebApplication;
import io.cloudbeaver.service.navigator.WebPropertyFilter;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebCommonUtils;
@@ -35,14 +39,25 @@ 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.DBConstants;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.access.DBAAuthCredentials;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNativeCredentials;
import org.jkiss.dbeaver.model.navigator.*;
import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings;
import org.jkiss.dbeaver.registry.DataSourceProviderDescriptor;
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
import org.jkiss.dbeaver.registry.driver.DriverDescriptor;
import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor;
import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry;
import org.jkiss.dbeaver.runtime.properties.PropertyCollector;
import org.jkiss.utils.CommonUtils;
@@ -59,6 +74,25 @@ public class WebServiceUtils extends WebCommonUtils {
private static final Gson gson = new GsonBuilder().create();
@NotNull
public static DBPDriver getDriverById(String id) throws DBWebException {
int divPos = id.indexOf(':');
if (divPos < 0) {
throw new DBWebException("Bad driver id [" + id + "]");
}
String dsId = id.substring(0, divPos);
String driverId = id.substring(divPos + 1);
DataSourceProviderDescriptor dsProvider = DataSourceProviderRegistry.getInstance().getDataSourceProvider(dsId);
if (dsProvider == null) {
throw new DBWebException("Data source provider '" + dsId + "' not found");
}
DriverDescriptor driver = dsProvider.getDriver(driverId);
if (driver == null) {
throw new DBWebException("Driver '" + driverId + "' not found in provider '" + dsId + "'");
}
return driver;
}
@NotNull
public static DBPDataSourceRegistry getGlobalDataSourceRegistry() throws DBWebException {
return WebDataSourceUtils.getGlobalDataSourceRegistry();
@@ -68,6 +102,198 @@ public class WebServiceUtils extends WebCommonUtils {
return WebServiceUtils.class.getClassLoader().getResourceAsStream(path);
}
@NotNull
public static DBPDataSourceContainer createConnectionFromConfig(WebConnectionConfig config, DBPDataSourceRegistry registry) throws DBWebException {
DBPDataSourceContainer newDataSource;
if (!CommonUtils.isEmpty(config.getDriverId())) {
String driverId = config.getDriverId();
if (CommonUtils.isEmpty(driverId)) {
throw new DBWebException("Driver not specified");
}
DBPDriver driver = getDriverById(driverId);
DBPConnectionConfiguration dsConfig = new DBPConnectionConfiguration();
setConnectionConfiguration(driver, dsConfig, config);
newDataSource = registry.createDataSource(driver, dsConfig);
} else {
throw new DBWebException("Template connection or driver must be specified");
}
newDataSource.setSavePassword(true);
newDataSource.setName(config.getName());
newDataSource.setDescription(config.getDescription());
newDataSource.setConnectionReadOnly(config.isReadOnly());
if (config.getFolder() != null) {
newDataSource.setFolder(registry.getFolder(config.getFolder()));
}
ServletApplication app = ServletAppUtils.getServletApplication();
if (app instanceof WebApplication webApplication) {
((DataSourceDescriptor) newDataSource).setNavigatorSettings(
webApplication.getAppConfiguration().getDefaultNavigatorSettings());
}
saveAuthProperties(
newDataSource,
newDataSource.getConnectionConfiguration(),
config.getCredentials(),
config.isSaveCredentials(),
config.isSharedCredentials()
);
return newDataSource;
}
public static void setConnectionConfiguration(DBPDriver driver, DBPConnectionConfiguration dsConfig, WebConnectionConfig config) {
setMainProperties(dsConfig, config);
if (config.getProperties() != null) {
Map<String, String> newProps = new LinkedHashMap<>();
for (Map.Entry<String, Object> pe : config.getProperties().entrySet()) {
newProps.put(pe.getKey(), CommonUtils.toString(pe.getValue()));
}
dsConfig.setProperties(newProps);
}
if (config.getUserName() != null) {
dsConfig.setUserName(config.getUserName());
}
if (config.getUserPassword() != null) {
dsConfig.setUserPassword(config.getUserPassword());
}
if (config.getAuthModelId() != null) {
dsConfig.setAuthModelId(config.getAuthModelId());
}
if (config.getKeepAliveInterval() >= 0) {
dsConfig.setKeepAliveInterval(config.getKeepAliveInterval());
}
if (config.isDefaultAutoCommit() != null) {
dsConfig.getBootstrap().setDefaultAutoCommit(config.isDefaultAutoCommit());
}
dsConfig.getBootstrap().setDefaultCatalogName(config.getDefaultCatalogName());
dsConfig.getBootstrap().setDefaultSchemaName(config.getDefaultSchemaName());
// Save provider props
if (config.getProviderProperties() != null) {
dsConfig.setProviderProperties(new LinkedHashMap<>());
for (Map.Entry<String, Object> e : config.getProviderProperties().entrySet()) {
dsConfig.setProviderProperty(e.getKey(), CommonUtils.toString(e.getValue()));
}
}
if (config.getConfigurationType() != null) {
dsConfig.setConfigurationType(config.getConfigurationType());
}
if (CommonUtils.isEmpty(config.getUrl())) {
dsConfig.setUrl(driver.getConnectionURL(dsConfig));
}
// Save network handlers
if (config.getNetworkHandlersConfig() != null) {
for (WebNetworkHandlerConfigInput nhc : config.getNetworkHandlersConfig()) {
DBWHandlerConfiguration handlerConfig = dsConfig.getHandler(nhc.getId());
if (handlerConfig == null) {
NetworkHandlerDescriptor handlerDescriptor = NetworkHandlerRegistry.getInstance().getDescriptor(nhc.getId());
if (handlerDescriptor == null) {
log.warn("Can't find network handler '" + nhc.getId() + "'");
continue;
} else {
handlerConfig = new DBWHandlerConfiguration(handlerDescriptor, null);
WebDataSourceUtils.updateHandlerConfig(handlerConfig, nhc);
}
} else {
WebDataSourceUtils.updateHandlerConfig(handlerConfig, nhc);
}
dsConfig.updateHandler(handlerConfig);
}
}
}
private static void setMainProperties(DBPConnectionConfiguration dsConfig, WebConnectionConfig config) {
if (CommonUtils.isNotEmpty(config.getUrl())) {
dsConfig.setUrl(config.getUrl());
return;
}
if (config.getMainPropertyValues() != null) {
for (Map.Entry<String, Object> e : config.getMainPropertyValues().entrySet()) {
if (e.getValue() == null) {
continue;
}
switch (e.getKey()) {
case DBConstants.PROP_HOST -> dsConfig.setHostName(CommonUtils.toString(e.getValue()));
case DBConstants.PROP_PORT -> dsConfig.setHostPort(CommonUtils.toString(e.getValue()));
case DBConstants.PROP_DATABASE -> dsConfig.setDatabaseName(CommonUtils.toString(e.getValue()));
case DBConstants.PROP_SERVER -> dsConfig.setServerName(CommonUtils.toString(e.getValue()));
default -> throw new IllegalStateException("Unexpected value: " + e.getKey());
}
}
return;
}
if (config.getHost() != null) {
dsConfig.setHostName(config.getHost());
}
if (config.getPort() != null) {
dsConfig.setHostPort(config.getPort());
}
if (config.getDatabaseName() != null) {
dsConfig.setDatabaseName(config.getDatabaseName());
}
if (config.getServerName() != null) {
dsConfig.setServerName(config.getServerName());
}
}
public static void saveAuthProperties(
@NotNull DBPDataSourceContainer dataSourceContainer,
@NotNull DBPConnectionConfiguration configuration,
@Nullable Map<String, Object> authProperties,
boolean saveCredentials,
boolean sharedCredentials
) {
saveAuthProperties(dataSourceContainer, configuration, authProperties, saveCredentials, sharedCredentials, false);
}
public static void saveAuthProperties(
@NotNull DBPDataSourceContainer dataSourceContainer,
@NotNull DBPConnectionConfiguration configuration,
@Nullable Map<String, Object> authProperties,
boolean saveCredentials,
boolean sharedCredentials,
boolean isTest
) {
dataSourceContainer.setSavePassword(saveCredentials);
dataSourceContainer.setSharedCredentials(sharedCredentials);
if (!saveCredentials) {
// Reset credentials
if (authProperties == null) {
authProperties = new LinkedHashMap<>();
}
authProperties.replace(AuthModelDatabaseNativeCredentials.PROP_USER_PASSWORD, null);
dataSourceContainer.resetPassword();
} else {
if (authProperties == null) {
// No changes
return;
}
}
{
// Read save credentials
DBAAuthCredentials credentials = configuration.getAuthModel().loadCredentials(dataSourceContainer, configuration);
if (isTest) {
var currentAuthProps = new HashMap<String, String>();
for (Map.Entry<String, Object> stringObjectEntry : authProperties.entrySet()) {
var value = stringObjectEntry.getValue() == null ? null : stringObjectEntry.getValue().toString();
currentAuthProps.put(stringObjectEntry.getKey(), value);
}
configuration.setAuthProperties(currentAuthProps);
}
if (!authProperties.isEmpty()) {
WebDataSourceUtils.updateCredentialsFromProperties(credentials, authProperties);
}
configuration.getAuthModel().saveCredentials(dataSourceContainer, configuration, credentials);
}
}
public static DBNBrowseSettings parseNavigatorSettings(Map<String, Object> settingsMap) {
return gson.fromJson(
gson.toJsonTree(settingsMap), DataSourceNavigatorSettings.class);
@@ -84,6 +310,13 @@ public class WebServiceUtils extends WebCommonUtils {
WebActionParameters.saveToSession(webSession, actionParameters);
}
public static String getConnectionContainerInfo(DBPDataSourceContainer container) {
if (container == null) {
return null;
}
return container.getName() + " [" + container.getId() + "]";
}
public static void refreshDatabases(WebSession session, String projectId) throws DBWebException {
DBNProject projectNode = session.getNavigatorModelOrThrow().getRoot().getProjectNode(session.getProjectById(projectId));
if (projectNode != null) {
@@ -34,8 +34,6 @@ public interface WebAppSessionManager {
BaseWebSession closeSession(@NotNull String sessionId);
BaseWebSession closeSession(@NotNull String sessionId, boolean sendSessionExpiredEvent);
@NotNull
WebSession getWebSession(
@NotNull HttpServletRequest request,
@@ -21,6 +21,7 @@ import io.cloudbeaver.model.app.ServletApplication;
import io.cloudbeaver.model.app.WebAppConfiguration;
import io.cloudbeaver.model.app.WebServerConfiguration;
import io.cloudbeaver.registry.WebDriverRegistry;
import io.cloudbeaver.service.ConnectionController;
import org.jkiss.code.NotNull;
import java.net.InetAddress;
@@ -52,4 +53,6 @@ public interface WebApplication extends ServletApplication {
WebServerConfig getWebServerConfig();
ConnectionController getConnectionController();
}
@@ -38,13 +38,6 @@ public class WSProjectUpdatedEventHandler extends WSAbstractProjectEventHandler<
} else if (WSProjectUpdateEvent.REMOVED.equals(eventId)) {
activeUserSession.removeSessionProject(projectId);
log.info("Project '" + projectId + "' removed from '" + activeUserSession.getSessionId() + "' session");
} else if (WSProjectUpdateEvent.UPDATED.equals(eventId)) {
if (event.getProjectInfo() == null) {
log.warn("No project info provided for update event: " + event);
return;
}
activeUserSession.updateSessionProject(projectId, event.getProjectInfo());
log.info("Project '" + projectId + "' updated in '" + activeUserSession.getSessionId() + "' session");
}
activeUserSession.addSessionEvent(event);
} catch (DBException e) {
@@ -55,7 +48,7 @@ public class WSProjectUpdatedEventHandler extends WSAbstractProjectEventHandler<
@Override
protected boolean isAcceptableInSession(@NotNull BaseWebSession activeUserSession, @NotNull WSProjectUpdateEvent event) {
return !WSWebUtils.isSessionIdEquals(activeUserSession, event.getSessionId()) &&
(!event.getId().equals(WSProjectUpdateEvent.ADDED) ||
(event.getId().equals(WSProjectUpdateEvent.REMOVED) ||
activeUserSession.getUserContext().hasPermission(DBWConstants.PERMISSION_ADMIN));
}
}
@@ -246,7 +246,14 @@ 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;
@@ -16,27 +16,24 @@
*/
package io.cloudbeaver.server.graphql;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.cloudbeaver.WebParameterSecure;
import io.cloudbeaver.model.log.Sensitive;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.WebAppUtils;
import io.cloudbeaver.server.WebApplication;
import jakarta.servlet.http.HttpServletRequest;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.utils.CommonUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.StringJoiner;
import java.util.Set;
public class GraphQLLoggerUtil {
private static final Log log = Log.getLog(GraphQLLoggerUtil.class);
public static final String LOG_API_GRAPHQL_DEBUG_PARAMETER = "log.api.graphql.debug";
public static final Gson GSON = new GsonBuilder().create();
public static final String MASK_STRING = "****";
private static final Set<String> PROHIBITED_VARIABLES =
Set.of("password", "config", "parameters", "settings", "licenseText", "credentials", "username");
public static String getUserId(HttpServletRequest request) {
WebSession session = getWebSession(request);
@@ -70,58 +67,55 @@ public class GraphQLLoggerUtil {
}
public static String buildLoggerMessage(String sessionId, String userId, Method method, Object[] args) {
StringBuilder sb = new StringBuilder(64)
.append(" [user: ").append(userId)
.append(", sessionId: ").append(sessionId)
.append("]");
StringBuilder loggerMessage = new StringBuilder(" [user: ").append(userId)
.append(", sessionId: ").append(sessionId).append("]");
if (WebAppUtils.getWebPlatform().getPreferenceStore().getBoolean(LOG_API_GRAPHQL_DEBUG_PARAMETER)) {
sb.append('(');
String text = maskArgsToString(method, args);
if (CommonUtils.isNotEmpty(text)) {
sb.append(text);
// if (WebAppUtils.getWebPlatform().getPreferenceStore().getBoolean(LOG_API_GRAPHQL_DEBUG_PARAMETER)
if (true
) {
loggerMessage.append(" [variables] ");
String parsedVariables = maskArgsToString(method, args);
if (CommonUtils.isNotEmpty(parsedVariables)) {
loggerMessage.append(parsedVariables);
}
sb.append(')');
}
return sb.toString();
return loggerMessage.toString();
}
public static String maskArgsToString(Method method, Object[] args) {
StringBuilder sb = new StringBuilder();
Parameter[] params = method.getParameters();
if (params.length == 0 || args == null || args.length == 0) {
return "";
}
int limit = Math.min(args.length, params.length);
StringJoiner joiner = new StringJoiner(", ");
for (int i = 0; i < params.length; i++) {
//fixme can't get real parameter names without -parameters compiler option
String name = params[i].getName();
Object value = (args != null && i < args.length) ? args[i] : null;
for (int i = 0; i < limit; i++) {
Object value = args[i];
if (value instanceof WebSession) {
//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;
}
sb.append(name).append(": ");
if (value != null && !isSimple(value.getClass())) {
String stringValue;
try {
stringValue = GSON.toJson(value);
} catch (Exception e) {
stringValue = value.toString();
if (params[i].isAnnotationPresent(Sensitive.class)) {
sb.append("**** ");
} else if (value != null && !isSimple(value.getClass())) {
sb.append("{ ");
for (Field field : value.getClass().getDeclaredFields()) {
field.setAccessible(true);
sb.append(field.getName()).append(": ");
try {
if (field.isAnnotationPresent(Sensitive.class)) {
sb.append("**** ");
} else {
sb.append(field.get(value)).append(" ");
}
} catch (IllegalAccessException e) {
sb.append("<err> ");
}
}
joiner.add(stringValue);
sb.append("} ");
} else {
joiner.add(String.valueOf(value));
sb.append(value).append(" ");
}
}
return joiner.toString();
return sb.toString().trim();
}
private static boolean isSimple(Class<?> cls) {
@@ -131,5 +125,4 @@ public class GraphQLLoggerUtil {
|| Boolean.class.equals(cls)
|| Enum.class.isAssignableFrom(cls);
}
}
@@ -25,7 +25,10 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
import org.jkiss.dbeaver.model.websocket.event.client.*;
import org.jkiss.dbeaver.model.websocket.event.client.WSSessionPingClientEvent;
import org.jkiss.dbeaver.model.websocket.event.client.WSSubscribeOnTopicClientEvent;
import org.jkiss.dbeaver.model.websocket.event.client.WSUnsubscribeFromTopicClientEvent;
import org.jkiss.dbeaver.model.websocket.event.client.WSUpdateActiveProjectsClientEvent;
import org.jkiss.dbeaver.model.websocket.event.session.WSAccessTokenExpiredEvent;
import org.jkiss.dbeaver.model.websocket.event.session.WSSocketConnectedEvent;
import org.jkiss.utils.CommonUtils;
@@ -107,17 +110,6 @@ public class CBEventsWebSocket extends CBAbstractWebSocket implements CBWebSessi
}
break;
}
case WSSessionTaskConfirmationEvent.ID: {
if (webSession instanceof WebSession session) {
var taskConfirmationEvent = (WSSessionTaskConfirmationEvent) clientEvent;
session.handleTaskConfirmation(
taskConfirmationEvent.getTaskId(),
taskConfirmationEvent.isConfirmed(),
taskConfirmationEvent.isSkipConfirmations()
);
}
break;
}
default:
var e = new DBWebException("Unknown websocket client event: " + clientEvent.getId());
log.error(e.getMessage(), e);
@@ -0,0 +1,99 @@
/*
* 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.service;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebObjectId;
import io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.WebNetworkHandlerConfigInput;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import java.util.List;
import java.util.Map;
public interface ConnectionController {
DBPDataSourceContainer createDataSourceContainer(
@NotNull WebSession webSession,
@Nullable @WebObjectId String projectId,
@NotNull WebConnectionConfig connectionConfig
) throws DBWebException;
WebConnectionInfo createConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
DBPDataSourceRegistry sessionRegistry,
DBPDataSourceContainer newDataSource
) throws DBWebException;
DBPDataSourceContainer getDatasourceConnection(
@NotNull WebSession webSession,
@Nullable @WebObjectId String projectId,
@NotNull WebConnectionConfig connectionConfig) throws DBWebException;
WebConnectionInfo updateConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull WebConnectionConfig config,
DBPDataSourceContainer dataSource,
DBPDataSourceRegistry sessionRegistry
) throws DBWebException;
boolean deleteConnection(
@NotNull WebSession webSession,
@Nullable @WebObjectId String projectId,
@NotNull String connectionId) throws DBWebException;
DataSourceDescriptor prepareTestConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull WebConnectionConfig connectionConfig) throws DBWebException;
WebConnectionInfo testConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull WebConnectionConfig connectionConfig,
DataSourceDescriptor dataSource
) throws DBWebException;
WebConnectionInfo getConnectionState(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull String connectionId
) throws DBWebException;
WebConnectionInfo initConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull String connectionId,
@NotNull Map<String, Object> authProperties,
@Nullable List<WebNetworkHandlerConfigInput> networkCredentials,
boolean saveCredentials,
boolean sharedCredentials,
@Nullable String selectedSecretId
) throws DBWebException;
void validateConnection(DBPDataSourceContainer dataSourceContainer) throws DBWebException;
}
@@ -0,0 +1,496 @@
/*
* 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.service;
import io.cloudbeaver.*;
import io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.WebNetworkHandlerConfigInput;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
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.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.exec.DBCConnectException;
import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration;
import org.jkiss.dbeaver.model.net.DBWHandlerType;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.secret.DBSSecretController;
import org.jkiss.dbeaver.model.secret.DBSSecretValue;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.runtime.jobs.ConnectionTestJob;
import org.jkiss.dbeaver.utils.RuntimeUtils;
import org.jkiss.utils.CommonUtils;
import java.util.List;
import java.util.Map;
public class ConnectionControllerCE implements ConnectionController {
private static final Log log = Log.getLog(ConnectionController.class);
@Override
public DBPDataSourceContainer createDataSourceContainer(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull WebConnectionConfig connectionConfig
) throws DBWebException {
WebSessionProjectImpl project = getProjectById(webSession, projectId);
var rmProject = project.getRMProject();
if (rmProject.getType() == RMProjectType.USER
&& !webSession.hasPermission(DBWConstants.PERMISSION_ADMIN)
&& !ServletAppUtils.getServletApplication().getAppConfiguration().isSupportsCustomConnections()
) {
throw new DBWebException("New connection create is restricted by server configuration");
}
webSession.addInfoMessage("Create new connection");
DBPDataSourceRegistry sessionRegistry = project.getDataSourceRegistry();
DBPDataSourceContainer newDataSource = WebServiceUtils.createConnectionFromConfig(connectionConfig,
sessionRegistry);
if (CommonUtils.isEmpty(newDataSource.getName())) {
newDataSource.setName(CommonUtils.notNull(connectionConfig.getName(), "NewConnection"));
}
return newDataSource;
}
@Override
@NotNull
public WebConnectionInfo createConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
DBPDataSourceRegistry sessionRegistry,
DBPDataSourceContainer newDataSource
) throws DBWebException {
WebSessionProjectImpl project = getProjectById(webSession, projectId);
try {
sessionRegistry.addDataSource(newDataSource);
sessionRegistry.checkForErrors();
} catch (DBException e) {
sessionRegistry.removeDataSource(newDataSource);
throw new DBWebException("Failed to create connection", e);
}
WebConnectionInfo connectionInfo = project.addConnection(newDataSource);
webSession.addInfoMessage("New connection was created - " + WebServiceUtils.getConnectionContainerInfo(
newDataSource));
log.info(String.format(
"New connection was created: [info=%s, user=%s]",
WebServiceUtils.getConnectionContainerInfo(newDataSource),
webSession.getUserId()
));
return connectionInfo;
}
@Override
public DBPDataSourceContainer getDatasourceConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull WebConnectionConfig config
) throws DBWebException {
// Do not check for custom connection option. Already created connections can be edited.
// Also template connections can be edited
// if (!CBApplication.getInstance().getAppConfiguration().isSupportsCustomConnections()) {
// throw new DBWebException("Connection edit is restricted by server configuration");
// }
WebConnectionInfo connectionInfo = WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, config.getConnectionId());
DBPDataSourceContainer dataSource = connectionInfo.getDataSourceContainer();
webSession.addInfoMessage("Update connection - " + WebServiceUtils.getConnectionContainerInfo(dataSource));
getOldDataSource(dataSource);
if (!CommonUtils.isEmpty(config.getName())) {
dataSource.setName(config.getName());
}
if (config.getDescription() != null) {
dataSource.setDescription(config.getDescription());
}
WebSessionProjectImpl project = getProjectById(webSession, projectId);
DBPDataSourceRegistry sessionRegistry = project.getDataSourceRegistry();
dataSource.setFolder(config.getFolder() != null ? sessionRegistry.getFolder(config.getFolder()) : null);
if (config.isDefaultAutoCommit() != null) {
dataSource.setDefaultAutoCommit(config.isDefaultAutoCommit());
}
dataSource.setConnectionReadOnly(config.isReadOnly());
WebServiceUtils.setConnectionConfiguration(dataSource.getDriver(),
dataSource.getConnectionConfiguration(),
config);
// we should check that the config has changed but not check for password changes
dataSource.setSharedCredentials(config.isSharedCredentials());
dataSource.setSavePassword(config.isSaveCredentials());
boolean sharedCredentials = isSharedCredentials(dataSource);
if (sharedCredentials) {
//we must notify about the shared password change
WebServiceUtils.saveAuthProperties(
dataSource,
dataSource.getConnectionConfiguration(),
config.getCredentials(),
config.isSaveCredentials(),
config.isSharedCredentials()
);
}
connectionInfo.setCredentialsSavedInSession(null);
// same here
return dataSource;
}
private static boolean isSharedCredentials(DBPDataSourceContainer dataSource) {
return dataSource.isSharedCredentials() || !dataSource.getProject()
.isUseSecretStorage() && dataSource.isSavePassword();
}
private static DataSourceDescriptor getOldDataSource(DBPDataSourceContainer dataSource) {
DataSourceDescriptor oldDataSource;
oldDataSource = dataSource.getRegistry().createDataSource(dataSource);
oldDataSource.setId(dataSource.getId());
return oldDataSource;
}
@Override
public WebConnectionInfo updateConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull WebConnectionConfig config,
DBPDataSourceContainer dataSource,
DBPDataSourceRegistry sessionRegistry
) throws DBWebException {
WebConnectionInfo connectionInfo = WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, config.getConnectionId());
if (!isSharedCredentials(dataSource)) {
// secret controller is responsible for notification, password changes applied after checks
WebServiceUtils.saveAuthProperties(
dataSource,
dataSource.getConnectionConfiguration(),
config.getCredentials(),
config.isSaveCredentials(),
config.isSharedCredentials()
);
}
try {
sessionRegistry.updateDataSource(dataSource);
sessionRegistry.checkForErrors();
} catch (DBException e) {
throw new DBWebException("Failed to update connection", e);
}
log.info(String.format(
"Connection updated: [info=%s, userId=%s]",
WebServiceUtils.getConnectionContainerInfo(dataSource),
webSession.getUser()
));
return connectionInfo;
}
@Override
public boolean deleteConnection(
@NotNull WebSession webSession, @Nullable String projectId, @NotNull String connectionId
) throws DBWebException {
WebConnectionInfo connectionInfo = WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, connectionId);
if (connectionInfo.getDataSourceContainer().getProject() != getProjectById(webSession, projectId)) {
throw new DBWebException("Global connection '" + connectionInfo.getName() + "' configuration cannot be deleted");
}
webSession.addInfoMessage("Delete connection - " +
WebServiceUtils.getConnectionContainerInfo(connectionInfo.getDataSourceContainer()));
closeAndDeleteConnection(webSession, projectId, connectionId, true);
log.info(String.format(
"Connection deleted: [info=%s, userId=%s]",
WebServiceUtils.getConnectionContainerInfo(connectionInfo.getDataSourceContainer()),
webSession.getUserId()
));
return true;
}
@Override
public DataSourceDescriptor prepareTestConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull WebConnectionConfig connectionConfig
) throws DBWebException {
String connectionId = connectionConfig.getConnectionId();
connectionConfig.setSaveCredentials(true); // It is used in createConnectionFromConfig
DataSourceDescriptor dataSource = (DataSourceDescriptor) WebDataSourceUtils.getLocalOrGlobalDataSource(
webSession, projectId, connectionId);
validateConnection(dataSource);
WebProjectImpl project = getProjectById(webSession, projectId);
DBPDataSourceRegistry sessionRegistry = project.getDataSourceRegistry();
DataSourceDescriptor testDataSource;
if (dataSource != null) {
try {
// Check that creds are saved to trigger secrets resolve
dataSource.isCredentialsSaved();
} catch (DBException e) {
throw new DBWebException("Can't determine whether datasource credentials are saved", e);
}
testDataSource = (DataSourceDescriptor) dataSource.createCopy(dataSource.getRegistry());
WebServiceUtils.setConnectionConfiguration(
testDataSource.getDriver(),
testDataSource.getConnectionConfiguration(),
connectionConfig
);
if (connectionConfig.getSelectedSecretId() != null) {
try {
dataSource.listSharedCredentials()
.stream()
.filter(secret -> connectionConfig.getSelectedSecretId().equals(secret.getSubjectId()))
.findFirst()
.ifPresent(testDataSource::setSelectedSharedCredentials);
} catch (DBException e) {
throw new DBWebException("Failed to load secret value: " + connectionConfig.getSelectedSecretId());
}
}
WebServiceUtils.saveAuthProperties(
testDataSource,
testDataSource.getConnectionConfiguration(),
connectionConfig.getCredentials(),
true,
false,
true
);
} else {
testDataSource = (DataSourceDescriptor) WebServiceUtils.createConnectionFromConfig(connectionConfig,
sessionRegistry);
}
return testDataSource;
}
@Override
@NotNull
public WebConnectionInfo testConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull WebConnectionConfig connectionConfig,
DataSourceDescriptor testDataSource
) throws DBWebException {
validateDriverLibrariesPresence(testDataSource);
webSession.provideAuthParameters(webSession.getProgressMonitor(),
testDataSource,
testDataSource.getConnectionConfiguration());
testDataSource.setSavePassword(true); // We need for test to avoid password callback
if (DataSourceDescriptor.class.isAssignableFrom(testDataSource.getClass())) {
testDataSource.setAccessCheckRequired(!webSession.hasPermission(DBWConstants.PERMISSION_ADMIN));
}
try {
ConnectionTestJob ct = new ConnectionTestJob(testDataSource, param -> {
});
ct.run(webSession.getProgressMonitor());
if (ct.getConnectError() != null) {
if (ct.getConnectError() instanceof DBCConnectException error) {
Throwable rootCause = CommonUtils.getRootCause(error);
if (rootCause instanceof ClassNotFoundException) {
throwDriverNotFoundException(testDataSource);
}
}
throw new DBWebException("Connection failed", ct.getConnectError());
}
WebConnectionInfo connectionInfo = new WebConnectionInfo(webSession, testDataSource);
connectionInfo.setConnectError(ct.getConnectError());
connectionInfo.setServerVersion(ct.getServerVersion());
connectionInfo.setClientVersion(ct.getClientVersion());
connectionInfo.setConnectTime(RuntimeUtils.formatExecutionTime(ct.getConnectTime()));
return connectionInfo;
} catch (DBException e) {
throw new DBWebException("Error connecting to database", e);
}
}
private WebSessionProjectImpl getProjectById(WebSession webSession, String projectId) throws DBWebException {
WebSessionProjectImpl project = webSession.getProjectById(projectId);
if (project == null) {
throw new DBWebException("Project '" + projectId + "' not found");
}
return project;
}
@NotNull
private WebConnectionInfo closeAndDeleteConnection(
@NotNull WebSession webSession,
@NotNull String projectId,
@NotNull String connectionId,
boolean forceDelete
) throws DBWebException {
WebSessionProjectImpl project = getProjectById(webSession, projectId);
WebConnectionInfo connectionInfo = project.getWebConnectionInfo(connectionId);
DBPDataSourceContainer dataSourceContainer = connectionInfo.getDataSourceContainer();
boolean disconnected = WebDataSourceUtils.disconnectDataSource(webSession, dataSourceContainer);
if (forceDelete) {
DBPDataSourceRegistry registry = project.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);
}
project.removeConnection(dataSourceContainer);
} else {
// Just reset saved credentials
connectionInfo.clearCache();
}
return connectionInfo;
}
@Override
public WebConnectionInfo getConnectionState(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull String connectionId
) throws DBWebException {
return WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, connectionId);
}
@Override
public WebConnectionInfo initConnection(@NotNull WebSession webSession, @Nullable String projectId, @NotNull String connectionId, @NotNull Map<String, Object> authProperties, @Nullable List<WebNetworkHandlerConfigInput> networkCredentials, boolean saveCredentials, boolean sharedCredentials, @Nullable String selectedSecretId) throws DBWebException {
WebConnectionInfo connectionInfo = WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, connectionId);
connectionInfo.setSavedCredentials(authProperties, networkCredentials);
var dataSourceContainer = connectionInfo.getDataSourceContainer();;
validateConnection(dataSourceContainer);
if (dataSourceContainer.isConnected()) {
throw new DBWebException("Datasource '" + dataSourceContainer.getName() + "' is already connected");
}
if (dataSourceContainer.isSharedCredentials() && selectedSecretId != null) {
List<DBSSecretValue> allSecrets;
try {
allSecrets = dataSourceContainer.listSharedCredentials();
} catch (DBException e) {
throw new DBWebException("Error loading connection secret", e);
}
DBSSecretValue selectedSecret =
allSecrets.stream()
.filter(secret -> selectedSecretId.equals(secret.getUniqueId()))
.findFirst().orElse(null);
if (selectedSecret == null) {
throw new DBWebException("Secret not found:" + selectedSecretId);
}
dataSourceContainer.setSelectedSharedCredentials(selectedSecret);
}
boolean oldSavePassword = dataSourceContainer.isSavePassword();
DBRProgressMonitor monitor = webSession.getProgressMonitor();
validateDriverLibrariesPresence(dataSourceContainer);
try {
boolean connect = dataSourceContainer.connect(monitor, true, false);
} catch (Exception e) {
if (e instanceof DBCConnectException) {
Throwable rootCause = CommonUtils.getRootCause(e);
if (rootCause instanceof ClassNotFoundException) {
throwDriverNotFoundException(dataSourceContainer);
}
}
throw new DBWebException("Error connecting to database", e);
} finally {
dataSourceContainer.setSavePassword(oldSavePassword);
connectionInfo.clearCache();
}
// Mark all specified network configs as saved
boolean[] saveConfig = new boolean[1];
if (networkCredentials != null) {
networkCredentials.forEach(c -> {
if (CommonUtils.toBoolean(c.isSavePassword())) {
DBWHandlerConfiguration handlerCfg = dataSourceContainer.getConnectionConfiguration()
.getHandler(c.getId());
if (handlerCfg != null &&
// check username param only for ssh config
!(CommonUtils.isEmpty(c.getUserName()) && CommonUtils.equalObjects(handlerCfg.getType(),
DBWHandlerType.TUNNEL))
) {
WebDataSourceUtils.updateHandlerCredentials(handlerCfg, c);
handlerCfg.setSavePassword(true);
saveConfig[0] = true;
}
}
});
}
if (saveCredentials) {
// Save all passed credentials in the datasource container
WebServiceUtils.saveAuthProperties(
dataSourceContainer,
dataSourceContainer.getConnectionConfiguration(),
authProperties,
true,
sharedCredentials
);
var project = dataSourceContainer.getProject();
if (project.isUseSecretStorage()) {
try {
dataSourceContainer.persistSecrets(
DBSSecretController.getProjectSecretController(dataSourceContainer.getProject())
);
} catch (DBException e) {
throw new DBWebException("Failed to save credentials", e);
}
}
WebDataSourceUtils.saveCredentialsInDataSource(connectionInfo,
dataSourceContainer,
dataSourceContainer.getConnectionConfiguration());
saveConfig[0] = true;
}
if (WebServiceUtils.isGlobalProject(dataSourceContainer.getProject())) {
// Do not flush config for global project (only admin can do it - CB-2415)
if (saveCredentials) {
connectionInfo.setCredentialsSavedInSession(true);
}
saveConfig[0] = false;
}
if (saveConfig[0]) {
dataSourceContainer.persistConfiguration();
}
return connectionInfo;
}
@Override
public void validateConnection(DBPDataSourceContainer dataSourceContainer) throws DBWebException {
}
private void validateDriverLibrariesPresence(@NotNull DBPDataSourceContainer container) throws DBWebException {
if (!DBWorkbench.isDistributed() && container.getDriver().getDriverLoader(container).needsExternalDependencies()) {
throwDriverNotFoundException(container);
}
}
@NotNull
private static String throwDriverNotFoundException(@NotNull DBPDataSourceContainer container) throws DBWebException {
throw new DBWebException("Driver files for %s are not found. Please ask the administrator to download it."
.formatted(container.getDriver().getName()));
}
}
@@ -16,7 +16,6 @@
*/
package io.cloudbeaver.service;
import graphql.GraphQLContext;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
@@ -117,11 +116,12 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
@Nullable
protected static WebSession getSessionFromContext(DataFetchingEnvironment env) {
return env.getGraphQlContext().get(WebSession.class.getName());
WebSession webSession = env.getGraphQlContext().get(WebSession.class.getName());
return webSession;
}
@NotNull
protected static WebSession getSessionFromContextOrThrow(@NotNull DataFetchingEnvironment env) throws DBWebException {
protected static WebSession getSessionFromContextOrThrow(DataFetchingEnvironment env) throws DBWebException {
WebSession webSession = env.getGraphQlContext().get(WebSession.class.getName());
if (webSession == null) {
throw new DBWebException("Web session not found in GraphQL context");
@@ -129,7 +129,7 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
return webSession;
}
protected static WebSession getWebSession(@NotNull DataFetchingEnvironment env, boolean errorOnNotFound) throws DBWebException {
protected static WebSession getWebSession(DataFetchingEnvironment env, boolean errorOnNotFound) throws DBWebException {
if (env.getGraphQlContext().getBoolean(CloudbeaverCliConstants.CLI_MODE)) {
return getSessionFromContextOrThrow(env);
}
@@ -137,20 +137,20 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
GraphQLEndpoint.getServletRequestOrThrow(env), getServletResponse(env), errorOnNotFound);
}
protected static String getProjectReference(@NotNull DataFetchingEnvironment env) {
protected static String getProjectReference(DataFetchingEnvironment env) {
return env.getArgument("projectId");
}
@NotNull
protected static WebConnectionInfo getWebConnection(@NotNull DataFetchingEnvironment env) throws DBWebException {
return getWebConnection(getWebSession(env), getProjectReference(env), getArgumentVal(env, "connectionId"));
protected static WebConnectionInfo getWebConnection(DataFetchingEnvironment env) throws DBWebException {
return getWebConnection(getWebSession(env), getProjectReference(env), env.getArgument("connectionId"));
}
/**
* Returns WebSession from cache or null
*/
@Nullable
public static WebSession findWebSession(@NotNull DataFetchingEnvironment env) {
public static WebSession findWebSession(DataFetchingEnvironment env) {
if (env.getGraphQlContext().getBoolean(CloudbeaverCliConstants.CLI_MODE)) {
return getSessionFromContext(env);
}
@@ -158,30 +158,16 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
GraphQLEndpoint.getServletRequestOrThrow(env));
}
public static WebSession findWebSession(@NotNull DataFetchingEnvironment env, boolean errorOnNotFound) throws DBWebException {
public static WebSession findWebSession(DataFetchingEnvironment env, boolean errorOnNotFound) throws DBWebException {
return WebAppUtils.getWebApplication().getSessionManager().findWebSession(
GraphQLEndpoint.getServletRequestOrThrow(env), errorOnNotFound);
}
@NotNull
public static WebConnectionInfo getWebConnection(@NotNull WebSession session, @Nullable String projectId, @NotNull String connectionId) throws DBWebException {
public static WebConnectionInfo getWebConnection(WebSession session, String projectId, String connectionId) throws DBWebException {
return WebDataSourceUtils.getWebConnectionInfo(session, projectId, connectionId);
}
@Nullable
protected static <T> T getArgument(@NotNull DataFetchingEnvironment env, @NotNull String name) {
return env.getArgument(name);
}
@NotNull
protected static <T> T getArgumentVal(@NotNull DataFetchingEnvironment env, @NotNull String name) throws DBWebException {
T value = env.getArgument(name);
if (value == null) {
throw new DBWebException("Argument '" + name + "' is null");
}
return value;
}
private class ServiceInvocationHandler implements InvocationHandler {
private final API_TYPE impl;
private final DataFetchingEnvironment env;
@@ -197,7 +183,7 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
try {
WebActionSet actionSet = method.getDeclaringClass().getAnnotation(WebActionSet.class);
if (actionSet != null) {
checkServicePermissions(actionSet);
checkServicePermissions(method, actionSet);
}
WebAction webAction = method.getAnnotation(WebAction.class);
if (webAction != null) {
@@ -282,7 +268,7 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
}
}
private void checkServicePermissions(WebActionSet actionSet) throws DBWebException {
private void checkServicePermissions(Method method, WebActionSet actionSet) throws DBWebException {
String[] features = actionSet.requireFeatures();
ServletApplication servletApplication = ServletAppUtils.getServletApplication();
for (String feature : features) {
@@ -346,16 +332,15 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
// Perform any checks before action call
protected void beforeWebActionCall(WebAction webAction, Method method, Object[] args) throws DBException {
GraphQLContext graphQlContext = this.env.getGraphQlContext();
HttpServletRequest request = graphQlContext.get("request");
if (request == null) {
return;
}
HttpServletRequest request = this.env.getGraphQlContext().get("request");
String sessionId = GraphQLLoggerUtil.getSmSessionId(request);
String userId = GraphQLLoggerUtil.getUserId(request);
String loggerMessage = GraphQLLoggerUtil.buildLoggerMessage(sessionId, userId, method, args);
log.debug("API > " + method.getName() + loggerMessage);
if (method.getName() != null) {
log.debug("API > " + method.getName() + loggerMessage);
}
setLogContext(method, args);
}
@@ -366,9 +351,9 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
}
protected void setLogContext(Method method, @Nullable Object[] args) {
protected void setLogContext(Method method, Object[] args) {
WebSession activeSession = null;
if (args != null) {
if (args != null && args.length > 0) {
for (Object arg : args) {
if (arg instanceof WebSession) {
activeSession = (WebSession) arg;
@@ -16,7 +16,10 @@
*/
package io.cloudbeaver.service.core;
import io.cloudbeaver.*;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebAction;
import io.cloudbeaver.WebObjectId;
import io.cloudbeaver.WebProjectAction;
import io.cloudbeaver.model.*;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.DBWService;
@@ -116,7 +119,7 @@ public interface DBWServiceCore extends DBWService {
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull String connectionId,
@WebParameterSecure @Nullable Map<String, Object> authProperties,
@NotNull Map<String, Object> authProperties,
@Nullable List<WebNetworkHandlerConfigInput> networkCredentials,
boolean saveCredentials,
boolean sharedCredentials,
@@ -127,37 +130,31 @@ public interface DBWServiceCore extends DBWService {
WebConnectionInfo createConnection(
@NotNull WebSession webSession,
@Nullable @WebObjectId String projectId,
@WebParameterSecure @NotNull Map<String, Object> connectionConfig
@NotNull WebConnectionConfig connectionConfig
) throws DBWebException;
@WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_DATASOURCES_EDIT})
WebConnectionInfo updateConnection(
@NotNull WebSession webSession,
@Nullable @WebObjectId String projectId,
@NotNull Map<String, Object> connectionConfig
) throws DBWebException;
@NotNull WebConnectionConfig connectionConfig) throws DBWebException;
@WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_DATASOURCES_EDIT})
boolean deleteConnection(
@NotNull WebSession webSession,
@Nullable @WebObjectId String projectId,
@NotNull String connectionId
) throws DBWebException;
@NotNull String connectionId) throws DBWebException;
@WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_DATASOURCES_EDIT})
WebConnectionInfo copyConnectionFromNode(
@NotNull WebSession webSession,
@Nullable @WebObjectId String projectId,
@NotNull String nodePath,
@NotNull Map<String, Object> connectionConfig
) throws DBWebException;
@NotNull WebConnectionConfig config) throws DBWebException;
@WebAction
WebConnectionInfo testConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull Map<String, Object> connectionConfig
) throws DBWebException;
@NotNull WebSession webSession, @Nullable String projectId, @NotNull WebConnectionConfig connectionConfig) throws DBWebException;
@WebAction
WebNetworkEndpointInfo testNetworkHandler(@NotNull WebSession webSession, @NotNull WebNetworkHandlerConfigInput nhConfig) throws DBWebException;
@@ -178,7 +175,7 @@ public interface DBWServiceCore extends DBWService {
WebConnectionFolderInfo createConnectionFolder(
@NotNull WebSession session,
@Nullable @WebObjectId String projectId,
@Nullable String parentNodePath,
@NotNull String parentNodePath,
@NotNull String newName) throws DBWebException;
@WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_DATASOURCES_EDIT})
@@ -17,9 +17,11 @@
package io.cloudbeaver.service.core;
import graphql.TypeResolutionEnvironment;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.idl.TypeRuntimeWiring;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.WebNetworkHandlerConfigInput;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.WebAppSessionManager;
@@ -54,20 +56,20 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
.dataFetcher("systemInfo", env -> getService(env).getSystemInformationProperties(getWebSession(env)))
.dataFetcher("productSettings", env -> getService(env).getProductSettings(getWebSession(env)))
.dataFetcher("driverList", env -> getService(env).getDriverList(getWebSession(env), getArgument(env, "id")))
.dataFetcher("driverList", env -> getService(env).getDriverList(getWebSession(env), env.getArgument("id")))
.dataFetcher("authModels", env -> getService(env).getAuthModels(getWebSession(env)))
.dataFetcher("networkHandlers", env -> getService(env).getNetworkHandlers(getWebSession(env)))
.dataFetcher("userConnections", env -> getService(env).getUserConnections(
getWebSession(env), getProjectReference(env), getArgument(env, "id"), getArgument(env, "projectIds")))
getWebSession(env), getProjectReference(env), env.getArgument("id"), env.getArgument("projectIds")))
.dataFetcher("connectionFolders", env -> getService(env).getConnectionFolders(
getWebSession(env), getProjectReference(env), getArgument(env, "path")))
getWebSession(env), getProjectReference(env), env.getArgument("path")))
.dataFetcher("sessionPermissions", env -> getService(env).getSessionPermissions(getWebSession(env)))
.dataFetcher("sessionState", env -> getService(env).getSessionState(findWebSession(env, true)))
.dataFetcher("connectionInfo", env -> getService(env).getConnectionState(
getWebSession(env), getProjectReference(env), getArgument(env, "id")))
getWebSession(env), getProjectReference(env), env.getArgument("id")))
.dataFetcher("listProjects", env -> getService(env).getProjects(getWebSession(env)))
.dataFetcher("readSessionLog", env -> {
@@ -78,8 +80,8 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
}
return getService(env).readSessionLog(
session,
getArgument(env, "maxEntries"),
getArgument(env, "clearEntries"));
env.getArgument("maxEntries"),
env.getArgument("clearEntries"));
})
;
@@ -89,7 +91,7 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
HttpServletResponse servletResponse = GraphQLEndpoint.getServletResponse(env);
return getService(env).openSession(
sessionManager.getWebSession(servletRequest, servletResponse, false),
getArgument(env, "defaultLocale"),
env.getArgument("defaultLocale"),
servletRequest,
servletResponse);
})
@@ -100,25 +102,21 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
GraphQLEndpoint.getServletRequestOrThrow(env), GraphQLEndpoint.getServletResponse(env)))
.dataFetcher("refreshSessionConnections", env -> getService(env).refreshSessionConnections(
GraphQLEndpoint.getServletRequestOrThrow(env), GraphQLEndpoint.getServletResponse(env)))
.dataFetcher("changeSessionLanguage", env -> getService(env).changeSessionLanguage(getWebSession(env), getArgument(env, "locale")))
.dataFetcher("changeSessionLanguage", env -> getService(env).changeSessionLanguage(getWebSession(env), env.getArgument("locale")))
.dataFetcher("createConnection", env -> getService(env).createConnection(
getWebSession(env), getProjectReference(env), getArgumentVal(env, "config"))
)
getWebSession(env), getProjectReference(env), getConnectionConfig(env)))
.dataFetcher("updateConnection", env -> getService(env).updateConnection(
getWebSession(env), getProjectReference(env), getArgumentVal(env, "config"))
)
getWebSession(env), getProjectReference(env), getConnectionConfig(env)))
.dataFetcher("deleteConnection", env -> getService(env).deleteConnection(
getWebSession(env), getProjectReference(env), getArgumentVal(env, "id")))
getWebSession(env), getProjectReference(env), env.getArgument("id")))
.dataFetcher("copyConnectionFromNode", env -> getService(env).copyConnectionFromNode(
getWebSession(env),
getProjectReference(env),
getArgumentVal(env, "nodePath"),
getArgumentVal(env, "config")
)
)
env.getArgument("nodePath"),
new WebConnectionConfig(env.getArgument("config"))))
.dataFetcher("initConnection", env -> {
List<Map<String, Object>> networkCredentials = getArgument(env, "networkCredentials");
List<Map<String, Object>> networkCredentials = env.getArgument("networkCredentials");
List<WebNetworkHandlerConfigInput> nhc = null;
if (networkCredentials != null) {
nhc = networkCredentials.stream().map(WebNetworkHandlerConfigInput::new).collect(Collectors.toList());
@@ -126,55 +124,52 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
return getService(env).initConnection(
getWebSession(env),
getProjectReference(env),
getArgumentVal(env, "id"),
getArgument(env, "credentials"),
env.getArgument("id"),
env.getArgument("credentials"),
nhc,
CommonUtils.toBoolean(getArgument(env, "saveCredentials")),
CommonUtils.toBoolean(getArgument(env, "sharedCredentials")),
getArgument(env, "selectedSecretId")
CommonUtils.toBoolean(env.getArgument("saveCredentials")),
CommonUtils.toBoolean(env.getArgument("sharedCredentials")),
env.getArgument("selectedSecretId")
);
}
)
.dataFetcher("testConnection", env -> getService(env).testConnection(
getWebSession(env), getProjectReference(env), getArgumentVal(env, "config")
getWebSession(env), getProjectReference(env), getConnectionConfig(env)
))
.dataFetcher("testNetworkHandler", env -> getService(env).testNetworkHandler(
getWebSession(env), new WebNetworkHandlerConfigInput(getArgument(env, "config"))
getWebSession(env), new WebNetworkHandlerConfigInput(env.getArgument("config"))
))
.dataFetcher("closeConnection", env -> getService(env).closeConnection(
getWebSession(env),
getProjectReference(env),
getArgumentVal(env, "id")))
.dataFetcher("closeConnection", env -> getService(env).closeConnection(getWebSession(env), getProjectReference(env), env.getArgument("id")))
.dataFetcher("setConnectionNavigatorSettings", env -> getService(env).setConnectionNavigatorSettings(
getWebSession(env),
getProjectReference(env),
getArgument(env, "id"),
WebServiceUtils.parseNavigatorSettings(getArgument(env, "settings"))
env.getArgument("id"),
WebServiceUtils.parseNavigatorSettings(env.getArgument("settings"))
))
.dataFetcher("asyncTaskInfo", env -> getService(env).getAsyncTaskInfo(
getWebSession(env),
getArgument(env, "id"),
getArgument(env, "removeOnFinish")))
.dataFetcher("asyncTaskCancel", env -> getService(env).cancelAsyncTask(getWebSession(env), getArgument(env, "id")))
env.getArgument("id"),
env.getArgument("removeOnFinish")))
.dataFetcher("asyncTaskCancel", env -> getService(env).cancelAsyncTask(getWebSession(env), env.getArgument("id")))
.dataFetcher("createConnectionFolder", env -> getService(env).createConnectionFolder(
getWebSession(env),
getProjectReference(env),
getArgument(env, "parentFolderPath"),
getArgumentVal(env, "folderName")
env.getArgument("parentFolderPath"),
env.getArgument("folderName")
))
.dataFetcher("renameConnectionFolder", env -> getService(env).renameConnectionFolder(
getWebSession(env),
getProjectReference(env),
getArgumentVal(env, "folderPath"),
getArgumentVal(env, "newName")
env.getArgument("folderPath"),
env.getArgument("newName")
))
.dataFetcher("deleteConnectionFolder", env -> getService(env).deleteConnectionFolder(
getWebSession(env),
getProjectReference(env),
getArgumentVal(env, "folderPath")
env.getArgument("folderPath")
))
;
@@ -182,4 +177,8 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
);
}
private WebConnectionConfig getConnectionConfig(DataFetchingEnvironment env) {
return new WebConnectionConfig(env.getArgument("config"));
}
}
@@ -20,7 +20,6 @@ package io.cloudbeaver.service.core.impl;
import io.cloudbeaver.*;
import io.cloudbeaver.model.*;
import io.cloudbeaver.model.app.ServletApplication;
import io.cloudbeaver.model.app.ServletSystemInformationCollector;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.registry.WebHandlerRegistry;
import io.cloudbeaver.registry.WebSessionHandlerDescriptor;
@@ -45,29 +44,23 @@ import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.exec.DBCConnectException;
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
import org.jkiss.dbeaver.model.navigator.DBNDataSource;
import org.jkiss.dbeaver.model.navigator.DBNModel;
import org.jkiss.dbeaver.model.navigator.DBNNode;
import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration;
import org.jkiss.dbeaver.model.net.DBWHandlerType;
import org.jkiss.dbeaver.model.net.DBWNetworkHandler;
import org.jkiss.dbeaver.model.net.DBWTunnel;
import org.jkiss.dbeaver.model.net.ssh.SSHSession;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.secret.DBSSecretController;
import org.jkiss.dbeaver.model.secret.DBSSecretValue;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor;
import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry;
import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor;
import org.jkiss.dbeaver.registry.settings.ProductSettingsRegistry;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.runtime.jobs.ConnectionTestJob;
import org.jkiss.dbeaver.utils.RuntimeUtils;
import org.jkiss.utils.CommonUtils;
import java.util.*;
@@ -90,13 +83,10 @@ public class WebServiceCore implements DBWServiceCore {
@Override
public WebPropertyInfo[] getSystemInformationProperties(@NotNull WebSession webSession) {
ServletSystemInformationCollector<?> collector = WebAppUtils.getWebApplication().getSystemInformationCollector();
try {
collector.collectInternalDatabaseUseInformation();
} catch (DBException e) {
log.error("Error collecting system information", e);
}
return WebCommonUtils.getObjectProperties(webSession, collector);
return WebCommonUtils.getObjectProperties(
webSession,
WebAppUtils.getWebApplication().getSystemInformationCollector()
);
}
@Override
@@ -290,7 +280,7 @@ public class WebServiceCore implements DBWServiceCore {
@Nullable String projectId,
@NotNull String connectionId
) throws DBWebException {
return WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, connectionId);
return WebAppUtils.getWebApplication().getConnectionController().getConnectionState(webSession, projectId, connectionId);
}
@@ -299,144 +289,71 @@ public class WebServiceCore implements DBWServiceCore {
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull String connectionId,
@Nullable Map<String, Object> authProperties,
@NotNull Map<String, Object> authProperties,
@Nullable List<WebNetworkHandlerConfigInput> networkCredentials,
boolean saveCredentials,
boolean sharedCredentials,
@Nullable String selectedSecretId
) throws DBWebException {
WebConnectionInfo connectionInfo = WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, connectionId);
connectionInfo.validateConnection();
connectionInfo.setSavedCredentials(authProperties, networkCredentials);
var dataSourceContainer = connectionInfo.getDataSourceContainer();
if (dataSourceContainer.isConnected()) {
throw new DBWebException("Datasource '" + dataSourceContainer.getName() + "' is already connected");
}
if (dataSourceContainer.isSharedCredentials() && selectedSecretId != null) {
List<DBSSecretValue> allSecrets;
try {
allSecrets = dataSourceContainer.listSharedCredentials();
} catch (DBException e) {
throw new DBWebException("Error loading connection secret", e);
}
DBSSecretValue selectedSecret =
allSecrets.stream()
.filter(secret -> selectedSecretId.equals(secret.getUniqueId()))
.findFirst().orElse(null);
if (selectedSecret == null) {
throw new DBWebException("Secret not found:" + selectedSecretId);
}
dataSourceContainer.setSelectedSharedCredentials(selectedSecret);
}
boolean oldSavePassword = dataSourceContainer.isSavePassword();
DBRProgressMonitor monitor = webSession.getProgressMonitor();
validateDriverLibrariesPresence(dataSourceContainer);
try {
boolean connect = dataSourceContainer.connect(monitor, true, false);
} catch (Exception e) {
if (e instanceof DBCConnectException) {
Throwable rootCause = CommonUtils.getRootCause(e);
if (rootCause instanceof ClassNotFoundException) {
log.error(e);
throwDriverNotFoundException(dataSourceContainer);
}
}
throw new DBWebException("Error connecting to database", e);
} finally {
dataSourceContainer.setSavePassword(oldSavePassword);
connectionInfo.clearCache();
}
// Mark all specified network configs as saved
boolean[] saveConfig = new boolean[1];
if (networkCredentials != null) {
networkCredentials.forEach(c -> {
if (CommonUtils.toBoolean(c.isSavePassword())) {
DBWHandlerConfiguration handlerCfg = dataSourceContainer.getConnectionConfiguration()
.getHandler(c.getId());
if (handlerCfg != null &&
// check username param only for ssh config
!(CommonUtils.isEmpty(c.getUserName()) && CommonUtils.equalObjects(
handlerCfg.getType(),
DBWHandlerType.TUNNEL
))
) {
WebDataSourceUtils.updateHandlerCredentials(handlerCfg, c);
handlerCfg.setSavePassword(true);
saveConfig[0] = true;
}
}
});
}
if (saveCredentials) {
// Save all passed credentials in the datasource container
WebDataSourceUtils.saveAuthProperties(
dataSourceContainer,
dataSourceContainer.getConnectionConfiguration(),
authProperties,
true,
sharedCredentials
);
var project = dataSourceContainer.getProject();
if (project.isUseSecretStorage()) {
try {
dataSourceContainer.persistSecrets(
DBSSecretController.getProjectSecretController(dataSourceContainer.getProject())
);
} catch (DBException e) {
throw new DBWebException("Failed to save credentials", e);
}
}
WebDataSourceUtils.saveCredentialsInDataSource(
connectionInfo,
dataSourceContainer,
dataSourceContainer.getConnectionConfiguration()
);
saveConfig[0] = true;
}
if (WebServiceUtils.isGlobalProject(dataSourceContainer.getProject())) {
// Do not flush config for global project (only admin can do it - CB-2415)
if (saveCredentials) {
connectionInfo.setCredentialsSavedInSession(true);
}
saveConfig[0] = false;
}
if (saveConfig[0]) {
dataSourceContainer.persistConfiguration();
}
return connectionInfo;
return WebAppUtils.getWebApplication().getConnectionController().initConnection(webSession, projectId,
connectionId, authProperties, networkCredentials, saveCredentials, sharedCredentials, selectedSecretId);
}
@Override
public WebConnectionInfo createConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull Map<String, Object> connectionConfig
@NotNull WebConnectionConfig connectionConfig
) throws DBWebException {
return getProjectById(webSession, projectId).createConnection(connectionConfig);
DBPDataSourceContainer dataSourceContainer =
WebAppUtils.getWebApplication().getConnectionController().createDataSourceContainer(webSession, projectId, connectionConfig);
return WebAppUtils.getWebApplication().getConnectionController().createConnection(
webSession,
projectId,
dataSourceContainer.getRegistry(),
dataSourceContainer
);
}
@Override
public WebConnectionInfo updateConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull Map<String, Object> connectionConfig
@NotNull WebConnectionConfig config
) throws DBWebException {
return getProjectById(webSession, projectId).updateConnection(connectionConfig);
DBPDataSourceContainer dataSourceContainer =
WebAppUtils.getWebApplication().getConnectionController().getDatasourceConnection(webSession, projectId, config);
return WebAppUtils.getWebApplication().getConnectionController().updateConnection(
webSession,
projectId,
config,
dataSourceContainer,
dataSourceContainer.getRegistry()
);
}
private WSDataSourceProperty getDatasourceEventProperty(
DataSourceDescriptor oldDataSource,
DBPDataSourceContainer dataSource
) {
if (!oldDataSource.equalConfiguration((DataSourceDescriptor) dataSource)) {
return WSDataSourceProperty.CONFIGURATION;
}
var nameChanged = !CommonUtils.equalObjects(oldDataSource.getName(), dataSource.getName());
var descriptionChanged = !CommonUtils.equalObjects(oldDataSource.getDescription(), dataSource.getDescription());
if (nameChanged && descriptionChanged) {
return WSDataSourceProperty.CONFIGURATION;
}
return nameChanged ? WSDataSourceProperty.NAME : WSDataSourceProperty.CONFIGURATION;
}
@Override
public boolean deleteConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull String connectionId
@NotNull WebSession webSession, @Nullable String projectId, @NotNull String connectionId
) throws DBWebException {
return getProjectById(webSession, projectId).deleteConnection(connectionId);
return WebAppUtils.getWebApplication().getConnectionController().deleteConnection(webSession, projectId, connectionId);
}
@@ -445,7 +362,7 @@ public class WebServiceCore implements DBWServiceCore {
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull String nodePath,
@NotNull Map<String, Object> connectionConfig
@NotNull WebConnectionConfig config
) throws DBWebException {
try {
DBNModel navigatorModel = webSession.getNavigatorModelOrThrow();
@@ -456,20 +373,19 @@ public class WebServiceCore implements DBWServiceCore {
if (srcNode == null) {
throw new DBException("Node '" + nodePath + "' not found");
}
if (!(srcNode instanceof DBNDataSource dbnDataSource)) {
if (!(srcNode instanceof DBNDataSource)) {
throw new DBException("Node '" + nodePath + "' is not a datasource node");
}
DBPDataSourceContainer dataSourceTemplate = dbnDataSource.getDataSourceContainer();
DBPDataSourceContainer dataSourceTemplate = ((DBNDataSource) srcNode).getDataSourceContainer();
DataSourceDescriptor newDataSource = dataSourceRegistry.createDataSource(dataSourceTemplate);
DBPDataSourceContainer newDataSource = dataSourceRegistry.createDataSource(dataSourceTemplate);
ServletApplication app = ServletAppUtils.getServletApplication();
if (app instanceof WebApplication webApplication) {
newDataSource.setNavigatorSettings(webApplication.getAppConfiguration().getDefaultNavigatorSettings());
((DataSourceDescriptor) newDataSource).setNavigatorSettings(
webApplication.getAppConfiguration().getDefaultNavigatorSettings());
}
WebConnectionConfig config = project.getConnectionConfigInput(connectionConfig);
// Copy props from config
if (!CommonUtils.isEmpty(config.getName())) {
newDataSource.setName(config.getName());
@@ -491,87 +407,12 @@ public class WebServiceCore implements DBWServiceCore {
public WebConnectionInfo testConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull Map<String, Object> connectionConfig
@NotNull WebConnectionConfig connectionConfig
) throws DBWebException {
WebSessionProjectImpl project = getProjectById(webSession, projectId);
WebConnectionConfig configInput = project.getConnectionConfigInput(connectionConfig);
configInput.setSaveCredentials(true); // It is used in createConnectionFromConfig
DataSourceDescriptor dataSource = (DataSourceDescriptor) WebDataSourceUtils.getLocalOrGlobalDataSource(
webSession, projectId, configInput.getConnectionId());
DataSourceDescriptor testDataSource;
if (dataSource != null) {
try {
// Check that creds are saved to trigger secrets resolve
dataSource.isCredentialsSaved();
} catch (DBException e) {
throw new DBWebException("Can't determine whether datasource credentials are saved", e);
}
testDataSource = (DataSourceDescriptor) dataSource.createCopy(dataSource.getRegistry());
WebDataSourceUtils.setConnectionConfiguration(
testDataSource.getDriver(),
testDataSource.getConnectionConfiguration(),
configInput
);
if (configInput.getSelectedSecretId() != null) {
try {
dataSource.listSharedCredentials()
.stream()
.filter(secret -> configInput.getSelectedSecretId().equals(secret.getSubjectId()))
.findFirst()
.ifPresent(testDataSource::setSelectedSharedCredentials);
} catch (DBException e) {
throw new DBWebException("Failed to load secret value: " + configInput.getSelectedSecretId());
}
}
WebDataSourceUtils.saveAuthProperties(
testDataSource,
testDataSource.getConnectionConfiguration(),
configInput.getCredentials(),
true,
false,
true
);
} else {
testDataSource = project.getDataSourceContainerFromInput(configInput);
}
validateDriverLibrariesPresence(testDataSource);
webSession.provideAuthParameters(
webSession.getProgressMonitor(),
testDataSource,
testDataSource.getConnectionConfiguration()
);
testDataSource.setSavePassword(true); // We need for test to avoid password callback
testDataSource.setAccessCheckRequired(!webSession.hasPermission(DBWConstants.PERMISSION_ADMIN));
try {
ConnectionTestJob ct = new ConnectionTestJob(
testDataSource, param -> {
}
);
ct.run(webSession.getProgressMonitor());
if (ct.getConnectError() != null) {
if (ct.getConnectError() instanceof DBCConnectException error) {
Throwable rootCause = CommonUtils.getRootCause(error);
if (rootCause instanceof ClassNotFoundException) {
log.error(error);
throwDriverNotFoundException(testDataSource);
}
}
throw new DBWebException("Connection failed", ct.getConnectError());
}
WebConnectionInfo connectionInfo = project.createConnectionInfo(testDataSource);
connectionInfo.setConnectError(ct.getConnectError());
connectionInfo.setServerVersion(ct.getServerVersion());
connectionInfo.setClientVersion(ct.getClientVersion());
connectionInfo.setConnectTime(RuntimeUtils.formatExecutionTime(ct.getConnectTime()));
return connectionInfo;
} catch (DBException e) {
throw new DBWebException("Error connecting to database", e);
}
DataSourceDescriptor dataSourceDescriptor = WebAppUtils.getWebApplication().getConnectionController()
.prepareTestConnection(webSession, projectId, connectionConfig);
return WebAppUtils.getWebApplication().getConnectionController()
.testConnection(webSession, projectId, connectionConfig, dataSourceDescriptor);
}
@Override
@@ -630,15 +471,42 @@ public class WebServiceCore implements DBWServiceCore {
@Override
public WebConnectionInfo closeConnection(
@NotNull WebSession webSession, @Nullable String projectId, @NotNull String connectionId
) throws DBWebException {
return closeAndDeleteConnection(webSession, projectId, connectionId, false);
}
@NotNull
private WebConnectionInfo closeAndDeleteConnection(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull String connectionId
@NotNull String projectId,
@NotNull String connectionId,
boolean forceDelete
) throws DBWebException {
WebSessionProjectImpl project = getProjectById(webSession, projectId);
WebConnectionInfo connectionInfo = project.getWebConnectionInfo(connectionId);
DBPDataSourceContainer dataSourceContainer = connectionInfo.getDataSourceContainer();
WebDataSourceUtils.disconnectDataSource(webSession, dataSourceContainer);
boolean disconnected = WebDataSourceUtils.disconnectDataSource(webSession, dataSourceContainer);
if (forceDelete) {
DBPDataSourceRegistry registry = project.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);
}
project.removeConnection(dataSourceContainer);
} else {
// Just reset saved credentials
connectionInfo.clearCache();
}
return connectionInfo;
}
@@ -743,23 +611,11 @@ public class WebServiceCore implements DBWServiceCore {
return new WebGroupPropertiesInfo<>(webSession, ProductSettingsRegistry.getInstance().getSettings());
}
@NotNull
private WebSessionProjectImpl getProjectById(@NotNull WebSession webSession, @Nullable String projectId) throws DBWebException {
private WebSessionProjectImpl getProjectById(WebSession webSession, String projectId) throws DBWebException {
WebSessionProjectImpl project = webSession.getProjectById(projectId);
if (project == null) {
throw new DBWebException("Project '" + projectId + "' not found");
}
return project;
}
private void validateDriverLibrariesPresence(@NotNull DBPDataSourceContainer container) throws DBWebException {
if (!DBWorkbench.isDistributed() && container.getDriver().getDriverLoader(container).needsExternalDependencies()) {
throwDriverNotFoundException(container);
}
}
private static void throwDriverNotFoundException(@NotNull DBPDataSourceContainer container) throws DBWebException {
throw new DBWebException("Driver files for %s are not found. Please ask the administrator to download it."
.formatted(container.getDriver().getName()));
}
}
@@ -34,15 +34,15 @@ public interface DBWServiceNavigator extends DBWService {
@WebAction
List<WebNavigatorNodeInfo> getNavigatorNodeChildren(
@NotNull WebSession session,
@NotNull String parentPath,
@Nullable Integer offset,
@Nullable Integer limit,
@Nullable Boolean onlyFolders) throws DBWebException;
String parentPath,
Integer offset,
Integer limit,
Boolean onlyFolders) throws DBWebException;
@WebAction
List<WebNavigatorNodeInfo> getNavigatorNodeParents(
@NotNull WebSession session,
@NotNull String nodePath) throws DBWebException;
String nodePath) throws DBWebException;
@WebAction
WebNavigatorNodeInfo getNavigatorNodeInfo(
@@ -38,61 +38,61 @@ public class WebServiceBindingNavigator extends WebServiceBindingBase<DBWService
model.getQueryType()
.dataFetcher("navNodeChildren", env -> getService(env).getNavigatorNodeChildren(
getWebSession(env),
getArgumentVal(env, "parentPath"),
getArgument(env, "offset"),
getArgument(env, "limit"),
getArgument(env, "onlyFolders")
env.getArgument("parentPath"),
env.getArgument("offset"),
env.getArgument("limit"),
env.getArgument("onlyFolders")
))
.dataFetcher("navNodeParents", env -> getService(env).getNavigatorNodeParents(
getWebSession(env),
getArgumentVal(env, "nodePath")
env.getArgument("nodePath")
))
.dataFetcher("navNodeInfo", env -> getService(env).getNavigatorNodeInfo(
getWebSession(env),
getArgumentVal(env, "nodePath")
env.getArgument("nodePath")
))
.dataFetcher("navRefreshNode", env -> getService(env).refreshNavigatorNode(
getWebSession(env),
getArgumentVal(env, "nodePath"),
env.getArgument("nodePath"),
false
) != null)
.dataFetcher("navGetStructContainers", env -> getService(env).getStructContainers(
getProjectReference(env),
getWebConnection(env),
getArgument(env, "contextId"),
getArgument(env, "catalog")
env.getArgument("contextId"),
env.getArgument("catalog")
));
model.getMutationType()
.dataFetcher("navReloadNode", env -> getService(env).refreshNavigatorNode(
getWebSession(env),
getArgumentVal(env, "nodePath"),
env.getArgument("nodePath"),
true
))
.dataFetcher("navSetFolderFilter", env -> getService(env).setNavigatorNodeFilter(
getWebSession(env),
getArgumentVal(env, "nodePath"),
getArgument(env, "include"),
getArgument(env, "exclude")
env.getArgument("nodePath"),
env.getArgument("include"),
env.getArgument("exclude")
))
.dataFetcher("navRenameNode", env -> getService(env).renameNode(
getWebSession(env),
getArgumentVal(env, "nodePath"),
getArgumentVal(env, "newName")
env.getArgument("nodePath"),
env.getArgument("newName")
))
.dataFetcher("navDeleteNodes", env -> getService(env).deleteNodes(
getWebSession(env),
getArgumentVal(env, "nodePaths")
env.getArgument("nodePaths")
))
.dataFetcher("navMoveNodesToFolder", env -> getService(env).moveNodesToFolder(
getWebSession(env),
getArgumentVal(env, "nodePaths"),
getArgumentVal(env, "folderPath")
env.getArgument("nodePaths"),
env.getArgument("folderPath")
));
model.getRuntimeWiring().type(TypeRuntimeWiring.newTypeWiring("DatabaseObjectInfo")
.dataFetcher("properties", env -> {
Map<String, Object> filterProps = getArgument(env, "filter");
Map<String, Object> filterProps = env.getArgument("filter");
WebPropertyFilter filter = filterProps == null ? null : new WebPropertyFilter(filterProps);
return ((WebDatabaseObjectInfo)env.getSource()).filterProperties(filter);
})
@@ -16,7 +16,6 @@
*/
package io.cloudbeaver.service.navigator;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import java.util.ArrayList;
@@ -35,8 +34,6 @@ public class WebStructContainers {
private List<WebNavigatorNodeInfo> schemaList = new ArrayList<>();
private boolean supportsCatalogChange = false;
private boolean supportsSchemaChange = false;
private String defaultCatalog = null;
private String defaultSchema = null;
public Boolean getSupportsCatalogChange() {
return supportsCatalogChange;
@@ -77,22 +74,4 @@ public class WebStructContainers {
public void setSupportsSchemaChange(boolean support) {
this.supportsSchemaChange = support;
}
@Nullable
public String getDefaultCatalog() {
return defaultCatalog;
}
public void setDefaultCatalog(@Nullable String defaultCatalog) {
this.defaultCatalog = defaultCatalog;
}
@Nullable
public String getDefaultSchema() {
return defaultSchema;
}
public void setDefaultSchema(@Nullable String defaultSchema) {
this.defaultSchema = defaultSchema;
}
}
@@ -17,7 +17,9 @@
package io.cloudbeaver.service.navigator.impl;
import io.cloudbeaver.*;
import io.cloudbeaver.BaseWebProjectImpl;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.model.WebCommandContext;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.rm.DBNAbstractResourceManagerNode;
@@ -42,7 +44,6 @@ import org.jkiss.dbeaver.model.exec.DBCExecutionContext;
import org.jkiss.dbeaver.model.exec.DBCExecutionContextDefaults;
import org.jkiss.dbeaver.model.exec.DBExecUtils;
import org.jkiss.dbeaver.model.navigator.*;
import org.jkiss.dbeaver.model.navigator.fs.DBNPathBase;
import org.jkiss.dbeaver.model.navigator.meta.DBXTreeItem;
import org.jkiss.dbeaver.model.rm.RMControllerProvider;
import org.jkiss.dbeaver.model.rm.RMProject;
@@ -108,14 +109,6 @@ public class WebServiceNavigator implements DBWServiceNavigator {
Set<String> nodeIds = new HashSet<>(); // filter duplicate node ids
for (DBNNode node : nodeChildren) {
// TODO: find a better way to filter out empty folder nodes for non-admin users
if (node instanceof DBNLocalFolder localFolderNode &&
localFolderNode.getDataSourceRegistry() instanceof WebGlobalProjectRegistryProxy globalProjectRegistryProxy
) {
if (!globalProjectRegistryProxy.getAllFolders().contains(localFolderNode.getFolder())) {
continue;
}
}
if (node instanceof DBNDatabaseFolder folderNode && CommonUtils.isEmpty(folderNode.getMeta().getChildren(null))) {
// Skip empty folders. Folder may become empty if their nested elements are provided by UI plugins.
continue;
@@ -164,7 +157,7 @@ public class WebServiceNavigator implements DBWServiceNavigator {
@Override
public List<WebNavigatorNodeInfo> getNavigatorNodeParents(
@NotNull WebSession session,
@NotNull String nodePath
String nodePath
) throws DBWebException {
try {
DBRProgressMonitor monitor = session.getProgressMonitor();
@@ -330,17 +323,8 @@ public class WebServiceNavigator implements DBWServiceNavigator {
WebStructContainers structContainers = new WebStructContainers();
if (contextDefaults != null) {
structContainers.setSupportsCatalogChange(contextDefaults.supportsCatalogChange());
structContainers.setSupportsSchemaChange(contextDefaults.supportsSchemaChange());
if (contextDefaults.getDefaultSchema() != null) {
structContainers.setDefaultSchema(contextDefaults.getDefaultSchema().getName());
}
if (contextDefaults.getDefaultCatalog() != null) {
structContainers.setDefaultCatalog(contextDefaults.getDefaultCatalog().getName());
}
}
structContainers.setSupportsCatalogChange(contextDefaults != null && contextDefaults.supportsCatalogChange());
structContainers.setSupportsSchemaChange(contextDefaults != null && contextDefaults.supportsSchemaChange());
DBRProgressMonitor monitor = connection.getSession().getProgressMonitor();
List<? extends DBSObject> dbsObjects = this.getCatalogs(
@@ -469,14 +453,12 @@ public class WebServiceNavigator implements DBWServiceNavigator {
@NotNull
private String renameConnectionFolder(@NotNull WebSession session, DBNNode node, @NotNull String newName) throws DBException {
WebConnectionFolderUtils.validateConnectionFolder(newName);
DBNNode[] children = ((DBNLocalFolder) node).getLogicalParent().getChildren(session.getProgressMonitor());
if (children != null) {
List<String> siblings = Arrays.stream(children)
.filter(n -> n instanceof DBNLocalFolder)
.map(DBNNode::getName).toList();
if (siblings.contains(newName)) {
throw new DBWebException("Name " + newName + " is unavailable or invalid");
}
List<String> siblings = Arrays.stream(
((DBNLocalFolder) node).getLogicalParent().getChildren(session.getProgressMonitor()))
.filter(n -> n instanceof DBNLocalFolder)
.map(DBNNode::getName).toList();
if (siblings.contains(newName)) {
throw new DBWebException("Name " + newName + " is unavailable or invalid");
}
node.rename(session.getProgressMonitor(), newName);
return node.getName();
@@ -605,8 +587,6 @@ public class WebServiceNavigator implements DBWServiceNavigator {
return SMUtils.hasProjectPermission(session, rmProject, RMProjectPermission.DATA_SOURCES_EDIT);
} else if (node instanceof DBNAbstractResourceManagerNode) {
return SMUtils.hasProjectPermission(session, rmProject, RMProjectPermission.RESOURCE_EDIT);
} else if (node instanceof DBNPathBase) {
return !DBWorkbench.isDistributed() || session.hasPermission(DBWConstants.PERMISSION_FS_RESOURCE_EDIT);
}
return true;
}
@@ -101,8 +101,7 @@ public interface DBWServiceSQL extends DBWService {
@Nullable String resultId,
@Nullable WebSQLDataFilter filter,
@Nullable WebDataFormat dataFormat,
boolean readLogs,
boolean useEvents
boolean readLogs
) throws DBException;
@WebAction
@@ -69,7 +69,7 @@ public class WebSQLContextInfo implements WebSessionProvider {
private final transient WebSQLProcessor processor;
private final String id;
private final String projectId;
private final transient Map<String, WebSQLResultsInfo> resultInfoMap = new HashMap<>();
private final Map<String, WebSQLResultsInfo> resultInfoMap = new HashMap<>();
private final AtomicInteger resultId = new AtomicInteger();
@@ -71,7 +71,7 @@ public class WebSQLExecuteInfo {
@Property
public String getOriginalQuery() {
//TODO implement
return null; // should return the original query without data filters
return null;
}
@Property
@@ -19,7 +19,6 @@ package io.cloudbeaver.service.sql;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.app.ServletApplication;
import io.cloudbeaver.model.session.WebSession;
@@ -34,7 +33,6 @@ import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import java.io.IOException;
import java.lang.reflect.Type;
@@ -75,10 +73,6 @@ public class WebSQLFileLoaderServlet extends WebServiceServletBase {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Update for users only");
return;
}
if (DBWorkbench.isDistributed() && !session.hasPermission(DBWConstants.PERMISSION_SQL_RESULT_UPDATE)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Permission denied");
return;
}
if (!"POST".equalsIgnoreCase(request.getMethod())) {
return;
@@ -17,14 +17,11 @@
package io.cloudbeaver.service.sql;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.session.WebSessionPreferenceStore;
import io.cloudbeaver.model.session.WebSessionProvider;
import io.cloudbeaver.server.WebAppUtils;
import io.cloudbeaver.server.jobs.SqlOutputLogReaderJob;
import io.cloudbeaver.service.sql.messages.WebSQLMessages;
import org.eclipse.jface.text.Document;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
@@ -51,26 +48,17 @@ import org.jkiss.dbeaver.model.sql.parser.SQLParserContext;
import org.jkiss.dbeaver.model.sql.parser.SQLRuleManager;
import org.jkiss.dbeaver.model.sql.parser.SQLScriptParser;
import org.jkiss.dbeaver.model.struct.*;
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
import org.jkiss.dbeaver.model.websocket.event.WSTransactionalCountEvent;
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionTaskConfirmationRequestEvent;
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionTaskQueryConfirmationRequestEvent;
import org.jkiss.dbeaver.registry.confirmation.ConfirmationConstants;
import org.jkiss.dbeaver.registry.confirmation.ConfirmationDescriptor;
import org.jkiss.dbeaver.registry.confirmation.ConfirmationRegistry;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@@ -178,9 +166,7 @@ public class WebSQLProcessor implements WebSessionProvider {
@Nullable WebSQLDataFilter filter,
@Nullable WebDataFormat dataFormat,
@NotNull WebSession webSession,
@NotNull WebAsyncTaskInfo asyncTask,
boolean readLogs,
boolean useEvents
boolean readLogs
) throws DBWebException, DBCException {
if (filter == null) {
// Use default filter
@@ -216,11 +202,9 @@ public class WebSQLProcessor implements WebSessionProvider {
SQLScriptElement element = SQLScriptParser.extractActiveQuery(parserContext, 0, sql.length());
boolean isGenerated = false;
if (element instanceof SQLControlCommand command) {
SQLControlResult controlResult = dataContainer.getScriptContext().executeControlCommand(monitor, command);
if (controlResult.getTransformed() != null) {
isGenerated = true;
element = controlResult.getTransformed();
} else {
WebSQLQueryResults stats = new WebSQLQueryResults(webSession, dataFormat);
@@ -228,13 +212,6 @@ public class WebSQLProcessor implements WebSessionProvider {
}
}
if (element instanceof SQLQuery mainQuery) {
if (useEvents) {
boolean isConfirmed = confirmQueryIfNeeded(mainQuery.getScriptElements(), asyncTask, isGenerated);
if (!isConfirmed) {
throw new DBWebException("Query execution was cancelled by user");
}
}
DBExecUtils.tryExecuteRecover(monitor, connection.getDataSource(), param -> {
try (DBCSession session = context.openSession(monitor, resolveQueryPurpose(dataFilter), "Execute SQL")) {
List<SQLScriptElement> sqlQueries = mainQuery.getScriptElements();
@@ -292,6 +269,8 @@ public class WebSQLProcessor implements WebSessionProvider {
sqlOutputLogReaderJob.join();
}
fillQueryResults(contextInfo, dataContainer, dbStat, hasResultSet, executeInfo, webDataFilter, dataFilter, dataFormat);
} catch (DBException e) {
throw new InvocationTargetException(e);
}
}
}
@@ -333,9 +312,7 @@ public class WebSQLProcessor implements WebSessionProvider {
DBDDataFilter dataFilter = filter.makeDataFilter((resultId == null ? null : contextInfo.getResults(resultId)));
DBExecUtils.tryExecuteRecover(monitor, connection.getDataSource(), param -> {
try (DBCSession session = executionContext.openSession(monitor, resolveQueryPurpose(dataFilter), "Read data from container")) {
try (
WebSQLQueryDataReceiver dataReceiver = new WebSQLQueryDataReceiver(contextInfo, dataContainer, dataFormat, dataFilter)
) {
try (WebSQLQueryDataReceiver dataReceiver = new WebSQLQueryDataReceiver(contextInfo, dataContainer, dataFormat)) {
DBCStatistics statistics = dataContainer.readData(
new WebExecutionSource(dataContainer, executionContext, this),
session,
@@ -352,12 +329,14 @@ public class WebSQLProcessor implements WebSessionProvider {
results.setResultSet(resultSet);
executeInfo.setResults(new WebSQLQueryResults[]{results});
setResultFilterText(session.getDataSource(), executeInfo, dataFilter);
setResultFilterText(dataContainer, session.getDataSource(), executeInfo, dataFilter);
executeInfo.setFullQuery(statistics.getQueryText());
if (resultSet != null && resultSet.getRowsWithMetaData() != null && resultSet.getResultsInfo() != null) {
resultSet.getResultsInfo().setQueryText(statistics.getQueryText());
executeInfo.setStatusMessage(resultSet.getRowsWithMetaData().size() + " row(s) fetched");
}
} catch (DBException e) {
throw new InvocationTargetException(e);
}
}
});
@@ -397,7 +376,7 @@ public class WebSQLProcessor implements WebSessionProvider {
for (var rowIdentifier : rowIdentifierList) {
Map<DBSDataManipulator.ExecuteBatch, Object[]> resultBatches = new LinkedHashMap<>();
DBSDataManipulator dataManipulator = generateUpdateResultsDataBatch(
monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, resultBatches, keyReceiver);
monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, dataFormat, resultBatches, keyReceiver);
DBCExecutionContext executionContext = getExecutionContext(dataManipulator);
try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Update data in container")) {
@@ -498,7 +477,7 @@ public class WebSQLProcessor implements WebSessionProvider {
@NotNull Set<WebSQLQueryResultSetRow> newResultSetRows,
@Nullable WebDataFormat dataFormat,
@NotNull DBRProgressMonitor monitor)
throws DBException {
throws DBCException {
try (DBCSession session = getExecutionContext().openSession(
monitor,
DBCExecutionPurpose.UTIL,
@@ -574,8 +553,9 @@ public class WebSQLProcessor implements WebSessionProvider {
@NotNull String resultsId,
@Nullable List<WebSQLResultsRow> updatedRows,
@Nullable List<WebSQLResultsRow> deletedRows,
@Nullable List<WebSQLResultsRow> addedRows
) throws DBException {
@Nullable List<WebSQLResultsRow> addedRows,
@Nullable WebDataFormat dataFormat) throws DBException
{
Map<DBSDataManipulator.ExecuteBatch, Object[]> resultBatches = new LinkedHashMap<>();
@@ -591,7 +571,7 @@ public class WebSQLProcessor implements WebSessionProvider {
StringBuilder sqlBuilder = new StringBuilder();
for (var rowIdentifier : rowIdentifierList) {
DBSDataManipulator dataManipulator = generateUpdateResultsDataBatch(
monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, resultBatches, null);
monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, dataFormat, resultBatches, null);
List<DBEPersistAction> actions = new ArrayList<>();
@@ -617,6 +597,7 @@ public class WebSQLProcessor implements WebSessionProvider {
@Nullable List<WebSQLResultsRow> updatedRows,
@Nullable List<WebSQLResultsRow> deletedRows,
@Nullable List<WebSQLResultsRow> addedRows,
@Nullable WebDataFormat dataFormat,
@NotNull Map<DBSDataManipulator.ExecuteBatch, Object[]> resultBatches,
@Nullable DBDDataReceiver keyReceiver)
throws DBException
@@ -876,6 +857,7 @@ public class WebSQLProcessor implements WebSessionProvider {
@NotNull
public WebSQLExecutionPlan explainExecutionPlan(
@NotNull DBRProgressMonitor monitor,
@NotNull WebSQLContextInfo contextInfo,
@NotNull String sql,
@NotNull Map<String, Object> configuration) throws DBWebException {
@@ -900,6 +882,8 @@ public class WebSQLProcessor implements WebSessionProvider {
DBCQueryPlannerConfiguration planConfig = new DBCQueryPlannerConfiguration();
planConfig.getParameters().putAll(configuration);
dbcPlan[0] = planner.planQueryExecution(session, sql, planConfig);
} catch (DBException e) {
throw new InvocationTargetException(e);
}
});
} catch (DBException e) {
@@ -939,14 +923,16 @@ public class WebSQLProcessor implements WebSessionProvider {
private void readCellDataValue(
@NotNull DBRProgressMonitor monitor,
@NotNull WebSQLResultsInfo resultsInfo,
@NotNull WebSQLResultsRow row,
@Nullable WebSQLResultsRow row,
@NotNull WebSQLCellValueReceiver dataReceiver
) throws DBException {
DBSDataContainer dataContainer = resultsInfo.getDataContainer();
DBCExecutionContext executionContext = getExecutionContext(dataContainer);
try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Generate data update batches")) {
DBDDataFilter dataFilter = new DBDDataFilter();
addKeyAttributes(resultsInfo, row, dataContainer, session, dataFilter);
if (!resultsInfo.isSingleRow()) {
addKeyAttributes(resultsInfo, row, dataContainer, session, dataFilter);
}
WebExecutionSource executionSource = new WebExecutionSource(dataContainer, executionContext, this);
dataContainer.readData(
executionSource, session, dataReceiver, dataFilter,
@@ -956,16 +942,12 @@ public class WebSQLProcessor implements WebSessionProvider {
private void addKeyAttributes(
@NotNull WebSQLResultsInfo resultsInfo,
@NotNull WebSQLResultsRow row,
@Nullable WebSQLResultsRow row,
@NotNull DBSDataContainer dataContainer,
@NotNull DBCSession session,
@NotNull DBDDataFilter dataFilter
) throws DBException {
DBDRowIdentifier rowIdentifier = resultsInfo.getDefaultRowIdentifier();
if (rowIdentifier == null || rowIdentifier.isIncomplete()) {
return;
}
DBDAttributeBinding[] keyAttributes = rowIdentifier.getAttributes().toArray(new DBDAttributeBinding[0]);
DBDAttributeBinding[] keyAttributes = resultsInfo.getDefaultRowIdentifier().getAttributes().toArray(new DBDAttributeBinding[0]);
Object[] rowValues = new Object[keyAttributes.length];
List<DBDAttributeConstraint> constraints = new ArrayList<>();
for (int i = 0; i < keyAttributes.length; i++) {
@@ -1069,16 +1051,10 @@ public class WebSQLProcessor implements WebSessionProvider {
if (resultSet == null) {
break;
}
try (
WebSQLQueryDataReceiver dataReceiver = new WebSQLQueryDataReceiver(
contextInfo,
dataContainer,
dataFormat,
dataFilter
)
) {
try (WebSQLQueryDataReceiver dataReceiver = new WebSQLQueryDataReceiver(contextInfo, dataContainer, dataFormat)) {
readResultSet(dbStat.getSession(), resultSet, webDataFilter, dataReceiver);
results.setResultSet(dataReceiver.getResultSet());
dataReceiver.getResultSet().getResultsInfo().setQueryText(resultSet.getSourceStatement().getQueryString());
}
}
resultList.add(results);
@@ -1098,15 +1074,11 @@ public class WebSQLProcessor implements WebSessionProvider {
}
executeInfo.setResults(resultList.toArray(new WebSQLQueryResults[0]));
setResultFilterText(dbStat.getSession().getDataSource(), executeInfo, dataFilter);
setResultFilterText(dataContainer, dbStat.getSession().getDataSource(), executeInfo, dataFilter);
executeInfo.setFullQuery(dbStat.getQueryString());
}
private void setResultFilterText(
@NotNull DBPDataSource dataSource,
@NotNull WebSQLExecuteInfo executeInfo,
@NotNull DBDDataFilter filter
) throws DBException {
private void setResultFilterText(@NotNull DBSDataContainer dataContainer, @NotNull DBPDataSource dataSource, @NotNull WebSQLExecuteInfo executeInfo, @NotNull DBDDataFilter filter) throws DBException {
if (!filter.getConstraints().isEmpty() || !CommonUtils.isEmpty(filter.getWhere())) {
StringBuilder where = new StringBuilder();
SQLUtils.appendConditionString(
@@ -1119,13 +1091,8 @@ public class WebSQLProcessor implements WebSessionProvider {
}
}
private void readResultSet(
@NotNull DBCSession session,
@NotNull DBCResultSet dbResult,
@NotNull WebSQLDataFilter filter,
@NotNull WebSQLQueryDataReceiver dataReceiver
) throws DBException {
DBDDataReceiver.startFetchWorkflow(dataReceiver, session, dbResult, filter.getOffset(), filter.getLimit());
private void readResultSet(@NotNull DBCSession session, @NotNull DBCResultSet dbResult, @NotNull WebSQLDataFilter filter, @NotNull WebSQLQueryDataReceiver dataReceiver) throws DBCException {
dataReceiver.fetchStart(session, dbResult, filter.getOffset(), filter.getLimit());
int rowCount = 0;
while (dbResult.nextRow()) {
if (rowCount > filter.getLimit()) {
@@ -1135,6 +1102,7 @@ public class WebSQLProcessor implements WebSessionProvider {
dataReceiver.fetchRow(session, dbResult);
rowCount++;
}
dataReceiver.fetchEnd(session, dbResult);
}
/**
@@ -1172,6 +1140,7 @@ public class WebSQLProcessor implements WebSessionProvider {
if (keyValue == null) {
continue;
}
boolean updated = false;
if (!CommonUtils.isEmpty(keyAttribute.getName())) {
DBDAttributeBinding binding = DBUtils.findObject(resultsAttributes, keyAttribute.getName());
if (binding != null) {
@@ -1242,7 +1211,8 @@ public class WebSQLProcessor implements WebSessionProvider {
private Object setCellRowValue(Object cellRow, WebSession webSession, DBCSession dbcSession, DBDAttributeBinding allAttributes, boolean withoutExecution)
throws DBException {
if (cellRow instanceof Map<?, ?> variables) {
if (cellRow instanceof Map<?, ?>) {
Map<String, Object> variables = (Map<String, Object>) cellRow;
if (variables.get(FILE_ID) != null) {
Path path = WebAppUtils.getWebPlatform()
.getTempFolder(webSession.getProgressMonitor(), TEMP_FILE_FOLDER)
@@ -1259,112 +1229,4 @@ public class WebSQLProcessor implements WebSessionProvider {
}
return convertInputCellValue(dbcSession, allAttributes, cellRow, withoutExecution);
}
private boolean confirmQueryIfNeeded(
@NotNull List<SQLScriptElement> scriptElements,
@NotNull WebAsyncTaskInfo asyncTask,
boolean isGenerated
) throws DBWebException {
Boolean skipConfirmations = webSession.getAttribute(WebSQLConstants.SKIP_TASK_CONFIRMATIONS_ATTR);
if (skipConfirmations != null && skipConfirmations) {
return true;
}
boolean hasGeneratedUpdates = false;
boolean hasDangerousUpdates = false;
boolean hasDropStatement = false;
String title = null;
String message = null;
String queryPreview = null;
if (isGenerated) {
Set<SQLQueryCategory> categories = SQLQueryCategory.categorizeScript(scriptElements);
hasGeneratedUpdates = categories.contains(SQLQueryCategory.DDL) ||
categories.contains(SQLQueryCategory.DML) ||
categories.contains(SQLQueryCategory.UNKNOWN);
title = WebSQLMessages.model_web_ai_query_confirmation_title;
message = WebSQLMessages.model_web_ai_query_confirmation_message;
queryPreview = scriptElements.stream()
.map(SQLScriptElement::getText)
.collect(Collectors.joining("\n\n"));
} else {
WebSessionPreferenceStore store = webSession.getUserPreferenceStore();
boolean confirmDangerousQueries = store.getUserPreferenceBoolean(ConfirmationConstants.CONFIRM_DANGER_SQL_KEY, true);
boolean confirmDropQueries = store.getUserPreferenceBoolean(ConfirmationConstants.CONFIRM_DROP_SQL_KEY, true);
for (SQLScriptElement scriptElement : scriptElements) {
if (scriptElement instanceof SQLQuery sqlQuery) {
if (confirmDangerousQueries && sqlQuery.isDeleteUpdateDangerous()) {
hasDangerousUpdates = true;
ConfirmationDescriptor descriptor = ConfirmationRegistry.getInstance()
.getConfirmation(ConfirmationConstants.CONFIRM_DANGER_SQL_ID);
title = descriptor.getLocalizedTitle(webSession.getLocale());
var entityMetadata = sqlQuery.getEntityMetadata(false);
message = MessageFormat.format(
descriptor.getLocalizedMessage(webSession.getLocale()),
sqlQuery.getType().name(),
entityMetadata != null ? entityMetadata.getEntityName() : "multiple tables"
);
break;
}
if (confirmDropQueries && sqlQuery.isDropTableDangerous()) {
hasDropStatement = true;
ConfirmationDescriptor descriptor = ConfirmationRegistry.getInstance()
.getConfirmation(ConfirmationConstants.CONFIRM_DROP_SQL_ID);
title = descriptor.getLocalizedTitle(webSession.getLocale());
message = descriptor.getLocalizedMessage(webSession.getLocale());
break;
}
}
}
}
if (!hasGeneratedUpdates && !hasDangerousUpdates && !hasDropStatement) {
return true;
} else {
return requestConfirmation(asyncTask, queryPreview, title, message);
}
}
private boolean requestConfirmation(
@NotNull WebAsyncTaskInfo asyncTask,
@Nullable String query,
@NotNull String title,
@NotNull String message
) throws DBWebException {
String attributeName = WebSQLConstants.TASK_CONFIRMATION_ATTR_PREFIX + asyncTask.getId();
CompletableFuture<Boolean> confirmationFuture = new CompletableFuture<>();
webSession.setAttribute(attributeName, confirmationFuture);
webSession.addSessionEvent(createConfirmationEvent(asyncTask, query, title, message));
try {
Boolean isConfirmed = confirmationFuture.get(WebSQLConstants.TASK_CONFIRMATION_TIMEOUT_SECONDS, TimeUnit.SECONDS);
return isConfirmed != null && isConfirmed;
} catch (TimeoutException e) {
throw new DBWebException("Query confirmation timeout");
} catch (Exception e) {
throw new DBWebException("Error when processing confirmation response", e);
} finally {
webSession.removeAttribute(attributeName);
}
}
@NotNull
private WSEvent createConfirmationEvent(
@NotNull WebAsyncTaskInfo asyncTask,
@Nullable String query,
@NotNull String title,
@NotNull String message
) {
WSEvent confirmationEvent;
if (query != null) {
confirmationEvent = new WSSessionTaskQueryConfirmationRequestEvent(
asyncTask.getId(), title, message, query
);
} else {
confirmationEvent = new WSSessionTaskConfirmationRequestEvent(
asyncTask.getId(), title, message
);
}
return confirmationEvent;
}
}
@@ -81,13 +81,12 @@ public class WebSQLQueryDataContainer implements DBSDataContainer, DBPContextPro
return queryDataContainer.getDescription();
}
@NotNull
@Nullable
@Override
public DBPDataSource getDataSource() {
return dataSource;
}
@NotNull
@Override
public String[] getSupportedFeatures() {
return queryDataContainer.getSupportedFeatures();
@@ -104,25 +103,14 @@ public class WebSQLQueryDataContainer implements DBSDataContainer, DBPContextPro
long maxRows,
long flags,
int fetchSize
) throws DBException {
) throws DBCException {
return queryDataContainer.readData(source, session, dataReceiver, dataFilter, firstRow, maxRows, flags, fetchSize);
}
@Override
public long countData(@NotNull DBCExecutionSource source, @NotNull DBCSession session, @Nullable DBDDataFilter dataFilter, long flags) throws DBCException {
try {
String filteredQuery = dataFilter == null ? query : getDataSource().getSQLDialect().addFiltersToQuery(
session.getProgressMonitor(),
getDataSource(),
query,
dataFilter
);
SQLQuery countQuery = new SQLQueryTransformerCount().transformQuery(
dataSource,
syntaxManager,
new SQLQuery(dataSource, filteredQuery)
);
SQLQuery countQuery = new SQLQueryTransformerCount().transformQuery(dataSource, syntaxManager, new SQLQuery(dataSource, query));
return DBUtils.countDataFromQuery(source, session, countQuery);
} catch (DBException e) {
throw new DBCException("Error executing row count", e);
@@ -19,7 +19,6 @@ package io.cloudbeaver.service.sql;
import io.cloudbeaver.model.session.WebSession;
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.DBPDataKind;
@@ -42,14 +41,9 @@ import java.util.stream.Collectors;
class WebSQLQueryDataReceiver implements DBDDataReceiver {
private static final Log log = Log.getLog(WebSQLQueryDataReceiver.class);
@NotNull
private final WebSQLContextInfo contextInfo;
@NotNull
private final DBSDataContainer dataContainer;
@Nullable
private final WebDataFormat dataFormat;
@Nullable
private final DBDDataFilter dataFilter;
private final WebSQLQueryResultSet webResultSet = new WebSQLQueryResultSet();
private DBDAttributeBinding[] bindings;
@@ -57,16 +51,10 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
private List<WebSQLQueryResultSetRow> rows = new ArrayList<>();
private final Number rowLimit;
WebSQLQueryDataReceiver(
@NotNull WebSQLContextInfo contextInfo,
@NotNull DBSDataContainer dataContainer,
@Nullable WebDataFormat dataFormat,
@Nullable DBDDataFilter dataFilter
) {
WebSQLQueryDataReceiver(WebSQLContextInfo contextInfo, DBSDataContainer dataContainer, WebDataFormat dataFormat) {
this.contextInfo = contextInfo;
this.dataContainer = dataContainer;
this.dataFormat = dataFormat;
this.dataFilter = dataFilter;
rowLimit = ServletAppUtils.getServletApplication()
.getAppConfiguration()
.getResourceQuota(WebSQLConstants.QUOTA_PROP_ROW_LIMIT);
@@ -174,8 +162,6 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
webResultSet.setReadOnlyInfo(contextInfo.getProcessor().getExecutionContext());
WebSQLResultsInfo resultsInfo = contextInfo.saveResult(dataContainer, trace, bindings, rows.size() == 1);
resultsInfo.setDataFilter(dataFilter);
resultsInfo.setQueryText(resultSet.getSourceStatement().getQueryString());
webResultSet.setResultsInfo(resultsInfo);
boolean isSingleEntity = DBExecUtils.detectSingleSourceTable(bindings) != null;
@@ -246,6 +232,6 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
@Override
public void close() {
// no-op
rows.clear();
}
}
@@ -22,10 +22,12 @@ import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.CBConstants;
import io.cloudbeaver.service.WebServiceServletBase;
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jkiss.code.NotNull;
import jakarta.servlet.http.Part;
import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.utils.CommonUtils;
@@ -35,6 +37,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
import java.util.regex.Pattern;
@MultipartConfig
@@ -54,41 +57,44 @@ public class WebSQLResultServlet extends WebServiceServletBase {
}
@Override
protected void processServiceRequest(
WebSession session,
HttpServletRequest request,
HttpServletResponse response
) throws DBException, IOException {
if (!request.getMethod().equals("GET")) {
return;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setAttribute(ServletContextRequest.MULTIPART_CONFIG_ELEMENT, MULTI_PART_CONFIG);
String fileName = UUID.randomUUID().toString();
for (Part part : request.getParts()) {
part.write(WebSQLDataLOBReceiver.DATA_EXPORT_FOLDER + "/" + fileName);
}
Path dataFile = getDataFile(request);
session.addInfoMessage("Download LOB file ...");
response.setHeader("Content-Type", "application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=\"" + dataFile.getFileName().toString() + "\"");
response.setHeader("Content-Length", String.valueOf(Files.size(dataFile)));
response.setDateHeader("Expires", System.currentTimeMillis() + CBConstants.STATIC_CACHE_SECONDS * 1000);
response.setHeader("Cache-Control", "public, max-age=" + CBConstants.STATIC_CACHE_SECONDS);
try (InputStream is = Files.newInputStream(dataFile)) {
IOUtils.copyStream(is, response.getOutputStream());
}
Files.deleteIfExists(dataFile);
response.addHeader("fileName", fileName);
}
@NotNull
private Path getDataFile(HttpServletRequest request) throws DBWebException {
String valuePath = request.getPathInfo();
if (CommonUtils.isEmpty(valuePath)) {
throw new DBWebException("Result value ID not specified");
@Override
protected void processServiceRequest(WebSession session, HttpServletRequest request, HttpServletResponse response) throws DBException, IOException {
if (request.getMethod().equals("POST")) {
try {
doPost(request, response);
} catch (Exception e) {
throw new DBWebException("Servlet exception ", e);
}
} else {
String valuePath = request.getPathInfo();
if (CommonUtils.isEmpty(valuePath)) {
throw new DBWebException("Result value ID not specified");
}
while (valuePath.startsWith("/")) {
valuePath = valuePath.substring(1);
}
Path dataFile = WebSQLDataLOBReceiver.DATA_EXPORT_FOLDER.resolve(valuePath);
session.addInfoMessage("Download LOB file ...");
response.setHeader("Content-Type", "application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=\"" + dataFile.getFileName().toString() + "\"");
response.setHeader("Content-Length", String.valueOf(Files.size(dataFile)));
response.setDateHeader("Expires", System.currentTimeMillis() + CBConstants.STATIC_CACHE_SECONDS * 1000);
response.setHeader("Cache-Control", "public, max-age=" + CBConstants.STATIC_CACHE_SECONDS);
try (InputStream is = Files.newInputStream(dataFile)) {
IOUtils.copyStream(is, response.getOutputStream());
}
Files.deleteIfExists(dataFile);
}
while (valuePath.startsWith("/")) {
valuePath = valuePath.substring(1);
}
Path dataFile = WebSQLDataLOBReceiver.DATA_EXPORT_FOLDER.resolve(valuePath).normalize();
if (!dataFile.startsWith(WebSQLDataLOBReceiver.DATA_EXPORT_FOLDER)) {
throw new DBWebException("Bad result value ID");
}
return dataFile;
}
}
@@ -277,10 +277,8 @@ public class WebSQLUtils {
@Nullable String resultId,
@Nullable WebSQLDataFilter filter,
@Nullable WebDataFormat dataFormat,
boolean readLogs,
boolean useEvents
boolean readLogs
) {
final WebAsyncTaskInfo task = webSession.createAsyncTask("SQL execute");
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
@Override
public void run(DBRProgressMonitor monitor) throws InvocationTargetException {
@@ -288,8 +286,7 @@ public class WebSQLUtils {
monitor.beginTask("Execute query", 1);
monitor.subTask("Process query " + sql);
WebSQLExecuteInfo executeResults = contextInfo.getProcessor().processQuery(
monitor, contextInfo, sql, resultId, filter, dataFormat, webSession, task, readLogs, useEvents
);
monitor, contextInfo, sql, resultId, filter, dataFormat, webSession, readLogs);
this.result = executeResults.getStatusMessage();
this.extendedResults = executeResults;
} catch (Throwable e) {
@@ -299,6 +296,6 @@ public class WebSQLUtils {
}
}
};
return webSession.runAsyncTask(task, runnable);
return webSession.createAndRunAsyncTask("SQL execute", runnable);
}
}
@@ -55,55 +55,55 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
.dataFetcher("sqlListContexts", env ->
getService(env).listContexts(getWebSession(env),
getProjectReference(env),
getArgument(env, "connectionId"),
getArgument(env, "contextId"))
env.getArgument("connectionId"),
env.getArgument("contextId"))
)
.dataFetcher("sqlCompletionProposals", env ->
getService(env).getCompletionProposals(
getSQLContext(env),
getArgumentVal(env, "query"),
getArgument(env, "position"),
getArgument(env, "maxResults"),
getArgument(env, "simpleMode")
env.getArgument("query"),
env.getArgument("position"),
env.getArgument("maxResults"),
env.getArgument("simpleMode")
)
)
.dataFetcher("sqlFormatQuery", env ->
getService(env).formatQuery(
getSQLContext(env),
getArgumentVal(env, "query")
env.getArgument("query")
)
)
.dataFetcher("sqlSupportedOperations", env ->
getService(env).getSupportedOperations(
getSQLContext(env),
getArgumentVal(env, "resultsId"),
getArgumentVal(env, "attributeIndex"))
env.getArgument("resultsId"),
env.getArgument("attributeIndex"))
)
.dataFetcher("sqlEntityQueryGenerators", env ->
getService(env).getEntityQueryGenerators(
getWebSession(env),
getArgumentVal(env, "nodePathList"))
env.getArgument("nodePathList"))
)
.dataFetcher("sqlGenerateEntityQuery", env ->
getService(env).generateEntityQuery(
getWebSession(env),
getArgumentVal(env, "generatorId"),
getArgumentVal(env, "options"),
getArgumentVal(env, "nodePathList"))
env.getArgument("generatorId"),
env.getArgument("options"),
env.getArgument("nodePathList"))
).dataFetcher("sqlParseScript", env ->
getService(env).parseSqlScript(getWebConnection(env), getArgumentVal(env, "script"))
getService(env).parseSqlScript(getWebConnection(env), env.getArgument("script"))
).dataFetcher("sqlParseQuery", env ->
getService(env).parseSqlQuery(
getWebConnection(env),
getArgumentVal(env, "script"),
getArgumentVal(env, "position"))
env.getArgument("script"),
env.getArgument("position"))
).dataFetcher("sqlGenerateGroupingQuery", env ->
getService(env).generateGroupByQuery(
getSQLContext(env),
getArgumentVal(env, "resultsId"),
getArgumentVal(env, "columnNames"),
getArgument(env, "functions"),
getArgument(env, "showDuplicatesOnly"))
env.getArgument("resultsId"),
env.getArgument("columnNames"),
env.getArgument("functions"),
env.getArgument("showDuplicatesOnly"))
)
;
@@ -111,14 +111,14 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
.dataFetcher("sqlContextCreate", env -> getService(env).createContext(
getSQLProcessor(env),
getProjectReference(env),
getArgument(env, "defaultCatalog"),
getArgument(env, "defaultSchema")))
env.getArgument("defaultCatalog"),
env.getArgument("defaultSchema")))
.dataFetcher("sqlContextDestroy", env -> { getService(env).destroyContext(getSQLContext(env)); return true; } )
.dataFetcher("sqlContextSetDefaults", env -> {
getService(env).setContextDefaults(
getSQLContext(env),
getArgument(env, "defaultCatalog"),
getArgument(env, "defaultSchema"));
env.getArgument("defaultCatalog"),
env.getArgument("defaultSchema"));
return true;
})
@@ -130,36 +130,36 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
}
return getService(env).closeResult(
getSQLContext(env),
getArgumentVal(env, "resultId"));
env.getArgument("resultId"));
})
.dataFetcher("readLobValue", env -> // deprecated
getService(env).readLobValue(
getSQLContext(env),
getArgumentVal(env, "resultsId"),
getArgumentVal(env, "lobColumnIndex"),
env.getArgument("resultsId"),
env.getArgument("lobColumnIndex"),
getResultsRow(env, "row").get(0)))
.dataFetcher("sqlReadLobValue", env ->
getService(env).readLobValue(
getSQLContext(env),
getArgumentVal(env, "resultsId"),
getArgumentVal(env, "lobColumnIndex"),
new WebSQLResultsRow(getArgument(env, "row"))))
env.getArgument("resultsId"),
env.getArgument("lobColumnIndex"),
new WebSQLResultsRow(env.getArgument("row"))))
.dataFetcher("sqlReadStringValue", env ->
getService(env).getCellValue(
getSQLContext(env),
getArgumentVal(env, "resultsId"),
getArgumentVal(env, "columnIndex"),
new WebSQLResultsRow(getArgument(env, "row"))))
env.getArgument("resultsId"),
env.getArgument("columnIndex"),
new WebSQLResultsRow(env.getArgument("row"))))
.dataFetcher("sqlGetDynamicTrace", env ->
getService(env).readDynamicTrace(
getWebSession(env),
getSQLContext(env),
getArgumentVal(env, "resultsId")
env.getArgument("resultsId")
))
.dataFetcher("updateResultsDataBatch", env ->
getService(env).updateResultsDataBatch(
getSQLContext(env),
getArgumentVal(env, "resultsId"),
env.getArgument("resultsId"),
getResultsRow(env, "updatedRows"),
getResultsRow(env, "deletedRows"),
getResultsRow(env, "addedRows"),
@@ -168,7 +168,7 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
getService(env).asyncUpdateResultsDataBatch(
getWebSession(env),
getSQLContext(env),
getArgumentVal(env, "resultsId"),
env.getArgument("resultsId"),
getResultsRow(env, "updatedRows"),
getResultsRow(env, "deletedRows"),
getResultsRow(env, "addedRows"),
@@ -176,7 +176,7 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
.dataFetcher("updateResultsDataBatchScript", env ->
getService(env).updateResultsDataBatchScript(
getSQLContext(env),
getArgumentVal(env, "resultsId"),
env.getArgument("resultsId"),
getResultsRow(env, "updatedRows"),
getResultsRow(env, "deletedRows"),
getResultsRow(env, "addedRows"),
@@ -187,52 +187,51 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
getWebSession(env),
getProjectReference(env),
getSQLContext(env),
getArgumentVal(env, "sql"),
getArgument(env, "resultId"),
env.getArgument("sql"),
env.getArgument("resultId"),
getDataFilter(env),
getDataFormat(env),
CommonUtils.toBoolean(getArgument(env, "readLogs")),
CommonUtils.toBoolean(getArgument(env, "isInteractive"))
CommonUtils.toBoolean(env.getArgument("readLogs"))
)
)
.dataFetcher("asyncReadDataFromContainer", env ->
getService(env).asyncReadDataFromContainer(
getSQLContext(env),
getArgumentVal(env, "containerNodePath"),
getArgument(env, "resultId"),
env.getArgument("containerNodePath"),
env.getArgument("resultId"),
getDataFilter(env),
getDataFormat(env)
))
.dataFetcher("asyncSqlExecuteResults", env ->
getService(env).asyncGetQueryResults(
getWebSession(env), getArgumentVal(env, "taskId")
getWebSession(env), env.getArgument("taskId")
))
.dataFetcher("asyncSqlExplainExecutionPlan", env ->
getService(env).asyncSqlExplainExecutionPlan(
getSQLContext(env),
getArgumentVal(env, "query"),
getArgumentVal(env, "configuration")
env.getArgument("query"),
env.getArgument("configuration")
))
.dataFetcher("asyncSqlExplainExecutionPlanResult", env ->
getService(env).asyncSqlExplainExecutionPlanResult(
getWebSession(env), getArgumentVal(env, "taskId")
getWebSession(env), env.getArgument("taskId")
))
.dataFetcher("asyncSqlRowDataCount", env ->
getService(env).getRowDataCount(
getWebSession(env),
getSQLContext(env),
getArgumentVal(env, "resultsId")
env.getArgument("resultsId")
))
.dataFetcher("asyncSqlRowDataCountResult", env ->
getService(env).getRowDataCountResult(
getWebSession(env),
getArgumentVal(env, "taskId")
env.getArgument("taskId")
))
.dataFetcher("asyncSqlSetAutoCommit", env ->
getService(env).asyncSqlSetAutoCommit(
getWebSession(env),
getSQLContext(env),
getArgumentVal(env, "autoCommit")
env.getArgument("autoCommit")
))
.dataFetcher("asyncSqlCommitTransaction", env ->
getService(env).asyncSqlCommitTransaction(
@@ -253,7 +252,7 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
@NotNull
public static WebDataFormat getDataFormat(DataFetchingEnvironment env) {
String dataFormat = getArgument(env, "dataFormat");
String dataFormat = env.getArgument("dataFormat");
return CommonUtils.valueOf(WebDataFormat.class, dataFormat, WebDataFormat.resultset);
}
@@ -299,7 +298,7 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
if (processor == null) {
return null;
}
String contextId = getArgument(env, "contextId");
String contextId = env.getArgument("contextId");
return getSQLContext(processor, contextId);
}
@@ -375,12 +374,12 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
///////////////////////////////////////
// Helpers
public static WebSQLDataFilter getDataFilter(DataFetchingEnvironment env) {
Map<String, Object> filterProps = getArgument(env, "filter");
Map<String, Object> filterProps = env.getArgument("filter");
return filterProps == null ? null : new WebSQLDataFilter(filterProps);
}
private static List<WebSQLResultsRow> getResultsRow(DataFetchingEnvironment env, String param) {
List<Map<String, Object>> mapList = getArgument(env, param);
List<Map<String, Object>> mapList = env.getArgument(param);
if (CommonUtils.isEmpty(mapList)) {
return null;
}
@@ -110,7 +110,7 @@ public class WebServiceSQL implements DBWServiceSQL {
@Override
@NotNull
public WebSQLDialectInfo getDialectInfo(@NotNull WebConnectionInfo connectionInfo) {
public WebSQLDialectInfo getDialectInfo(@NotNull WebConnectionInfo connectionInfo) throws DBWebException {
DBPDataSourceContainer dataSourceContainer = connectionInfo.getDataSourceContainer();
SQLDialect dialect = getSqlDialectFromConnection(dataSourceContainer);
return new WebSQLDialectInfo(dataSourceContainer.getDataSource(), dialect);
@@ -400,16 +400,21 @@ public class WebServiceSQL implements DBWServiceSQL {
DBExecUtils.tryExecuteRecover(
monitor,
contextInfo.getProcessor().getConnection().getDataSource(),
monitor1 ->
result[0] = contextInfo.getProcessor().updateResultsDataBatch(
monitor1, contextInfo, resultsId, updatedRows, deletedRows, addedRows, dataFormat)
monitor1 -> {
try {
result[0] = contextInfo.getProcessor().updateResultsDataBatch(
monitor1, contextInfo, resultsId, updatedRows, deletedRows, addedRows, dataFormat);
} catch (Exception e) {
throw new InvocationTargetException(e);
}
}
);
return result[0];
}
@FunctionalInterface
private interface ThrowableFunction<T, R> {
R apply(T obj) throws DBException;
R apply(T obj) throws Exception;
}
@Override
@@ -432,6 +437,9 @@ public class WebServiceSQL implements DBWServiceSQL {
@NotNull Integer lobColumnIndex,
@NotNull WebSQLResultsRow row
) throws DBWebException {
if (row == null) {
throw new DBWebException("Results row is not found");
}
WebSQLProcessor processor = contextInfo.getProcessor();
ThrowableFunction<DBRProgressMonitor, String> function = monitor -> processor.readStringValue(
monitor, contextInfo, resultsId, lobColumnIndex, row);
@@ -449,7 +457,13 @@ public class WebServiceSQL implements DBWServiceSQL {
DBExecUtils.tryExecuteRecover(
processor.getWebSession().getProgressMonitor(),
processor.getConnection().getDataSource(),
monitor -> result.append(function.apply(monitor))
monitor -> {
try {
result.append(function.apply(monitor));
} catch (Exception e) {
throw new InvocationTargetException(e);
}
}
);
return result.toString();
} catch (DBException e) {
@@ -462,8 +476,7 @@ public class WebServiceSQL implements DBWServiceSQL {
try {
return contextInfo.getProcessor().generateResultsDataUpdateScript(
contextInfo.getProcessor().getWebSession().getProgressMonitor(),
contextInfo, resultsId, updatedRows, deletedRows, addedRows
);
contextInfo, resultsId, updatedRows, deletedRows, addedRows, dataFormat);
} catch (DBException e) {
throw new DBWebException("Error genering update script", e);
}
@@ -479,8 +492,7 @@ public class WebServiceSQL implements DBWServiceSQL {
@Nullable String resultId,
@Nullable WebSQLDataFilter filter,
@Nullable WebDataFormat dataFormat,
boolean readLogs,
boolean useEvents
boolean readLogs
) throws DBException {
if (DBWorkbench.isDistributed() && !webSession.hasPermission(DBWConstants.PERMISSION_SQL_EXECUTE_QUERY)) {
throw new DBWebException("Permission denied");
@@ -492,8 +504,7 @@ public class WebServiceSQL implements DBWServiceSQL {
resultId,
filter,
dataFormat,
readLogs,
useEvents
readLogs
);
}
@@ -503,11 +514,10 @@ public class WebServiceSQL implements DBWServiceSQL {
@NotNull String nodePath,
@Nullable String resultId,
@Nullable WebSQLDataFilter filter,
@Nullable WebDataFormat dataFormat
) {
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
@Nullable WebDataFormat dataFormat) throws DBWebException {
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<String>() {
@Override
public void run(DBRProgressMonitor monitor) throws InvocationTargetException {
public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
try {
monitor.beginTask("Read data", 1);
monitor.subTask("Extra data from " + nodePath);
@@ -521,8 +531,7 @@ public class WebServiceSQL implements DBWServiceSQL {
dataContainer,
resultId,
filter != null ? filter : new WebSQLDataFilter(),
dataFormat
);
dataFormat);
this.result = executeResults.getStatusMessage();
this.extendedResults = executeResults;
} catch (Throwable e) {
@@ -553,25 +562,24 @@ public class WebServiceSQL implements DBWServiceSQL {
@Override
public WebSQLExecuteInfo asyncGetQueryResults(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException {
WebAsyncTaskInfo taskStatus = webSession.asyncTaskStatus(taskId, false);
return (WebSQLExecuteInfo) taskStatus.getExtendedResult();
if (taskStatus != null) {
return (WebSQLExecuteInfo) taskStatus.getExtendedResult();
}
return null;
}
////////////////////////////////////////////////////
// Explain plan
@Override
public WebAsyncTaskInfo asyncSqlExplainExecutionPlan(
@NotNull WebSQLContextInfo contextInfo,
@NotNull String sql,
@NotNull Map<String, Object> configuration
) {
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
public WebAsyncTaskInfo asyncSqlExplainExecutionPlan(@NotNull WebSQLContextInfo contextInfo, @NotNull String sql, @NotNull Map<String, Object> configuration) throws DBException {
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<String>() {
@Override
public void run(DBRProgressMonitor monitor) throws InvocationTargetException {
public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
try {
monitor.beginTask("Explain execution plan", 1);
monitor.subTask("Explain query [" + sql + "] execution plan");
WebSQLExecutionPlan executeResults = contextInfo.getProcessor().explainExecutionPlan(monitor, sql, configuration);
WebSQLExecutionPlan executeResults = contextInfo.getProcessor().explainExecutionPlan(monitor, contextInfo, sql, configuration);
this.result = "Execution plan explain has been scheduled";
this.extendedResults = executeResults;
} catch (Throwable e) {
@@ -587,11 +595,14 @@ public class WebServiceSQL implements DBWServiceSQL {
@Override
public WebSQLExecutionPlan asyncSqlExplainExecutionPlanResult(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException {
WebAsyncTaskInfo taskStatus = webSession.asyncTaskStatus(taskId, false);
return (WebSQLExecutionPlan) taskStatus.getExtendedResult();
if (taskStatus != null) {
return (WebSQLExecutionPlan) taskStatus.getExtendedResult();
}
return null;
}
@Override
public WebSQLScriptInfo parseSqlScript(@NotNull WebConnectionInfo connectionInfo, @NotNull String sqlScript) {
public WebSQLScriptInfo parseSqlScript(@NotNull WebConnectionInfo connectionInfo, @NotNull String sqlScript) throws DBWebException {
SQLDialect dialect = getSqlDialectFromConnection(connectionInfo.getDataSourceContainer());
List<SQLScriptElement> queries = SQLScriptParser.parseScript(
connectionInfo.getDataSource(),
@@ -605,7 +616,7 @@ public class WebServiceSQL implements DBWServiceSQL {
}
@Override
public WebSQLQueryInfo parseSqlQuery(@NotNull WebConnectionInfo connectionInfo, @NotNull String sqlScript, int cursorPosition) {
public WebSQLQueryInfo parseSqlQuery(@NotNull WebConnectionInfo connectionInfo, @NotNull String sqlScript, int cursorPosition) throws DBWebException {
SQLDialect dialect = getSqlDialectFromConnection(connectionInfo.getDataSourceContainer());
SQLScriptElement query = SQLScriptParser.parseQuery(
connectionInfo.getDataSource(),
@@ -648,20 +659,14 @@ public class WebServiceSQL implements DBWServiceSQL {
}
@Override
public WebAsyncTaskInfo getRowDataCount(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo, @NotNull String resultsId) {
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
public WebAsyncTaskInfo getRowDataCount(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo, @NotNull String resultsId) throws DBWebException {
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<String>() {
@Override
public void run(DBRProgressMonitor monitor) throws InvocationTargetException {
public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
try {
monitor.beginTask("Get row data count", 1);
WebSQLResultsInfo results = contextInfo.getResults(resultsId);
long rowCount = DBUtils.readRowCount(
monitor,
contextInfo.getProcessor().getExecutionContext(),
results.getDataContainer(),
results.getDataFilter(),
this
);
long rowCount = DBUtils.readRowCount(monitor, contextInfo.getProcessor().getExecutionContext(), results.getDataContainer(), null, this);
this.result = "Row data count completed";
this.extendedResults = rowCount;
} catch (Throwable e) {
@@ -678,16 +683,19 @@ public class WebServiceSQL implements DBWServiceSQL {
@Nullable
public Long getRowDataCountResult(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException {
WebAsyncTaskInfo taskStatus = webSession.asyncTaskStatus(taskId, false);
return (Long) taskStatus.getExtendedResult();
if (taskStatus != null) {
return (Long) taskStatus.getExtendedResult();
}
return null;
}
@Override
public WebAsyncTaskInfo asyncSqlSetAutoCommit(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo, boolean autoCommit) {
public WebAsyncTaskInfo asyncSqlSetAutoCommit(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo, boolean autoCommit) throws DBWebException {
return contextInfo.setAutoCommit(autoCommit);
}
@Override
public WebAsyncTaskInfo asyncSqlRollbackTransaction(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo) {
public WebAsyncTaskInfo asyncSqlRollbackTransaction(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo) throws DBWebException {
return contextInfo.rollbackTransaction();
}
@@ -1,35 +0,0 @@
/*
* 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.service.sql.messages;
import org.eclipse.osgi.util.NLS;
public class WebSQLMessages extends NLS {
static final String BUNDLE_NAME = "io.cloudbeaver.service.sql.messages.WebSQLResources"; //$NON-NLS-1$
// TODO: add localizations
public static String model_web_ai_query_confirmation_title;
public static String model_web_ai_query_confirmation_message;
static {
NLS.initializeMessages(BUNDLE_NAME, WebSQLMessages.class);
}
private WebSQLMessages() {
}
}
@@ -1,2 +0,0 @@
model_web_ai_query_confirmation_title = Confirm query execution
model_web_ai_query_confirmation_message = The query you're about to execute can modify existing data or schema.\nDo you want to continue?
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Administration
Bundle-SymbolicName: io.cloudbeaver.service.admin;singleton:=true
Bundle-Version: 1.0.134.qualifier
Bundle-Release-Date: 20251201
Bundle-Version: 1.0.130.qualifier
Bundle-Release-Date: 20251006
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.admin</artifactId>
<version>1.0.134-SNAPSHOT</version>
<version>1.0.130-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -16,7 +16,10 @@
*/
package io.cloudbeaver.service.admin;
import io.cloudbeaver.*;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.DBWFeatureSet;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebAction;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.registry.WebAuthProviderConfiguration;
@@ -71,63 +74,39 @@ public interface DBWServiceAdmin extends DBWService {
List<String> listTeamRoles();
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
boolean deleteUser(@NotNull WebSession webSession, @NotNull String userName) throws DBWebException;
boolean deleteUser(@NotNull WebSession webSession, String userName) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
@NotNull
AdminTeamInfo createTeam(
@NotNull WebSession webSession,
@NotNull String teamId,
@NotNull String teamName,
@Nullable String description
) throws DBWebException;
AdminTeamInfo createTeam(@NotNull WebSession webSession, String teamId, String teamName, String description) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
@NotNull
AdminTeamInfo updateTeam(
@NotNull WebSession webSession,
@NotNull String teamId,
@NotNull String teamName,
@Nullable String description
) throws DBWebException;
AdminTeamInfo updateTeam(@NotNull WebSession webSession, String teamId, String teamName, String description) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
boolean deleteTeam(@NotNull WebSession webSession, @NotNull String teamId, boolean force) throws DBWebException;
boolean deleteTeam(@NotNull WebSession webSession, String teamId, boolean force) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
boolean grantUserTeam(@NotNull WebSession webSession, @NotNull String user, @NotNull String team) throws DBWebException;
boolean grantUserTeam(@NotNull WebSession webSession, String user, String team) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
boolean revokeUserTeam(@NotNull WebSession webSession, @NotNull String user, @NotNull String team) throws DBWebException;
boolean revokeUserTeam(@NotNull WebSession webSession, String user, String team) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
@NotNull
List<AdminPermissionInfo> setSubjectPermissions(
@NotNull WebSession webSession,
@NotNull String subjectID,
@NotNull List<String> permissions
) throws DBWebException;
List<AdminPermissionInfo> setSubjectPermissions(@NotNull WebSession webSession, String subjectID, List<String> permissions) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
boolean setUserCredentials(
@NotNull WebSession webSession,
@NotNull String userID,
@NotNull String providerId,
@WebParameterSecure @NotNull Map<String, Object> credentials
) throws DBWebException;
boolean setUserCredentials(@NotNull WebSession webSession, @NotNull String userID, @NotNull String providerId, @NotNull Map<String, Object> credentials) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
boolean deleteUserCredentials(
@NotNull WebSession webSession,
@NotNull String userId,
@NotNull String providerId
) throws DBWebException;
boolean deleteUserCredentials(@NotNull WebSession webSession, @NotNull String userId, @NotNull String providerId) throws DBWebException;
////////////////////////////////////////////////////////////////////
// Connection management
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
List<AdminConnectionSearchInfo> searchConnections(WebSession webSession, @NotNull List<String> hostNames) throws DBWebException;
List<AdminConnectionSearchInfo> searchConnections(WebSession webSession, List<String> hostNames) throws DBWebException;
////////////////////////////////////////////////////////////////////
// Features
@@ -150,10 +129,9 @@ public interface DBWServiceAdmin extends DBWService {
@NotNull String providerId,
@NotNull String id,
@NotNull String displayName,
boolean disabled,
@Nullable String iconURL,
boolean disabled, @Nullable String iconURL,
@Nullable String description,
@WebParameterSecure @Nullable Map<String, Object> parameters) throws DBWebException;
@Nullable Map<String, Object> parameters) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
boolean deleteAuthProviderConfiguration(@NotNull WebSession webSession, @NotNull String id) throws DBWebException;
@@ -161,12 +139,12 @@ public interface DBWServiceAdmin extends DBWService {
// Server configuration
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
boolean configureServer(@NotNull WebSession webSession, @NotNull Map<String, Object> params) throws DBWebException;
boolean configureServer(WebSession webSession, Map<String, Object> params) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
boolean setDefaultNavigatorSettings(@NotNull WebSession webSession, @NotNull DBNBrowseSettings settings) throws DBWebException;
boolean setDefaultNavigatorSettings(WebSession webSession, DBNBrowseSettings settings) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
boolean updateProductConfiguration(@NotNull WebSession webSession, @NotNull Map<String, Object> productConfiguration) throws DBWebException;
boolean updateProductConfiguration(WebSession webSession, Map<String, Object> productConfiguration) throws DBWebException;
////////////////////////////////////////////////////////////////////
// Permissions
@@ -175,7 +153,7 @@ public interface DBWServiceAdmin extends DBWService {
SMDataSourceGrant[] getConnectionSubjectAccess(
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull String connectionId) throws DBWebException;
String connectionId) throws DBWebException;
@Deprecated
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
@@ -213,50 +191,26 @@ public interface DBWServiceAdmin extends DBWService {
// User meta parameters
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
WebPropertyInfo saveUserMetaParameter(
@NotNull WebSession webSession,
@NotNull String id,
@NotNull String displayName,
@Nullable String description,
@Nullable Boolean required
) throws DBWebException;
WebPropertyInfo saveUserMetaParameter(WebSession webSession, String id, String displayName, String description, Boolean required) throws
DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
Boolean deleteUserMetaParameter(@NotNull WebSession webSession, @NotNull String id) throws DBWebException;
Boolean deleteUserMetaParameter(WebSession webSession, String id) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
Boolean setUserMetaParameterValues(
@NotNull WebSession webSession,
@NotNull String userId,
@NotNull Map<String, String> parameters
) throws DBWebException;
Boolean setUserMetaParameterValues(WebSession webSession, String userId, Map<String, String> parameters) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
Boolean setTeamMetaParameterValues(WebSession webSession, String teamId, Map<String, String> parameters) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
Boolean setTeamMetaParameterValues(
@NotNull WebSession webSession,
@NotNull String teamId,
@NotNull Map<String, String> parameters
) throws DBWebException;
Boolean enableUser(WebSession webSession, String userId, Boolean enabled) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
Boolean enableUser(
@NotNull WebSession webSession,
@NotNull String userId,
@NotNull Boolean enabled
) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
Boolean setUserAuthRole(
@NotNull WebSession webSession,
@NotNull String userId,
@NotNull String authRole
) throws DBWebException;
Boolean setUserAuthRole(WebSession webSession, String userId, String authRole) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
Boolean setUserTeamRole(
@NotNull WebSession webSession,
@NotNull String userId,
@NotNull String teamId,
@Nullable String teamRole
@NotNull WebSession webSession, @NotNull String userId,
@NotNull String teamId, @Nullable String teamRole
) throws DBWebException;
}

Some files were not shown because too many files have changed in this diff Show More