mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge branch 'dbeaver:devel' into fix/db2_i
This commit is contained in:
@@ -22,6 +22,96 @@ You can see live demo of CloudBeaver here: https://demo.cloudbeaver.io
|
||||
|
||||
## Changelog
|
||||
|
||||
### CloudBeaver 21.1.0 - 2021-07-15
|
||||
|
||||
Changes since 21.0.0
|
||||
- Navigation tree:
|
||||
- User's and shared connections are divided into different groups.
|
||||
- Connection form:
|
||||
- Read-only connections are marked in the connection form.
|
||||
- It is possible to show and hide password in the connection dialog.
|
||||
- Data editor:
|
||||
- New actions are available from the context menu:
|
||||
- filtering
|
||||
- ordering
|
||||
- cell editing.
|
||||
- Support for different formats is added to the Value panel.
|
||||
- Work with links is improved:
|
||||
- Links to the web pages can be opened from the tables
|
||||
- Links to the pictures are automatically transformed to the pictures on the Value panel.
|
||||
- It is possible to edit Boolean values as checkboxes in the tables.
|
||||
- Read-only columns and tables are marked in the result set.
|
||||
- SQL editor:
|
||||
- Query execution plan is added.
|
||||
- Database logos are displayed in the top menu selector.
|
||||
- Auto-complete for the SQL dialects works automatically.
|
||||
- Log viewer:
|
||||
- Detailed information for errors is opened on the panel.
|
||||
- Administration part:
|
||||
- Access management for users and connections is improved.
|
||||
- It is possible to change the user password from the public part of CloudBeaver.
|
||||
- New design for dialogs is implemented.
|
||||
- Possibility to export data for users can be configured.
|
||||
- A lot of UI fixes, enhancements and improvements are made.
|
||||
|
||||
### CloudBeaver 21.0.5 - 2021-06-30
|
||||
- Query execution plan was added.
|
||||
- Option to show/hide password in the connection dialog was added.
|
||||
- Minor UI fixes and improvements.
|
||||
|
||||
### CloudBeaver 21.0.3 - 2021-06-11
|
||||
|
||||
- Filtering and Ordering from the context menu were added for the data editor .
|
||||
- Read-only columns and result sets were marked in the data editor.
|
||||
- Possibility to remove data export feature from UI was added.
|
||||
- CloudBeaver docker image is based on Ubuntu Slim now.
|
||||
- Minor UI fixes and improvements.
|
||||
|
||||
### CloudBeaver 21.0.2 - 2021-05-20
|
||||
|
||||
- User's and shared connections are divided to different groups in the navigation tree.
|
||||
- New design for authentication and driver properties dialogs was applied.
|
||||
- Management for administrators' accounts was improved.
|
||||
- Minor UI fixes and improvements.
|
||||
|
||||
### CloudBeaver 21.0.1 - 2021-04-28
|
||||
|
||||
- Database logos were added to the top menu selector.
|
||||
- New panel to show error details were added to Log Viewer.
|
||||
- Read-only connections were marked in the connection details form.
|
||||
- New design for dialogs was implemented.
|
||||
- Minor UI fixes and improvements.
|
||||
|
||||
### CloudBeaver 21.0.0 - 2021-04-15
|
||||
|
||||
Changes since 20.0.0:
|
||||
- Navigation tree:
|
||||
- Simple and advanced view for database objects
|
||||
- Show system objects option
|
||||
- Connection form:
|
||||
- New drivers: Oracle, SQL Server, Trino (Presto), Derby Server
|
||||
- SSH support
|
||||
- Editing for manual connections
|
||||
- Custom fields for advanced connection settings
|
||||
- Connection form redesign
|
||||
- Fixes for the name duplicates and deletion of the template connections
|
||||
- Data editor:
|
||||
- Value panel for data review and editing
|
||||
- Spatial data viewer
|
||||
- Sorting indication for the data grid
|
||||
- Performance improvements
|
||||
- SQL editor:
|
||||
- Improvements for autosuggest and highlighting
|
||||
- Log viewer:
|
||||
- Quick review for the error details
|
||||
- All errors and warnings on the panel
|
||||
- New page with the product information
|
||||
- Administration part:
|
||||
- Redesign for the authentication engine
|
||||
- Server re-configuration without re-login
|
||||
- Additional labels for the connections with SSH
|
||||
- A lot of UI fixes, enhancements and improvements.
|
||||
|
||||
### CloudBeaver 20.0.6 - 2021-03-31
|
||||
|
||||
- Value panel for Data Viewer was added: a full text of your cell value on a special panel to view and edit.
|
||||
@@ -36,7 +126,7 @@ You can see live demo of CloudBeaver here: https://demo.cloudbeaver.io
|
||||
- Possibility to edit connections created manually in the public part of the application
|
||||
- SSH support for connections in public part of the application
|
||||
- Possibility to deny credentials saving
|
||||
- Drivers for SQL Server, Oracle, DB2 LUW, Derby, Trino
|
||||
- Drivers for MS SQL Server, Oracle, DB2 LUW, Derby, Trino (Presto SQL)
|
||||
- Performance improvements for data grid
|
||||
- A lot of minor UI fixes and improvements
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
FROM adoptopenjdk/openjdk11:x86_64-ubuntu-jdk-11.0.7_10
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y nano
|
||||
FROM adoptopenjdk/openjdk11:jdk-11.0.10_9-ubuntu-slim
|
||||
|
||||
COPY cloudbeaver /opt/cloudbeaver
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{
|
||||
roleId: "user",
|
||||
name: "User",
|
||||
description: "Standard user.",
|
||||
description: "Standard user",
|
||||
permissions: [ "public" ]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
plugin_data_spreadsheet_new: {
|
||||
hidden: false
|
||||
},
|
||||
plugin_data_export: {
|
||||
disabled: false
|
||||
},
|
||||
// Notifications config
|
||||
core_events: {
|
||||
notificationsPool: 5
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: %Bundle-Vendor
|
||||
Bundle-Name: %Bundle-Name
|
||||
Bundle-SymbolicName: io.cloudbeaver.connect.provider.sample;singleton:=true
|
||||
Bundle-Version: 1.0.25.qualifier
|
||||
Bundle-Release-Date: 20210405
|
||||
Bundle-Version: 1.0.33.qualifier
|
||||
Bundle-Release-Date: 20210802
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-ClassPath: .
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.connect.provider.sample</artifactId>
|
||||
<version>1.0.25-SNAPSHOT</version>
|
||||
<version>1.0.33-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: %Bundle-Vendor
|
||||
Bundle-Name: %Bundle-Name
|
||||
Bundle-SymbolicName: io.cloudbeaver.product.ce;singleton:=true
|
||||
Bundle-Version: 20.0.6.qualifier
|
||||
Bundle-Release-Date: 20210405
|
||||
Bundle-Version: 21.1.1.qualifier
|
||||
Bundle-Release-Date: 20210802
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-ClassPath: .
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.product.ce</artifactId>
|
||||
<version>20.0.6-SNAPSHOT</version>
|
||||
<version>21.1.1-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -2,8 +2,8 @@ Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: Base JDBC drivers
|
||||
Bundle-SymbolicName: io.cloudbeaver.resources.drivers.base;singleton:=true
|
||||
Bundle-Version: 1.0.22.qualifier
|
||||
Bundle-Release-Date: 20210405
|
||||
Bundle-Version: 1.0.30.qualifier
|
||||
Bundle-Release-Date: 20210802
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<driver id="oracle:oracle_thin"/>
|
||||
<driver id="postgresql:postgres-jdbc"/>
|
||||
<driver id="jaybird:jaybird"/>
|
||||
<driver id="generic:yandex_clickhouse"/>
|
||||
<driver id="clickhouse:yandex_clickhouse"/>
|
||||
<driver id="generic:derby_server"/>
|
||||
<driver id="generic:h2_embedded"/>
|
||||
<driver id="generic:sqlite_jdbc"/>
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
|
||||
<version>1.0.22-SNAPSHOT</version>
|
||||
<version>1.0.30-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
</project>
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: %Bundle-Vendor
|
||||
Bundle-Name: %Bundle-Name
|
||||
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
|
||||
Bundle-Version: 20.0.6.qualifier
|
||||
Bundle-Release-Date: 20210405
|
||||
Bundle-Version: 21.1.1.qualifier
|
||||
Bundle-Release-Date: 20210802
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-Activator: io.cloudbeaver.server.WebPlatformActivator
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
<extension-point id="io.cloudbeaver.service" name="Web services" schema="schema/io.cloudbeaver.service.exsd"/>
|
||||
<extension-point id="io.cloudbeaver.driver" name="Web drivers" schema="schema/io.cloudbeaver.driver.exsd"/>
|
||||
<extension-point id="io.cloudbeaver.valueSerializer" name="Web value serializers" schema="schema/io.cloudbeaver.valueSerializer.exsd"/>
|
||||
|
||||
<extension point="org.eclipse.core.runtime.adapters">
|
||||
<factory adaptableType="org.jkiss.dbeaver.runtime.DBWorkbench" class="io.cloudbeaver.server.WebPlatformAdapterFactory">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.server</artifactId>
|
||||
<version>20.0.6-SNAPSHOT</version>
|
||||
<version>21.1.1-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<schema targetNamespace="org.jkiss.dbeaver.core" xmlns="http://www.w3.org/2001/XMLSchema">
|
||||
<annotation>
|
||||
<appInfo>
|
||||
<meta.schema plugin="io.cloudbeaver.server" id="io.cloudbeaver.valueSerializer" name="Web value serializers"/>
|
||||
</appInfo>
|
||||
<documentation>
|
||||
DBeaver web value serializers
|
||||
</documentation>
|
||||
</annotation>
|
||||
|
||||
<element name="extension">
|
||||
<annotation>
|
||||
<appInfo>
|
||||
<meta.element />
|
||||
</appInfo>
|
||||
</annotation>
|
||||
<complexType>
|
||||
<sequence>
|
||||
<element ref="serializer"/>
|
||||
</sequence>
|
||||
</complexType>
|
||||
</element>
|
||||
|
||||
<element name="serializer">
|
||||
<annotation>
|
||||
<documentation>
|
||||
DBeaver web value serializer
|
||||
</documentation>
|
||||
</annotation>
|
||||
</element>
|
||||
|
||||
|
||||
</schema>
|
||||
@@ -90,6 +90,10 @@ type ServerConfig {
|
||||
version: String!
|
||||
workspaceId: ID!
|
||||
|
||||
serverURL: String!
|
||||
|
||||
rootURI: String!
|
||||
|
||||
anonymousAccessEnabled: Boolean
|
||||
authenticationEnabled: Boolean @deprecated
|
||||
supportsCustomConnections: Boolean
|
||||
|
||||
@@ -3,29 +3,31 @@
|
||||
####################################################
|
||||
|
||||
type SQLDialectInfo {
|
||||
name: String
|
||||
name: String!
|
||||
|
||||
dataTypes: [ String ]
|
||||
functions: [ String ]
|
||||
reservedWords: [ String ]
|
||||
quoteStrings: [ [ String ] ]
|
||||
singleLineComments: [ String ]
|
||||
multiLineComments: [ [ String ] ]
|
||||
dataTypes: [ String ]!
|
||||
functions: [ String ]!
|
||||
reservedWords: [ String ]!
|
||||
quoteStrings: [ [ String ] ]!
|
||||
singleLineComments: [ String ]!
|
||||
multiLineComments: [ [ String ] ]!
|
||||
|
||||
catalogSeparator: String
|
||||
structSeparator: String
|
||||
scriptDelimiter: String
|
||||
|
||||
supportsExplainExecutionPlan: Boolean!
|
||||
|
||||
}
|
||||
|
||||
type SQLCompletionProposal {
|
||||
displayString: String
|
||||
type: String
|
||||
displayString: String!
|
||||
type: String!
|
||||
score: Int
|
||||
|
||||
replacementString: String
|
||||
replacementOffset: Int
|
||||
replacementLength: Int
|
||||
replacementString: String!
|
||||
replacementOffset: Int!
|
||||
replacementLength: Int!
|
||||
cursorPosition: Int
|
||||
|
||||
icon: String
|
||||
@@ -40,6 +42,7 @@ type SQLCompletionProposal {
|
||||
type SQLContextInfo {
|
||||
|
||||
id: ID!
|
||||
connectionId: ID!
|
||||
defaultCatalog: String
|
||||
defaultSchema: String
|
||||
|
||||
@@ -80,8 +83,13 @@ type SQLResultColumn {
|
||||
scale: Int
|
||||
precision: Int
|
||||
|
||||
required: Boolean!
|
||||
|
||||
readOnly: Boolean!
|
||||
readOnlyStatus: String
|
||||
|
||||
# Operations supported for this attribute
|
||||
supportedOperations: [DataTypeLogicalOperation!]!
|
||||
}
|
||||
|
||||
type DatabaseDocument {
|
||||
@@ -112,8 +120,13 @@ type SQLQueryResults {
|
||||
|
||||
type SQLExecuteInfo {
|
||||
|
||||
# Status message
|
||||
statusMessage: String
|
||||
duration: Int
|
||||
# Execute time (ms)
|
||||
duration: Int!
|
||||
# Actual conditions applied to query
|
||||
filterText: String
|
||||
# Results
|
||||
results: [ SQLQueryResults! ]!
|
||||
}
|
||||
|
||||
@@ -122,20 +135,59 @@ input SQLResultRow {
|
||||
updateValues: Object
|
||||
}
|
||||
|
||||
type DataTypeLogicalOperation {
|
||||
id: ID!
|
||||
expression: String!
|
||||
argumentCount: Int
|
||||
}
|
||||
|
||||
####################################################
|
||||
# SQL Executio plan
|
||||
####################################################
|
||||
|
||||
type SQLExecutionPlan {
|
||||
query: String!
|
||||
nodes: [SQLExecutionPlanNode!]!
|
||||
}
|
||||
|
||||
type SQLExecutionPlanNode {
|
||||
id: ID!
|
||||
parentId: ID
|
||||
|
||||
kind: String!
|
||||
name: String
|
||||
type: String!
|
||||
condition: String
|
||||
description: String
|
||||
properties: [ObjectPropertyInfo!]!
|
||||
}
|
||||
|
||||
####################################################
|
||||
# Query and Mutation
|
||||
####################################################
|
||||
|
||||
extend type Query {
|
||||
|
||||
sqlDialectInfo( connectionId: ID! ): SQLDialectInfo
|
||||
|
||||
sqlListContexts( connectionId: ID! ): [ SQLContextInfo ]
|
||||
# Lists SQL contexts for a connection (optional) or returns the particular context info
|
||||
sqlListContexts( connectionId: ID, contextId: ID ): [ SQLContextInfo ]!
|
||||
|
||||
sqlCompletionProposals(
|
||||
connectionId: ID!,
|
||||
contextId: ID!,
|
||||
query: String!,
|
||||
position: Int!,
|
||||
maxResults: Int
|
||||
maxResults: Int,
|
||||
simpleMode: Boolean
|
||||
): [ SQLCompletionProposal ]
|
||||
|
||||
sqlSupportedOperations(
|
||||
connectionId: ID!,
|
||||
contextId: ID!,
|
||||
resultsId: ID!,
|
||||
attributeIndex: Int!
|
||||
): [DataTypeLogicalOperation!]!
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
@@ -171,9 +223,9 @@ extend type Mutation {
|
||||
connectionId: ID!,
|
||||
contextId: ID!,
|
||||
resultsId: ID!,
|
||||
|
||||
# Row cell values (to identify row)
|
||||
updateRow: [ Object ]!,
|
||||
# Map<Int,Object>
|
||||
# Values to update - Map<Int,Object>
|
||||
updateValues: Object
|
||||
): SQLExecuteInfo
|
||||
|
||||
@@ -191,4 +243,16 @@ extend type Mutation {
|
||||
# Returns SQLExecuteInfo
|
||||
asyncSqlExecuteResults(taskId: ID!): SQLExecuteInfo !
|
||||
|
||||
|
||||
# Read data from table
|
||||
asyncSqlExplainExecutionPlan(
|
||||
connectionId: ID!,
|
||||
contextId: ID!,
|
||||
query: String!,
|
||||
configuration: Object!
|
||||
): AsyncTaskInfo!
|
||||
|
||||
# Returns SQLExecutionPlan
|
||||
asyncSqlExplainExecutionPlanResult(taskId: ID!): SQLExecutionPlan !
|
||||
|
||||
}
|
||||
|
||||
@@ -45,6 +45,17 @@ public interface DBWAuthProviderExternal<AUTH_SESSION extends DBASession> extend
|
||||
@NotNull Map<String, Object> authParameters // Passed auth parameters (e.g. user name or password)
|
||||
) throws DBException;
|
||||
|
||||
/**
|
||||
* Returns new identifying credentials which can be used to find/create user in database
|
||||
*/
|
||||
@NotNull
|
||||
Map<String, Object> authFederatedUser(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull Map<String, Object> providerConfig, // Auth provider configuration (e.g. 3rd party auth server address)
|
||||
@NotNull String faProvider,
|
||||
@NotNull Map<String, Object> faParameters // Passed federated auth parameters (e.g. assertion ID, session token, etc)
|
||||
) throws DBException;
|
||||
|
||||
/**
|
||||
* Validates that external user may be associated with local user
|
||||
* @param userCredentials credentials from authExternalUser
|
||||
|
||||
@@ -85,6 +85,8 @@ public interface DBWSecurityController {
|
||||
|
||||
void createRole(WebRole role) throws DBCException;
|
||||
|
||||
void updateRole(WebRole role) throws DBCException;
|
||||
|
||||
void deleteRole(String roleId) throws DBCException;
|
||||
|
||||
///////////////////////////////////////////
|
||||
|
||||
@@ -71,10 +71,6 @@ public class WebServiceUtils {
|
||||
return icon == null ? null : icon.getLocation();
|
||||
}
|
||||
|
||||
public static String makeDriverFullId(DBPDriver driver) {
|
||||
return driver.getProviderId() + ":" + driver.getId();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static DBPDriver getDriverById(String id) throws DBWebException {
|
||||
int divPos = id.indexOf(':');
|
||||
@@ -236,7 +232,12 @@ public class WebServiceUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveAuthProperties(DBPDataSourceContainer dataSourceContainer, DBPConnectionConfiguration configuration, Map<String, Object> authProperties, boolean saveCredentials) {
|
||||
public static void saveAuthProperties(
|
||||
@NotNull DBPDataSourceContainer dataSourceContainer,
|
||||
@NotNull DBPConnectionConfiguration configuration,
|
||||
@Nullable Map<String, Object> authProperties,
|
||||
boolean saveCredentials)
|
||||
{
|
||||
dataSourceContainer.setSavePassword(saveCredentials);
|
||||
if (!saveCredentials) {
|
||||
// Reset credentials
|
||||
@@ -247,6 +248,9 @@ public class WebServiceUtils {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!saveCredentials) {
|
||||
configuration.setUserPassword(null);
|
||||
}
|
||||
{
|
||||
// Read save credentials
|
||||
DBAAuthCredentials credentials = configuration.getAuthModel().loadCredentials(dataSourceContainer, configuration);
|
||||
|
||||
+11
@@ -18,14 +18,20 @@ package io.cloudbeaver.auth.provider;
|
||||
|
||||
import io.cloudbeaver.DBWAuthProviderExternal;
|
||||
import io.cloudbeaver.model.user.WebUser;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.access.DBASession;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Abstract external auth provider
|
||||
*/
|
||||
public abstract class AbstractExternalAuthProvider<SESSION extends DBASession> implements DBWAuthProviderExternal<SESSION> {
|
||||
|
||||
@Deprecated
|
||||
protected void setUserOrigin(WebUser user, String type, String subType) {
|
||||
user.setMetaParameter(META_AUTH_PROVIDER, type);
|
||||
if (!CommonUtils.isEmpty(subType)) {
|
||||
@@ -33,4 +39,9 @@ public abstract class AbstractExternalAuthProvider<SESSION extends DBASession> i
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Map<String, Object> authFederatedUser(@NotNull DBRProgressMonitor monitor, @NotNull Map<String, Object> providerConfig, @NotNull String faProvider, @NotNull Map<String, Object> faParameters) throws DBException {
|
||||
throw new DBException("Federated authorization not supported");
|
||||
}
|
||||
}
|
||||
|
||||
+27
@@ -77,4 +77,31 @@ public class LocalAuthProvider implements DBWAuthProvider<LocalAuthSession> {
|
||||
return SecurityUtils.makeDigest(password);
|
||||
}
|
||||
|
||||
public static boolean changeUserPassword(@NotNull WebSession webSession, @NotNull String oldPassword, @NotNull String newPassword) throws DBException {
|
||||
String userName = webSession.getUser().getUserId();
|
||||
|
||||
WebAuthProviderDescriptor authProvider = WebServiceRegistry.getInstance().getAuthProvider(PROVIDER_ID);
|
||||
Map<String, Object> storedCredentials = CBApplication.getInstance().getSecurityController().getUserCredentials(userName, authProvider);
|
||||
if (storedCredentials == null) {
|
||||
throw new DBException("Invalid user name or password");
|
||||
}
|
||||
String storedPasswordHash = CommonUtils.toString(storedCredentials.get(CRED_PASSWORD), null);
|
||||
if (CommonUtils.isEmpty(storedPasswordHash)) {
|
||||
throw new DBException("User has no saved credentials");
|
||||
}
|
||||
if (CommonUtils.isEmpty(oldPassword)) {
|
||||
throw new DBException("No user password provided");
|
||||
}
|
||||
String oldPasswordHash = WebAuthProviderPropertyEncryption.hash.encrypt(userName, oldPassword);
|
||||
if (!storedPasswordHash.equals(oldPasswordHash)) {
|
||||
throw new DBException("Invalid user name or password");
|
||||
}
|
||||
|
||||
//String newPasswordHash = WebAuthProviderPropertyEncryption.hash.encrypt(userName, newPassword);
|
||||
|
||||
storedCredentials.put(CRED_PASSWORD, newPassword);
|
||||
CBApplication.getInstance().getSecurityController().setUserCredentials(userName, authProvider, storedCredentials);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -77,7 +77,7 @@ public class WebConnectionInfo {
|
||||
|
||||
@Property
|
||||
public String getDriverId() {
|
||||
return WebServiceUtils.makeDriverFullId(dataSourceContainer.getDriver());
|
||||
return dataSourceContainer.getDriver().getFullId();
|
||||
}
|
||||
|
||||
@Property
|
||||
@@ -263,7 +263,7 @@ public class WebConnectionInfo {
|
||||
// Fill session and user provided credentials
|
||||
boolean hasContextCredentials = session.hasContextCredentials();
|
||||
DBPConnectionConfiguration configWithAuth = new DBPConnectionConfiguration(dataSourceContainer.getConnectionConfiguration());
|
||||
session.provideAuthParameters(dataSourceContainer, configWithAuth);
|
||||
session.provideAuthParameters(session.getProgressMonitor(), dataSourceContainer, configWithAuth);
|
||||
|
||||
|
||||
DBPPropertySource credentialsSource = authModel.createCredentialsSource(dataSourceContainer, configWithAuth);
|
||||
|
||||
+1
-2
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
package io.cloudbeaver.model;
|
||||
|
||||
import io.cloudbeaver.WebServiceUtils;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
|
||||
@@ -40,7 +39,7 @@ public class WebDataSourceConfig {
|
||||
this.id = ds.getId();
|
||||
this.name = ds.getName();
|
||||
this.description = ds.getDescription();
|
||||
this.driverId = WebServiceUtils.makeDriverFullId(ds.getDriver());
|
||||
this.driverId = ds.getDriver().getFullId();
|
||||
this.host = ds.getConnectionConfiguration().getHostName();
|
||||
this.port = ds.getConnectionConfiguration().getHostPort();
|
||||
this.url = ds.getConnectionConfiguration().getUrl();
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ public class WebDatabaseDriverConfig {
|
||||
public WebDatabaseDriverConfig(WebSession webSession, DBPDriver driver) {
|
||||
this.webSession = webSession;
|
||||
this.driver = driver;
|
||||
this.id = WebServiceUtils.makeDriverFullId(driver);
|
||||
this.id = driver.getFullId();
|
||||
}
|
||||
|
||||
@Property
|
||||
|
||||
@@ -59,6 +59,16 @@ public class WebServerConfig {
|
||||
return DBWorkbench.getPlatform().getWorkspace().getWorkspaceId();
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getServerURL() {
|
||||
return CommonUtils.notEmpty(application.getServerURL());
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getRootURI() {
|
||||
return CommonUtils.notEmpty(application.getRootURI());
|
||||
}
|
||||
|
||||
@Property
|
||||
public boolean isAnonymousAccessEnabled() {
|
||||
return application.getAppConfiguration().isAnonymousAccessEnabled();
|
||||
|
||||
+19
-3
@@ -31,6 +31,7 @@ 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.DBASession;
|
||||
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
|
||||
@@ -56,10 +57,13 @@ import org.jkiss.dbeaver.registry.ProjectMetadata;
|
||||
import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
@@ -74,6 +78,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
|
||||
private static final Log log = Log.getLog(WebSession.class);
|
||||
|
||||
private static final String ATTR_LOCALE = "locale";
|
||||
private static final String SESSION_TEMP_COOKIE = "cb-session";
|
||||
|
||||
private static final AtomicInteger TASK_ID = new AtomicInteger();
|
||||
|
||||
@@ -407,7 +412,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void updateInfo(HttpServletRequest request) {
|
||||
synchronized void updateInfo(HttpServletRequest request, HttpServletResponse response) {
|
||||
HttpSession httpSession = request.getSession();
|
||||
this.lastAccessTime = System.currentTimeMillis();
|
||||
this.lastRemoteAddr = request.getRemoteAddr();
|
||||
@@ -431,6 +436,17 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
|
||||
log.error("Error persisting web session", e);
|
||||
}
|
||||
}
|
||||
{
|
||||
long maxSessionIdleTime = CBApplication.getInstance().getMaxSessionIdleTime();
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(DBConstants.DEFAULT_ISO_TIMESTAMP_FORMAT);
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
Cookie sessionCookie = new Cookie(SESSION_TEMP_COOKIE, sdf.format(new Date(System.currentTimeMillis() + maxSessionIdleTime)));
|
||||
sessionCookie.setMaxAge((int) (maxSessionIdleTime / 1000));
|
||||
sessionCookie.setPath(CBApplication.getInstance().getRootURI());
|
||||
//sessionCookie.setComment("CB session cookie");
|
||||
response.addCookie(sessionCookie);
|
||||
}
|
||||
}
|
||||
|
||||
@Association
|
||||
@@ -718,13 +734,13 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap
|
||||
// Auth credentials provider
|
||||
// Adds auth properties passed from web (by user)
|
||||
@Override
|
||||
public boolean provideAuthParameters(DBPDataSourceContainer dataSourceContainer, DBPConnectionConfiguration configuration) {
|
||||
public boolean provideAuthParameters(@NotNull DBRProgressMonitor monitor, @NotNull DBPDataSourceContainer dataSourceContainer, @NotNull DBPConnectionConfiguration configuration) {
|
||||
try {
|
||||
// Properties from nested auth sessions
|
||||
// FIXME: we need to support multiple credential providers (e.g. multiple clouds).
|
||||
DBAAuthCredentialsProvider nestedProvider = getAdapter(DBAAuthCredentialsProvider.class);
|
||||
if (nestedProvider != null) {
|
||||
if (!nestedProvider.provideAuthParameters(dataSourceContainer, configuration)) {
|
||||
if (!nestedProvider.provideAuthParameters(monitor, dataSourceContainer, configuration)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-9
@@ -25,6 +25,7 @@ import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.*;
|
||||
|
||||
@@ -65,21 +66,21 @@ public class WebSessionManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean touchSession(@NotNull HttpServletRequest request) throws DBWebException {
|
||||
WebSession webSession = getWebSession(request, false);
|
||||
webSession.updateInfo(request);
|
||||
public boolean touchSession(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws DBWebException {
|
||||
WebSession webSession = getWebSession(request, response, false);
|
||||
webSession.updateInfo(request, response);
|
||||
return true;
|
||||
}
|
||||
|
||||
public WebSession getWebSession(@NotNull HttpServletRequest request) throws DBWebException {
|
||||
return getWebSession(request, true);
|
||||
public WebSession getWebSession(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws DBWebException {
|
||||
return getWebSession(request, response, true);
|
||||
}
|
||||
|
||||
public WebSession getWebSession(@NotNull HttpServletRequest request, boolean errorOnNoFound) throws DBWebException {
|
||||
return getWebSession(request, true, errorOnNoFound);
|
||||
public WebSession getWebSession(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, boolean errorOnNoFound) throws DBWebException {
|
||||
return getWebSession(request, response, true, errorOnNoFound);
|
||||
}
|
||||
|
||||
public WebSession getWebSession(HttpServletRequest request, boolean updateInfo, boolean errorOnNoFound) throws DBWebException {
|
||||
public WebSession getWebSession(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, boolean updateInfo, boolean errorOnNoFound) throws DBWebException {
|
||||
HttpSession httpSession = request.getSession(true);
|
||||
String sessionId = httpSession.getId();
|
||||
WebSession webSession;
|
||||
@@ -103,7 +104,7 @@ public class WebSessionManager {
|
||||
if (updateInfo) {
|
||||
// Update only once per request
|
||||
if (!CommonUtils.toBoolean(request.getAttribute("sessionUpdated"))) {
|
||||
webSession.updateInfo(request);
|
||||
webSession.updateInfo(request, response);
|
||||
request.setAttribute("sessionUpdated", true);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
package io.cloudbeaver.registry;
|
||||
|
||||
import io.cloudbeaver.WebServiceUtils;
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
import org.eclipse.core.runtime.IConfigurationElement;
|
||||
import org.eclipse.core.runtime.IExtensionRegistry;
|
||||
@@ -63,7 +62,7 @@ public class WebDriverRegistry {
|
||||
}
|
||||
|
||||
public boolean isDriverEnabled(DBPDriver driver) {
|
||||
String driverId = WebServiceUtils.makeDriverFullId(driver);
|
||||
String driverId = driver.getFullId();
|
||||
if (webDrivers.contains(driverId)) {
|
||||
String[] enabledDrivers = CBApplication.getInstance().getAppConfiguration().getEnabledDrivers();
|
||||
if (enabledDrivers.length > 0 && !ArrayUtils.contains(enabledDrivers, driverId)) {
|
||||
|
||||
+26
-4
@@ -17,16 +17,15 @@
|
||||
package io.cloudbeaver.registry;
|
||||
|
||||
import io.cloudbeaver.service.DBWServiceBinding;
|
||||
import io.cloudbeaver.service.sql.DBWValueSerializer;
|
||||
import org.eclipse.core.runtime.IConfigurationElement;
|
||||
import org.eclipse.core.runtime.IExtensionRegistry;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.registry.RegistryConstants;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
public class WebServiceRegistry {
|
||||
|
||||
@@ -50,6 +49,7 @@ public class WebServiceRegistry {
|
||||
private DBWServiceBinding[] webServiceInstances;
|
||||
|
||||
private final Map<String, WebAuthProviderDescriptor> authProviders = new LinkedHashMap<>();
|
||||
private final Map<String, WebValueSerializerDescriptor> valueSerializers = new HashMap<>();
|
||||
|
||||
private WebServiceRegistry() {
|
||||
}
|
||||
@@ -90,6 +90,15 @@ public class WebServiceRegistry {
|
||||
}
|
||||
}
|
||||
webServiceInstances = instances.toArray(new DBWServiceBinding[0]);
|
||||
|
||||
{
|
||||
IConfigurationElement[] extConfigs = registry.getConfigurationElementsFor(WebValueSerializerDescriptor.EXTENSION_ID);
|
||||
for (IConfigurationElement ext : extConfigs) {
|
||||
WebValueSerializerDescriptor descriptor = new WebValueSerializerDescriptor(ext);
|
||||
valueSerializers.put(descriptor.getValueType(), descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<WebServiceDescriptor> getWebServices() {
|
||||
@@ -125,4 +134,17 @@ public class WebServiceRegistry {
|
||||
return authProviders.get(id);
|
||||
}
|
||||
|
||||
public DBWValueSerializer<?> createValueSerializer(String valueType) {
|
||||
WebValueSerializerDescriptor descriptor = valueSerializers.get(valueType);
|
||||
if (descriptor == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return descriptor.createInstance();
|
||||
} catch (DBException e) {
|
||||
log.error("Error creating value '" + valueType + "' serializer", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2021 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.registry;
|
||||
|
||||
import io.cloudbeaver.service.sql.DBWValueSerializer;
|
||||
import org.eclipse.core.runtime.IConfigurationElement;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.impl.AbstractDescriptor;
|
||||
|
||||
/**
|
||||
* Value serializer descriptor
|
||||
*/
|
||||
public class WebValueSerializerDescriptor extends AbstractDescriptor {
|
||||
|
||||
public static final String EXTENSION_ID = "io.cloudbeaver.valueSerializer"; //$NON-NLS-1$
|
||||
|
||||
private final IConfigurationElement cfg;
|
||||
|
||||
private final String valueType;
|
||||
private final ObjectType implType;
|
||||
|
||||
public WebValueSerializerDescriptor(IConfigurationElement cfg) {
|
||||
super(cfg);
|
||||
this.cfg = cfg;
|
||||
this.valueType = cfg.getAttribute("type");
|
||||
this.implType = new ObjectType(cfg, "class");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getValueType() {
|
||||
return valueType;
|
||||
}
|
||||
|
||||
public DBWValueSerializer<?> createInstance() throws DBException {
|
||||
return implType.createInstance(DBWValueSerializer.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package io.cloudbeaver.server;
|
||||
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
|
||||
import io.cloudbeaver.registry.WebAuthProviderDescriptor;
|
||||
import io.cloudbeaver.registry.WebServiceRegistry;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
|
||||
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings;
|
||||
|
||||
@@ -46,6 +47,7 @@ public class CBAppConfig {
|
||||
private String[] enabledAuthProviders = null;
|
||||
private DataSourceNavigatorSettings defaultNavigatorSettings = DEFAULT_VIEW_SETTINGS;
|
||||
private Map<String, Object> plugins = new LinkedHashMap<>();
|
||||
private Map<String, Object> authConfiguration = new LinkedHashMap<>();
|
||||
|
||||
public boolean isAnonymousAccessEnabled() {
|
||||
return anonymousAccessEnabled;
|
||||
@@ -132,15 +134,16 @@ public class CBAppConfig {
|
||||
this.defaultNavigatorSettings = new DataSourceNavigatorSettings(defaultNavigatorSettings);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<String, Object> getPlugins() {
|
||||
return plugins;
|
||||
}
|
||||
|
||||
public Map<String, Object> getPluginConfig(String pluginId) {
|
||||
public Map<String, Object> getPluginConfig(@NotNull String pluginId) {
|
||||
return getPluginConfig(pluginId, false);
|
||||
}
|
||||
|
||||
public Map<String, Object> getPluginConfig(String pluginId, boolean create) {
|
||||
public Map<String, Object> getPluginConfig(@NotNull String pluginId, boolean create) {
|
||||
Object config = plugins.get(pluginId);
|
||||
if (config instanceof Map) {
|
||||
return (Map<String, Object>) config;
|
||||
@@ -155,8 +158,21 @@ public class CBAppConfig {
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T getPluginOption(String pluginId, String option) {
|
||||
public <T> T getPluginOption(@NotNull String pluginId, @NotNull String option) {
|
||||
return (T)getPluginConfig(pluginId).get(option);
|
||||
}
|
||||
|
||||
public Map<String, Object> getAuthConfiguration() {
|
||||
return authConfiguration;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<String, Object> getAuthConfiguration(@NotNull String providerId) {
|
||||
Object apConfig = authConfiguration.get(providerId);
|
||||
if (apConfig instanceof Map) {
|
||||
return (Map<String, Object>) apConfig;
|
||||
}
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ public class CBApplication extends BaseApplicationImpl {
|
||||
return (CBApplication) BaseApplicationImpl.getInstance();
|
||||
}
|
||||
|
||||
private String serverURL;
|
||||
private int serverPort = CBConstants.DEFAULT_SERVER_PORT;
|
||||
private String serverName = null;
|
||||
private String contentRoot = CBConstants.DEFAULT_CONTENT_ROOT;
|
||||
@@ -103,6 +104,10 @@ public class CBApplication extends BaseApplicationImpl {
|
||||
public CBApplication() {
|
||||
}
|
||||
|
||||
public String getServerURL() {
|
||||
return serverURL;
|
||||
}
|
||||
|
||||
public int getServerPort() {
|
||||
return serverPort;
|
||||
}
|
||||
@@ -174,7 +179,7 @@ public class CBApplication extends BaseApplicationImpl {
|
||||
try {
|
||||
loadConfiguration(configPath);
|
||||
|
||||
File runtimeConfigFile = getRuntimeConfigFile();
|
||||
File runtimeConfigFile = getRuntimeAppConfigFile();
|
||||
if (runtimeConfigFile.exists()) {
|
||||
log.debug("Runtime configuration [" + runtimeConfigFile.getAbsolutePath() + "]");
|
||||
parseConfiguration(runtimeConfigFile);
|
||||
@@ -274,6 +279,11 @@ public class CBApplication extends BaseApplicationImpl {
|
||||
});
|
||||
Runtime.getRuntime().addShutdownHook(shutdownThread);
|
||||
|
||||
if (configurationMode) {
|
||||
// Try to configure automatically
|
||||
performAutoConfiguration(new File(configPath).getParentFile());
|
||||
}
|
||||
|
||||
try {
|
||||
initializeServer();
|
||||
} catch (DBException e) {
|
||||
@@ -287,6 +297,55 @@ public class CBApplication extends BaseApplicationImpl {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures server automatically.
|
||||
* Called on startup
|
||||
* @param configPath
|
||||
*/
|
||||
protected void performAutoConfiguration(File configPath) {
|
||||
String autoServerName = System.getenv(CBConstants.VAR_AUTO_CB_SERVER_NAME);
|
||||
String autoServerURL = System.getenv(CBConstants.VAR_AUTO_CB_SERVER_URL);
|
||||
String autoAdminName = System.getenv(CBConstants.VAR_AUTO_CB_ADMIN_NAME);
|
||||
String autoAdminPassword = System.getenv(CBConstants.VAR_AUTO_CB_ADMIN_PASSWORD);
|
||||
|
||||
if (CommonUtils.isEmpty(autoServerName) || CommonUtils.isEmpty(autoAdminName) || CommonUtils.isEmpty(autoAdminPassword)) {
|
||||
// Try to load from auto config file
|
||||
if (configPath.exists()) {
|
||||
File autoConfigFile = new File(configPath, CBConstants.AUTO_CONFIG_FILE_NAME);
|
||||
if (autoConfigFile.exists()) {
|
||||
Properties autoProps = new Properties();
|
||||
try (InputStream is = new FileInputStream(autoConfigFile)) {
|
||||
autoProps.load(is);
|
||||
|
||||
autoServerName = autoProps.getProperty(CBConstants.VAR_AUTO_CB_SERVER_NAME);
|
||||
autoServerURL = autoProps.getProperty(CBConstants.VAR_AUTO_CB_SERVER_URL);
|
||||
autoAdminName = autoProps.getProperty(CBConstants.VAR_AUTO_CB_ADMIN_NAME);
|
||||
autoAdminPassword = autoProps.getProperty(CBConstants.VAR_AUTO_CB_ADMIN_PASSWORD);
|
||||
} catch (IOException e) {
|
||||
log.error("Error loading auto configuration file '" + autoConfigFile.getAbsolutePath() + "'", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CommonUtils.isEmpty(autoServerName) || CommonUtils.isEmpty(autoAdminName) || CommonUtils.isEmpty(autoAdminPassword)) {
|
||||
log.info("No auto configuration was found. Server must be configured manually");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
finishConfiguration(
|
||||
autoServerName,
|
||||
autoServerURL,
|
||||
autoAdminName,
|
||||
autoAdminPassword,
|
||||
Collections.emptyList(),
|
||||
maxSessionIdleTime,
|
||||
getAppConfiguration());
|
||||
} catch (Exception e) {
|
||||
log.error("Error loading server auto configuration", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void initializeServer() throws DBException {
|
||||
|
||||
}
|
||||
@@ -327,8 +386,13 @@ public class CBApplication extends BaseApplicationImpl {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private File getRuntimeConfigFile() {
|
||||
return new File(getDataDirectory(false), CBConstants.RUNTIME_CONFIG_FILE_NAME);
|
||||
private File getRuntimeAppConfigFile() {
|
||||
return new File(getDataDirectory(false), CBConstants.RUNTIME_APP_CONFIG_FILE_NAME);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private File getRuntimeProductConfigFile() {
|
||||
return new File(getDataDirectory(false), CBConstants.RUNTIME_PRODUCT_CONFIG_FILE_NAME);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -398,6 +462,12 @@ public class CBApplication extends BaseApplicationImpl {
|
||||
|
||||
Map<String, Object> serverConfig = JSONUtils.getObject(configProps, "server");
|
||||
serverPort = JSONUtils.getInteger(serverConfig, CBConstants.PARAM_SERVER_PORT, serverPort);
|
||||
if (serverConfig.containsKey(CBConstants.PARAM_SERVER_URL)) {
|
||||
serverURL = JSONUtils.getString(serverConfig, CBConstants.PARAM_SERVER_URL, serverURL);
|
||||
} else if (serverURL == null) {
|
||||
serverURL = "http://" + InetAddress.getLocalHost().getHostName() + ":" + serverPort;
|
||||
}
|
||||
|
||||
serverName = JSONUtils.getString(serverConfig, CBConstants.PARAM_SERVER_NAME, serverName);
|
||||
contentRoot = getRelativePath(
|
||||
JSONUtils.getString(serverConfig, CBConstants.PARAM_CONTENT_ROOT, contentRoot), homeFolder);
|
||||
@@ -439,6 +509,19 @@ public class CBApplication extends BaseApplicationImpl {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add product config from runtime
|
||||
{
|
||||
File rtConfig = getRuntimeProductConfigFile();
|
||||
if (rtConfig.exists()) {
|
||||
log.debug("Load product runtime configuration from '" + rtConfig.getAbsolutePath() + "'");
|
||||
try (Reader reader = new InputStreamReader(new FileInputStream(rtConfig), StandardCharsets.UTF_8)) {
|
||||
productConfiguration.putAll(JSONUtils.parseMap(gson, reader));
|
||||
} catch (Exception e) {
|
||||
log.error("Error reading product runtime configuration", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String getRelativePath(String path, String curDir) {
|
||||
@@ -504,6 +587,7 @@ public class CBApplication extends BaseApplicationImpl {
|
||||
|
||||
public synchronized void finishConfiguration(
|
||||
@NotNull String newServerName,
|
||||
@NotNull String newServerURL,
|
||||
@NotNull String adminName,
|
||||
@Nullable String adminPassword,
|
||||
@NotNull List<WebAuthInfo> authInfoList,
|
||||
@@ -520,7 +604,7 @@ public class CBApplication extends BaseApplicationImpl {
|
||||
|
||||
// Save runtime configuration
|
||||
log.debug("Saving runtime configuration");
|
||||
saveRuntimeConfig(newServerName, sessionExpireTime, appConfig);
|
||||
saveRuntimeConfig(newServerName, newServerURL, sessionExpireTime, appConfig);
|
||||
|
||||
// Grant permissions to predefined connections
|
||||
if (isConfigurationMode() && appConfig.isAnonymousAccessEnabled()) {
|
||||
@@ -530,7 +614,7 @@ public class CBApplication extends BaseApplicationImpl {
|
||||
// Re-load runtime configuration
|
||||
try {
|
||||
log.debug("Reloading application configuration");
|
||||
File runtimeConfigFile = getRuntimeConfigFile();
|
||||
File runtimeConfigFile = getRuntimeAppConfigFile();
|
||||
if (runtimeConfigFile.exists()) {
|
||||
log.debug("Runtime configuration [" + runtimeConfigFile.getAbsolutePath() + "]");
|
||||
parseConfiguration(runtimeConfigFile);
|
||||
@@ -543,7 +627,7 @@ public class CBApplication extends BaseApplicationImpl {
|
||||
}
|
||||
|
||||
public synchronized void flushConfiguration() throws DBException {
|
||||
saveRuntimeConfig(serverName, maxSessionIdleTime, appConfiguration);
|
||||
saveRuntimeConfig(serverName, serverURL, maxSessionIdleTime, appConfiguration);
|
||||
}
|
||||
|
||||
|
||||
@@ -565,9 +649,9 @@ public class CBApplication extends BaseApplicationImpl {
|
||||
}
|
||||
}
|
||||
|
||||
private void saveRuntimeConfig(String newServerName, long sessionExpireTime, CBAppConfig appConfig) throws DBException {
|
||||
private void saveRuntimeConfig(String newServerName, String newServerURL, long sessionExpireTime, CBAppConfig appConfig) throws DBException {
|
||||
|
||||
File runtimeConfigFile = getRuntimeConfigFile();
|
||||
File runtimeConfigFile = getRuntimeAppConfigFile();
|
||||
try (Writer out = new OutputStreamWriter(new FileOutputStream(runtimeConfigFile), StandardCharsets.UTF_8)) {
|
||||
Gson gson = new GsonBuilder()
|
||||
.setLenient()
|
||||
@@ -582,6 +666,9 @@ public class CBApplication extends BaseApplicationImpl {
|
||||
if (!CommonUtils.isEmpty(newServerName)) {
|
||||
JSONUtils.field(json, CBConstants.PARAM_SERVER_NAME, newServerName);
|
||||
}
|
||||
if (!CommonUtils.isEmpty(newServerURL)) {
|
||||
JSONUtils.field(json, CBConstants.PARAM_SERVER_URL, newServerURL);
|
||||
}
|
||||
if (sessionExpireTime > 0) {
|
||||
JSONUtils.field(json, CBConstants.PARAM_SESSION_EXPIRE_PERIOD, sessionExpireTime);
|
||||
}
|
||||
@@ -619,12 +706,15 @@ public class CBApplication extends BaseApplicationImpl {
|
||||
json.endObject();
|
||||
}
|
||||
if (appConfig.getEnabledAuthProviders() != null) {
|
||||
JSONUtils.serializeStringList(json, "enabledAuthProviders", Arrays.asList(appConfig.getEnabledAuthProviders()));
|
||||
JSONUtils.serializeStringList(json, "enabledAuthProviders", Arrays.asList(appConfig.getEnabledAuthProviders()), true);
|
||||
}
|
||||
|
||||
if (!CommonUtils.isEmpty(appConfig.getPlugins())) {
|
||||
JSONUtils.serializeProperties(json, "plugins", appConfig.getPlugins());
|
||||
}
|
||||
if (!CommonUtils.isEmpty(appConfig.getAuthConfiguration())) {
|
||||
JSONUtils.serializeProperties(json, "authConfiguration", appConfig.getAuthConfiguration());
|
||||
}
|
||||
|
||||
json.endObject();
|
||||
}
|
||||
|
||||
@@ -30,13 +30,16 @@ public class CBConstants {
|
||||
|
||||
public static final String DEFAULT_CONFIG_FILE_PATH = "/etc/cloudbeaver.conf";
|
||||
public static final String RUNTIME_DATA_DIR_NAME = ".data";
|
||||
public static final String RUNTIME_CONFIG_FILE_NAME = ".cloudbeaver.runtime.conf";
|
||||
public static final String RUNTIME_APP_CONFIG_FILE_NAME = ".cloudbeaver.runtime.conf";
|
||||
public static final String RUNTIME_PRODUCT_CONFIG_FILE_NAME = ".product.runtime.conf";
|
||||
public static final String AUTO_CONFIG_FILE_NAME = ".cloudbeaver.auto.conf";
|
||||
|
||||
public static final String CLI_PARAM_WEB_CONFIG = "-web-config";
|
||||
|
||||
public static final String PARAM_SERVER_PORT = "serverPort";
|
||||
public static final String PARAM_SERVER_NAME = "serverName";
|
||||
public static final String PARAM_CONTENT_ROOT = "contentRoot";
|
||||
public static final String PARAM_SERVER_URL = "serverURL";
|
||||
public static final String PARAM_ROOT_URI = "rootURI";
|
||||
public static final String PARAM_SERVICES_URI = "serviceURI";
|
||||
public static final String PARAM_DRIVERS_LOCATION = "driversLocation";
|
||||
@@ -66,9 +69,15 @@ public class CBConstants {
|
||||
|
||||
public static final String DEFAUL_APP_ANONYMOUS_ROLE_NAME = "user";
|
||||
|
||||
|
||||
public static final String DEFAULT_SERVER_ADDR = "localhost";
|
||||
|
||||
public static final String VAR_CB_LOCAL_HOST_ADDR = "CB_LOCAL_HOST_ADDR";
|
||||
|
||||
|
||||
public static final String VAR_HOST_DOCKER_INTERNAL = "host.docker.internal";
|
||||
|
||||
public static final String VAR_AUTO_CB_SERVER_NAME = "CB_SERVER_NAME";
|
||||
public static final String VAR_AUTO_CB_SERVER_URL = "CB_SERVER_URL";
|
||||
public static final String VAR_AUTO_CB_ADMIN_NAME = "CB_ADMIN_NAME";
|
||||
public static final String VAR_AUTO_CB_ADMIN_PASSWORD = "CB_ADMIN_PASSWORD";
|
||||
|
||||
}
|
||||
|
||||
+22
@@ -463,6 +463,28 @@ class CBSecurityController implements DBWSecurityController {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRole(WebRole role) throws DBCException {
|
||||
if (!isSubjectExists(role.getRoleId())) {
|
||||
throw new DBCException("Role '" + role.getRoleId() + "' doesn't exists");
|
||||
}
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
|
||||
createAuthSubject(dbCon, role.getRoleId(), SUBJECT_ROLE);
|
||||
try (PreparedStatement dbStat = dbCon.prepareStatement(
|
||||
"UPDATE CB_ROLE SET ROLE_NAME=?,ROLE_DESCRIPTION=? WHERE ROLE_ID=?")) {
|
||||
dbStat.setString(1, CommonUtils.notEmpty(role.getName()));
|
||||
dbStat.setString(2, CommonUtils.notEmpty(role.getDescription()));
|
||||
dbStat.setString(3, role.getRoleId());
|
||||
dbStat.execute();
|
||||
}
|
||||
txn.commit();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBCException("Error updating role info in database", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRole(String roleId) throws DBCException {
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
|
||||
+9
@@ -319,6 +319,15 @@ public class GraphQLEndpoint extends HttpServlet {
|
||||
return request;
|
||||
}
|
||||
|
||||
public static HttpServletResponse getServletResponse(DataFetchingEnvironment env) {
|
||||
GraphQLContext context = env.getContext();
|
||||
HttpServletResponse response = context.get("response");
|
||||
if (response == null) {
|
||||
throw new IllegalStateException("Null response");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
public static GraphQLBindingContext getBindingContext(DataFetchingEnvironment env) {
|
||||
GraphQLContext context = env.getContext();
|
||||
return context.get("bindingContext");
|
||||
|
||||
+3
-2
@@ -45,8 +45,9 @@ public class CBJettyServer {
|
||||
// Handler configuration
|
||||
ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||
servletContextHandler.setResourceBase(application.getContentRoot());
|
||||
servletContextHandler.setContextPath(application.getRootURI());
|
||||
servletContextHandler.addServlet(new ServletHolder("static", new CBStaticServlet()), application.getRootURI());
|
||||
String rootURI = application.getRootURI();
|
||||
servletContextHandler.setContextPath(rootURI);
|
||||
servletContextHandler.addServlet(new ServletHolder("static", new CBStaticServlet()), "/*");
|
||||
servletContextHandler.addServlet(new ServletHolder("images", new CBImageServlet()), application.getServicesURI() + "images/*");
|
||||
servletContextHandler.addServlet(new ServletHolder("graphql", new GraphQLEndpoint()), application.getServicesURI() + "gql/*");
|
||||
servletContextHandler.addEventListener(new CBServerContextListener());
|
||||
|
||||
+29
-1
@@ -1,5 +1,7 @@
|
||||
package io.cloudbeaver.server.jetty;
|
||||
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
import org.eclipse.jetty.http.HttpContent;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.server.ResourceService;
|
||||
@@ -9,7 +11,10 @@ import javax.servlet.ServletException;
|
||||
import javax.servlet.annotation.WebServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Enumeration;
|
||||
|
||||
@WebServlet(urlPatterns = "/")
|
||||
public class CBStaticServlet extends DefaultServlet {
|
||||
@@ -26,10 +31,33 @@ public class CBStaticServlet extends DefaultServlet {
|
||||
}
|
||||
|
||||
private static ResourceService makeResourceService() {
|
||||
ResourceService resourceService = new ResourceService();
|
||||
ResourceService resourceService = new ProxyResourceService();
|
||||
resourceService.setCacheControl(new HttpField(HttpHeader.CACHE_CONTROL, "public, max-age=" + STATIC_CACHE_SECONDS));
|
||||
return resourceService;
|
||||
}
|
||||
|
||||
|
||||
private static class ProxyResourceService extends ResourceService {
|
||||
@Override
|
||||
protected boolean sendData(HttpServletRequest request, HttpServletResponse response, boolean include, HttpContent content, Enumeration<String> reqRanges) throws IOException {
|
||||
if (content.getResource().getName().endsWith("index.html")) {
|
||||
return patchIndexHtml(response, content);
|
||||
}
|
||||
return super.sendData(request, response, include, content, reqRanges);
|
||||
}
|
||||
|
||||
private boolean patchIndexHtml(HttpServletResponse response, HttpContent content) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
content.getResource().writeTo(baos, 0, content.getContentLengthValue());
|
||||
String indexContents = new String(baos.toByteArray(), StandardCharsets.UTF_8);
|
||||
indexContents = indexContents.replace("{ROOT_URI}", CBApplication.getInstance().getRootURI());
|
||||
byte[] indexBytes = indexContents.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
putHeaders(response, content, indexBytes.length);
|
||||
response.getOutputStream().write(indexBytes);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+7
-2
@@ -31,6 +31,7 @@ import org.jkiss.utils.ArrayUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -92,18 +93,22 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
|
||||
return GraphQLEndpoint.getServletRequest(env);
|
||||
}
|
||||
|
||||
protected static HttpServletResponse getServletResponse(DataFetchingEnvironment env) {
|
||||
return GraphQLEndpoint.getServletResponse(env);
|
||||
}
|
||||
|
||||
protected static DBWBindingContext getBindingContext(DataFetchingEnvironment env) {
|
||||
return GraphQLEndpoint.getBindingContext(env);
|
||||
}
|
||||
|
||||
protected static WebSession getWebSession(DataFetchingEnvironment env) throws DBWebException {
|
||||
return CBPlatform.getInstance().getSessionManager().getWebSession(
|
||||
getServletRequest(env));
|
||||
getServletRequest(env), getServletResponse(env));
|
||||
}
|
||||
|
||||
protected static WebSession getWebSession(DataFetchingEnvironment env, boolean errorOnNotFound) throws DBWebException {
|
||||
return CBPlatform.getInstance().getSessionManager().getWebSession(
|
||||
getServletRequest(env), errorOnNotFound);
|
||||
getServletRequest(env), getServletResponse(env), errorOnNotFound);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
+4
-10
@@ -26,6 +26,7 @@ import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -75,13 +76,13 @@ public interface DBWServiceCore extends DBWService {
|
||||
boolean closeSession(HttpServletRequest request) throws DBWebException;
|
||||
|
||||
@WebAction(requirePermissions = {})
|
||||
boolean touchSession(HttpServletRequest request) throws DBWebException;
|
||||
boolean touchSession(@NotNull HttpServletRequest request, @NotNull HttpServletResponse servletResponse) throws DBWebException;
|
||||
|
||||
@WebAction(requirePermissions = {})
|
||||
boolean refreshSessionConnections(HttpServletRequest request) throws DBWebException;
|
||||
boolean refreshSessionConnections(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
boolean changeSessionLanguage(WebSession webSession, String locale) throws DBWebException;
|
||||
boolean changeSessionLanguage(@NotNull WebSession webSession, String locale) throws DBWebException;
|
||||
|
||||
///////////////////////////////////////////
|
||||
// Connections
|
||||
@@ -89,13 +90,6 @@ public interface DBWServiceCore extends DBWService {
|
||||
@WebAction
|
||||
WebConnectionInfo getConnectionState(WebSession webSession, String connectionId) throws DBWebException;
|
||||
|
||||
// Replaced with initConnection
|
||||
@Deprecated
|
||||
@WebAction
|
||||
WebConnectionInfo openConnection(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull WebConnectionConfig connectionConfig) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
WebConnectionInfo initConnection(
|
||||
@NotNull WebSession webSession,
|
||||
|
||||
+6
-4
@@ -79,13 +79,15 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
|
||||
;
|
||||
|
||||
model.getMutationType()
|
||||
.dataFetcher("openSession", env -> getService(env).openSession(sessionManager.getWebSession(GraphQLEndpoint.getServletRequest(env), false)))
|
||||
.dataFetcher("openSession", env -> getService(env).openSession(
|
||||
sessionManager.getWebSession(GraphQLEndpoint.getServletRequest(env), GraphQLEndpoint.getServletResponse(env), false)))
|
||||
.dataFetcher("closeSession", env -> getService(env).closeSession(GraphQLEndpoint.getServletRequest(env)))
|
||||
.dataFetcher("touchSession", env -> getService(env).touchSession(GraphQLEndpoint.getServletRequest(env)))
|
||||
.dataFetcher("refreshSessionConnections", env -> getService(env).refreshSessionConnections(GraphQLEndpoint.getServletRequest(env)))
|
||||
.dataFetcher("touchSession", env -> getService(env).touchSession(
|
||||
GraphQLEndpoint.getServletRequest(env), GraphQLEndpoint.getServletResponse(env)))
|
||||
.dataFetcher("refreshSessionConnections", env -> getService(env).refreshSessionConnections(
|
||||
GraphQLEndpoint.getServletRequest(env), GraphQLEndpoint.getServletResponse(env)))
|
||||
.dataFetcher("changeSessionLanguage", env -> getService(env).changeSessionLanguage(getWebSession(env), env.getArgument("locale")))
|
||||
|
||||
.dataFetcher("openConnection", env -> getService(env).openConnection(getWebSession(env), getConnectionConfig(env)))
|
||||
.dataFetcher("createConnection", env -> getService(env).createConnection(getWebSession(env), getConnectionConfig(env)))
|
||||
.dataFetcher("updateConnection", env -> getService(env).updateConnection(getWebSession(env), getConnectionConfig(env)))
|
||||
.dataFetcher("deleteConnection", env -> getService(env).deleteConnection(getWebSession(env), env.getArgument("id")))
|
||||
|
||||
+16
-59
@@ -33,8 +33,6 @@ import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
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.model.data.json.JSONUtils;
|
||||
import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNative;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNDataSource;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNModel;
|
||||
@@ -54,6 +52,7 @@ import org.jkiss.dbeaver.utils.RuntimeUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -76,7 +75,7 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
public List<WebDatabaseDriverConfig> getDriverList(@NotNull WebSession webSession, String driverId) {
|
||||
List<WebDatabaseDriverConfig> result = new ArrayList<>();
|
||||
for (DBPDriver driver : CBPlatform.getInstance().getApplicableDrivers()) {
|
||||
if (driverId == null || driverId.equals(WebServiceUtils.makeDriverFullId(driver))) {
|
||||
if (driverId == null || driverId.equals(driver.getFullId())) {
|
||||
result.add(new WebDatabaseDriverConfig(webSession, driver));
|
||||
}
|
||||
}
|
||||
@@ -173,13 +172,13 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean touchSession(HttpServletRequest request) throws DBWebException {
|
||||
return CBPlatform.getInstance().getSessionManager().touchSession(request);
|
||||
public boolean touchSession(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws DBWebException {
|
||||
return CBPlatform.getInstance().getSessionManager().touchSession(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean refreshSessionConnections(HttpServletRequest request) throws DBWebException {
|
||||
WebSession session = CBPlatform.getInstance().getSessionManager().getWebSession(request);
|
||||
public boolean refreshSessionConnections(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws DBWebException {
|
||||
WebSession session = CBPlatform.getInstance().getSessionManager().getWebSession(request, response);
|
||||
if (session == null) {
|
||||
return false;
|
||||
} else {
|
||||
@@ -190,7 +189,7 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean changeSessionLanguage(WebSession webSession, String locale) {
|
||||
public boolean changeSessionLanguage(@NotNull WebSession webSession, String locale) {
|
||||
webSession.setLocale(locale);
|
||||
return true;
|
||||
}
|
||||
@@ -200,51 +199,6 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
return webSession.getWebConnectionInfo(connectionId);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public WebConnectionInfo openConnection(@NotNull WebSession webSession, @NotNull WebConnectionConfig config) throws DBWebException {
|
||||
String templateId = config.getTemplateId();
|
||||
if (CommonUtils.isEmpty(templateId)) {
|
||||
throw new DBWebException("Only preconfigured data sources are supported yet");
|
||||
}
|
||||
DBPDataSourceRegistry templateRegistry = WebServiceUtils.getGlobalDataSourceRegistry();
|
||||
DBPDataSourceContainer dataSourceTemplate = templateRegistry.getDataSource(templateId);
|
||||
if (dataSourceTemplate == null) {
|
||||
throw new DBWebException("Datasource '" + templateId + "' not found");
|
||||
}
|
||||
|
||||
DBPDataSourceRegistry sessionRegistry = webSession.getSingletonProject().getDataSourceRegistry();
|
||||
DBPDataSourceContainer newDataSource = sessionRegistry.createDataSource(dataSourceTemplate);
|
||||
newDataSource.setSavePassword(true);
|
||||
((DataSourceDescriptor)newDataSource).setTemporary(true);
|
||||
|
||||
DBPConnectionConfiguration cfg = newDataSource.getConnectionConfiguration();
|
||||
if (AuthModelDatabaseNative.ID.equals(config.getAuthModelId())) {
|
||||
cfg.setUserName(JSONUtils.getString(config.getCredentials(), "userName"));
|
||||
cfg.setUserPassword(JSONUtils.getString(config.getCredentials(), "userPassword"));
|
||||
} else {
|
||||
cfg.setUserName(config.getUserName());
|
||||
cfg.setUserPassword(config.getUserPassword());
|
||||
}
|
||||
if (!CommonUtils.isEmpty(config.getName())) {
|
||||
newDataSource.setName(config.getName());
|
||||
}
|
||||
if (!CommonUtils.isEmpty(config.getDescription())) {
|
||||
newDataSource.setDescription(config.getDescription());
|
||||
}
|
||||
try {
|
||||
newDataSource.connect(webSession.getProgressMonitor(), true, false);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error connecting to database", e);
|
||||
}
|
||||
sessionRegistry.addDataSource(newDataSource);
|
||||
|
||||
WebConnectionInfo connectionInfo = new WebConnectionInfo(webSession, newDataSource);
|
||||
webSession.addConnection(connectionInfo);
|
||||
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebConnectionInfo initConnection(
|
||||
@NotNull WebSession webSession,
|
||||
@@ -321,9 +275,11 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
|
||||
@Override
|
||||
public WebConnectionInfo updateConnection(@NotNull WebSession webSession, @NotNull WebConnectionConfig config) throws DBWebException {
|
||||
if (!CBApplication.getInstance().getAppConfiguration().isSupportsCustomConnections()) {
|
||||
throw new DBWebException("Connection edit is restricted by server configuration");
|
||||
}
|
||||
// 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");
|
||||
// }
|
||||
DBPDataSourceRegistry sessionRegistry = webSession.getSingletonProject().getDataSourceRegistry();
|
||||
|
||||
WebConnectionInfo connectionInfo = webSession.getWebConnectionInfo(config.getConnectionId());
|
||||
@@ -345,8 +301,9 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
|
||||
@Override
|
||||
public boolean deleteConnection(@NotNull WebSession webSession, @NotNull String connectionId) throws DBWebException {
|
||||
if (!CBApplication.getInstance().getAppConfiguration().isSupportsCustomConnections()) {
|
||||
throw new DBWebException("Connection delete is restricted by server configuration");
|
||||
WebConnectionInfo connectionInfo = webSession.getWebConnectionInfo(connectionId);
|
||||
if (connectionInfo.getDataSourceContainer().getProject() != webSession.getSingletonProject()) {
|
||||
throw new DBWebException("Global connection '" + connectionInfo.getName() + "' configuration cannot be deleted");
|
||||
}
|
||||
closeAndDeleteConnection(webSession, connectionId, true);
|
||||
return true;
|
||||
@@ -431,7 +388,7 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
} else {
|
||||
testDataSource = WebServiceUtils.createConnectionFromConfig(connectionConfig, sessionRegistry);
|
||||
}
|
||||
webSession.provideAuthParameters(testDataSource, testDataSource.getConnectionConfiguration());
|
||||
webSession.provideAuthParameters(webSession.getProgressMonitor(), testDataSource, testDataSource.getConnectionConfiguration());
|
||||
testDataSource.setSavePassword(true); // We need for test to avoid password callback
|
||||
try {
|
||||
ConnectionTestJob ct = new ConnectionTestJob(testDataSource, param -> {});
|
||||
|
||||
+28
-8
@@ -132,14 +132,7 @@ public class WebDatabaseObjectInfo {
|
||||
@Property
|
||||
public String[] getFeatures() {
|
||||
List<String> features = new ArrayList<>();
|
||||
if (object instanceof DBPScriptObject) features.add("script");
|
||||
if (object instanceof DBPScriptObjectExt) features.add("scriptExtended");
|
||||
if (object instanceof DBSDataContainer) features.add("dataContainer");
|
||||
if (object instanceof DBSDataManipulator) features.add("dataManipulator");
|
||||
if (object instanceof DBSEntity) features.add("entity");
|
||||
if (object instanceof DBSSchema) features.add("schema");
|
||||
if (object instanceof DBSCatalog) features.add("catalog");
|
||||
if (object instanceof DBSObjectContainer) features.add("objectContainer");
|
||||
getObjectFeatures(object, features);
|
||||
if (object instanceof DBPDataSourceContainer) {
|
||||
features.add("dataSource");
|
||||
DBPDataSourceContainer dbpDataSourceContainer = (DBPDataSourceContainer) this.object;
|
||||
@@ -149,10 +142,37 @@ public class WebDatabaseObjectInfo {
|
||||
if (dbpDataSourceContainer.isTemporary()) {
|
||||
features.add("dataSourceTemporary");
|
||||
}
|
||||
if (dbpDataSourceContainer.isConnected()) {
|
||||
DBPDataSource dataSource = dbpDataSourceContainer.getDataSource();
|
||||
if (dataSource != null) {
|
||||
getObjectFeatures(dataSource, features);
|
||||
}
|
||||
}
|
||||
}
|
||||
return features.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private static void getObjectFeatures(DBSObject object, List<String> features) {
|
||||
if (object instanceof DBPScriptObject) features.add("script");
|
||||
if (object instanceof DBPScriptObjectExt) features.add("scriptExtended");
|
||||
if (object instanceof DBSDataContainer) features.add("dataContainer");
|
||||
if (object instanceof DBSDataManipulator) features.add("dataManipulator");
|
||||
if (object instanceof DBSEntity) features.add("entity");
|
||||
if (object instanceof DBSSchema) features.add("schema");
|
||||
if (object instanceof DBSCatalog) features.add("catalog");
|
||||
if (object instanceof DBSObjectContainer) {
|
||||
features.add("objectContainer");
|
||||
try {
|
||||
Class<? extends DBSObject> childType = ((DBSObjectContainer) object).getPrimaryChildType(null);
|
||||
if (DBSEntity.class.isAssignableFrom(childType)) {
|
||||
features.add("entityContainer");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Property
|
||||
public String[] getEditors() {
|
||||
return null;
|
||||
|
||||
+9
@@ -118,6 +118,15 @@ public class WebNavigatorNodeInfo {
|
||||
if (node instanceof DBNContainer) {
|
||||
features.add("container");
|
||||
}
|
||||
boolean isShared = false;
|
||||
if (node instanceof DBNDatabaseNode) {
|
||||
isShared = !((DBNDatabaseNode) node).getDataSourceContainer().isManageable();
|
||||
} else if (node instanceof DBNLocalFolder) {
|
||||
//isShared = ((DBNLocalFolder) node).getDataSourceRegistry().is
|
||||
}
|
||||
if (isShared) {
|
||||
features.add("shared");
|
||||
}
|
||||
return features.toArray(new String[0]);
|
||||
}
|
||||
|
||||
|
||||
+26
-1
@@ -24,6 +24,7 @@ import io.cloudbeaver.model.session.WebSession;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -33,11 +34,25 @@ import java.util.Map;
|
||||
*/
|
||||
public interface DBWServiceSQL extends DBWService {
|
||||
|
||||
@WebAction
|
||||
WebSQLContextInfo[] listContexts(@NotNull WebSession session, @Nullable String connectionId, @Nullable String contextId) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
WebSQLDialectInfo getDialectInfo(@NotNull WebSQLProcessor processor) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
WebSQLCompletionProposal[] getCompletionProposals(@NotNull WebSQLContextInfo sqlContext, @NotNull String query, Integer position, Integer maxResults) throws DBWebException;
|
||||
WebSQLCompletionProposal[] getCompletionProposals(
|
||||
@NotNull WebSQLContextInfo sqlContext,
|
||||
@NotNull String query,
|
||||
Integer position,
|
||||
Integer maxResults,
|
||||
Boolean simpleMode) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
DBCLogicalOperator[] getSupportedOperations(
|
||||
@NotNull WebSQLContextInfo contextInfo,
|
||||
@NotNull String resultsId,
|
||||
int attributeIndex) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
WebSQLContextInfo createContext(@NotNull WebSQLProcessor processor, String defaultCatalog, String defaultSchema) throws DBWebException;
|
||||
@@ -82,4 +97,14 @@ public interface DBWServiceSQL extends DBWService {
|
||||
|
||||
@WebAction
|
||||
WebSQLExecuteInfo asyncGetQueryResults(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
WebAsyncTaskInfo asyncSqlExplainExecutionPlan(
|
||||
@NotNull WebSQLContextInfo contextInfo,
|
||||
@NotNull String sql,
|
||||
@NotNull Map<String, Object> options) throws DBException;
|
||||
|
||||
@WebAction
|
||||
WebSQLExecutionPlan asyncSqlExplainExecutionPlanResult(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException;
|
||||
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2021 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.sql;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.data.DBDValue;
|
||||
import org.jkiss.dbeaver.model.exec.DBCException;
|
||||
import org.jkiss.dbeaver.model.exec.DBCSession;
|
||||
import org.jkiss.dbeaver.model.struct.DBSTypedObject;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Web value serializer
|
||||
*/
|
||||
public interface DBWValueSerializer<VALUE_TYPE extends DBDValue> {
|
||||
|
||||
@NotNull
|
||||
VALUE_TYPE deserializeValue(@NotNull DBCSession session, DBSTypedObject attribute, @NotNull Map<String, Object> webValue) throws DBCException;
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2021 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;
|
||||
|
||||
/**
|
||||
* Web SQL constants.
|
||||
*/
|
||||
public class WebSQLConstants {
|
||||
|
||||
public static final int BINARY_PREVIEW_LENGTH = 255;
|
||||
public static final int BINARY_MAX_LENGTH = 1 * 1024 * 1024;
|
||||
|
||||
public static final String VALUE_TYPE_ATTR = "$type";
|
||||
|
||||
public static final String VALUE_TYPE_COLLECTION = "collection";
|
||||
public static final String VALUE_TYPE_MAP = "map";
|
||||
public static final String VALUE_TYPE_DOCUMENT = "document";
|
||||
public static final String VALUE_TYPE_CONTENT = "content";
|
||||
public static final String VALUE_TYPE_GEOMETRY = "geometry";
|
||||
|
||||
public static final String ATTR_TEXT = "text";
|
||||
public static final String ATTR_BINARY = "binary";
|
||||
public static final String ATTR_DATA = "data";
|
||||
|
||||
}
|
||||
+4
@@ -150,6 +150,10 @@ public class WebSQLContextInfo {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getConnectionId() {
|
||||
return processor.getConnection().getId();
|
||||
}
|
||||
|
||||
public String getDefaultCatalog() {
|
||||
return defaultCatalog == null ? null : defaultCatalog.getName();
|
||||
}
|
||||
|
||||
+24
-3
@@ -16,9 +16,14 @@
|
||||
*/
|
||||
package io.cloudbeaver.service.sql;
|
||||
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.data.DBDAttributeConstraint;
|
||||
import org.jkiss.dbeaver.model.data.DBDDataFilter;
|
||||
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
|
||||
import org.jkiss.dbeaver.model.struct.DBSEntity;
|
||||
import org.jkiss.dbeaver.model.struct.DBSEntityAttribute;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -38,7 +43,7 @@ public class WebSQLDataFilter {
|
||||
private int offset;
|
||||
private int limit ;
|
||||
private String where;
|
||||
private List<WebSQLDataFilterConstraint> constraints = new ArrayList<>();
|
||||
private final List<WebSQLDataFilterConstraint> constraints = new ArrayList<>();
|
||||
|
||||
public WebSQLDataFilter() {
|
||||
this.offset = 0;
|
||||
@@ -85,13 +90,29 @@ public class WebSQLDataFilter {
|
||||
return constraints;
|
||||
}
|
||||
|
||||
public DBDDataFilter makeDataFilter() {
|
||||
public String getWhere() {
|
||||
return where;
|
||||
}
|
||||
|
||||
public DBDDataFilter makeDataFilter(DBRProgressMonitor monitor, DBSDataContainer dataContainer) throws DBException {
|
||||
DBDDataFilter dataFilter = new DBDDataFilter();
|
||||
dataFilter.setWhere(where);
|
||||
if (!CommonUtils.isEmpty(constraints)) {
|
||||
List<DBDAttributeConstraint> dbdConstraints = new ArrayList<>();
|
||||
for (WebSQLDataFilterConstraint webConstr : constraints) {
|
||||
DBDAttributeConstraint dbConstraint = new DBDAttributeConstraint(webConstr.getAttribute(), -1);
|
||||
DBDAttributeConstraint dbConstraint;
|
||||
if (dataContainer instanceof DBSEntity) {
|
||||
DBSEntityAttribute attribute = ((DBSEntity) dataContainer).getAttribute(monitor, webConstr.getAttribute());
|
||||
if (attribute == null) {
|
||||
dbConstraint = new DBDAttributeConstraint(webConstr.getAttribute(), -1);
|
||||
} else {
|
||||
dbConstraint = new DBDAttributeConstraint(attribute, -1);
|
||||
}
|
||||
} else {
|
||||
dbConstraint = new DBDAttributeConstraint(webConstr.getAttribute(), -1);
|
||||
}
|
||||
dbConstraint.setPlainNameReference(true);
|
||||
|
||||
if (webConstr.getOrderPosition() != null) {
|
||||
dbConstraint.setOrderPosition(webConstr.getOrderPosition());
|
||||
}
|
||||
|
||||
+5
@@ -18,8 +18,10 @@ package io.cloudbeaver.service.sql;
|
||||
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBPDataSource;
|
||||
import org.jkiss.dbeaver.model.exec.plan.DBCQueryPlanner;
|
||||
import org.jkiss.dbeaver.model.sql.SQLDialect;
|
||||
import org.jkiss.dbeaver.model.sql.SQLUtils;
|
||||
import org.jkiss.dbeaver.utils.GeneralUtils;
|
||||
import org.jkiss.utils.Pair;
|
||||
|
||||
/**
|
||||
@@ -81,4 +83,7 @@ public class WebSQLDialectInfo {
|
||||
return SQLUtils.getDefaultScriptDelimiter(dialect);
|
||||
}
|
||||
|
||||
public boolean isSupportsExplainExecutionPlan() {
|
||||
return GeneralUtils.adapt(dataSource, DBCQueryPlanner.class) != null;
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -28,6 +28,7 @@ public class WebSQLExecuteInfo {
|
||||
|
||||
private String statusMessage;
|
||||
private long duration;
|
||||
private String filterText;
|
||||
private WebSQLQueryResults[] results;
|
||||
|
||||
@Property
|
||||
@@ -48,6 +49,15 @@ public class WebSQLExecuteInfo {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getFilterText() {
|
||||
return filterText;
|
||||
}
|
||||
|
||||
public void setFilterText(String filterText) {
|
||||
this.filterText = filterText;
|
||||
}
|
||||
|
||||
@Property
|
||||
public WebSQLQueryResults[] getResults() {
|
||||
return results;
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2021 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.sql;
|
||||
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.exec.plan.DBCPlan;
|
||||
import org.jkiss.dbeaver.model.exec.plan.DBCPlanNode;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* WebSQLExecutionPlan.
|
||||
*/
|
||||
public class WebSQLExecutionPlan {
|
||||
|
||||
private static final Log log = Log.getLog(WebSQLExecutionPlan.class);
|
||||
|
||||
private final WebSession webSession;
|
||||
private final DBCPlan plan;
|
||||
|
||||
public WebSQLExecutionPlan(WebSession webSession, DBCPlan plan) {
|
||||
this.webSession = webSession;
|
||||
this.plan = plan;
|
||||
}
|
||||
|
||||
public String getQuery() {
|
||||
return plan.getQueryString();
|
||||
}
|
||||
|
||||
public WebSQLExecutionPlanNode[] getNodes() {
|
||||
Map<String, Object> options = new LinkedHashMap<>();
|
||||
Map<DBCPlanNode, String> idMap = new IdentityHashMap<>();
|
||||
|
||||
List<WebSQLExecutionPlanNode> result = new ArrayList<>();
|
||||
|
||||
for (DBCPlanNode node : plan.getPlanNodes(options)) {
|
||||
addNodeWithNested(result, null, node, idMap);
|
||||
}
|
||||
|
||||
return result.toArray(new WebSQLExecutionPlanNode[0]);
|
||||
}
|
||||
|
||||
private void addNodeWithNested(List<WebSQLExecutionPlanNode> result, DBCPlanNode parentNode, DBCPlanNode node, Map<DBCPlanNode, String> idMap) {
|
||||
String parentId = idMap.get(parentNode);
|
||||
String nodeId = String.valueOf(idMap.size());
|
||||
idMap.put(node, nodeId);
|
||||
result.add(new WebSQLExecutionPlanNode(webSession, node, nodeId, parentId));
|
||||
Collection<? extends DBCPlanNode> nested = node.getNested();
|
||||
if (nested != null) {
|
||||
for (DBCPlanNode nn : nested) {
|
||||
addNodeWithNested(result, node, nn, idMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2021 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.sql;
|
||||
|
||||
import io.cloudbeaver.model.WebPropertyInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.exec.plan.DBCPlanNode;
|
||||
import org.jkiss.dbeaver.runtime.properties.ObjectPropertyDescriptor;
|
||||
import org.jkiss.dbeaver.runtime.properties.PropertyCollector;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* WebSQLExecutionPlanNode.
|
||||
*/
|
||||
public class WebSQLExecutionPlanNode {
|
||||
|
||||
private static final Log log = Log.getLog(WebSQLExecutionPlanNode.class);
|
||||
|
||||
private final WebSession webSession;
|
||||
private final DBCPlanNode node;
|
||||
private final String id;
|
||||
private final String parentId;
|
||||
|
||||
public WebSQLExecutionPlanNode(WebSession webSession, DBCPlanNode node, String id, String parentId) {
|
||||
this.webSession = webSession;
|
||||
this.node = node;
|
||||
this.id = id;
|
||||
this.parentId = parentId;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getParentId() {
|
||||
return parentId;
|
||||
}
|
||||
|
||||
public String getKind() {
|
||||
return node.getNodeKind().name();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return node.getNodeName();
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return node.getNodeType();
|
||||
}
|
||||
|
||||
public String getCondition() {
|
||||
return node.getNodeCondition();
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return node.getNodeDescription();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public WebPropertyInfo[] getProperties() {
|
||||
PropertyCollector propertyCollector = new PropertyCollector(node, false);
|
||||
propertyCollector.collectProperties();
|
||||
return Arrays.stream(propertyCollector.getProperties())
|
||||
.filter(p -> !(p instanceof ObjectPropertyDescriptor && ((ObjectPropertyDescriptor) p).isHidden()))
|
||||
.map(p -> new WebPropertyInfo(webSession, p, propertyCollector)).toArray(WebPropertyInfo[]::new);
|
||||
}
|
||||
|
||||
}
|
||||
+113
-29
@@ -25,20 +25,23 @@ import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBPDataKind;
|
||||
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.DBDDocument;
|
||||
import org.jkiss.dbeaver.model.data.DBDRowIdentifier;
|
||||
import org.jkiss.dbeaver.model.data.*;
|
||||
import org.jkiss.dbeaver.model.exec.*;
|
||||
import org.jkiss.dbeaver.model.exec.plan.DBCPlan;
|
||||
import org.jkiss.dbeaver.model.exec.plan.DBCQueryPlanner;
|
||||
import org.jkiss.dbeaver.model.exec.plan.DBCQueryPlannerConfiguration;
|
||||
import org.jkiss.dbeaver.model.impl.AbstractExecutionSource;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNDatabaseItem;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNNode;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.sql.SQLQuery;
|
||||
import org.jkiss.dbeaver.model.sql.SQLSyntaxManager;
|
||||
import org.jkiss.dbeaver.model.sql.SQLUtils;
|
||||
import org.jkiss.dbeaver.model.sql.parser.SQLRuleManager;
|
||||
import org.jkiss.dbeaver.model.struct.*;
|
||||
import org.jkiss.dbeaver.utils.GeneralUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -96,7 +99,7 @@ public class WebSQLProcessor {
|
||||
return ruleManager;
|
||||
}
|
||||
|
||||
DBCExecutionContext getExecutionContext() {
|
||||
public DBCExecutionContext getExecutionContext() {
|
||||
return DBUtils.getDefaultContext(connection.getDataSource(), false);
|
||||
}
|
||||
|
||||
@@ -153,22 +156,22 @@ public class WebSQLProcessor {
|
||||
|
||||
DBCExecutionContext context = getExecutionContext(dataContainer);
|
||||
|
||||
{
|
||||
DBDDataFilter dataFilter = filter.makeDataFilter();
|
||||
if (dataFilter.hasFilters()) {
|
||||
sql = context.getDataSource().getSQLDialect().addFiltersToQuery(
|
||||
monitor,
|
||||
context.getDataSource(),
|
||||
sql,
|
||||
dataFilter);
|
||||
}
|
||||
}
|
||||
|
||||
final WebSQLDataFilter dataFilter = filter;
|
||||
final String sqlQueryText = sql;
|
||||
SQLQuery sqlQuery = new SQLQuery(context.getDataSource(), sqlQueryText);
|
||||
|
||||
try {
|
||||
{
|
||||
DBDDataFilter dataFilter = filter.makeDataFilter(monitor, dataContainer);
|
||||
if (dataFilter.hasFilters()) {
|
||||
sql = context.getDataSource().getSQLDialect().addFiltersToQuery(
|
||||
monitor,
|
||||
context.getDataSource(),
|
||||
sql,
|
||||
dataFilter);
|
||||
}
|
||||
}
|
||||
|
||||
final WebSQLDataFilter dataFilter = filter;
|
||||
final String sqlQueryText = sql;
|
||||
SQLQuery sqlQuery = new SQLQuery(context.getDataSource(), sqlQueryText);
|
||||
|
||||
DBExecUtils.tryExecuteRecover(monitor, connection.getDataSource(), param -> {
|
||||
try (DBCSession session = context.openSession(monitor, DBCExecutionPurpose.USER, "Execute SQL")) {
|
||||
AbstractExecutionSource source = new AbstractExecutionSource(
|
||||
@@ -215,7 +218,7 @@ public class WebSQLProcessor {
|
||||
WebSQLExecuteInfo executeInfo = new WebSQLExecuteInfo();
|
||||
|
||||
DBCExecutionContext executionContext = getExecutionContext(dataContainer);
|
||||
DBDDataFilter dataFilter = filter.makeDataFilter();
|
||||
DBDDataFilter dataFilter = filter.makeDataFilter(monitor, dataContainer);
|
||||
DBExecUtils.tryExecuteRecover(monitor, connection.getDataSource(), param -> {
|
||||
try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Read data from container")) {
|
||||
try (WebSQLQueryDataReceiver dataReceiver = new WebSQLQueryDataReceiver(contextInfo, dataContainer, dataFormat)) {
|
||||
@@ -231,10 +234,14 @@ public class WebSQLProcessor {
|
||||
executeInfo.setDuration(statistics.getTotalTime());
|
||||
|
||||
WebSQLQueryResults results = new WebSQLQueryResults(webSession, dataFormat);
|
||||
results.setResultSet(dataReceiver.getResultSet());
|
||||
WebSQLQueryResultSet resultSet = dataReceiver.getResultSet();
|
||||
results.setResultSet(resultSet);
|
||||
executeInfo.setResults(new WebSQLQueryResults[]{results});
|
||||
setResultFilterText(dataContainer, session.getDataSource(), executeInfo, filter);
|
||||
|
||||
executeInfo.setStatusMessage(dataReceiver.getResultSet().getRows().length + " row(s) fetched");
|
||||
if (resultSet != null && resultSet.getRows() != null) {
|
||||
executeInfo.setStatusMessage(resultSet.getRows().length + " row(s) fetched");
|
||||
}
|
||||
} catch (DBException e) {
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
@@ -379,7 +386,12 @@ public class WebSQLProcessor {
|
||||
for (int i = 0; i < updateAttributes.length; i++) {
|
||||
DBDAttributeBinding updateAttribute = updateAttributes[i];
|
||||
Object cellRawValue = updateValues.get(String.valueOf(updateAttribute.getOrdinalPosition()));
|
||||
Object realCellValue = updateAttribute.getValueHandler().getValueFromObject(session, updateAttribute, cellRawValue, false, true);
|
||||
cellRawValue = WebSQLUtils.makePlainCellValue(session, updateAttribute, cellRawValue);
|
||||
Object realCellValue = cellRawValue;
|
||||
// In some cases we already have final value here
|
||||
if (!(realCellValue instanceof DBDValue)) {
|
||||
realCellValue = updateAttribute.getValueHandler().getValueFromObject(session, updateAttribute, cellRawValue, false, true);
|
||||
}
|
||||
rowValues[i] = realCellValue;
|
||||
finalRow[updateAttribute.getOrdinalPosition()] = WebSQLUtils.makeWebCellValue(webSession, null, realCellValue, dataFormat);
|
||||
}
|
||||
@@ -387,19 +399,30 @@ public class WebSQLProcessor {
|
||||
DBDAttributeBinding keyAttribute = keyAttributes[i];
|
||||
if (keyAttributes.length == 1 && keyAttribute.getDataKind() == DBPDataKind.DOCUMENT && dataContainer instanceof DBSDocumentLocator) {
|
||||
// Document reference
|
||||
DBDDocument document = null;
|
||||
Map<String, Object> keyMap = new LinkedHashMap<>();
|
||||
DBDAttributeBinding[] attributes = resultsInfo.getAttributes();
|
||||
for (int j = 0; j < attributes.length; j++) {
|
||||
DBDAttributeBinding attr = attributes[j];
|
||||
keyMap.put(attr.getName(), row.getData().get(j));
|
||||
Object plainValue = WebSQLUtils.makePlainCellValue(session, attr, row.getData().get(j));
|
||||
if (plainValue instanceof DBDDocument) {
|
||||
// FIXME: Hack for DynamoDB. We pass entire document as a key
|
||||
// FIXME: Let's just return it back for now
|
||||
document = (DBDDocument) plainValue;
|
||||
break;
|
||||
}
|
||||
keyMap.put(attr.getName(), plainValue);
|
||||
}
|
||||
DBDDocument document = ((DBSDocumentLocator) dataContainer).findDocument(session.getProgressMonitor(), keyMap);
|
||||
if (document == null) {
|
||||
throw new DBCException("Error finding document by key " + keyMap);
|
||||
document = ((DBSDocumentLocator) dataContainer).findDocument(session.getProgressMonitor(), keyMap);
|
||||
if (document == null) {
|
||||
throw new DBCException("Error finding document by key " + keyMap);
|
||||
}
|
||||
}
|
||||
rowValues[updateAttributes.length + i] = document;
|
||||
} else {
|
||||
Object cellValueRaw = row.getData().get(keyAttribute.getOrdinalPosition());
|
||||
cellValueRaw = WebSQLUtils.makePlainCellValue(session, keyAttribute, cellValueRaw);
|
||||
rowValues[updateAttributes.length + i] = keyAttribute.getValueHandler().getValueFromObject(session, keyAttribute, cellValueRaw, false, true);
|
||||
}
|
||||
}
|
||||
@@ -440,6 +463,51 @@ public class WebSQLProcessor {
|
||||
return result;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// ExecutionPlan
|
||||
|
||||
@NotNull
|
||||
public WebSQLExecutionPlan explainExecutionPlan(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull WebSQLContextInfo contextInfo,
|
||||
@NotNull String sql,
|
||||
@NotNull Map<String, Object> configuration) throws DBWebException {
|
||||
|
||||
DBCQueryPlanner planner;
|
||||
DBCExecutionContext executionContext = getExecutionContext();
|
||||
if (executionContext != null) {
|
||||
DBPDataSource dataSource = executionContext.getDataSource();
|
||||
planner = GeneralUtils.adapt(dataSource, DBCQueryPlanner.class);
|
||||
} else {
|
||||
throw new DBWebException("Not connected to data source");
|
||||
}
|
||||
|
||||
if (planner == null) {
|
||||
throw new DBWebException("Datasource '" + executionContext.getDataSource() + "' doesn't support execution plan");
|
||||
}
|
||||
|
||||
DBCPlan[] dbcPlan = new DBCPlan[1];
|
||||
|
||||
try {
|
||||
DBExecUtils.tryExecuteRecover(monitor, connection.getDataSource(), param -> {
|
||||
try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Execute SQL")) {
|
||||
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) {
|
||||
throw new DBWebException("Error explaining execution plan", e);
|
||||
}
|
||||
|
||||
return new WebSQLExecutionPlan(webSession, dbcPlan[0]);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// Misc
|
||||
|
||||
private void checkRowIdentifier(WebSQLResultsInfo resultsInfo, DBDRowIdentifier rowIdentifier) throws DBWebException {
|
||||
if (rowIdentifier == null || !rowIdentifier.isValidIdentifier()) {
|
||||
throw new DBWebException("Can't detect row identifier for data container '" + resultsInfo.getDataContainer().getName() + "'. It must have at least one unique key.");
|
||||
@@ -470,7 +538,8 @@ public class WebSQLProcessor {
|
||||
|
||||
|
||||
private void fillQueryResults(
|
||||
@NotNull WebSQLContextInfo contextInfo, @NotNull DBSDataContainer dataContainer,
|
||||
@NotNull WebSQLContextInfo contextInfo,
|
||||
@NotNull DBSDataContainer dataContainer,
|
||||
@NotNull DBCStatement dbStat,
|
||||
boolean hasResultSet,
|
||||
@NotNull WebSQLExecuteInfo executeInfo,
|
||||
@@ -490,7 +559,7 @@ public class WebSQLProcessor {
|
||||
results.setResultSet(dataReceiver.getResultSet());
|
||||
}
|
||||
} else {
|
||||
int updateRowCount = dbStat.getUpdateRowCount();
|
||||
long updateRowCount = dbStat.getUpdateRowCount();
|
||||
if (updateRowCount >= 0) {
|
||||
results.setUpdateRowCount(updateRowCount);
|
||||
} else {
|
||||
@@ -502,6 +571,21 @@ public class WebSQLProcessor {
|
||||
}
|
||||
|
||||
executeInfo.setResults(resultList.toArray(new WebSQLQueryResults[0]));
|
||||
|
||||
setResultFilterText(dataContainer, dbStat.getSession().getDataSource(), executeInfo, filter);
|
||||
}
|
||||
|
||||
private void setResultFilterText(@NotNull DBSDataContainer dataContainer, @NotNull DBPDataSource dataSource, @NotNull WebSQLExecuteInfo executeInfo, @NotNull WebSQLDataFilter filter) throws DBException {
|
||||
if (!filter.getConstraints().isEmpty() || !CommonUtils.isEmpty(filter.getWhere())) {
|
||||
StringBuilder where = new StringBuilder();
|
||||
SQLUtils.appendConditionString(
|
||||
filter.makeDataFilter(webSession.getProgressMonitor(), dataContainer),
|
||||
dataSource,
|
||||
null,
|
||||
where,
|
||||
true);
|
||||
executeInfo.setFilterText(where.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void readResultSet(@NotNull DBCSession session, @NotNull DBCResultSet dbResult, @NotNull WebSQLDataFilter filter, @NotNull WebSQLQueryDataReceiver dataReceiver) throws DBCException {
|
||||
|
||||
+3
-3
@@ -40,9 +40,9 @@ public class WebSQLQueryDataContainer implements DBSDataContainer, DBPContextPro
|
||||
|
||||
private static final Log log = Log.getLog(WebSQLQueryDataContainer.class);
|
||||
|
||||
private DBPDataSource dataSource;
|
||||
private String query;
|
||||
private DBSDataContainer queryDataContainer;
|
||||
private final DBPDataSource dataSource;
|
||||
private final String query;
|
||||
private final DBSDataContainer queryDataContainer;
|
||||
|
||||
public WebSQLQueryDataContainer(DBPDataSource dataSource, String query) {
|
||||
this.dataSource = dataSource;
|
||||
|
||||
+11
@@ -21,6 +21,7 @@ import org.jkiss.dbeaver.model.DBPDataKind;
|
||||
import org.jkiss.dbeaver.model.DBPEvaluationContext;
|
||||
import org.jkiss.dbeaver.model.DBValueFormatting;
|
||||
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
|
||||
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
|
||||
import org.jkiss.dbeaver.model.exec.DBExecUtils;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
|
||||
@@ -99,6 +100,11 @@ public class WebSQLQueryResultColumn {
|
||||
return attrMeta.getPrecision();
|
||||
}
|
||||
|
||||
@Property
|
||||
public boolean isRequired() {
|
||||
return attrMeta.isRequired();
|
||||
}
|
||||
|
||||
@Property
|
||||
public boolean isReadOnly() {
|
||||
return DBExecUtils.isAttributeReadOnly(attrMeta);
|
||||
@@ -109,6 +115,11 @@ public class WebSQLQueryResultColumn {
|
||||
return DBExecUtils.getAttributeReadOnlyStatus(attrMeta);
|
||||
}
|
||||
|
||||
@Property
|
||||
public DBCLogicalOperator[] getSupportedOperations() {
|
||||
return attrMeta.getValueHandler().getSupportedOperators(attrMeta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return attrMeta.getName();
|
||||
|
||||
+59
-16
@@ -17,11 +17,13 @@
|
||||
package io.cloudbeaver.service.sql;
|
||||
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.registry.WebServiceRegistry;
|
||||
import io.cloudbeaver.server.CBConstants;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.data.*;
|
||||
import org.jkiss.dbeaver.model.exec.DBCException;
|
||||
import org.jkiss.dbeaver.model.exec.DBCSession;
|
||||
import org.jkiss.dbeaver.model.gis.DBGeometry;
|
||||
import org.jkiss.dbeaver.model.gis.GisConstants;
|
||||
import org.jkiss.dbeaver.model.gis.GisTransformUtils;
|
||||
@@ -29,9 +31,11 @@ import org.jkiss.dbeaver.model.struct.DBSAttributeBase;
|
||||
import org.jkiss.dbeaver.model.struct.DBSTypedObject;
|
||||
import org.jkiss.dbeaver.utils.ContentUtils;
|
||||
import org.jkiss.dbeaver.utils.GeneralUtils;
|
||||
import org.jkiss.utils.Base64;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
@@ -42,11 +46,18 @@ public class WebSQLUtils {
|
||||
|
||||
private static final Log log = Log.getLog(WebSQLUtils.class);
|
||||
|
||||
public static final int BINARY_PREVIEW_LENGTH = 255;
|
||||
|
||||
public static Object makeWebCellValue(WebSession session, DBSTypedObject type, Object cellValue, WebDataFormat dataFormat) throws DBCException {
|
||||
if (cellValue instanceof Date) {
|
||||
return CBConstants.ISO_DATE_FORMAT.format(cellValue);
|
||||
} else if (cellValue instanceof Number) {
|
||||
if (cellValue instanceof Double) {
|
||||
return CommonUtils.niceFormatDouble((Double) cellValue);
|
||||
} else if (cellValue instanceof Float) {
|
||||
return CommonUtils.niceFormatDouble((Float) cellValue);
|
||||
} else if (cellValue instanceof BigDecimal) {
|
||||
return ((BigDecimal) cellValue).toPlainString();
|
||||
}
|
||||
return cellValue.toString();
|
||||
}
|
||||
if (cellValue instanceof DBDValue) {
|
||||
DBDValue dbValue = (DBDValue) cellValue;
|
||||
@@ -75,7 +86,7 @@ public class WebSQLUtils {
|
||||
items[i] = makeWebCellValue(session, collection.getComponentType(), collection.getItem(i), dataFormat);
|
||||
}
|
||||
|
||||
Map<String, Object> map = createMapOfType("collection");
|
||||
Map<String, Object> map = createMapOfType(WebSQLConstants.VALUE_TYPE_COLLECTION);
|
||||
map.put("value", items);
|
||||
return map;
|
||||
} else if (value instanceof DBDComposite) {
|
||||
@@ -85,7 +96,7 @@ public class WebSQLUtils {
|
||||
struct.put(attr.getName(), makeWebCellValue(session, attr, composite.getAttributeValue(attr), dataFormat));
|
||||
}
|
||||
|
||||
Map<String, Object> map = createMapOfType("map");
|
||||
Map<String, Object> map = createMapOfType(WebSQLConstants.VALUE_TYPE_MAP);
|
||||
map.put("value", struct);
|
||||
return map;
|
||||
}
|
||||
@@ -95,7 +106,7 @@ public class WebSQLUtils {
|
||||
@NotNull
|
||||
private static Map<String, Object> createMapOfType(String type) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("$type", type);
|
||||
map.put(WebSQLConstants.VALUE_TYPE_ATTR, type);
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -109,30 +120,37 @@ public class WebSQLUtils {
|
||||
throw new DBCException("Error serializing document", e);
|
||||
}
|
||||
|
||||
Map<String, Object> map = createMapOfType("document");
|
||||
Map<String, Object> map = createMapOfType(WebSQLConstants.VALUE_TYPE_DOCUMENT);
|
||||
map.put("id", CommonUtils.toString(document.getDocumentId()));
|
||||
map.put("contentType", document.getDocumentContentType());
|
||||
map.put("properties", Collections.emptyMap());
|
||||
map.put("data", documentData);
|
||||
map.put(WebSQLConstants.ATTR_DATA, documentData);
|
||||
return map;
|
||||
}
|
||||
|
||||
private static Object serializeContentValue(WebSession session, DBDContent value) throws DBCException {
|
||||
|
||||
Map<String, Object> map = createMapOfType("content");
|
||||
Map<String, Object> map = createMapOfType(WebSQLConstants.VALUE_TYPE_CONTENT);
|
||||
if (ContentUtils.isTextContent(value)) {
|
||||
String stringValue = ContentUtils.getContentStringValue(session.getProgressMonitor(), value);
|
||||
map.put("text", stringValue);
|
||||
map.put(WebSQLConstants.ATTR_TEXT, stringValue);
|
||||
} else {
|
||||
map.put("binary", true);
|
||||
map.put(WebSQLConstants.ATTR_BINARY, true);
|
||||
byte[] binaryValue = ContentUtils.getContentBinaryValue(session.getProgressMonitor(), value);
|
||||
if (binaryValue != null) {
|
||||
if (binaryValue.length > BINARY_PREVIEW_LENGTH) {
|
||||
binaryValue = Arrays.copyOf(binaryValue, BINARY_PREVIEW_LENGTH);
|
||||
byte[] previewValue = binaryValue;
|
||||
if (previewValue.length > WebSQLConstants.BINARY_PREVIEW_LENGTH) {
|
||||
previewValue = Arrays.copyOf(previewValue, WebSQLConstants.BINARY_PREVIEW_LENGTH);
|
||||
}
|
||||
map.put("text", GeneralUtils.convertToString(binaryValue, 0, binaryValue.length));
|
||||
map.put(WebSQLConstants.ATTR_TEXT, GeneralUtils.convertToString(previewValue, 0, previewValue.length));
|
||||
|
||||
byte[] inlineValue = binaryValue;
|
||||
if (inlineValue.length > WebSQLConstants.BINARY_MAX_LENGTH) {
|
||||
inlineValue = Arrays.copyOf(inlineValue, WebSQLConstants.BINARY_PREVIEW_LENGTH);
|
||||
}
|
||||
map.put(WebSQLConstants.ATTR_BINARY, Base64.encode(inlineValue));
|
||||
} else {
|
||||
map.put("text", null);
|
||||
map.put(WebSQLConstants.ATTR_TEXT, null);
|
||||
}
|
||||
}
|
||||
map.put("contentType", value.getContentType());
|
||||
@@ -141,9 +159,9 @@ public class WebSQLUtils {
|
||||
}
|
||||
|
||||
private static Object serializeGeometryValue(DBGeometry value) {
|
||||
Map<String, Object> map = createMapOfType("geometry");
|
||||
Map<String, Object> map = createMapOfType(WebSQLConstants.VALUE_TYPE_GEOMETRY);
|
||||
map.put("srid", value.getSRID());
|
||||
map.put("text", value.toString());
|
||||
map.put(WebSQLConstants.ATTR_TEXT, value.toString());
|
||||
map.put("properties", value.getProperties());
|
||||
|
||||
DBGeometry xValue = GisTransformUtils.transformToSRID(value, GisConstants.SRID_4326);
|
||||
@@ -153,4 +171,29 @@ public class WebSQLUtils {
|
||||
return map;
|
||||
}
|
||||
|
||||
public static Object makePlainCellValue(DBCSession session, DBSTypedObject attribute, Object value) throws DBCException {
|
||||
if (value instanceof Map) {
|
||||
Map<String, Object> map = (Map<String, Object>) value;
|
||||
Object typeAttr = map.get(WebSQLConstants.VALUE_TYPE_ATTR);
|
||||
if (typeAttr instanceof String) {
|
||||
switch ((String)typeAttr) {
|
||||
case WebSQLConstants.VALUE_TYPE_CONTENT: {
|
||||
if (map.get(WebSQLConstants.ATTR_BINARY) != null) {
|
||||
throw new DBCException("Binary content edit is not supported yet");
|
||||
}
|
||||
value = map.get(WebSQLConstants.ATTR_TEXT);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
DBWValueSerializer<?> valueSerializer = WebServiceRegistry.getInstance().createValueSerializer((String) typeAttr);
|
||||
if (valueSerializer == null) {
|
||||
throw new DBCException("Value type '" + typeAttr + "' edit is not supported yet");
|
||||
}
|
||||
value = valueSerializer.deserializeValue(session, attribute, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
+38
-2
@@ -28,6 +28,7 @@ import io.cloudbeaver.service.sql.impl.WebServiceSQL;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
@@ -51,14 +52,27 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL> i
|
||||
.dataFetcher("sqlDialectInfo", env ->
|
||||
getService(env).getDialectInfo(getSQLProcessor(env))
|
||||
)
|
||||
.dataFetcher("sqlListContexts", env ->
|
||||
getService(env).listContexts(getWebSession(env),
|
||||
env.getArgument("connectionId"),
|
||||
env.getArgument("contextId"))
|
||||
)
|
||||
.dataFetcher("sqlCompletionProposals", env ->
|
||||
getService(env).getCompletionProposals(
|
||||
getSQLContext(env),
|
||||
env.getArgument("query"),
|
||||
env.getArgument("position"),
|
||||
env.getArgument("maxResults")
|
||||
env.getArgument("maxResults"),
|
||||
env.getArgument("simpleMode")
|
||||
)
|
||||
);
|
||||
)
|
||||
.dataFetcher("sqlSupportedOperations", env ->
|
||||
getService(env).getSupportedOperations(
|
||||
getSQLContext(env),
|
||||
env.getArgument("resultsId"),
|
||||
env.getArgument("attributeIndex"))
|
||||
)
|
||||
;
|
||||
|
||||
model.getMutationType()
|
||||
.dataFetcher("sqlContextCreate", env -> getService(env).createContext(
|
||||
@@ -111,6 +125,16 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL> i
|
||||
.dataFetcher("asyncSqlExecuteResults", env ->
|
||||
getService(env).asyncGetQueryResults(
|
||||
getWebSession(env), env.getArgument("taskId")
|
||||
))
|
||||
.dataFetcher("asyncSqlExplainExecutionPlan", env ->
|
||||
getService(env).asyncSqlExplainExecutionPlan(
|
||||
getSQLContext(env),
|
||||
env.getArgument("query"),
|
||||
env.getArgument("configuration")
|
||||
))
|
||||
.dataFetcher("asyncSqlExplainExecutionPlanResult", env ->
|
||||
getService(env).asyncSqlExplainExecutionPlanResult(
|
||||
getWebSession(env), env.getArgument("taskId")
|
||||
));
|
||||
}
|
||||
|
||||
@@ -136,6 +160,11 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL> i
|
||||
return getSQLConfiguration(connectionInfo.getSession()).getSQLProcessor(connectionInfo);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static WebSQLProcessor getSQLProcessor(WebConnectionInfo connectionInfo, boolean connect) throws DBWebException {
|
||||
return getSQLConfiguration(connectionInfo.getSession()).getSQLProcessor(connectionInfo, connect);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static WebSQLContextInfo getSQLContext(DataFetchingEnvironment env) throws DBWebException {
|
||||
WebSQLProcessor processor = getSQLProcessor(env);
|
||||
@@ -163,7 +192,14 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL> i
|
||||
private final Map<WebConnectionInfo, WebSQLProcessor> processors = new HashMap<>();
|
||||
|
||||
WebSQLProcessor getSQLProcessor(WebConnectionInfo connectionInfo) throws DBWebException {
|
||||
return WebServiceBindingSQL.getSQLProcessor(connectionInfo, true);
|
||||
}
|
||||
|
||||
WebSQLProcessor getSQLProcessor(WebConnectionInfo connectionInfo, boolean connect) throws DBWebException {
|
||||
if (connectionInfo.getDataSource() == null) {
|
||||
if (!connect) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
connectionInfo.getDataSourceContainer().connect(connectionInfo.getSession().getProgressMonitor(), true, false);
|
||||
} catch (DBException e) {
|
||||
|
||||
+102
-4
@@ -20,15 +20,19 @@ package io.cloudbeaver.service.sql.impl;
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.WebAction;
|
||||
import io.cloudbeaver.model.WebAsyncTaskInfo;
|
||||
import io.cloudbeaver.model.WebConnectionInfo;
|
||||
import io.cloudbeaver.model.session.WebAsyncTaskProcessor;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.service.WebServiceBindingBase;
|
||||
import io.cloudbeaver.service.sql.*;
|
||||
import org.eclipse.jface.text.Document;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.DBPDataSource;
|
||||
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
|
||||
import org.jkiss.dbeaver.model.exec.DBCException;
|
||||
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.sql.SQLDialect;
|
||||
import org.jkiss.dbeaver.model.sql.SQLQuery;
|
||||
@@ -37,9 +41,14 @@ import org.jkiss.dbeaver.model.sql.SQLUtils;
|
||||
import org.jkiss.dbeaver.model.sql.completion.SQLCompletionAnalyzer;
|
||||
import org.jkiss.dbeaver.model.sql.completion.SQLCompletionProposalBase;
|
||||
import org.jkiss.dbeaver.model.sql.completion.SQLCompletionRequest;
|
||||
import org.jkiss.dbeaver.model.sql.parser.SQLParserContext;
|
||||
import org.jkiss.dbeaver.model.sql.parser.SQLScriptParser;
|
||||
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -48,6 +57,30 @@ import java.util.Map;
|
||||
*/
|
||||
public class WebServiceSQL implements DBWServiceSQL {
|
||||
|
||||
@Override
|
||||
public WebSQLContextInfo[] listContexts(@NotNull WebSession session, @Nullable String connectionId, @Nullable String contextId) throws DBWebException {
|
||||
List<WebConnectionInfo> conToRead = new ArrayList<>();
|
||||
if (connectionId != null) {
|
||||
WebConnectionInfo webConnection = WebServiceBindingBase.getWebConnection(session, connectionId);
|
||||
conToRead.add(webConnection);
|
||||
} else {
|
||||
conToRead.addAll(session.getConnections());
|
||||
}
|
||||
|
||||
List<WebSQLContextInfo> contexts = new ArrayList<>();
|
||||
for (WebConnectionInfo con : conToRead) {
|
||||
WebSQLProcessor sqlProcessor = WebServiceBindingSQL.getSQLProcessor(con, false);
|
||||
if (sqlProcessor != null) {
|
||||
WebSQLContextInfo[] conContexts = sqlProcessor.getContexts();
|
||||
contexts.addAll(Arrays.asList(conContexts));
|
||||
}
|
||||
}
|
||||
if (contextId != null) {
|
||||
contexts.removeIf(c -> !c.getId().equals(contextId));
|
||||
}
|
||||
return contexts.toArray(new WebSQLContextInfo[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public WebSQLDialectInfo getDialectInfo(@NotNull WebSQLProcessor processor) throws DBWebException {
|
||||
@@ -57,19 +90,43 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public WebSQLCompletionProposal[] getCompletionProposals(@NotNull WebSQLContextInfo sqlContext, @NotNull String query, Integer position, Integer maxResults) throws DBWebException {
|
||||
public WebSQLCompletionProposal[] getCompletionProposals(
|
||||
@NotNull WebSQLContextInfo sqlContext,
|
||||
@NotNull String query,
|
||||
Integer position,
|
||||
Integer maxResults,
|
||||
Boolean simpleMode) throws DBWebException
|
||||
{
|
||||
try {
|
||||
DBPDataSource dataSource = sqlContext.getProcessor().getConnection().getDataSourceContainer().getDataSource();
|
||||
|
||||
Document document = new Document();
|
||||
document.set(query);
|
||||
SQLScriptElement activeQuery = new SQLQuery(dataSource, query);
|
||||
|
||||
WebSQLCompletionContext completionContext = new WebSQLCompletionContext(sqlContext);
|
||||
|
||||
SQLScriptElement activeQuery;
|
||||
|
||||
if (position != null) {
|
||||
SQLParserContext parserContext = new SQLParserContext(
|
||||
() -> sqlContext.getProcessor().getExecutionContext(),
|
||||
completionContext.getSyntaxManager(),
|
||||
completionContext.getRuleManager(),
|
||||
document);
|
||||
activeQuery = SQLScriptParser.extractActiveQuery(parserContext, position, 0);
|
||||
} else {
|
||||
activeQuery = new SQLQuery(dataSource, query);
|
||||
}
|
||||
|
||||
|
||||
SQLCompletionRequest request = new SQLCompletionRequest(
|
||||
new WebSQLCompletionContext(sqlContext),
|
||||
completionContext,
|
||||
document,
|
||||
position == null ? 0 : position,
|
||||
activeQuery,
|
||||
false
|
||||
CommonUtils.getBoolean(simpleMode, false)
|
||||
);
|
||||
|
||||
SQLCompletionAnalyzer analyzer = new SQLCompletionAnalyzer(request);
|
||||
analyzer.runAnalyzer(sqlContext.getProcessor().getWebSession().getProgressMonitor());
|
||||
List<SQLCompletionProposalBase> proposals = analyzer.getProposals();
|
||||
@@ -88,6 +145,16 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBCLogicalOperator[] getSupportedOperations(@NotNull WebSQLContextInfo contextInfo, @NotNull String resultsId, int attributeIndex) throws DBWebException {
|
||||
WebSQLResultsInfo results = contextInfo.getResults(resultsId);
|
||||
if (attributeIndex < 0 || attributeIndex >= results.getAttributes().length) {
|
||||
throw new DBWebException("Invalid attribute index (" + attributeIndex + ")");
|
||||
}
|
||||
DBDAttributeBinding attribute = results.getAttributes()[attributeIndex];
|
||||
return attribute.getValueHandler().getSupportedOperators(attribute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSQLContextInfo createContext(@NotNull WebSQLProcessor processor, String defaultCatalog, String defaultSchema) throws DBWebException {
|
||||
try {
|
||||
@@ -199,5 +266,36 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
return null;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Explain plan
|
||||
|
||||
@Override
|
||||
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, InterruptedException {
|
||||
try {
|
||||
monitor.beginTask("Explain execution plan", 1);
|
||||
monitor.subTask("Explain query [" + sql + "] execution plan");
|
||||
WebSQLExecutionPlan executeResults = contextInfo.getProcessor().explainExecutionPlan(monitor, contextInfo, sql, configuration);
|
||||
this.result = "Execution plan explain has been scheduled";
|
||||
this.extendedResults = executeResults;
|
||||
} catch (Throwable e) {
|
||||
throw new InvocationTargetException(e);
|
||||
} finally {
|
||||
monitor.done();
|
||||
}
|
||||
}
|
||||
};
|
||||
return contextInfo.getProcessor().getWebSession().createAndRunAsyncTask("SQL query execution plan explain", runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSQLExecutionPlan asyncSqlExplainExecutionPlanResult(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException {
|
||||
WebAsyncTaskInfo taskStatus = webSession.asyncTaskStatus(taskId, false);
|
||||
if (taskStatus != null) {
|
||||
return (WebSQLExecutionPlan) taskStatus.getExtendedResult();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: %Bundle-Vendor
|
||||
Bundle-Name: %Bundle-Name
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.admin;singleton:=true
|
||||
Bundle-Version: 1.0.21.qualifier
|
||||
Bundle-Release-Date: 20210405
|
||||
Bundle-Version: 1.0.29.qualifier
|
||||
Bundle-Release-Date: 20210802
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-ClassPath: .
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.admin</artifactId>
|
||||
<version>1.0.21-SNAPSHOT</version>
|
||||
<version>1.0.29-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -26,12 +26,15 @@ type AdminUserInfo {
|
||||
grantedRoles: [ID!]!
|
||||
grantedConnections: [AdminConnectionGrantInfo!]!
|
||||
|
||||
origin: ObjectOrigin!
|
||||
origins: [ObjectOrigin!]!
|
||||
|
||||
linkedAuthProviders: [String!]!
|
||||
}
|
||||
|
||||
type AdminRoleInfo {
|
||||
roleId: ID!
|
||||
roleName: String
|
||||
description: String
|
||||
|
||||
rolePermissions: [ID]!
|
||||
}
|
||||
@@ -47,6 +50,7 @@ type AdminPermissionInfo {
|
||||
|
||||
input ServerConfigInput {
|
||||
serverName: String
|
||||
serverURL: String
|
||||
|
||||
adminName: String
|
||||
adminPassword: String
|
||||
@@ -73,7 +77,8 @@ extend type Query {
|
||||
createUser(userId: ID!): AdminUserInfo!
|
||||
deleteUser(userId: ID!): Boolean
|
||||
|
||||
createRole(roleId: ID!): AdminRoleInfo!
|
||||
createRole(roleId: ID!, roleName: String, description: String): AdminRoleInfo!
|
||||
updateRole(roleId: ID!, roleName: String, description: String): AdminRoleInfo!
|
||||
deleteRole(roleId: ID!): Boolean
|
||||
|
||||
grantUserRole(userId: ID!, roleId: ID!): Boolean
|
||||
|
||||
+4
@@ -40,6 +40,10 @@ public class AdminRoleInfo {
|
||||
return role.getName();
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return role.getDescription();
|
||||
}
|
||||
|
||||
public List<String> getRolePermissions() {
|
||||
return rolePermissions;
|
||||
}
|
||||
|
||||
+14
-4
@@ -30,20 +30,22 @@ import java.util.Map;
|
||||
public class AdminServerConfig {
|
||||
|
||||
private String serverName;
|
||||
private String serverURL;
|
||||
|
||||
private String adminName;
|
||||
private String adminPassword;
|
||||
|
||||
private boolean anonymousAccessEnabled;
|
||||
private boolean customConnectionsEnabled;
|
||||
private boolean publicCredentialsSaveEnabled;
|
||||
private boolean adminCredentialsSaveEnabled;
|
||||
private final boolean anonymousAccessEnabled;
|
||||
private final boolean customConnectionsEnabled;
|
||||
private final boolean publicCredentialsSaveEnabled;
|
||||
private final boolean adminCredentialsSaveEnabled;
|
||||
private final List<String> enabledAuthProviders;
|
||||
|
||||
private long sessionExpireTime;
|
||||
|
||||
public AdminServerConfig(Map<String, Object> params) {
|
||||
this.serverName = JSONUtils.getString(params, "serverName");
|
||||
this.serverURL = JSONUtils.getString(params, "serverURL");
|
||||
this.adminName = JSONUtils.getString(params, "adminName");
|
||||
this.adminPassword = JSONUtils.getString(params, "adminPassword");
|
||||
|
||||
@@ -69,6 +71,14 @@ public class AdminServerConfig {
|
||||
this.serverName = serverName;
|
||||
}
|
||||
|
||||
public String getServerURL() {
|
||||
return serverURL;
|
||||
}
|
||||
|
||||
public void setServerURL(String serverURL) {
|
||||
this.serverURL = serverURL;
|
||||
}
|
||||
|
||||
public String getAdminName() {
|
||||
return adminName;
|
||||
}
|
||||
|
||||
+32
-11
@@ -16,21 +16,22 @@
|
||||
*/
|
||||
package io.cloudbeaver.service.admin;
|
||||
|
||||
import io.cloudbeaver.DBWAuthProviderExternal;
|
||||
import io.cloudbeaver.DBWConnectionGrant;
|
||||
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.model.user.WebRole;
|
||||
import io.cloudbeaver.model.user.WebUser;
|
||||
import io.cloudbeaver.model.user.WebUserOriginInfo;
|
||||
import io.cloudbeaver.registry.WebAuthProviderDescriptor;
|
||||
import io.cloudbeaver.registry.WebServiceRegistry;
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
import io.cloudbeaver.server.CBPlatform;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.exec.DBCException;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -42,6 +43,8 @@ public class AdminUserInfo {
|
||||
|
||||
private final WebSession session;
|
||||
private final WebUser user;
|
||||
private String[] userLinkedProviders;
|
||||
|
||||
|
||||
public AdminUserInfo(WebSession session, WebUser user) {
|
||||
this.session = session;
|
||||
@@ -78,16 +81,34 @@ public class AdminUserInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
public WebUserOriginInfo getOrigin() {
|
||||
String providerId = user.getMetaParameter(DBWAuthProviderExternal.META_AUTH_PROVIDER);
|
||||
if (CommonUtils.isEmpty(providerId)) {
|
||||
providerId = LocalAuthProvider.PROVIDER_ID;
|
||||
public WebUserOriginInfo[] getOrigins() throws DBWebException {
|
||||
List<WebUserOriginInfo> result = new ArrayList<>();
|
||||
for (String provider : getUserLinkedProviders()) {
|
||||
WebAuthProviderDescriptor authProvider = WebServiceRegistry.getInstance().getAuthProvider(provider);
|
||||
if (authProvider == null) {
|
||||
log.error("Auth provider '" + provider + "' not found");
|
||||
} else {
|
||||
result.add(new WebUserOriginInfo(session, user, authProvider));
|
||||
}
|
||||
}
|
||||
WebAuthProviderDescriptor authProvider = WebServiceRegistry.getInstance().getAuthProvider(providerId);
|
||||
if (authProvider == null) {
|
||||
log.error("Auth provider '" + providerId + "' not found");
|
||||
return result.toArray(new WebUserOriginInfo[0]);
|
||||
}
|
||||
|
||||
@Property
|
||||
public String[] getLinkedAuthProviders() throws DBWebException {
|
||||
return getUserLinkedProviders();
|
||||
}
|
||||
|
||||
private String[] getUserLinkedProviders() throws DBWebException {
|
||||
if (userLinkedProviders != null) {
|
||||
return userLinkedProviders;
|
||||
}
|
||||
return new WebUserOriginInfo(session, user, authProvider);
|
||||
try {
|
||||
userLinkedProviders = CBApplication.getInstance().getSecurityController().getUserLinkedProviders(user.getUserId());
|
||||
} catch (DBCException e) {
|
||||
throw new DBWebException("Error reading user linked providers", e);
|
||||
}
|
||||
return userLinkedProviders;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-1
@@ -53,7 +53,11 @@ public interface DBWServiceAdmin extends DBWService {
|
||||
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
@NotNull
|
||||
AdminRoleInfo createRole(@NotNull WebSession webSession, String roleId) throws DBWebException;
|
||||
AdminRoleInfo createRole(@NotNull WebSession webSession, String roleId, String roleName, String description) throws DBWebException;
|
||||
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
@NotNull
|
||||
AdminRoleInfo updateRole(@NotNull WebSession webSession, String roleId, String roleName, String description) throws DBWebException;
|
||||
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
boolean deleteRole(@NotNull WebSession webSession, String roleId) throws DBWebException;
|
||||
|
||||
+11
-1
@@ -53,7 +53,17 @@ public class WebServiceBindingAdmin extends WebServiceBindingBase<DBWServiceAdmi
|
||||
.dataFetcher("deleteUser",
|
||||
env -> getService(env).deleteUser(getWebSession(env), env.getArgument("userId")))
|
||||
.dataFetcher("createRole",
|
||||
env -> getService(env).createRole(getWebSession(env), env.getArgument("roleId")))
|
||||
env -> getService(env).createRole(
|
||||
getWebSession(env),
|
||||
env.getArgument("roleId"),
|
||||
env.getArgument("roleName"),
|
||||
env.getArgument("description")))
|
||||
.dataFetcher("updateRole",
|
||||
env -> getService(env).createRole(
|
||||
getWebSession(env),
|
||||
env.getArgument("roleId"),
|
||||
env.getArgument("roleName"),
|
||||
env.getArgument("description")))
|
||||
.dataFetcher("deleteRole",
|
||||
env -> getService(env).deleteRole(getWebSession(env), env.getArgument("roleId")))
|
||||
|
||||
|
||||
+1
-2
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
package io.cloudbeaver.service.admin.impl;
|
||||
|
||||
import io.cloudbeaver.WebServiceUtils;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.server.CBPlatform;
|
||||
import io.cloudbeaver.service.admin.AdminConnectionSearchInfo;
|
||||
@@ -135,7 +134,7 @@ public class ConnectionSearcher implements DBRRunnableWithProgress {
|
||||
}
|
||||
|
||||
AdminConnectionSearchInfo searchInfo = portCache.get(driverPort);
|
||||
String driverId = WebServiceUtils.makeDriverFullId(driver);
|
||||
String driverId = driver.getFullId();
|
||||
if (searchInfo != null) {
|
||||
searchInfo.getPossibleDrivers().add(driverId);
|
||||
return;
|
||||
|
||||
+30
-2
@@ -137,12 +137,14 @@ public class WebServiceAdmin implements DBWServiceAdmin {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public AdminRoleInfo createRole(@NotNull WebSession webSession, String roleId) throws DBWebException {
|
||||
public AdminRoleInfo createRole(@NotNull WebSession webSession, String roleId, String roleName, String description) throws DBWebException {
|
||||
if (roleId.isEmpty()) {
|
||||
throw new DBWebException("Empty role ID");
|
||||
}
|
||||
try {
|
||||
WebRole newRole = new WebRole(roleId);
|
||||
newRole.setName(roleName);
|
||||
newRole.setDescription(description);
|
||||
CBPlatform.getInstance().getApplication().getSecurityController().createRole(newRole);
|
||||
return new AdminRoleInfo(newRole);
|
||||
} catch (Exception e) {
|
||||
@@ -150,6 +152,23 @@ public class WebServiceAdmin implements DBWServiceAdmin {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public AdminRoleInfo updateRole(@NotNull WebSession webSession, String roleId, String roleName, String description) throws DBWebException {
|
||||
if (roleId.isEmpty()) {
|
||||
throw new DBWebException("Empty role ID");
|
||||
}
|
||||
try {
|
||||
WebRole newRole = new WebRole(roleId);
|
||||
newRole.setName(roleName);
|
||||
newRole.setDescription(description);
|
||||
CBPlatform.getInstance().getApplication().getSecurityController().updateRole(newRole);
|
||||
return new AdminRoleInfo(newRole);
|
||||
} catch (Exception e) {
|
||||
throw new DBWebException("Error updating role " + roleId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteRole(@NotNull WebSession webSession, String roleId) throws DBWebException {
|
||||
try {
|
||||
@@ -396,8 +415,11 @@ public class WebServiceAdmin implements DBWServiceAdmin {
|
||||
}
|
||||
}
|
||||
|
||||
boolean configurationMode = CBApplication.getInstance().isConfigurationMode();
|
||||
|
||||
CBApplication.getInstance().finishConfiguration(
|
||||
config.getServerName(),
|
||||
config.getServerURL(),
|
||||
adminName,
|
||||
adminPassword,
|
||||
authInfoList,
|
||||
@@ -405,7 +427,13 @@ public class WebServiceAdmin implements DBWServiceAdmin {
|
||||
appConfig);
|
||||
|
||||
// Refresh active session
|
||||
webSession.forceUserRefresh(null);
|
||||
if (configurationMode) {
|
||||
// In config mode we always refresh because admin user doesn't exist yet
|
||||
webSession.forceUserRefresh(null);
|
||||
} else {
|
||||
// Just reload session state
|
||||
webSession.forceUserRefresh(webSession.getUser());
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
throw new DBWebException("Error configuring server", e);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: %Bundle-Vendor
|
||||
Bundle-Name: %Bundle-Name
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.auth;singleton:=true
|
||||
Bundle-Version: 1.0.21.qualifier
|
||||
Bundle-Release-Date: 20210405
|
||||
Bundle-Version: 1.0.29.qualifier
|
||||
Bundle-Release-Date: 20210802
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-ClassPath: .
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.auth</artifactId>
|
||||
<version>1.0.21-SNAPSHOT</version>
|
||||
<version>1.0.29-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -13,6 +13,9 @@ type AuthCredentialInfo {
|
||||
admin: Boolean
|
||||
# This field must be shown in login form
|
||||
user: Boolean
|
||||
# This field can be used as user identifier
|
||||
|
||||
identifying: Boolean
|
||||
possibleValues: [String]
|
||||
encryption: AuthCredentialEncryption
|
||||
}
|
||||
@@ -52,7 +55,7 @@ type UserAuthToken {
|
||||
|
||||
type UserInfo {
|
||||
# User unique identifier
|
||||
userId: String!
|
||||
userId: ID!
|
||||
|
||||
# Human readable display name. It is taken from the first auth provider which was used for user login.
|
||||
displayName: String
|
||||
@@ -75,4 +78,6 @@ extend type Query {
|
||||
|
||||
authProviders: [AuthProviderInfo!]!
|
||||
|
||||
authChangeLocalPassword(oldPassword: String!, newPassword: String!): Boolean!
|
||||
|
||||
}
|
||||
|
||||
+2
@@ -48,4 +48,6 @@ public interface DBWServiceAuth extends DBWService {
|
||||
@WebAction(requirePermissions = {})
|
||||
WebAuthProviderInfo[] getAuthProviders();
|
||||
|
||||
@WebAction()
|
||||
boolean changeLocalPassword(@NotNull WebSession webSession, @NotNull String oldPassword, @NotNull String newPassword) throws DBWebException;
|
||||
}
|
||||
|
||||
+5
@@ -47,6 +47,11 @@ public class WebServiceBindingAuth extends WebServiceBindingBase<DBWServiceAuth>
|
||||
})
|
||||
.dataFetcher("activeUser", env -> getService(env).activeUser(getWebSession(env, false)))
|
||||
.dataFetcher("authProviders", env -> getService(env).getAuthProviders())
|
||||
.dataFetcher("authChangeLocalPassword", env -> getService(env).changeLocalPassword(
|
||||
getWebSession(env),
|
||||
env.getArgument("oldPassword"),
|
||||
env.getArgument("newPassword")
|
||||
))
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
+39
-7
@@ -17,6 +17,7 @@
|
||||
package io.cloudbeaver.service.auth.impl;
|
||||
|
||||
import io.cloudbeaver.*;
|
||||
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
|
||||
import io.cloudbeaver.model.session.WebAuthInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.model.user.WebAuthProviderInfo;
|
||||
@@ -74,7 +75,10 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
|
||||
}
|
||||
} else {
|
||||
if (!providerEnabled) {
|
||||
throw new DBWebException("Authentication provider '" + providerId + "' is disabled");
|
||||
// Admin can use local provider anytime
|
||||
if (!isAdminAuthTry(providerId, authParameters)) {
|
||||
throw new DBWebException("Authentication provider '" + providerId + "' is disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
WebAuthProviderDescriptor authProvider = WebServiceRegistry.getInstance().getAuthProvider(providerId);
|
||||
@@ -137,11 +141,10 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
|
||||
userId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We may need to associate new credentials with active user
|
||||
if (linkWithActiveUser) {
|
||||
securityController.setUserCredentials(userId, authProvider, userCredentials);
|
||||
}
|
||||
}
|
||||
// We may need to associate new credentials with active user
|
||||
if (linkWithActiveUser) {
|
||||
securityController.setUserCredentials(userId, authProvider, userCredentials);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +152,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
|
||||
throw new DBCException("Invalid user credentials");
|
||||
}
|
||||
}
|
||||
if (curUser != null && !curUser.getUserId().equals(userId)) {
|
||||
if (linkWithActiveUser && curUser != null && !curUser.getUserId().equals(userId)) {
|
||||
log.debug("Attempt to authorize user '" + userId + "' while user '" + curUser.getUserId() + "' already authorized");
|
||||
throw new DBCException("You cannot authorize with different users credentials");
|
||||
}
|
||||
@@ -205,6 +208,23 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAdminAuthTry(@NotNull String providerId, @NotNull Map<String, Object> authParameters) {
|
||||
boolean isAdmin = false;
|
||||
if (LocalAuthProvider.PROVIDER_ID.equals(providerId)) {
|
||||
Object userId = authParameters.get(LocalAuthProvider.CRED_USER);
|
||||
if (userId != null) {
|
||||
try {
|
||||
isAdmin = CBPlatform.getInstance().getApplication().getSecurityController()
|
||||
.getUserPermissions(CommonUtils.toString(userId))
|
||||
.contains(DBWConstants.PERMISSION_ADMIN);
|
||||
} catch (DBCException e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return isAdmin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authLogout(@NotNull WebSession webSession, @Nullable String providerId) throws DBWebException {
|
||||
if (webSession.getUser() == null) {
|
||||
@@ -228,4 +248,16 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
|
||||
.toArray(WebAuthProviderInfo[]::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean changeLocalPassword(@NotNull WebSession webSession, @NotNull String oldPassword, @NotNull String newPassword) throws DBWebException {
|
||||
if (webSession.getUser() == null) {
|
||||
throw new DBWebException("User must be logged in");
|
||||
}
|
||||
try {
|
||||
return LocalAuthProvider.changeUserPassword(webSession, oldPassword, newPassword);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error changing user password", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: %Bundle-Vendor
|
||||
Bundle-Name: %Bundle-Name
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.data.transfer;singleton:=true
|
||||
Bundle-Version: 1.0.22.qualifier
|
||||
Bundle-Release-Date: 20210405
|
||||
Bundle-Version: 1.0.30.qualifier
|
||||
Bundle-Release-Date: 20210802
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-ClassPath: .
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.data.transfer</artifactId>
|
||||
<version>1.0.22-SNAPSHOT</version>
|
||||
<version>1.0.30-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
+3
-1
@@ -207,7 +207,9 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
|
||||
exporter,
|
||||
properties);
|
||||
|
||||
DatabaseTransferProducer producer = new DatabaseTransferProducer(dataContainer, parameters.getFilter() == null ? null : parameters.getFilter().makeDataFilter());
|
||||
DatabaseTransferProducer producer = new DatabaseTransferProducer(
|
||||
dataContainer,
|
||||
parameters.getFilter() == null ? null : parameters.getFilter().makeDataFilter(monitor, dataContainer));
|
||||
DatabaseProducerSettings producerSettings = new DatabaseProducerSettings();
|
||||
producerSettings.setExtractType(DatabaseProducerSettings.ExtractType.SINGLE_QUERY);
|
||||
producerSettings.setQueryRowCount(false);
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: %Bundle-Vendor
|
||||
Bundle-Name: %Bundle-Name
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.metadata;singleton:=true
|
||||
Bundle-Version: 1.0.25.qualifier
|
||||
Bundle-Release-Date: 20210405
|
||||
Bundle-Version: 1.0.33.qualifier
|
||||
Bundle-Release-Date: 20210802
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-ClassPath: .
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.metadata</artifactId>
|
||||
<version>1.0.25-SNAPSHOT</version>
|
||||
<version>1.0.33-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>drivers.clickhouse</artifactId>
|
||||
<version>1.0.0</version>
|
||||
@@ -18,7 +18,14 @@
|
||||
<dependency>
|
||||
<groupId>ru.yandex.clickhouse</groupId>
|
||||
<artifactId>clickhouse-jdbc</artifactId>
|
||||
<version>0.2.6</version>
|
||||
<classifier>shaded</classifier>
|
||||
<version>0.3.1-patch</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>42.2.5</version>
|
||||
<version>42.2.20</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.postgis</groupId>
|
||||
<artifactId>postgis-jdbc</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<version>2.5.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<feature
|
||||
id="io.cloudbeaver.ce.drivers.feature"
|
||||
label="Cloudbeaver CE 3rd Party Drivers"
|
||||
version="1.0.45.qualifier"
|
||||
version="1.0.53.qualifier"
|
||||
provider-name="DBeaver Corp">
|
||||
|
||||
<plugin id="io.cloudbeaver.resources.drivers.base" download-size="0" install-size="0" version="0.0.0"/>
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.ce.drivers.feature</artifactId>
|
||||
<version>1.0.45-SNAPSHOT</version>
|
||||
<version>1.0.53-SNAPSHOT</version>
|
||||
<packaging>eclipse-feature</packaging>
|
||||
</project>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<feature
|
||||
id="io.cloudbeaver.product.ce.feature"
|
||||
label="%featureName"
|
||||
version="20.0.6.qualifier"
|
||||
version="21.1.1.qualifier"
|
||||
provider-name="%providerName"
|
||||
plugin="io.cloudbeaver.product.ce">
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.product.ce.feature</artifactId>
|
||||
<version>20.0.6-SNAPSHOT</version>
|
||||
<version>21.1.1-SNAPSHOT</version>
|
||||
<packaging>eclipse-feature</packaging>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<feature
|
||||
id="io.cloudbeaver.sample.feature"
|
||||
label="%featureName"
|
||||
version="1.0.25.qualifier"
|
||||
version="1.0.33.qualifier"
|
||||
provider-name="%providerName">
|
||||
|
||||
<description>
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.sample.feature</artifactId>
|
||||
<version>1.0.25-SNAPSHOT</version>
|
||||
<version>1.0.33-SNAPSHOT</version>
|
||||
<packaging>eclipse-feature</packaging>
|
||||
</project>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<feature
|
||||
id="io.cloudbeaver.server.feature"
|
||||
label="%featureName"
|
||||
version="20.0.6.qualifier"
|
||||
version="21.1.1.qualifier"
|
||||
provider-name="%providerName"
|
||||
plugin="io.cloudbeaver.server">
|
||||
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.server.feature</artifactId>
|
||||
<version>20.0.6-SNAPSHOT</version>
|
||||
<version>21.1.1-SNAPSHOT</version>
|
||||
<packaging>eclipse-feature</packaging>
|
||||
</project>
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
|
||||
<properties>
|
||||
<dbeaver-product>CloudBeaver CE</dbeaver-product>
|
||||
<dbeaver-version>20.0.6</dbeaver-version>
|
||||
<dbeaver-version>21.1.1</dbeaver-version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?pde version="3.5"?>
|
||||
|
||||
<product name="Cloudbeaver Provided Sample" uid="io.cloudbeaver.provided.sample.product" id="io.cloudbeaver.provided.sample.product" application="io.cloudbeaver.server.standalone" version="20.0.6.qualifier" useFeatures="true" includeLaunchers="false">
|
||||
<product name="Cloudbeaver Provided Sample" uid="io.cloudbeaver.provided.sample.product" id="io.cloudbeaver.provided.sample.product" application="io.cloudbeaver.server.standalone" version="21.1.1.qualifier" useFeatures="true" includeLaunchers="false">
|
||||
|
||||
<configIni use="default">
|
||||
</configIni>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../</relativePath>
|
||||
</parent>
|
||||
<version>20.0.6-SNAPSHOT</version>
|
||||
<version>21.1.1-SNAPSHOT</version>
|
||||
<artifactId>provided-sample</artifactId>
|
||||
<packaging>eclipse-repository</packaging>
|
||||
<name>Cloudbeaver Provided Sample Product</name>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<?pde version="3.5"?>
|
||||
|
||||
<product name="CloudBeaver ce Server" uid="io.cloudbeaver.product" id="io.cloudbeaver.product.ce.product"
|
||||
application="io.cloudbeaver.product.ce.application" version="20.0.6.qualifier" useFeatures="true"
|
||||
application="io.cloudbeaver.product.ce.application" version="21.1.1.qualifier" useFeatures="true"
|
||||
includeLaunchers="false">
|
||||
|
||||
<configIni use="default">
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../</relativePath>
|
||||
</parent>
|
||||
<version>20.0.6-SNAPSHOT</version>
|
||||
<version>21.1.1-SNAPSHOT</version>
|
||||
<artifactId>web-server</artifactId>
|
||||
<packaging>eclipse-repository</packaging>
|
||||
<name>Cloudbeaver Server Product</name>
|
||||
|
||||
+19
-14
@@ -6,15 +6,18 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"nohoist": ["**/core-cli", "**/core-cli/**"]
|
||||
"nohoist": [
|
||||
"**/core-cli",
|
||||
"**/core-cli/**"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"prebuild": "npm run clean-dist",
|
||||
"clean-dist": "rimraf lib/*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^16.9.49",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react": "^16.9.12",
|
||||
"@types/react-dom": "^16.9.12",
|
||||
"@typescript-eslint/eslint-plugin": "^4.13.0",
|
||||
"@typescript-eslint/parser": "^4.13.0",
|
||||
"eslint": "^7.18.0",
|
||||
@@ -33,19 +36,21 @@
|
||||
"eslint-plugin-standard": "^5.0.0",
|
||||
"eslint-webpack-plugin": "^2.4.1",
|
||||
"lerna": "^3.22.1",
|
||||
"mobx": "^6.0.4",
|
||||
"mobx-react-lite": "^3.1.6",
|
||||
"mobx": "^6.3.0",
|
||||
"mobx-react-lite": "^3.2.0",
|
||||
"node-sass": "^5.0.0",
|
||||
"preact": "^10.5.10",
|
||||
"preact-render-to-string": "^5.1.12",
|
||||
"preact-ssr-prepass": "^1.1.3",
|
||||
"path-browserify": "^1.0.1",
|
||||
"preact": "^10.5.13",
|
||||
"preact-jsx-runtime": "^1.2.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"reakit": "^1.3.4",
|
||||
"preact-render-to-string": "^5.1.19",
|
||||
"preact-ssr-prepass": "^1.1.3",
|
||||
"react": "^16.14.0",
|
||||
"react-dom": "^16.14.0",
|
||||
"reakit": "^1.3.8",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"reshadow": "^0.0.1-alpha.78",
|
||||
"typescript": "^4.2.3"
|
||||
"reshadow": "^0.0.1-alpha.80",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.2.4"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
}
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import styled from 'reshadow';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const AdministrationTools: React.FC<Props> = function AdministrationTools({ children, className }) {
|
||||
return styled()(
|
||||
<administration-tools as='div' className={className}>
|
||||
{children}
|
||||
</administration-tools>
|
||||
);
|
||||
};
|
||||
+31
-8
@@ -23,6 +23,7 @@ interface IActivationData {
|
||||
screen: IAdministrationItemRoute;
|
||||
configurationWizard: boolean;
|
||||
outside: boolean;
|
||||
outsideAdminPage: boolean;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
@@ -161,17 +162,19 @@ export class AdministrationItemService {
|
||||
async activate(
|
||||
screen: IAdministrationItemRoute,
|
||||
configurationWizard: boolean,
|
||||
outside: boolean
|
||||
outside: boolean,
|
||||
outsideAdminPage: boolean
|
||||
): Promise<void> {
|
||||
await this.activationTask.execute({ screen, configurationWizard, outside });
|
||||
await this.activationTask.execute({ screen, configurationWizard, outside, outsideAdminPage });
|
||||
}
|
||||
|
||||
async deActivate(
|
||||
screen: IAdministrationItemRoute,
|
||||
configurationWizard: boolean,
|
||||
outside: boolean
|
||||
outside: boolean,
|
||||
outsideAdminPage: boolean
|
||||
): Promise<void> {
|
||||
await this.deActivationTask.execute({ screen, configurationWizard, outside });
|
||||
await this.deActivationTask.execute({ screen, configurationWizard, outside, outsideAdminPage });
|
||||
}
|
||||
|
||||
async canActivate(
|
||||
@@ -198,7 +201,22 @@ export class AdministrationItemService {
|
||||
return true;
|
||||
}
|
||||
|
||||
private activateHandler: IExecutorHandler<IActivationData> = async ({ screen, configurationWizard, outside }) => {
|
||||
private activateHandler: IExecutorHandler<IActivationData> = async ({
|
||||
screen,
|
||||
configurationWizard,
|
||||
outside,
|
||||
outsideAdminPage,
|
||||
}) => {
|
||||
let lastItem = 0;
|
||||
while (true) {
|
||||
const items = this.getActiveItems(configurationWizard);
|
||||
if (lastItem === items.length) {
|
||||
break;
|
||||
}
|
||||
await items[lastItem]?.onLoad?.(configurationWizard, outside, outsideAdminPage);
|
||||
lastItem++;
|
||||
}
|
||||
|
||||
if (configurationWizard) {
|
||||
let item = 0;
|
||||
while (true) {
|
||||
@@ -216,20 +234,25 @@ export class AdministrationItemService {
|
||||
return;
|
||||
}
|
||||
|
||||
await item.onActivate?.(configurationWizard, outside);
|
||||
await item.onActivate?.(configurationWizard, outside, outsideAdminPage);
|
||||
|
||||
if (screen.sub) {
|
||||
await this.getItemSub(item, screen.sub)?.onActivate?.(screen.param, configurationWizard, outside);
|
||||
}
|
||||
};
|
||||
|
||||
private deActivateHandler: IExecutorHandler<IActivationData> = async ({ screen, configurationWizard, outside }) => {
|
||||
private deActivateHandler: IExecutorHandler<IActivationData> = async ({
|
||||
screen,
|
||||
configurationWizard,
|
||||
outside,
|
||||
outsideAdminPage,
|
||||
}) => {
|
||||
const item = this.getItem(screen.item, configurationWizard);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
await item.onDeActivate?.(configurationWizard, outside);
|
||||
await item.onDeActivate?.(configurationWizard, outside, outsideAdminPage);
|
||||
|
||||
if (screen.sub) {
|
||||
await this.getItemSub(item, screen.sub)?.onDeActivate?.(screen.param, configurationWizard, outside);
|
||||
|
||||
@@ -44,7 +44,11 @@ export type AdministrationItemSubContentProps = AdministrationItemContentProps &
|
||||
};
|
||||
export type AdministrationItemSubContentComponent = React.FunctionComponent<AdministrationItemSubContentProps>;
|
||||
|
||||
export type AdministrationItemEvent = (configurationWizard: boolean, outside: boolean) => Promise<void> | void;
|
||||
export type AdministrationItemEvent = (
|
||||
configurationWizard: boolean,
|
||||
outside: boolean,
|
||||
outsideAdminPage: boolean
|
||||
) => Promise<void> | void;
|
||||
export type AdministrationItemCanActivateEvent = (
|
||||
configurationWizard: boolean,
|
||||
administration: boolean
|
||||
@@ -91,6 +95,7 @@ export interface IAdministrationItemOptions {
|
||||
replace?: IAdministrationItemReplaceOptions;
|
||||
getDrawerComponent: () => AdministrationItemDrawerComponent;
|
||||
getContentComponent: () => AdministrationItemContentComponent;
|
||||
onLoad?: AdministrationItemEvent;
|
||||
onActivate?: AdministrationItemEvent;
|
||||
onDeActivate?: AdministrationItemEvent;
|
||||
canActivate?: AdministrationItemCanActivateEvent;
|
||||
|
||||
+35
-24
@@ -13,7 +13,7 @@ import { IExecutor, Executor } from '@cloudbeaver/core-executor';
|
||||
import { PermissionsResource, PermissionsService, ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { ScreenService, RouterState } from '@cloudbeaver/core-routing';
|
||||
import { LocalStorageSaveService } from '@cloudbeaver/core-settings';
|
||||
import { BuildVersion } from '@cloudbeaver/core-utils';
|
||||
import { GlobalConstants } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { AdministrationItemService } from '../AdministrationItem/AdministrationItemService';
|
||||
import type { IAdministrationItemRoute } from '../AdministrationItem/IAdministrationItemRoute';
|
||||
@@ -68,15 +68,9 @@ export class AdministrationScreenService {
|
||||
private autoSaveService: LocalStorageSaveService,
|
||||
private serverConfigResource: ServerConfigResource
|
||||
) {
|
||||
makeObservable(this, {
|
||||
info: observable,
|
||||
itemState: observable,
|
||||
activeScreen: computed,
|
||||
});
|
||||
|
||||
this.info = {
|
||||
workspaceId: '',
|
||||
version: BuildVersion.version || '',
|
||||
version: GlobalConstants.version || '',
|
||||
serverVersion: '',
|
||||
configurationMode: false,
|
||||
};
|
||||
@@ -84,6 +78,12 @@ export class AdministrationScreenService {
|
||||
this.activationEvent = new Executor();
|
||||
this.ensurePermissions = new Executor();
|
||||
|
||||
makeObservable(this, {
|
||||
info: observable,
|
||||
itemState: observable,
|
||||
activeScreen: computed,
|
||||
});
|
||||
|
||||
this.autoSaveService.withAutoSave(this.itemState, ADMINISTRATION_ITEMS_STATE);
|
||||
this.autoSaveService.withAutoSave(this.info, ADMINISTRATION_INFO);
|
||||
this.permissionsResource.onDataUpdate.addPostHandler(() => {
|
||||
@@ -150,18 +150,7 @@ export class AdministrationScreenService {
|
||||
if (!this.serverConfigResource.isLoaded()) {
|
||||
throw new Error('Administration screen getItemState can be used only after server configuration loaded');
|
||||
}
|
||||
if (
|
||||
this.info.workspaceId !== this.serverConfigResource.workspaceId
|
||||
|| this.info.configurationMode !== this.isConfigurationMode
|
||||
|| this.info.serverVersion !== this.serverConfigResource.serverVersion
|
||||
|| this.info.version !== BuildVersion.version
|
||||
) {
|
||||
this.clearItemsState();
|
||||
this.info.workspaceId = this.serverConfigResource.workspaceId;
|
||||
this.info.configurationMode = this.isConfigurationMode;
|
||||
this.info.serverVersion = this.serverConfigResource.serverVersion;
|
||||
this.info.version = BuildVersion.version || '';
|
||||
}
|
||||
this.validateState();
|
||||
|
||||
if (defaultState) {
|
||||
if (!this.itemState.has(name) || update) {
|
||||
@@ -194,11 +183,13 @@ export class AdministrationScreenService {
|
||||
|
||||
const toScreen = this.getScreen(nextState);
|
||||
const screen = this.getScreen(state);
|
||||
|
||||
if (screen) {
|
||||
await this.administrationItemService.deActivate(
|
||||
screen,
|
||||
this.isConfigurationMode,
|
||||
screen.item !== toScreen?.item
|
||||
screen.item !== toScreen?.item,
|
||||
toScreen === null
|
||||
);
|
||||
}
|
||||
|
||||
@@ -239,17 +230,35 @@ export class AdministrationScreenService {
|
||||
await this.administrationItemService.activate(
|
||||
screen,
|
||||
this.isConfigurationMode,
|
||||
screen.item !== fromScreen?.item
|
||||
screen.item !== fromScreen?.item,
|
||||
fromScreen === null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private validateState() {
|
||||
if (
|
||||
this.info.workspaceId !== this.serverConfigResource.workspaceId
|
||||
|| this.info.configurationMode !== this.isConfigurationMode
|
||||
|| this.info.serverVersion !== this.serverConfigResource.serverVersion
|
||||
|| this.info.version !== GlobalConstants.version
|
||||
) {
|
||||
this.clearItemsState();
|
||||
this.info.workspaceId = this.serverConfigResource.workspaceId;
|
||||
this.info.configurationMode = this.isConfigurationMode;
|
||||
this.info.serverVersion = this.serverConfigResource.serverVersion;
|
||||
this.info.version = GlobalConstants.version || '';
|
||||
}
|
||||
}
|
||||
|
||||
private async checkPermissions(state: RouterState): Promise<boolean> {
|
||||
if (!this.isAdministrationRouteActive(state.name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(await this.isAccessProvided(state))) {
|
||||
const accessProvided = await this.isAccessProvided(state);
|
||||
|
||||
if (!accessProvided) {
|
||||
this.screenService.navigateToRoot();
|
||||
return false;
|
||||
}
|
||||
@@ -269,7 +278,9 @@ export class AdministrationScreenService {
|
||||
|
||||
await this.ensurePermissions.execute();
|
||||
|
||||
if (!(await this.permissionsService.hasAsync(EAdminPermission.admin))) {
|
||||
const administrator = await this.permissionsService.hasAsync(EAdminPermission.admin);
|
||||
|
||||
if (!administrator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user