Compare commits

...
Author SHA1 Message Date
mayer 02c9a1bea1 Merge branch 'devel' into devops-2221-base-java-repo 2026-01-05 16:42:52 +01:00
Daria Marutkina a836412ee4 dbeaver/pro#7916 Update README file (#4027)
* dbeaver/pro#7916 Update README file

* Update README.md

* Update README.md
2026-01-05 11:54:49 +01:00
Serge Rider 38b0246240 dbeaver/pro#7850 Base model refactoring (#4022)
* dbeaver/pro#7906 App shutdown fixes (listeners unregister)

* dbeaver/pro#7906 File systems usage refactoring (move to model)

* dbeaver/pro#7906 Logging fix

* dbeaver/pro#7906 Logging fix

* dbeaver/pro#7850 Logging fix
2026-01-03 09:17:15 +01:00
Serge Rider 0bf769a662 dbeaver/pro#2787 Adapters annotations and interface 2025-12-31 10:59:38 +01:00
dbeaver-devops 0349bcee4c dbeaver/pro#7916 Bump version 25.3.3 2025-12-29 19:42:03 +01:00
44b89296d2 dbeaver/pro#7798 [CB] Fix: menu after migration to new (#4008)
* dbeaver/pro#7798 fix: remove unwanted wrapper and relevant CSS module

* dbeaver/pro#7798 feat: add refresh action to user administration tools menu

* dbeaver/pro#7798 fix: add loader to context menu rendering

The menu was failing to open on first click due to lazy-loaded custom items (ContextMenuSearchItem) suspending the entire top navigation bar on initial load, causing portal recreation and loss of open state. Wrapped menu items in a local Loader to ensure lazy components load within the popover without affecting the parent component.

* dbeaver/pro#7798 fix: use suspense for each renderMenuItem

* dbeaver/pro#7798 refactor: simplify orderItems method in CreateUserBootstrap

* dbeaver/pro#7798 fix: fix menuBarItem styles

* dbeaver/pro#7798 refactor: update CreateUserBootstrap to include AdministrationScreenService

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-12-29 18:38:06 +01:00
Sychev AndreyandDaria Marutkina b75d802883 dbeaver/pro#4612 feat: remove closeOnBlur option from autocompletion configuration (#4014)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-29 17:03:46 +01:00
9652520073 dbeaver/pro#7090 move auth island, show desc as placeholder (#4012)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-12-29 17:03:33 +01:00
AinurandEvgenia 7e7d6634de dbeaver/pro#7157 save origin in web session (#3976)
* dbeaver/pro#7157 save origin in web session

* dbeaver/pro#7157 rename origin and add event object

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-12-29 14:00:26 +01:00
c12561965f dbeaver/pro#7481 ability to read/write to an external fs (#4007)
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2025-12-29 13:50:10 +01:00
Dmitrii BarnukovandDaria Marutkina 7a12ad4b91 dbeaver/pro#7693 added key attributes row id check (#4019)
* dbeaver/pro#7693 added key attributes row id check

* dbeaver/pro#7693 added fix for single row tables

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-29 12:35:47 +01:00
serge-rider c39de17df8 dbeaver/pro#7906 App initialization error handle 2025-12-29 10:29:59 +01:00
serge-rider 147edde923 dbeaver/pro#7850 Remove cloud config parametrization 2025-12-28 17:05:39 +01:00
serge-rider aeaa864f57 dbeaver/pro#7850 Logging fix 2025-12-28 16:35:00 +01:00
serge-rider bcda3e4312 dbeaver/pro#7253 Remove redundant code + comments. 2025-12-27 06:13:42 +01:00
serge-rider 72a007ceb6 dbeaver/pro#7900 Logging config added 2025-12-26 18:00:36 +01:00
serge-rider e08ba6845e dbeaver/pro#7900 Redundant logging removed 2025-12-26 17:34:21 +01:00
Georgii 8f1fc1f36e dbeaver/pro#7900 fix unit test run (#4021)
Signed-off-by: Georgii Gvinepadze <fireshowel@gmail.com>
2025-12-26 17:24:21 +01:00
b5cfcaef31 dbeaver/pro#7253 save nav settings as specific to user (#3949)
* dbeaver/pro#7253 save nav settings as specific to user

* dbeaver/pro#7253 delete all project settings fix

* dbeaver/pro#7253 fixes after review and add api for getting object settings

* dbeaver/pro#7253 delete object info from db and change logic of loading settings

* dbeaver/pro#7253 fixes after review

* dbeaver/pro#7253 serialize/deserialize nav settings in common place

* dbeaver/pro#7253 fix updating schema for postgres

* dbeaver/pro#7253 add oracle migration

* dbeaver/pro#7253 fix oracle migration

* dbeaver/pro#7253 fix getting object settings for anonymous user

* dbeaver/pro#7253 fix applying config for anonymous user

* dbeaver/pro#7253 add events to handle connection view update

* dbeaver/pro#7253 update schema and change user navigator settings logic

* dbeaver/pro#7253 setSettings fixes. Remove redundant json serialization. Use string->string map everywhere. Delete datasource settings handler.

* dbeaver/pro#7253 Simplify model. Add object type as parameter.

* dbeaver/pro#7253 Remove copy-paste in project settings impl.

* dbeaver/pro#7253 Remove copy-paste in project settings impl.

* dbeaver/pro#7253 Navigator settings API and update fixes

* dbeaver/pro#7253 Navigator settings save/clear redesign (keep in separate options)

* dbeaver/pro#7253 User settings test. Test refactoring.

* dbeaver/pro#7253 setUserSettings API and test

* dbeaver/pro#7253 setUserSettings API and test

* dbeaver/pro#7253 setUserSettings API and test

* dbeaver/pro#7253 Fix drivers Maven config

* dbeaver/pro#7253 Schema update

* dbeaver/pro#7894 API comments

* dbeaver/pro#7862 Object setting cleanup fix (do it on DS level)

* dbeaver/pro#7862 REST API fixes

* dbeaver/pro#7253 Fix REST parameters encoding + annotations

---------

Co-authored-by: Serge Rider <serge@jkiss.org>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
Co-authored-by: serge-rider <serge@dbeaver.com>
Co-authored-by: Daria Markaryan <43252954+DariaMarkaryan@users.noreply.github.com>
2025-12-26 17:17:22 +01:00
Ruslan Musaev e9b1079f85 dbeaver/cloudbeaver#3998 ldap transitive group matching (#4015) 2025-12-25 16:18:41 +00:00
Georgii 326514f914 dbeaver/pro#6739 remove classifier (#4018)
Signed-off-by: Georgii Gvinepadze <fireshowel@gmail.com>
2025-12-25 14:27:48 +04:00
Viktor Kirst 67489a03b3 dbeaver/pro#7865 Do not extend web sessions on long-poll GET requests (#4010) 2025-12-25 09:38:15 +00:00
GeorgiiandMatvey16 4ea0baf79b dbeaver/pro#6739 update clickhouse to version 0.9.5 (#3999)
Signed-off-by: Georgii Gvinepadze <fireshowel@gmail.com>
Co-authored-by: Matvey16 <82543000+Matvey16@users.noreply.github.com>
2025-12-25 07:12:39 +01:00
Sychev Andreyandsergeyteleshev 939f4d046a dbeaver/pro#7564 feat: Add "File Tree Selection" to the Tree component (#3974)
* dbeaver/pro#7564 feat: add selectChildren option to tree selection API

* dbeaver/pro#7564 feat: add node multi selection API to the new tree component

* dbeaver/pro#7564 feat: add new selection type

* dbeaver/pro#7564 refactor: remove getSelectedNodes method from tree selection interfaces

* dbeaver/pro#7564 fix: update selection clearing logic in useTreeClickSelection to avoid multiple callback calls

* dbeaver/pro#7564 refactor: optimize node collection in collectVisibleNodes function

* dbeaver/pro#7564 refactor: simplify selection handling logic in Node component

* dbeaver/pro#7564 refactor: streamline selection state handling in useTreeClickSelection

* dbeaver/pro#7564 refactor: replace inline default selection state with constant

* dbeaver/pro#7564 refactor: replace default node selection constant with a function

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2025-12-24 14:19:50 +01:00
cd3cc2e92f dbeaver/pro#7667 feat: add tree toolbar functionality to new tree (#3981)
* dbeaver/pro#7667 feat: add tree toolbar functionality to new tree

* dbeaver/pro#7667 refactor: remove unused ITreeSelection import and selection property from ITreeToolbar

* dbeaver/pro#7667 refactor: remove autoFocus from Filter component

* dbeaver/pro#7667 refactor: move onClick handler from Icon to IconButton

* dbeaver/pro#7667 refactor: add className prop to TreeToolbarMenu component

* dbeaver/pro#7667 feat: implement new navigation tree toolbar using composition and data context

* dbeaver/pro#7667 feat: update toolbar menu localization files

* dbeaver/pro#7667 refactor: navigation tree components to use lazy loading and improve type definitions

* dbeaver/pro#7667 refactor: restructure Tree component removing extra wrapper

* dbeaver/pro#7667 refactor: update treeToolbarActionHandler to use async/await for refresh actions

* dbeaver/pro#7667 refactor: remove undefined type from data context creation

* dbeaver/pro#7667 fix: restructure Tree component to fix Toolbar with scroll

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-12-24 14:04:07 +01:00
7a8c4a141f [CB] Ability to pin column in the Data Editor (#3794)
* dbeaver/pro#6859 adds pin/unpin methods for data grid

* resets state when pins + makes pin icon stay when pinned

* adds TableHeaderCellIcon component with new Menu

* make actions alive

* cleanup

* moves pin logic into data-viewer

* cleanup

* restructures pin logic via contexts

* cleanup

* synchronizes column ids with view ids so pining works fine

* fixes actions visibility

* fix: correct layout on editing frozen cell

* fixes layout so frozen cell box-shadow can divide pinned and unpinned columns

* fixes border and box shadow interactions for frozen and editing cell

* makes pinned columns non transparent

* makes frozen border 3px and reverts previous style changes

* styles cleanup

* adds column true and visual indexes and reuses it across the data grid features

* cleanup

* reverts removed code

* reverts hacks with ids

* dbeaver/pro#7748 updates yarn.locks

* fixes incorrect types

* updates helper libs

* updates vitest + fixes test cases incorrect mocks

* runs tests sequentially with document and windows objects to prevent mock collisions

* Revert "runs tests sequentially with document and windows objects to prevent mock collisions"

This reverts commit f871837df3.

* skips download test

* removes export from resetDeprecatedSettings

* updates react-data-grid to beta-59 and react to 19.2

* updates storybook

* pr fixes

* updates yarn lock

* removes skip for download test

* cleanups download ts with empty body html

* updates vitest for core-connections

* updates yarn.lock

* linter fix + mocks once

* fixes hotkeys

* reverts version of react-hotkeys-hook library

* pr fixes

* code style fix

* dbeaver/pro#6859 adds selectCellByKey for opening editor

* reverts styles

* adds pin/unpin icons

* Revert "reverts styles"

This reverts commit 7857ef672d.

* on add actions sets correct focus

* removes incorrect positioning when editing pinned column

* tunes dark theme colors

* adds DnD to pin/unpin feature

* pr fixes

* fixes multiple selections for pinned & unpinned columns

* refactors multiple selections for pinned & unpinned columns so it uses data-grid api of getting sorted columns

* moves pin logic to usePinnedColumns hook

* moves color styles into data grid

* fixes letter

* adds styles for pinned border only for data editor

* adds unpin all columns action

* moves pinned logic to grid view action to save pinned state between tabs

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Sychev Andrey <44414066+SychevAndrey@users.noreply.github.com>
2025-12-24 13:44:02 +01:00
5db7a0de5e [CB] Setting to show spaces and line breaks in the SQL Editor (#3997)
* dbeaver/pro#7570 adds highlight for text spaces and new line

* refactors + changes colors

* allows to add props to extensions

* pointer events none for new line

* do not show enter symbol if line has no whitespaces below this line

* removes highlightTrailingWhitespace so the red background disappears

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: Sychev Andrey <44414066+SychevAndrey@users.noreply.github.com>
2025-12-24 13:09:02 +01:00
Ivan GagarkinandIvan Gagarkin 15c61239ec dbeaver/pro#7572 Refactor WebSession attribute handling to use ThrowableConsumer and ThrowableFunction (#4004)
Co-authored-by: Ivan Gagarkin <gagarkin@Ivans-MacBook-Pro.local>
2025-12-24 10:50:48 +01:00
alexandmr-anton-t 164d155e7d dbeaver/pro#7868 increase table font size (#4011)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-12-23 19:34:54 +01:00
821c9c8e56 [CB] Always preload specific icons for application (#3984)
* dbeaver/pro#6985 preloads icons

* simplifies the structure

* dbeaver/pro#6985 adds icons to preload

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: alex <48489896+devnaumov@users.noreply.github.com>
2025-12-23 16:47:16 +01:00
Ruslan Musaev b58e01794d dbeaver/pro#7688 Add missing fields description graphql (#4001)
* dbeaver/pro#7688 Add missing fields description graphql
2025-12-23 10:20:11 +01:00
sergeyteleshev fbc5f2d3ce dbeaver/pro#7848 removes storybook high CVE (#4005) 2025-12-22 19:51:14 +01:00
ae9e87950a dbeaver/pro#7809 use popover to show auth methods (#3995)
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-12-22 19:47:22 +01:00
Anastasiya 58db928e34 dbeaver/pro#7812 Add CloudBeaver CE release notes 25.3.1; add alt attribute for logo (#4006) 2025-12-22 16:22:53 +03:00
Serge Riderandkseniaguzeeva 583519d9a7 dbeaver/pro#7797 Product deployment model (#3988)
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2025-12-22 09:49:18 +01:00
Ruslan Musaev a96862a444 dbeaver/pro#7073 add grouping endpoint grahpql (#3960)
* dbeaver/pro#7073 add grouping endpoint grahpql

* dbeaver/pro#7073 add grouping endpoint grahpql

* dbeaver/pro#7073 add isInteractive var

* dbeaver/pro#7073 added working test

* dbeaver/pro#7073 fix test
2025-12-19 14:37:22 +01:00
Viktor Kirst b9d115ec33 Dbeaver/pro#7777 polling error http 400 (#3983)
* dbeaver/pro#7777 Handle expired access token in long-polling servlet

* dbeaver/pro#7777 Touch web session on long-poll activity

* dbeaver/pro#7804 Support cb_session_id cookie for HTTP long-polling sessions

* dbeaver/pro#7804 Refactor LP session resolvers and unify WebHttpRequestInfo creation

* dbeaver/pro#7777 Use token-expired HTTP status for LP auth failures

* dbeaver/pro#7777 Use SMException for session resolution errors and token expiration

* dbeaver/pro#7777 Remove unused constant
2025-12-19 13:51:24 +01:00
sergeyteleshevandmr-anton-t 78daa21c87 [CB] update frontend deps (#3973)
* dbeaver/pro#7748 updates yarn.locks

* fixes incorrect types

* updates helper libs

* updates vitest + fixes test cases incorrect mocks

* runs tests sequentially with document and windows objects to prevent mock collisions

* Revert "runs tests sequentially with document and windows objects to prevent mock collisions"

This reverts commit f871837df3.

* skips download test

* removes export from resetDeprecatedSettings

* updates react-data-grid to beta-59 and react to 19.2

* updates storybook

* pr fixes

* updates yarn lock

* removes skip for download test

* cleanups download ts with empty body html

* updates vitest for core-connections

* updates yarn.lock

* linter fix + mocks once

* fixes hotkeys

* reverts version of react-hotkeys-hook library

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-12-18 12:49:11 +01:00
Sychev AndreyandDaria Marutkina 50090a3035 dbeaver/pro#7810 fix: improve submenu visibility handling for lazy-lo… (#3993)
* dbeaver/pro#7810 fix: improve submenu visibility handling for lazy-loaded items

* dbeaver/pro#7810 fix: return back isDisabled for handler

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-18 10:59:40 +01:00
Sychev Andrey fc9b1d388f dbeaver/pro#7811 feat: fix file selection for uploads by removing focus and visibility listeners (#3992) 2025-12-17 16:51:25 +01:00
dbeaver-devops 0c053efd68 automerged by dbeaver-devops 2025-12-16 20:01:35 +01:00
abf07b8c5b dbeaver/pro#7189 add event for query params (#3866)
* dbeaver/pro#7189 add event for query params

* dbeaver/pro#7189 add event objects to graphql

* dbeaver/pro#7189 cancel future fix

* feat: query parameters

* dbeaver/pro#7389 fix: save confirmation

* dbeaver/pro#7189 extract base model from parameters provider

* dbeaver/pro#7189 move client events to cloudbeaver

* fix: do not ask previous params not presented in request

* dbeaver/pro#7189 resolve conflicts

* chore: change form UI

* fix: reset query parameters in SQL Editor

---------

Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-16 19:30:49 +01:00
Ruslan MusaevandDaria Marutkina c119c93a7d Dbeaver/pro#6123 add unauthorize audit loging (#3977)
* dbeaver/pro#6123 remove reset user state

* dbeaver/pro#6123 remove reset user state on auth

* dbeaver/pro#6123 remove reset user state on auth

* dbeaver/pro#6123 fix last remote addr

* dbeaver/pro#6123 fix last remote addr

* dbeaver/pro#6123 fix last remote addr

* dbeaver/pro#6123 revert taking ip from header

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-16 15:20:12 +01:00
c2045091d0 dbeaver/pro#7610 add polyfils (#3978)
* dbeaver/pro#7610 add polyfils

* dbeaver/pro#7610 sync browserlist config

* dbeaver/pro#7610 specify only major version

* dbeaver/pro#7610 change to fixed year

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2025-12-16 14:54:58 +01:00
alex 9483df70ac dbeaver/pro#7801 convert to boolean (#3986) 2025-12-16 14:49:12 +01:00
AinurandDaria Marutkina d774540803 dbeaver/pro#7743 update lz4 to 1.10.1 (#3962)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-16 12:27:25 +01:00
Serge Rider 736442be35 dbeaver/pro#7796 Annotations 2025-12-13 15:19:45 +01:00
serge-rider 8ee58e0ce8 dbeaver/pro#7796 Trace logging and fixes 2025-12-13 07:37:06 +01:00
serge-rider 163b96bcae dbeaver/pro#7796 Trace logging and fixes 2025-12-13 07:33:07 +01:00
Alexander SkoblikovandMashaKorax 6c915eedc3 dbeaver/pro#7778 ignore splash/nosplash options (#3980)
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2025-12-13 06:09:01 +01:00
b9d55bc121 dbeaver/pro#4503 migrate admin tables to data grid (#3942)
* dbeaver/pro#4503 migrate admin tables to data grid

* dbeaver/pro#4503 migrate users and teams pages

* dbeaver/pro#4503 add additional deps

* dbeaver/pro#4503 dont mutate original constant

* dbeaver/pro#4503 simplify conditional column adding

* dbeaver/pro#4503 unify styles

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>
2025-12-13 10:50:25 +08:00
Alexey Potsetsuevandsergeyteleshev cb23fda311 dbeaver/pro#7465 refactor: migrate context menu to ariakit (#3963)
* dbeaver/pro#7465 refactor: migrate context menu to ariakit

* refactor: remove unused modal prop from ContextMenu and clean up schema name handling

* chore: add memoization to the base context menu components

* fix: menu issues

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2025-12-13 10:44:15 +08:00
Viktor Kirst dc97f315ab dbeaver/pro#7767 Add cookie-based session support (#3975) 2025-12-12 11:13:00 +00:00
sergeyteleshev 5d2c6dc062 [CB] write unit tests (#3928)
* dbeaver/pro#7563 adds unit tests for core-utils

* removes restore all mocks

* adds eslint rules: now arrow function + no unnamed functions (to improve debug experience)

* fixes test according new eslint rules

* removes arrow function expression eslint

* adds Icon Or Image unit tests

* removes problematic test

* useSMock & sMock + reuses these mocks

* adds StaticImage tests

* adds UploadArea tests

* adds tests for useAdministrationSettings

* un-skips useCombinedHandler

* tries to fix useObjectRef via correct mocking (failed github action)

* adds useErrorDetails tests

* rewrites useObjectRef tests so it works stable

* unskips app-init & getTextFileReadingProcess

* adds useFuzzySearch tests

* adds useSearch tests

* pr fixes after copilot review

* self review fixes

* pr fixes

* introduces user events

* uses get by role id to check accessibility of the components

* fixes redefined focus in body

* removes mocks

* removes mocks 2

* removes mocks 3

* removes repeated test cases
2025-12-12 18:55:50 +08:00
Alexey Potsetsuevandmr-anton-t 435aac2860 dbeaver/pro#6962 refactor: replace complex loader with-simple solution (#3968)
* dbeaver/pro#6962 refactor: replace complex loader with-simple solution

* refactor: simplify function declarations in createLazyLoader

* fix: ensure notify is called after loading state updates in createLazyLoader

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-12-12 17:54:19 +08:00
sergeyteleshevandmr-anton-t e0cdf745ad [CB] Improve description for ordering icon (#3958)
* dbeaver/pro#7554 adds translation for order button

* moves translate context to render app level + adds 2 translations for the sorting with the flag

* cleanup

* linter fix

* pr fixes

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-12-12 17:53:45 +08:00
5e11a1d0b1 dbeaver/pro#6987 replace regex with js (#3954)
* dbeaver/pro#6987 replace regex with js

* dbeaver/pro#6987 update lock file

* dbeaver/pro#6987 add additional deps for testing

* dbeaver/pro#6987 use first index

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-11 22:36:43 +08:00
sergeyteleshevandmr-anton-t 83125bf8b5 [CB] Use only 1 Input component across the app (#3938)
* dbeaver/pro#7041 replaces uikit input with core-blocks InputField for PropertyItem (connection driver properties)

* uses Input from ui-kit for react-data-grid from common-react

* separates Input and InputField logic so we can reuse Input somewhere else

* uses input from core-blocks in search panel

* uses input from core-blocks inside fetch size action

* removes hover styles if input is focused

* adds Input to inline editor

* removes style registries

* replaces module styles with css layers api

* cleanup

* calcs padding from spacing

* reverts input field base

* reverts input fields base 2

* fixes styles for inline editor and fetch size action

* fixes outline for inputs

* fixes hover styles for combobox so it matches input hover behavior

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-12-11 18:23:04 +08:00
3b435459a2 dbeaver/pro#6545 allow to save anonym preferences (#3948)
* dbeaver/pro#6545 allow to save anonym preferences

* dbeaver/pro#6545 remove validation for updating user preferences

* dbeaver/pro#6545 fix returning anonymous user info

* dbeaver/pro#6545 add field to gql

---------

Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-11 18:22:41 +08:00
Ruslan MusaevandDaria Marutkina 5dad35b73d dbeaver/pro#7607 fix Firefox gql console (#3969)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-11 10:31:46 +01:00
AinurandDaria Marutkina e801f85e7c dbeaver/pro#7636 add and fix tests (#3940)
* dbeaver/pro#7636 add and fix tests

* dbeaver/pro#7636 fixes after review

* dbeaver/pro#7636 fix locale name

* dbeaver/pro#7636 fixes after review

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-11 10:31:14 +01:00
alexandDaria Marutkina a39af0fd1b dbeaver/pro#7527 persist right panel tabs (#3951)
* dbeaver/pro#7527 persist right panel tabs

* dbeaver/pro#7527 fix typo

* dbeaver/pro#7527 set open context right before opening panel

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-10 17:13:57 +08:00
sergeyteleshevandDaria Marutkina 9ec227ddb1 dbeaver/pro#6407 fixes error during sorting (#3967)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-10 17:13:18 +08:00
Ruslan MusaevandEvgenia 749c116743 dbeaver/pro#6612 ldap group mapping full dn (#3966)
* dbeaver/pro#6612 ldap group mapping full dn

* dbeaver/pro#6612 ldap group mapping full dn

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-12-09 14:10:36 +01:00
Ruslan MusaevandDaria Marutkina d6221e3d33 dbeaver/pro#7295 do not use GSON for connection creds serialization (#3937)
* dbeaver/pro#7295 do not use GSON for connection creds serialization

* dbeaver/pro#7295 do not use GSON for connection creds serialization

* dbeaver/pro#7295 do not use GSON for connection creds serialization

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-09 11:51:02 +01:00
sergeyteleshevandDaria Marutkina 6316ab0cbb [CB] Error updating metadata (#3953)
* dbeaver/pro#6480 checks if nodes parents loaded to verify if some entities are missing

* fixes linter await issues

* uses offset pagination key to check if all parents were loaded

* adds isNodeLoadedInAnyPage logic

* reverts all changes

* adds hasMorePagesForRootNode logic

* linter fix

* checks if parent has more data for each closest parent (not just a root one) + renames helper

* renames helper to sound shorter

* build fix

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-09 15:49:52 +08:00
sergeyteleshevandmr-anton-t e2e18e01e9 [CB] Migrate Clickable (reakit) to the Command (ariakit) (#3941)
* dbeaver/pro#7238 adds replaces Reakit Clickable element with AriaKit Command

* uses focusable ariakit element instead of command

* cleanup

* Revert "cleanup"

This reverts commit 84ef074242.

* Revert "uses focusable ariakit element instead of command"

This reverts commit ab255989b9.

* handles tree node expand click events + removes unused styles

* removes reakit-system dep

* fixes ref for formatting rule

* cleans storybook

* clickable cleanup

* replaces Clickable component with Command from ui-kit

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-12-09 15:49:24 +08:00
Ainur d042a18d46 dbeaver/pro#7615 do not load project settings fix (#3936) 2025-12-08 16:53:38 +01:00
alexandEvgenia 88d7a8d6eb dbeaver/pro#7662 adjust markdown styles (#3947)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-12-08 22:58:42 +08:00
Ruslan MusaevandEvgenia 33bf82970f dbeaver/cloudbeaver#3484 ldap rbac (#3959)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-12-07 16:47:55 +01:00
AinurandEvgenia ea552d613b dbeaver/pro#7704 update lz4 to 1.8.1 (#3956)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-12-05 12:35:04 +01:00
Serge Rider 0ae6a648d8 dbeaver/pro#7703 Annotations and code style (#3955)
* dbeaver/pro#7703 Annotations and code style

* dbeaver/pro#7703 Annotations and code style

* dbeaver/pro#7703 Remove redundant feature s config
2025-12-05 07:19:16 +01:00
Alexander Skoblikov a674e11960 dbeaver/pro#7352 remove cli dependency from model bundles (#3945) 2025-12-04 14:46:34 +01:00
Viktor Kirst d1065037b9 Dbeaver/pro#7442 http long polling fallback for ws event (#3900)
* dbeaver/pro#7442 Add Long-Polling transport support for event manager

* dbeaver/pro#7442 Inline headless-session creation in LongPolling servlet

* dbeaver/pro#7442 Improve long-poll session resolution and add unit tests

* dbeaver/pro#7442 Expose resolveSession for testing and remove reflection in tests

* dbeaver/pro#7442 Add copyright

* dbeaver/pro#7442 Improve long-poll CORS and interrupt handling
2025-12-04 12:08:21 +01:00
serge-rider dd3ab9d5a1 dbeaver/pro#6200 Fix GQL parameters logging 2025-12-04 09:04:40 +01:00
serge-rider 7b94dc60ab dbeaver/pro#7644 Auth model replace detect. GCP localization. 2025-12-04 08:50:20 +01:00
serge-rider 25089a9a70 dbeaver/pro#4996 Remove redundant logging 2025-12-04 08:36:41 +01:00
Serge Rider d645365869 dbeaver/pro#7644 Auth model replace detect. GCP localization. (#3950) 2025-12-04 08:25:14 +01:00
Solovev Anton eb4f11aa0a dbeaver/pro#7632: fix drop message creation (#3939) 2025-12-02 15:24:36 +01:00
Daria Marutkina ec3ba12699 dbeaver/pro#7593 Update README file (#3946)
* dbeaver/pro#7593 Update README file
2025-12-01 11:46:51 +01:00
Ainur a75a7ea5a4 dbeaver/pro#7648 global permission validation fix (#3943) 2025-11-28 16:14:33 +00:00
Sychev Andreyandmr-anton-t 3ca904924b dbeaver/pro#7106 refactor: replace ScriptImportDialog with Confirmati… (#3930)
* dbeaver/pro#7106 refactor: replace ScriptImportDialog with ConfirmationDialog

* dbeaver/pro#7106 feat: enable extra action in confirmation dialog for unsaved changes

* dbeaver/pro#7106 fix: properly handles extra action (no download)

* dbeaver/pro#7106 refactor: simplify import dialog

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-11-28 19:15:37 +08:00
ShadelessFox 46a0adb682 dbeaver/pro#7221 Change API (#3935) 2025-11-25 14:05:00 +00:00
dbeaver-devops b9b4dc040c automerged by dbeaver-devops 2025-11-24 20:50:25 +01:00
Sychev Andrey 5973bd708f dbeaver/pro#7567 [CB] [F] invalid style for dropdown button (#3932)
* dbeaver/pro#7567 fix: update disabled state styles for select and combobox components

* dbeaver/pro#7567 fix: update foreground variable for disclosure component
2025-11-24 17:58:37 +01:00
698ee3cba1 dbeaver/pro#7556 hide show sql button for editor and viewer (#3929)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2025-11-21 18:21:20 +01:00
9394667673 dbeaver/pro#7413 [CB] New Disclosure Component (#3927)
* dbeaver/pro#7413 feat: add Disclosure component to UI Kit

* dbeaver/pro#7413 feat: implement new disclosure component in CB with proper outline support and animations

* dbeaver/pro#7413 style: update disclosure gap to use tw spacing

* dbeaver/pro#7413 refactor: add Disclosure component story to Storybook (remove from ladle)

* dbeaver/pro#7413 refactor: update class names for disclosure components in Expandable

* dbeaver/pro#7413 feat: disable transitions for reduced motion preference

* dbeaver/pro#7413 refactor: convert arrow function to named function in Expandable component

* dbeaver/pro#7413 refactor: simplify media query for reduced motion transitions

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2025-11-21 18:16:31 +01:00
AinurandEvgenia 37877ccf66 dbeaver/pro#5020 fix counting rows filtered data (#3923)
* dbeaver/pro#5020 fix counting rows filtered data

* dbeaver/pro#5020 fix progress monitor

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-21 18:10:27 +01:00
Sychev Andreyandmr-anton-t dc28d68208 #6724 Accessibility improvements (#3877)
* dbeaver/pro#6724 feat: add authentication model label to connection form

* dbeaver/pro#6724 refactor: change interface in StaticImage component to pass native props to provide a11y properties

* dbeaver/pro#6724 fix: add role attribute for icons in Driver selector

Those driver icons are not adding any meaning to the list items, so we can easily hide them from a11y tools.

* dbeaver/pro#6724 refactor: rename autofillToken to autocompleteSectionName and update related logic in forms

We used to have autocomplete parameters that do not meet specifications of HTML Input Autocomplete attribute.
We can't use any string there, there are list of tokens that browsers support: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill plus two global states: "on" and "off".

Logic was refactored to use section token + username or current password tokens fo form where we provide auth details.
All other inputs use just general "on" state to let browsers decide how to fill this field, except cases where we disable password autocompletion.

also fixes a typo

* dbeaver/pro#6724 fix: don't turn on autocomplete if disabled

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-11-21 23:53:53 +08:00
b55398a969 dbeaver/pro#7320 inject mixins (#3901)
Co-authored-by: Serge Rider <serge@jkiss.org>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2025-11-21 13:22:13 +01:00
Ainur 6584a2252e dbeaver/pro#7553 remove redundant gql type param (#3925) 2025-11-21 12:15:21 +01:00
Alexey PotsetsuevandDaria Marutkina 411def42f9 dbeaver/pro#5083 feat: script status message (#3914)
* dbeaver/pro#5083 feat: script status message

* chore: use `TLocalizationToken`

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-21 16:58:47 +08:00
8b471138db [CB] Fix high CVE-2025-64756 on glob (#3913)
* dbeaver/pro#7520 updates glob lib to reduce high vulnerability

* sets version ^11.1

* uses yarn depute

* moves resolution to the webapp

* moves glob to dev deps

* reverts glob for devDeps

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-21 16:58:18 +08:00
Sychev Andreyandmr-anton-t e2a5e1fca2 dbeaver/pro#6416 refactor: adjust height property for treeNodeNested (#3891)
* dbeaver/pro#6416 refactor: adjust height property for treeNodeNested

* dbeaver/pro#6416 refactor: remove height styles for TreeNodeNested and refactor empty placeholder rendering in NodeChildren

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-11-21 16:53:23 +08:00
alexandDaria Marutkina 9c2bc100b1 dbeaver/pro#4893 add ability to reselect file (#3924)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-21 16:52:52 +08:00
sergeyteleshevandDaria Marutkina 8b08927af8 [CB] Context menu corrupted on Windows + remove delete shortcut (#3912)
* dbeaver/pro#7024 fixes corrupted layout + removes delete shortcut from erase action

* removes delete binding for "remove column filter" + adds translations

* adds quotes for columns

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-21 16:51:57 +08:00
7ea627a24f dbeaver/pro#7547 do not refresh user state on server config save (#3922)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2025-11-21 09:24:22 +01:00
Ruslan MusaevandEvgenia dc5f86c40c Dbeaver/pro#7192 ldap case insensitivity (#3907)
* dbeaver/pro#7192 ldap case insensitivity

* dbeaver/pro#7192 ldap case-insensitive matching

* dbeaver/pro#7192 case insensitive from auth descriptor

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-21 09:23:28 +01:00
24b7b6021d Add reorder option to tabs #4583 (#3872)
* dbeaver/pro#4583 feat: add tab reordering functionality in NavigationTabsService

* dbeaver/pro#4583 feat: update tab filtering to support tabs order

* dbeaver/pro#4583 feat: add tab drag-and-drop functionality and associated tests

* dbeaver/pro#4583 feat: implement tab drag-and-drop functionality

* dbeaver/pro#4583 fix: adjust tab reordering logic to handle dragged tab position correctly

since our reordering logic is duplicating in two components and, moreover, I found a bug in one of the implementations, I decided to put all the common logic to a dedicated helper with test coverage to be sure we use the right logic everywhere.

* dbeaver/pro#4583 test: remove redundant tests for onReorder in useTabDragAndDrop

* dbeaver/pro#4583 refactor: update reorderTab method to remove return value

* dbeaver/pro#4583 feat: add tab reordering functionality to SqlResultTabs component

* dbeaver/pro#4583 feat: add tabs order persistence via useUserData hook

* dbeaver/pro#4583 feat: integrate react-dnd for tab drag-and-drop functionality

* dbeaver/pro#4583 chore: update deps

* dbeaver/pro#4583 feat: integrate @dbeaver/js-helpers for array reordering in useHeaderDnD

* dbeaver/pro#4583 refactor: simplify reorderArray implementation by extracting some logic to helpers

* dbeaver/pro#4583 refactor: extracted tab reordering to a separate hook

- moved the tab order persistence logic into a new useTabOrderPersistence hook to make the flow is more obvious (we use this default persistence only with tabs container)
- introduced new reorderStateKey tab state to have an identifier for a tab panel, use it in canDrop check to prevent cross-panel DnD
-replaced reorderable prop with reorderStateKey, because without a key you can't use reordering

* dbeaver/pro#4583 refactor: add dropAllowed state to useTabDragAndDrop for improved drag-and-drop UI

* dbeaver/pro#4583 chore: add a comment about reorderStateKey

* dbeaver/pro#4583 refactor: add safe guard in SqlResultTabs.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* dbeaver/pro#4583 refactor: wrap onReorder function into useCallback for improved performance

* dbeaver/pro#4583 refactor: remove applyOrder method from ITabsContainer and update useTabOrderPersistence to not have dependencies on container

* dbeaver/pro#4583 refactor: use refactored hook in SideBarPanel and ToolsPanel

Since we are not mutating a container state anymore, we have to update sorting separately in TabList level because it reads directly from the container.

* dbeaver/pro#4583 refactor: remove fallback for stateKey

* dbeaver/pro#4583 refactor: replace hardcoded values with constants in useTabDragAndDrop

* dbeaver/pro#4583 refactor: add DragPosition type

* dbeaver/pro#4583 refactor: remove dependencies in TabsState effect

* dbeaver/pro#4583 refactor: remove stateKey from useTab hook

* dbeaver/pro#4583 feat: add panelId prop to SideBarPanel and use as persistence key

* dbeaver/pro#4583 refactor: persistence key in useTabOrderPersistence

* dbeaver/pro#4583 feat: add reorderArrayWithOrder helper and update SqlResultTabs to use it

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-11-20 19:25:04 +08:00
Sychev Andrey 2f64827ad4 dbeaver/pro#7549 refactor: adjust padding and color for disabled sele… (#3921)
* dbeaver/pro#7549 refactor: adjust padding and color for disabled select state to unify view between disabled controls

unify icon view as well

* dbeaver/pro#7549 style: fix if clause
2025-11-20 17:46:42 +08:00
Alexey PotsetsuevandEvgenia e443a88ae7 dbeaver/pro#5818 fix: error message (#3920)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-20 15:43:49 +08:00
Alexey PotsetsuevandEvgenia f46857fe6d dbeaver/pro#6188 fix: loader styles (#3918)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-20 15:42:45 +08:00
alexandEvgenia 11f8568238 dbeaver/pro#5581 use new method to check if user is presenter (#3917)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-20 15:42:09 +08:00
alexandEvgenia b81c7732d1 dbeaver/pro#7282 show alert for admin team in connection tab (#3911)
* dbeaver/pro#7282 show alert for admin team in connection tab

* dbeaver/pro#7282 use team part to get permissions

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-20 15:41:37 +08:00
3592e77851 dbeaver/pro#7171 fixes incorrect padding for comboboxes (#3887)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-11-20 15:40:56 +08:00
4b0e8b5e91 dbeaver/pro#7514 fix: sql query segment end inclusion (#3909)
* dbeaver/pro#7514 fix: sql query segment end inclusion

* fix: query execution in new tab

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-11-19 15:55:50 +01:00
Alexander SkoblikovandMashaKorax 802a3e69fe dbeaver/dbeaver#39682 fix -help command (#3915)
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2025-11-19 13:40:15 +01:00
Ruslan MusaevandEvgenia d409aad739 dbeaver/pro#7473 fix ldap port saving (#3910)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-19 12:44:51 +01:00
Sychev Andrey 9b01659a6f dbeaver/pro#7544 chore: replace deprecated VSCode intelliCode extension with GitHub Copilot Chat (#3919) 2025-11-19 18:46:37 +08:00
sergeyteleshevandEvgenia 354f13d0d4 [CB] put new user to top after creation (#3903)
* dbeaver/pro#5085 sorts users alphabetically + shows new users on top

* reverts sorting for enabled and disabled users (required backend to sort properly)

* cleanup

* removes repetitive filters and sorting

* simplifies users logic + user.createdAt is number type

* refactor: sortUsersById -> compareUsers

* compare new users

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-19 16:51:19 +08:00
Sychev AndreyandEvgenia e39b1b1ee2 dbeaver/pro#5833 feat: add animation for enabling pointer events when content is in place (#3908)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-19 16:50:34 +08:00
sergeyteleshev f818fb1c85 [CB] TE too many requests navNodeInfo (connection edit) which freezes interface (#3894)
* dbeaver/pro#7480 fix: preloads only connection info by key (not all) to prevent connectioInfoResource loading of all data and all following handlers actions

* adds console error and return empty array for origin info loader

* warning instead of error
2025-11-18 19:36:40 +08:00
AinurandDaria Marutkina 6253d3e543 dbeaver/cloudbeaver#3736 filter non root empty folders fix (#3902)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-18 11:16:30 +01:00
AinurandEvgenia c7a6cc967e dbeaver/pro#7164 fix test connection without saved creds (#3906)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-18 11:15:33 +01:00
Ainur c3ac257fdd dbeaver/pro#7474 fix stack overflow on gql extended logs (#3895) 2025-11-18 11:12:18 +01:00
sergeyteleshevandDaria Marutkina c9b1278788 dbeaver/pro#7316 changes order of inputs in folder dialog (#3897)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-18 17:11:34 +08:00
alexandEvgenia 58d316f396 dbeaver/pro#5591 add padding to popup (#3883)
* dbeaver/pro#5591 add padding to popup

* dbeaver/pro#5591 sanitize input

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-18 17:10:48 +08:00
sergeyteleshevandDaria Marutkina 37e5e1865c [CB] [f] invalid dialog name when export VQB diagram (#3889)
* dbeaver/pro#7419 renames header for export image dialog

* fixes text case for eng locale

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-18 17:10:28 +08:00
alexandDaria Marutkina 8cfe6a98e5 dbeaver/pro#7382 change combobox to select (#3888)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-18 17:09:50 +08:00
Alexey PotsetsuevandDaria Marutkina 20026d1d30 dbeaver/pro#7389 fix: save confirmation (#3904)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-17 23:07:26 +08:00
Daria Marutkina 7c72232215 dbeaver/pro#7470 Update README file (#3905) 2025-11-17 12:38:09 +01:00
alexandEvgenia 64118de61c dbeaver/pro#5094 use safe property access (#3893)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-17 19:10:16 +08:00
Alexey PotsetsuevandDaria Marutkina 17331e6a8f dbeaver/pro#6595 fix: pagination cache (#3886)
dbeaver/pro#6988 fix: allow empty query in VQB
dbeaver/pro#6512 fix: remove bind session from easy config
dbeaver/pro#6734 fix: allowed server urls description

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-17 18:52:10 +08:00
dbeaver-devops 46079dea6a dbeaver/pro#7470 25.3.0 version bump (#3890) 2025-11-12 20:07:34 +01:00
Serge Rider d8f2df0564 dbeaver/pro#7455 Remove redundant errors re-throws (#3885) 2025-11-12 17:10:09 +01:00
alexandDaria Marutkina f64bc1b435 dbeaver/pro#7359 add support service (#3878)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-12 23:35:01 +08:00
754927a1ae dbeaver/pro#6363 adds settings for dangerous queries dialogs (#3858)
* dbeaver/pro#6363 adds settings for dangerous queries dialogs

* cleanup

* dbeaver/pro#6363 Added confirmation preference checks

* dbeaver/pro#6363 Added defaults

* Revert "cleanup"

This reverts commit a1e16ae5a1.

* Revert "dbeaver/pro#6363 adds settings for dangerous queries dialogs"

This reverts commit a88ca77049.

* dbeaver/pro#6363 Moved properties to config

* dbeaver/pro#6363 adds default server settings service

* refactor: split `SettingsSource` into `SettingsSource` and `EditableSettingsSource`

* dbeaver/pro#6363 message improvement

* dbeaver/pro#6363 increase timeout

* fix: edited value fallback

* dbeaver/pro#6363 fix defaults

* dbeaver/pro#6363 fix localization labels/descriptions

* fix: localization change

* fix: settings label collision

* dbeaver/pro#6363 fixed auth provider localizations

---------

Co-authored-by: Dmitrii Barnukov <dmitrii.barnukov@dbeaver.com>
Co-authored-by: Wroud <wrouds@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
2025-11-12 16:27:19 +01:00
Dmitrii Barnukov cebbe2a89b dbeaver/cloudbeaver#3873 fix datafilter for a single filtered row (#3884) 2025-11-12 15:27:28 +01:00
alexandDaria Marutkina 8e44be20b0 dbeaver/pro#6525 add search to schema and connection lists (#3875)
* dbeaver/pro#6525 add search to schema and connection lists

* dbeaver/pro#6525 filter by real entities

* dbeaver/pro#6525 unify logic

* dbeaver/pro#6525 continue if excluded

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-11 16:58:18 +08:00
Serge Rider f37a753834 dbeaver/pro#7349 Redshift 2.2 (#3881)
* dbeaver/pro#7349 Statements close + error handling

* dbeaver/pro#7349 Use fetch workflow. It closes statement before fetchEnd call

* dbeaver/pro#7349 Code style and annotations

* dbeaver/pro#7349 Fix resultet fetch in CB
2025-11-10 18:50:04 +01:00
AinurandEvgenia 348fe0c9df dbeaver/pro#7343 add additional validation for token auth (#3876)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-10 13:37:01 +01:00
Serge Rider a62b528018 Revert "dbeaver/pro#6739 update clickhouse version (#3871)"
This reverts commit e1dc245446.
2025-11-10 09:29:49 +01:00
GeorgiiandSerge Rider e1dc245446 dbeaver/pro#6739 update clickhouse version (#3871)
Co-authored-by: Serge Rider <serge@jkiss.org>
2025-11-08 21:26:32 +01:00
AinurandEvgenia 4bbb9ccb93 dbeaver/pro#7364 update sql server driver to 13.2.1 (#3870)
* dbeaver/pro#7364 update sql server driver to 12.8.2

* dbeaver/pro#7364 update sql server driver to 13.2.1

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-07 10:20:12 +01:00
e60ea477f5 dbeaver/pro#6745 add viewer for request query (#3868)
* dbeaver/pro#6745 add viewer for request query

* dbeaver/pro#6745 remove duplicated import

* dbeaver/pro#6745 remove extra margin

* dbeaver/pro#6745 fallback to options query

---------

Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-06 22:14:35 +08:00
serge-rider 493424ce7d Merge remote-tracking branch 'origin/devel' into devel 2025-11-06 07:09:45 +01:00
serge-rider d59f06276c dbeaver/dbeaver#39381 Deps versions update 2025-11-06 07:09:03 +01:00
9593b7a6c3 dbeaver/pro#7210 feat: color palette (#3854)
* dbeaver/pro#7081 feat: conditional formatting for data editor

* feat: code cleanup after review + reworked rules management

* feat: add SVG icons for conditional formatting and update localization strings

* chore: after review

* feat: refactor conditional formatting presentation and add transition component

* refactor: reorder Props interface fields for consistency

* refactor: remove unused onAddRule prop from ColorScaleRuleForm and SingleColorRuleForm

* feat: add SVG icon for conditional formatting

* fix: charts

* feat: refactor conditional formatting utilities and improve code organization

* fix: localization

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* chore: hide conditional formatting from UI

* dbeaver/pro#7210 feat: color palette

* chore: codestyle

* chore: pr review, use `color` library for manipulating css colors

* chore: revert panel hiding

* chore: localization

* fix: use custom row renderer with mobx observer

* fix: line hightlight style

* chore: remove unused types

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-11-05 13:52:23 +01:00
alexandEvgenia 0955c18e38 dbeaver/pro#7237 change label (#3869)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-05 15:06:59 +08:00
Ruslan Musaev dd3f00a3a8 dbeaver/pro#7356 fix escaping special symbols for group search in LDAP (#3865) 2025-11-04 19:45:21 +01:00
Ruslan MusaevandEvgenia 22c7f0d3f2 Dbeaver/pro#6200 mask secret paramters (#3828)
* dbeaver/pro#6976 poc log masking

* dbeaver/pro#6364 mask secret params

* dbeaver/pro#6200 add masking annotation for sensitive parameters

* dbeaver/pro#6200 add masking annotation for sensitive parameters

* dbeaver/pro#6200 add masking annotation for sensitive parameters

* dbeaver/pro#6200 add masking annotation for sensitive parameters

* dbeaver/pro#6200 add masking annotation for sensitive parameters

* dbeaver/pro#6200 add masking annotation for sensitive parameters

* dbeaver/pro#6200 add masking annotation for sensitive parameters

* dbeaver/pro#7263 add log about count current users

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-04 19:42:36 +01:00
alexandDaria Marutkina 5997d4b1a2 dbeaver/pro#6096 add execute queries method (#3855)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-04 19:12:27 +08:00
cdae6efbd2 dbeaver/pro#7081 feat: conditional formatting for data editor (#3838)
* dbeaver/pro#7081 feat: conditional formatting for data editor

* feat: code cleanup after review + reworked rules management

* feat: add SVG icons for conditional formatting and update localization strings

* chore: after review

* feat: refactor conditional formatting presentation and add transition component

* refactor: reorder Props interface fields for consistency

* refactor: remove unused onAddRule prop from ColorScaleRuleForm and SingleColorRuleForm

* feat: add SVG icon for conditional formatting

* fix: charts

* feat: refactor conditional formatting utilities and improve code organization

* fix: localization

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* chore: hide conditional formatting from UI

* fix: use custom row renderer with mobx observer

* fix: line hightlight style

* chore: remove unused types

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-11-03 19:11:50 +08:00
Daria Marutkina ced66cd5e0 dbeaver/pro#7168 Update README file (#3864)
* dbeaver/pro#7168 Update README file

* Update README.md
2025-11-03 11:35:15 +01:00
mayerro c71639007e dbeaver/dbeaver-devops#2221 base java repo is nexus 2025-10-20 11:27:42 +02:00
867 changed files with 22042 additions and 6961 deletions
+2 -2
View File
@@ -15,11 +15,11 @@
"streetsidesoftware.code-spell-checker",
"streetsidesoftware.code-spell-checker-russian",
"syler.sass-indented",
"VisualStudioExptTeam.vscodeintellicode",
"github.copilot-chat",
"yzhang.markdown-all-in-one",
"GraphQL.vscode-graphql-syntax",
"mrmlnc.vscode-json5",
"esbenp.prettier-vscode",
"devnaumov.licensify"
]
}
}
+42 -42
View File
@@ -1,4 +1,4 @@
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/cloudbeaver-logo.png" align="right" width="250"/>
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/cloudbeaver-logo.png" alt="CloudBeaver logo" align="right" width="250"/>
# CloudBeaver Community
@@ -25,52 +25,52 @@ You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io
## Changelog
### 25.2.3 2025-10-20
- Added visualization for column reordering in the Data Editor;
- Fixed the critical issue with duplicating the execution of queries executed by a shortcut in the SQL Editor;
- Fixed the critical vulnerability (CVE-2025-61927) in the Happy DOM library. The library was updated to version 20.0.2.
### 25.3.2 2026-01-05
### 25.2.2 2025-10-06
- Replaced true/false text with intuitive checkboxes for boolean values in the metadata editor, making editing easier and faster;
- Keyboard navigation was improved across the application. Additional shortcuts are available in the main info panel (top-right corner). To help you discover them faster, relevant keyboard shortcuts are also shown directly in the tooltips of Data Editor buttons;
- Fixed an issue in the Data Editor where resizing one column in a wide table would cause previously resized columns to revert to their original width. Users can resize multiple columns as intended.
- The ability to pin columns has been added to the Data Editor, allowing users to keep key columns (e.g., IDs, names) visible while scrolling through wide tables;
- Added a new setting in the SQL Editor to highlight spaces, tabs, and other whitespace characters to help users read, debug, and maintain their scripts. It is turned off by default and can be configured in personal preferences;
- Redesigned the connection configuration page. Form fields and sections have been reorganized to provide more space for input and reduce visual clutter;
- Clickhouse: driver has been updated to version 0.9.5;
- Oracle: Added a new "Set Username to OS_USER" option in the Misc section of Oracle connection settings. When turned on, the tool automatically uses the current database username as the operating system user identifier in the session metadata.
### 25.2.1 2025-09-22
- Autocomplete values in SQL Editor are ordered alphabetically to simplify object search;
- "Show all databases" setting was added on the connection page for MySQL and MariaDB;
- Different bug fixes and enhancements have been made.
### 25.3.1 2025-12-22
### 25.2.0 2025-09-01
### Changes since 25.1.0:
- Administration:
- Added support for multiple server URLs to accommodate different access policies for internal and external users, improving flexibility in network-restricted environments. To set it up, use the Allowed Server URLs field in the Server configuration within the Administration section;
- Added the Force HTTPS mode setting in Server Configuration to enforce redirecting from HTTP to HTTPS. This setting helps avoid a potential man-in-the-middle attack. This setting is turned off by default. Please remember to configure your proxy before enabling;
- Added the CLOUDBEAVER_BIND_SESSION_TO_IP option to improve session security by linking user sessions to their IP address. It is disabled by default. When enabled, this helps protect against certain types of session hijacking attacks, where an attacker could try to take over a user’s session. Note: Users will be logged out automatically on the IP address change (switching networks or using mobile data);
- A password confirmation field has been added for administrators in the Easy Config section to prevent accidental misconfigurations.
- SQL Editor:
- Changed the default engine used for autocompletion in the SQL Editor. This Semantic engine offers improved suggestions for database objects, keywords, and functions. You can switch back to the Legacy engine in Preferences > SQL Editor;
- A “Clear” button was added to the output panel.
- Data Editor:
- Column descriptions were added in the Data Editor under column names to provide more metadata context. You can disable this in Preferences > Data Viewer;
- Added an ability to review the script before execution when users edit tables without primary keys.
- General:
- Added a new welcome screen for a freshly opened application. This screen contains shortcuts to create a new connection, open SQL Editor, or documentation;
- Added a search panel for SQL Editor and Value panel: press Ctrl/Cmd + F to open a panel that allows searching and replacing by keyword or regular expression;
- Changed the data transfer mechanism to avoid intermediate file creation. The parameter dataExportFileSizeLimit was removed from the server configuration as deprecated;
- The CloudBeaver default theme matches the device theme by default now. You can change this behavior in user preferences under the Theme section;
- Added the option to display tabs across multiple rows, allowing you to see all tabs without scrolling. You can enable this in Preferences > Interface;
- Improved dark theme accessibility by enhancing color contrast across the app for better readability and compliance with accessibility standards;
- The database navigator now automatically hides empty folders in shared projects, reducing visual clutter and speeding up the process of finding active connections.
- Databases and drivers:
- Clickhouse: fixed the presentation of tuples and map data types in the Data Editor;
- Databend database support has been added (thanks to @hantmac);
- DuckDB: driver has been updated to version 1.3;
- MySQL: Improved performance when retrieving foreign keys metadata;
- PostgreSQL: fixed misplaced comment for table DDL generation.
- Implemented an ability to add parameters and variables to queries in the SQL Editor. This feature allows for the reuse of queries by changing parameters at the execution stage. It is enabled by default and can be configured in personal preferences;
- Added support for mapping users to CloudBeaver teams based on LDAP memberOf group membership;
- Added support for previous browser versions, but not older than three years.
### 25.3.0 2025-12-01
### Changes since 25.2.0:
- Administration:
- Permission configuration for connections has been simplified, providing a clearer appearance and improved performance;
- LDAP authentication now supports case-insensitive Distinguished Names (DNs), allowing login with mixed-case DNS.
- Security:
- Fixed a security issue in the Spatial Viewer for the HTML content to prevent a third-party script execution;
- Fixed the critical vulnerability (CVE-2025-61927) in the Happy DOM library. The library was updated to version 20.0.2.
- SQL Editor:
- A confirmation prompt has been added to the SQL Editor to prevent accidental execution of potentially dangerous queries. You can customize this behavior in Preferences > SQL Editor;
- A search field has been added to the catalog/schema selector on the main toolbar for easy navigation;
- Alphabetical order has been added to the autocomplete values in the SQL Editor to simplify object search;
- Added support for temporary tables in semantic analysis.
- Data Editor:
- The ability to review the database query after applying filters and sorting in the Data Editor, Grouping panel, and Result sets has been added. The new Show SQL button is placed on the Filter panel;
- The value panel in the Data Editor has been redesigned to give users more space for viewing and editing data. Tab buttons for different data type visualizations are now aligned on a single level, creating a cleaner layout;
- Added visualization for column reordering in the Data Editor;
- Fixed an issue in the Data Editor where resizing one column in a wide table caused previously resized columns to revert to their original width. Users can resize multiple columns as intended.
- General:
- A reorder tabs option has been added to the application, allowing users to set editor tabs in custom positions;
- Keyboard navigation has been improved across the application. Additional shortcuts are available in the main info panel (top-right corner) to help discover them faster. Relevant keyboard shortcuts are also shown in the tooltips of Data Editor buttons;
- Boolean values have been replaced with intuitive checkboxes instead of the true/false text in the metadata editor, making editing easier and faster;
- The database navigator now automatically hides empty subfolders in shared projects, reducing visual clutter;
- Databases and drivers:
- ClickHouse: query canceling support has been added;
- DuckDB: fixed an issue where catalog selection was lost upon disconnection;
- PostgreSQL: fixed support for GIS data types;
- SQL Server: the driver has been updated to version 13.2.1;
- The "Show all databases" setting has been added on the connection page for MySQL and MariaDB.
## Contribution
As a community-driven open-source project, we warmly welcome contributions through GitHub pull requests.
[We are happy to reward](https://dbeaver.com/help-dbeaver/) our most active contributors every major sprint.
The most significant contribution to our code for the major release 25.2.0 was made by:
1. [hantmac](https://github.com/hantmac) - added support for Databend in CloudBeaver Community Edition.
+2 -1
View File
@@ -1,5 +1,6 @@
ARG BASE_JAVA_REPO="dbeaver/base-java"
ARG BASE_JAVA_TAG="stable"
FROM dbeaver/base-java:${BASE_JAVA_TAG}
FROM $BASE_JAVA_REPO:$BASE_JAVA_TAG
MAINTAINER DBeaver Corp, devops@dbeaver.com
@@ -0,0 +1,13 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model.cli;singleton:=true
Bundle-Version: 1.0.2.qualifier
Bundle-Release-Date: 20260119
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.model.cli;visibility:=reexport
Export-Package: io.cloudbeaver.model.cli
Automatic-Module-Name: io.cloudbeaver.model.cli
Import-Package: org.slf4j
@@ -0,0 +1,5 @@
source.. = src/
output.. = target/classes/
bin.includes = .,\
META-INF/,\
plugin.xml
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
</plugin>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.cloudbeaver</groupId>
<artifactId>bundles</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model.cli</artifactId>
<version>1.0.2-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -23,13 +23,23 @@ import org.jkiss.dbeaver.model.cli.model.CommandLineAuthenticator;
import picocli.CommandLine;
public class CloudBeaverCommandLine extends ApplicationCommandLine<ApplicationInstanceController> {
@Nullable
private final CommandLineAuthenticator authenticator;
private final CloudBeaverMixin mixin;
public CloudBeaverCommandLine(@Nullable CommandLineAuthenticator authenticator) {
super();
this.authenticator = authenticator;
this.mixin = new CloudBeaverMixin();
}
public CloudBeaverCommandLine(
@Nullable CommandLineAuthenticator authenticator,
@NotNull CloudBeaverMixin mixin
) {
super();
this.authenticator = authenticator;
this.mixin = mixin;
}
@Override
@@ -51,6 +61,8 @@ public class CloudBeaverCommandLine extends ApplicationCommandLine<ApplicationIn
@NotNull CommandLineContext context,
@NotNull CLIRunMeta runMeta
) {
return super.initCommandLine(applicationInstanceController, context, runMeta);
var cmd = super.initCommandLine(applicationInstanceController, context, runMeta);
cmd.addMixin("cloudbeaver", mixin);
return cmd;
}
}
@@ -32,7 +32,7 @@ public class CloudBeaverInstanceServer extends ApplicationInstanceServer<Applica
this.commandLine = new CloudBeaverCommandLine(null);
}
protected CloudBeaverInstanceServer(@NotNull CloudBeaverCommandLine commandLine) throws IOException {
public CloudBeaverInstanceServer(@NotNull CloudBeaverCommandLine commandLine) throws IOException {
super(ApplicationInstanceController.class);
this.commandLine = commandLine;
}
@@ -0,0 +1,32 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model.cli;
import picocli.CommandLine;
/**
* CloudBeaver specific mixin options
*/
public class CloudBeaverMixin {
@CommandLine.Option(
names = {"-web-config"},
arity = "1",
hidden = true,
scope = CommandLine.ScopeType.INHERIT
)
private String webConfig;
}
@@ -24,8 +24,15 @@ import org.jkiss.dbeaver.model.cli.CommandLineContext;
import org.jkiss.dbeaver.model.cli.command.AbstractTopLevelCommand;
import picocli.CommandLine;
@CommandLine.Command(name = "cbvr", description = "CloudBeaver commands", mixinStandardHelpOptions = true)
@CommandLine.Command(name = "cbvr", description = "CloudBeaver commands")
public class CloudBeaverTopLevelCommand extends AbstractTopLevelCommand {
@CommandLine.Option(
names = {NOSPASH_OPTION},
hidden = true,
scope = CommandLine.ScopeType.INHERIT
)
private boolean noSplash;
protected CloudBeaverTopLevelCommand(
@Nullable ApplicationInstanceController controller,
@NotNull CommandLineContext context,
@@ -3,13 +3,12 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
Bundle-Version: 1.0.89.qualifier
Bundle-Release-Date: 20251117
Bundle-Version: 1.0.93.qualifier
Bundle-Release-Date: 20260119
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
org.jkiss.dbeaver.model;visibility:=reexport,
org.jkiss.dbeaver.model.cli;visibility:=reexport,
org.jkiss.dbeaver.model.sm;visibility:=reexport,
org.jkiss.dbeaver.model.event;visibility:=reexport,
org.jkiss.dbeaver.model.nio;visibility:=reexport,
@@ -26,17 +25,16 @@ Export-Package: io.cloudbeaver,
io.cloudbeaver.auth.provider.local,
io.cloudbeaver.auth.provisioning,
io.cloudbeaver.websocket,
io.cloudbeaver.websocket.event.client,
io.cloudbeaver.websocket.event.task,
io.cloudbeaver.model,
io.cloudbeaver.model.app,
io.cloudbeaver.model.config,
io.cloudbeaver.model.apilog,
io.cloudbeaver.model.cli,
io.cloudbeaver.model.events,
io.cloudbeaver.model.fs,
io.cloudbeaver.model.log,
io.cloudbeaver.model.rm,
io.cloudbeaver.model.rm.local,
io.cloudbeaver.model.rm.lock,
io.cloudbeaver.model.session,
io.cloudbeaver.model.user,
io.cloudbeaver.model.utils,
@@ -25,4 +25,27 @@
label="Default secret controller" description="Default void secret controller"/>
</extension>
<extension point="org.jkiss.dbeaver.ws.event">
<event topicId="cb_session_task" id="cb_session_task_info_updated"
class="io.cloudbeaver.websocket.event.task.WSSessionTaskInfoEvent"/>
<event topicId="cb_session_task" id="cb_session_task_confirmation_request"
class="io.cloudbeaver.websocket.event.task.WSSessionTaskConfirmationRequestEvent"/>
<event topicId="cb_session_task" id="cb_session_task_query_confirmation_request"
class="io.cloudbeaver.websocket.event.task.WSSessionTaskQueryConfirmationRequestEvent"/>
<event topicId="cb_session_task" id="cb_session_task_query_params_confirmation_request"
class="io.cloudbeaver.websocket.event.task.WSSessionTaskQueryParamsConfirmationEvent"/>
<clientEvent id="cb_client_session_ping" class="io.cloudbeaver.websocket.event.client.WSSessionPingClientEvent"/>
<clientEvent id="cb_client_topic_subscribe" class="io.cloudbeaver.websocket.event.client.WSSubscribeOnTopicClientEvent"/>
<clientEvent id="cb_client_topic_unsubscribe"
class="io.cloudbeaver.websocket.event.client.WSUnsubscribeFromTopicClientEvent"/>
<clientEvent id="cb_client_projects_active"
class="io.cloudbeaver.websocket.event.client.WSUpdateActiveProjectsClientEvent"/>
<clientEvent id="cb_client_session_task_confirmation"
class="io.cloudbeaver.websocket.event.client.WSSessionTaskConfirmationEvent"/>
<clientEvent id="cb_client_session_task_with_parameters_confirmation"
class="io.cloudbeaver.websocket.event.client.WSSessionTaskWithParametersConfirmationEvent"/>
</extension>
</plugin>
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model</artifactId>
<version>1.0.89-SNAPSHOT</version>
<version>1.0.93-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -18,6 +18,7 @@ package io.cloudbeaver;
import io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.app.WebAppConfiguration;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import org.jkiss.code.NotNull;
@@ -32,8 +33,10 @@ public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C ex
private static final Log log = Log.getLog(WebConnectionConfigInputHandler.class);
protected final T input;
protected final DBPDataSourceRegistry registry;
protected final WebSession webSession;
public WebConnectionConfigInputHandler(@NotNull DBPDataSourceRegistry registry, T configInput) {
public WebConnectionConfigInputHandler(@NotNull WebSession webSession, @NotNull DBPDataSourceRegistry registry, T configInput) {
this.webSession = webSession;
this.registry = registry;
this.input = configInput;
}
@@ -52,6 +55,7 @@ public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C ex
}
WebDataSourceUtils.saveAuthProperties(
webSession.getProgressMonitor(),
newDataSource,
newDataSource.getConnectionConfiguration(),
input.getCredentials(),
@@ -81,6 +85,7 @@ public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C ex
input
);
WebDataSourceUtils.saveAuthProperties(
webSession.getProgressMonitor(),
dataSource,
dataSource.getConnectionConfiguration(),
input.getCredentials(),
@@ -24,53 +24,46 @@ import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPAdaptable;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.DBPObjectSettingsProvider;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistryCache;
import org.jkiss.dbeaver.model.navigator.DBNModel;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMUtils;
import org.jkiss.dbeaver.model.security.SMControllerUtils;
import org.jkiss.dbeaver.model.security.SMObjectType;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceEvent;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceRegistry;
import org.jkiss.dbeaver.registry.project.BaseProjectSettings;
import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
import org.jkiss.utils.CommonUtils;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.*;
public class WebSessionProjectImpl extends WebProjectImpl {
public class WebSessionProjectImpl extends WebProjectImpl implements DBPAdaptable {
private static final Log log = Log.getLog(WebSessionProjectImpl.class);
protected final WebSession webSession;
private final Map<String, WebConnectionInfo> connections = new HashMap<>();
private final Map<String, Object> projectSettings = new ConcurrentHashMap<>();
private final BaseProjectSettings projectSettings;
private boolean registryIsLoaded = false;
public WebSessionProjectImpl(
@NotNull WebSession webSession,
@NotNull RMProject project
) {
super(
webSession.getWorkspace(),
webSession.getRmController(),
webSession.getSessionContext(),
project,
webSession.getUserPreferenceStore(),
RMUtils.getProjectPath(project)
);
this.webSession = webSession;
this(webSession, project, null);
}
public WebSessionProjectImpl(
@NotNull WebSession webSession,
@NotNull RMProject project,
@NotNull Path path
@Nullable Path path
) {
super(
webSession.getWorkspace(),
@@ -78,9 +71,36 @@ public class WebSessionProjectImpl extends WebProjectImpl {
webSession.getSessionContext(),
project,
webSession.getUserPreferenceStore(),
path
path == null ? RMUtils.getProjectPath(project) : path
);
this.webSession = webSession;
this.projectSettings = new BaseProjectSettings(this) {
@NotNull
@Override
protected Map<SMObjectType, Map<String, Map<String, String>>> loadAllProjectSettings() throws DBException {
if (webSession.getUser() == null) {
return new LinkedHashMap<>();
}
return SMControllerUtils.getObjectSettingsMap(WebSessionProjectImpl.this, webSession.getSecurityController());
}
@Override
protected void saveProjectSettings(
@NotNull SMObjectType objectType,
@NotNull String objectId,
@NotNull Map<String, String> settings
) throws DBException {
if (webSession.getUserContext().isNonAnonymousUserAuthorizedInSM()) {
webSession.getSecurityController().setObjectSettings(getId(), objectType, objectId, settings);
}
}
};
}
@NotNull
public BaseProjectSettings getProjectSettings() {
return projectSettings;
}
@Nullable
@@ -89,6 +109,14 @@ public class WebSessionProjectImpl extends WebProjectImpl {
return webSession.getNavigatorModel();
}
@Override
public <T> T getAdapter(@NotNull Class<T> adapter) {
if (adapter == DBPObjectSettingsProvider.class) {
return adapter.cast(projectSettings);
}
return null;
}
@NotNull
@Override
protected DBPDataSourceRegistry createDataSourceRegistry() {
@@ -244,20 +272,6 @@ public class WebSessionProjectImpl extends WebProjectImpl {
return sendDataSourceUpdatedEvent;
}
public void refreshProjectSettings() throws DBException {
if (webSession.getUser() == null) {
projectSettings.clear();
return;
}
Map<String, Object> loadedSettings = webSession.getSecurityController().getObjectSettings(
getId(),
SMObjectType.project,
null
);
projectSettings.clear();
projectSettings.putAll(loadedSettings);
}
@NotNull
public WebConnectionInfo createConnectionInfo(@NotNull DBPDataSourceContainer dataSourceDescriptor) {
return new WebConnectionInfo(webSession, dataSourceDescriptor);
@@ -268,7 +282,7 @@ public class WebSessionProjectImpl extends WebProjectImpl {
if (CommonUtils.isEmpty(configMap)) {
throw new DBWebException("Connection configuration parameters are missing");
}
DBPDataSourceContainer newDataSource = getDataSourceContainerFromInput(configMap);
DBPDataSourceContainer newDataSource = getDataSourceContainerFromInput(getConnectionConfigInput(configMap));
return addDataSourceToProject(newDataSource);
}
@@ -302,7 +316,7 @@ public class WebSessionProjectImpl extends WebProjectImpl {
webSession.addInfoMessage("Update connection - " + WebDataSourceUtils.getConnectionContainerInfo(dataSource));
DBPDataSourceRegistry registry = getDataSourceRegistry();
getInputConfigHandler(configMap).updateDataSource(dataSource);
getInputConfigHandler(config).updateDataSource(dataSource);
connectionInfo.setCredentialsSavedInSession(null);
try {
registry.updateDataSource(dataSource);
@@ -330,26 +344,16 @@ public class WebSessionProjectImpl extends WebProjectImpl {
@NotNull
private WebConnectionInfo closeAndDeleteConnection(@NotNull WebConnectionInfo connectionInfo) throws DBWebException {
DBPDataSourceContainer dataSourceContainer = connectionInfo.getDataSourceContainer();
boolean disconnected = WebDataSourceUtils.disconnectDataSource(webSession, dataSourceContainer);
WebDataSourceUtils.disconnectDataSource(webSession, dataSourceContainer);
DBPDataSourceRegistry registry = getDataSourceRegistry();
registry.removeDataSource(dataSourceContainer);
try {
registry.checkForErrors();
} catch (DBException e) {
try {
registry.addDataSource(dataSourceContainer);
} catch (DBException ex) {
log.error("Error re-adding after delete attempt", e);
}
throw new DBWebException("Failed to delete connection", e);
}
removeConnection(dataSourceContainer);
return connectionInfo;
}
@NotNull
public DataSourceDescriptor getDataSourceContainerFromInput(@NotNull Map<String, Object> configMap) throws DBWebException {
return getInputConfigHandler(configMap).createDataSourceContainer();
public DataSourceDescriptor getDataSourceContainerFromInput(@NotNull WebConnectionConfig configInput) throws DBWebException {
return getInputConfigHandler(configInput).createDataSourceContainer();
}
@NotNull
@@ -358,8 +362,8 @@ public class WebSessionProjectImpl extends WebProjectImpl {
}
@NotNull
protected WebConnectionConfigInputHandler getInputConfigHandler(@NotNull Map<String, Object> configMap) {
return new WebConnectionConfigInputHandler<>(getDataSourceRegistry(), getConnectionConfigInput(configMap));
protected WebConnectionConfigInputHandler getInputConfigHandler(@NotNull WebConnectionConfig configInput) {
return new WebConnectionConfigInputHandler<>(webSession, getDataSourceRegistry(), configInput);
}
@@ -74,4 +74,8 @@ public interface SMAuthProviderExternal<AUTH_SESSION extends SMSession> extends
*/
default void postAuthentication() {}
@Override
default boolean supportsOpeningSessionAsChild() {
return true;
}
}
@@ -42,7 +42,7 @@ public class WebConnectionConfig {
private String databaseName;
private String url;
private int keepAliveInterval;
private Integer keepAliveInterval;
private String name;
private String description;
@@ -53,15 +53,15 @@ public class WebConnectionConfig {
private String authModelId;
private Map<String, Object> credentials;
private boolean saveCredentials;
private boolean sharedCredentials;
private Boolean saveCredentials;
private Boolean sharedCredentials;
private Map<String, Object> mainPropertyValues;
private Map<String, Object> expertSettingsValues;
private Map<String, Object> providerProperties;
private List<WebNetworkHandlerConfigInput> networkHandlersConfig;
private DBPDriverConfigurationType configurationType;
private String selectedSecretId;
private boolean defaultAutoCommit;
private Boolean defaultAutoCommit;
private String defaultCatalogName;
private String defaultSchemaName;
@@ -122,102 +122,178 @@ public class WebConnectionConfig {
return connectionId;
}
public void setConnectionId(String connectionId) {
this.connectionId = connectionId;
}
@Property
public String getDriverId() {
return driverId;
}
public void setDriverId(String driverId) {
this.driverId = driverId;
}
@Property
public boolean isReadOnly() {
return readOnly;
}
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
@Property
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Property
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Property
public String getFolder() {
return folder;
}
public void setFolder(String folder) {
this.folder = folder;
}
@Property
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
@Property
public String getPort() {
return port;
}
public void setPort(String port) {
this.port = port;
}
@Property
public String getServerName() {
return serverName;
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
@Property
public String getDatabaseName() {
return databaseName;
}
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
@Property
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Property
public Map<String, Object> getProperties() {
return properties;
}
public void setProperties(Map<String, Object> properties) {
this.properties = properties;
}
@Property
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Property
public String getUserPassword() {
return userPassword;
}
public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}
@Property
public String getAuthModelId() {
return authModelId;
}
public void setAuthModelId(String authModelId) {
this.authModelId = authModelId;
}
@Property
public DBPDriverConfigurationType getConfigurationType() {
return configurationType;
}
public void setConfigurationType(DBPDriverConfigurationType configurationType) {
this.configurationType = configurationType;
}
@Property
public Map<String, Object> getCredentials() {
return credentials;
}
public void setCredentials(Map<String, Object> credentials) {
this.credentials = credentials;
}
public List<WebNetworkHandlerConfigInput> getNetworkHandlersConfig() {
return networkHandlersConfig;
}
public void setNetworkHandlersConfig(List<WebNetworkHandlerConfigInput> networkHandlersConfig) {
this.networkHandlersConfig = networkHandlersConfig;
}
@Property
public boolean isSaveCredentials() {
return saveCredentials;
}
public void setSaveCredentials(boolean saveCredentials) {
this.saveCredentials = saveCredentials;
}
@Property
public boolean isSharedCredentials() {
return sharedCredentials;
}
public void setSaveCredentials(boolean saveCredentials) {
this.saveCredentials = saveCredentials;
public void setSharedCredentials(boolean sharedCredentials) {
this.sharedCredentials = sharedCredentials;
}
@Property
@@ -225,38 +301,70 @@ public class WebConnectionConfig {
return mainPropertyValues;
}
public void setMainPropertyValues(Map<String, Object> mainPropertyValues) {
this.mainPropertyValues = mainPropertyValues;
}
@Property
public Map<String, Object> getExpertSettingsValues() {
return expertSettingsValues;
}
public void setExpertSettingsValues(Map<String, Object> expertSettingsValues) {
this.expertSettingsValues = expertSettingsValues;
}
@Property
public Map<String, Object> getProviderProperties() {
return providerProperties;
}
public void setProviderProperties(Map<String, Object> providerProperties) {
this.providerProperties = providerProperties;
}
@Property
public Integer getKeepAliveInterval() {
return keepAliveInterval;
}
public void setKeepAliveInterval(int keepAliveInterval) {
this.keepAliveInterval = keepAliveInterval;
}
@Property
public Boolean isDefaultAutoCommit() {
return defaultAutoCommit;
}
public void setDefaultAutoCommit(boolean defaultAutoCommit) {
this.defaultAutoCommit = defaultAutoCommit;
}
@Nullable
public String getSelectedSecretId() {
return selectedSecretId;
}
public void setSelectedSecretId(String selectedSecretId) {
this.selectedSecretId = selectedSecretId;
}
@Property
public String getDefaultCatalogName() {
return defaultCatalogName;
}
public void setDefaultCatalogName(String defaultCatalogName) {
this.defaultCatalogName = defaultCatalogName;
}
@Property
public String getDefaultSchemaName() {
return defaultSchemaName;
}
public void setDefaultSchemaName(String defaultSchemaName) {
this.defaultSchemaName = defaultSchemaName;
}
}
@@ -341,6 +341,16 @@ public class WebConnectionInfo {
if (CommonUtils.isEmpty(authModelId)) {
authModelId = AuthModelDatabaseNative.ID;
}
// Patch auth model
DBPAuthModelDescriptor authModel = DBWorkbench.getPlatform().getDataSourceProviderRegistry().getAuthModel(authModelId);
if (authModel != null) {
DBPAuthModelDescriptor amReplace = authModel.getReplacedBy(dataSourceContainer.getDriver());
if (amReplace != null && amReplace != authModel) {
return amReplace.getId();
}
}
return authModelId;
}
@@ -20,13 +20,11 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.registry.fs.FileSystemProviderRegistry;
import org.jkiss.dbeaver.model.fs.DBFUtils;
import org.jkiss.utils.CommonUtils;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
/**
@@ -65,30 +63,20 @@ public abstract class BaseServerConfigurationController<T extends ServletServerC
if (CommonUtils.isEmpty(workspaceLocation)) {
return defaultWorkspaceLocation;
}
URI workspaceUri = URI.create(workspaceLocation);
if (workspaceUri.getScheme() == null) {
// default filesystem
return getHomeDirectory().resolve(workspaceLocation);
} else {
var externalFsProvider =
FileSystemProviderRegistry.getInstance().getFileSystemProviderBySchema(workspaceUri.getScheme());
if (externalFsProvider == null) {
log.error("File system not found for scheme: " + workspaceUri.getScheme() + " default workspace " +
"location will be used");
try {
Path externalFsPath = DBFUtils.getPathFromURI(workspaceLocation);
if (externalFsPath == null) {
log.warn("Failed to detect workspace path from URI: " + workspaceLocation +
" default workspace location will be used");
return defaultWorkspaceLocation;
}
ClassLoader fsClassloader = externalFsProvider.getInstance().getClass().getClassLoader();
try (FileSystem externalFileSystem = FileSystems.newFileSystem(workspaceUri,
System.getenv(),
fsClassloader)) {
log.info("Path from external filesystem used for workspace: " + workspaceUri);
return externalFileSystem.provider().getPath(workspaceUri);
} catch (Exception e) {
log.error("Failed to initialize workspace path: " + workspaceUri + " default workspace " +
"location will be used", e);
}
log.info("Path from external filesystem used for workspace: " + externalFsPath);
return externalFsPath;
} catch (DBException e) {
log.error("Failed to detect workspace path from URI: " + workspaceLocation +
" default workspace location will be used", e);
return defaultWorkspaceLocation;
}
return defaultWorkspaceLocation;
}
@Nullable
@@ -16,7 +16,6 @@
*/
package io.cloudbeaver.model.app;
import io.cloudbeaver.model.cli.CloudBeaverInstanceServer;
import io.cloudbeaver.model.log.SLF4JLogHandler;
import org.eclipse.core.runtime.Platform;
import org.eclipse.equinox.app.IApplicationContext;
@@ -28,7 +27,6 @@ import org.jkiss.dbeaver.model.DBFileController;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.impl.app.ApplicationRegistry;
import org.jkiss.dbeaver.model.impl.app.BaseApplicationImpl;
@@ -41,7 +39,6 @@ import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.dbeaver.utils.RuntimeUtils;
import org.jkiss.utils.CommonUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
@@ -60,7 +57,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
private static final Log log = Log.getLog(BaseServletApplication.class);
private String instanceId;
private CloudBeaverInstanceServer instanceServer;
@Override
public RMController createResourceController(
@NotNull SMCredentialsProvider credentialsProvider,
@@ -163,6 +160,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
* Method returns VoidSecretController instance.
* Advanced apps may implement it differently.
*/
@NotNull
@Override
public DBSSecretController getSecretController(
@NotNull SMCredentialsProvider credentialsProvider,
@@ -204,11 +202,6 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
public Object start(IApplicationContext context) {
initializeApplicationServices();
try {
try {
this.instanceServer = createInstanceServer();
} catch (Exception e) {
log.error("Error initializing instance server", e);
}
startServer();
} catch (Exception e) {
log.error(e.getMessage(), e);
@@ -217,12 +210,9 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
return EXIT_OK;
}
protected CloudBeaverInstanceServer createInstanceServer() throws IOException {
return new CloudBeaverInstanceServer();
}
protected abstract void startServer() throws DBException;
@NotNull
@Override
public synchronized String getApplicationInstanceId() throws DBException {
if (instanceId == null) {
@@ -247,6 +237,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
return BaseWorkspaceImpl.readWorkspaceIdProperty();
}
@NotNull
@Override
public Path getWorkspaceDirectory() {
return getServerConfigurationController().getWorkspacePath();
@@ -261,6 +252,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
}
}
@NotNull
@Override
public WSEventController getEventController() {
return null;
@@ -281,12 +273,6 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
}
}
@Nullable
@Override
public ApplicationInstanceController getInstanceServer() {
return instanceServer;
}
@Override
public boolean isAnonymousAccessEnabled() {
return getAppConfiguration().isAnonymousAccessEnabled();
@@ -17,6 +17,7 @@
package io.cloudbeaver.model.app;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.DBFileController;
import org.jkiss.dbeaver.model.app.DBPApplication;
@@ -42,14 +43,19 @@ public interface ServletApplication extends DBPApplication {
return false;
}
@NotNull
ServletAppConfiguration getAppConfiguration();
@NotNull
ServletServerConfiguration getServerConfiguration();
@NotNull
Path getDataDirectory(boolean create);
@NotNull
Path getWorkspaceDirectory();
@NotNull
Path getHomeDirectory();
boolean isMultiNode();
@@ -58,30 +64,38 @@ public interface ServletApplication extends DBPApplication {
SMAdminController getAdminSecurityController(@NotNull SMCredentialsProvider credentialsProvider) throws DBException;
@NotNull
DBSSecretController getSecretController(
@NotNull SMCredentialsProvider credentialsProvider,
SMSessionContext smSessionContext
) throws DBException;
@NotNull
RMController createResourceController(
@NotNull SMCredentialsProvider credentialsProvider,
@NotNull DBPWorkspace workspace
) throws DBException;
@NotNull
DBFileController createFileController(@NotNull SMCredentialsProvider credentialsProvider);
@Nullable
String getServerURL();
@NotNull
default String getServicesURI() {
return "/";
}
@NotNull
default String getRootURI() {
return "";
}
@NotNull
String getApplicationInstanceId() throws DBException;
@NotNull
WSEventController getEventController();
/**
@@ -94,10 +108,10 @@ public interface ServletApplication extends DBPApplication {
/**
* Collector that contains information about system.
*/
@NotNull
ServletSystemInformationCollector getSystemInformationCollector();
@Nullable
ServletSystemInformationCollector<?> getSystemInformationCollector();
default void getStatusInfo(Map<String, Object> infoMap) {
default void getStatusInfo(@NotNull Map<String, Object> infoMap) {
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@ import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import java.nio.file.Path;
public class FSUtils {
public class WebFSUtils {
@NotNull
public static String makeUniqueFsId(@NotNull DBFVirtualFileSystem fileSystem) {
return fileSystem.getType() + "://" + fileSystem.getId();
@@ -16,7 +16,6 @@
*/
package io.cloudbeaver.model.rm.local;
import io.cloudbeaver.BaseWebProjectImpl;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
@@ -28,7 +27,6 @@ import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.fs.lock.FileLockController;
import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMEvent;
import org.jkiss.dbeaver.model.rm.RMEventManager;
@@ -51,8 +49,6 @@ public abstract class BaseLocalResourceController implements RMController {
private static final Log log = Log.getLog(BaseLocalResourceController.class);
public static final String DEFAULT_CHANGE_ID = "0";
private static final String FILE_REGEX = "(?U)[\\w.$()@/\\\\ -]+";
private static final String PROJECT_REGEX = "(?U)[\\w.$()@ -]+"; // slash not allowed in project name
@NotNull
protected final DBPWorkspace workspace;
@@ -67,6 +63,7 @@ public abstract class BaseLocalResourceController implements RMController {
this.lockController = lockController;
}
@Nullable
@Override
public RMProject getProject(@NotNull String projectId, boolean readResources, boolean readProperties)
throws DBException {
@@ -85,6 +82,7 @@ public abstract class BaseLocalResourceController implements RMController {
return project;
}
@Nullable
@Override
public Object getProjectProperty(@NotNull String projectId, @NotNull String propName) throws DBException {
var project = getWebProject(projectId, false);
@@ -97,7 +95,7 @@ public abstract class BaseLocalResourceController implements RMController {
public void setProjectProperty(
@NotNull String projectId,
@NotNull String propName,
@NotNull Object propValue
@Nullable Object propValue
) throws DBException {
RMLocalProject webProject = getWebProject(projectId, false);
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
@@ -159,7 +157,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String configuration,
@Nullable List<String> dataSourceIds
) throws DBException {
try (var lock = lockController.lock(projectId, "updateProjectDataSources")) {
try (var ignoredLock = lockController.lock(projectId, "updateProjectDataSources")) {
DBPProject project = getWebProject(projectId, false);
return doFileWriteOperation(
projectId, project.getMetadataFolder(false),
@@ -190,7 +188,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String projectId,
@NotNull String[] dataSourceIds
) throws DBException {
try (var projectLock = lockController.lock(projectId, "deleteDatasources")) {
try (var ignoredLock = lockController.lock(projectId, "deleteDataSources")) {
DBPProject project = getWebProject(projectId, false);
doFileWriteOperation(projectId, project.getMetadataFolder(false), () -> {
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
@@ -215,7 +213,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String projectId,
@NotNull String folderPath
) throws DBException {
try (var projectLock = lockController.lock(projectId, "createDatasourceFolder")) {
try (var ignoredLock = lockController.lock(projectId, "createDatasourceFolder")) {
DBPProject project = getWebProject(projectId, false);
log.debug("Creating data source folder '" + folderPath + "' in project '" + projectId + "'");
doFileWriteOperation(projectId, project.getMetadataFolder(false),
@@ -226,8 +224,10 @@ public abstract class BaseLocalResourceController implements RMController {
GeneralUtils.validateResourceName(newName);
var parent = result.getParent();
var parentFolder = parent == null ? null : registry.getFolder(parent.toString().replace("\\", "/"));
DBPDataSourceFolder newFolder = registry.addFolder(parentFolder, newName);
((DataSourcePersistentRegistry) registry).saveDataSources();
registry.addFolder(parentFolder, newName);
if (registry instanceof DataSourcePersistentRegistry pr) {
pr.saveDataSources();
}
registry.checkForErrors();
return null;
}
@@ -241,21 +241,19 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String[] folderPaths,
boolean dropContents
) throws DBException {
try (var projectLock = lockController.lock(projectId, "createDatasourceFolder")) {
try (var ignoredLock = lockController.lock(projectId, "createDatasourceFolder")) {
DBPProject project = getWebProject(projectId, false);
doFileWriteOperation(projectId, project.getMetadataFolder(false),
() -> {
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
for (String folderPath : folderPaths) {
DBPDataSourceFolder folder = registry.getFolder(folderPath);
if (folder != null) {
log.debug("Deleting data source folder '" + folderPath + "' in project '" + projectId + "'");
registry.removeFolder(folder, dropContents);
} else {
log.warn("Can not find folder by path [" + folderPath + "] for deletion");
}
log.debug("Deleting data source folder '" + folderPath + "' in project '" + projectId + "'");
registry.removeFolder(folder, dropContents);
}
if (registry instanceof DataSourcePersistentRegistry pr) {
pr.saveDataSources();
}
((DataSourcePersistentRegistry) registry).saveDataSources();
registry.checkForErrors();
return null;
}
@@ -269,7 +267,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String oldPath,
@NotNull String newPath
) throws DBException {
try (var projectLock = lockController.lock(projectId, "createDatasourceFolder")) {
try (var ignoredLock = lockController.lock(projectId, "createDatasourceFolder")) {
DBPProject project = getWebProject(projectId, false);
log.debug("Moving data source folder from '" + oldPath + "' to '" + newPath + "' in project '" + projectId + "'");
doFileWriteOperation(projectId, project.getMetadataFolder(false),
@@ -284,19 +282,19 @@ public abstract class BaseLocalResourceController implements RMController {
}
}
protected abstract RMLocalProject getWebProject(String projectId, boolean refresh) throws DBException;
protected abstract RMLocalProject getWebProject(@NotNull String projectId, boolean refresh) throws DBException;
protected abstract <T> T doFileWriteOperation(String projectId, Path file, RMFileOperation<T> operation)
protected abstract <T> T doFileWriteOperation(@NotNull String projectId, @NotNull Path file, @NotNull RMFileOperation<T> operation)
throws DBException;
protected abstract <T> T doFileReadOperation(String projectId, Path file, RMFileOperation<T> operation)
protected abstract <T> T doFileReadOperation(@NotNull String projectId, @NotNull Path file, @NotNull RMFileOperation<T> operation)
throws DBException;
protected abstract <T> T doProjectOperation(String projectId, RMFileOperation<T> operation) throws DBException;
protected abstract <T> T doProjectOperation(@NotNull String projectId, @NotNull RMFileOperation<T> operation) throws DBException;
protected abstract RMProject makeProjectFromId(String projectId, boolean loadPermissions) throws DBException;
protected abstract RMProject makeProjectFromId(@NotNull String projectId, boolean loadPermissions) throws DBException;
protected void validateResourcePath(String resourcePath) throws DBException {
protected void validateResourcePath(@NotNull String resourcePath) throws DBException {
var fullPath = Paths.get(resourcePath);
for (Path path : fullPath) {
String fileName = IOUtils.getFileNameWithoutExtension(path);
@@ -314,28 +312,6 @@ public abstract class BaseLocalResourceController implements RMController {
}
}
protected class InternalWebProjectImpl extends BaseWebProjectImpl {
public InternalWebProjectImpl(
@NotNull SessionContextImpl sessionContext,
@NotNull RMProject rmProject,
@NotNull Path projectPath
) {
super(
BaseLocalResourceController.this.workspace,
BaseLocalResourceController.this,
sessionContext,
rmProject,
projectPath
);
}
@NotNull
@Override
protected DBPDataSourceRegistry createDataSourceRegistry() {
return new DataSourceRegistry(this);
}
}
protected void fireRmResourceAddEvent(@NotNull String projectId, @NotNull String resourcePath) throws DBException {
RMEventManager.fireEvent(
new RMEvent(RMEvent.Action.RESOURCE_ADD,
@@ -38,7 +38,7 @@ import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl;
import org.jkiss.dbeaver.model.navigator.DBNLocalFolder;
import org.jkiss.dbeaver.model.rm.*;
import org.jkiss.dbeaver.model.security.SMController;
import org.jkiss.dbeaver.model.security.SMAdminController;
import org.jkiss.dbeaver.model.security.SMObjectType;
import org.jkiss.dbeaver.model.sql.DBQuotaException;
import org.jkiss.dbeaver.model.websocket.event.MessageType;
@@ -78,18 +78,18 @@ public class LocalResourceController extends BaseLocalResourceController {
private final Path userProjectsPath;
private final Path sharedProjectsPath;
private final String globalProjectName;
private Supplier<SMController> smControllerSupplier;
private Supplier<SMAdminController> smControllerSupplier;
protected final List<RMFileOperationHandler> fileHandlers;
private final Map<String, RMLocalProject> projectRegistries = new LinkedHashMap<>();
public LocalResourceController(
DBPWorkspace workspace,
SMCredentialsProvider credentialsProvider,
Path rootPath,
Path userProjectsPath,
Path sharedProjectsPath,
Supplier<SMController> smControllerSupplier
@NotNull DBPWorkspace workspace,
@NotNull SMCredentialsProvider credentialsProvider,
@NotNull Path rootPath,
@NotNull Path userProjectsPath,
@NotNull Path sharedProjectsPath,
@NotNull Supplier<SMAdminController> smControllerSupplier
) throws DBException {
super(workspace, new FileLockController(ServletAppUtils.getServletApplication().getApplicationInstanceId()));
this.credentialsProvider = credentialsProvider;
@@ -102,21 +102,24 @@ public class LocalResourceController extends BaseLocalResourceController {
this.fileHandlers = RMFileOperationHandlersRegistry.getInstance().getFileHandlers();
}
protected SMController getSecurityController() {
@NotNull
protected SMAdminController getSecurityController() {
return smControllerSupplier.get();
}
@Nullable
private Path getGlobalProjectPath() {
return globalProjectName == null ? null : this.rootPath.resolve(this.globalProjectName);
}
@Nullable
private Path getPrivateProjectPath() {
SMCredentials activeUserCredentials = credentialsProvider.getActiveUserCredentials();
String userId = activeUserCredentials == null ? null : activeUserCredentials.getUserId();
return userId == null ? null : this.userProjectsPath.resolve(userId);
}
protected RMLocalProject getWebProject(String projectId, boolean refresh) throws DBException {
protected RMLocalProject getWebProject(@NotNull String projectId, boolean refresh) throws DBException {
synchronized (projectRegistries) {
RMLocalProject project = projectRegistries.get(projectId);
if (project == null || refresh) {
@@ -299,10 +302,11 @@ public class LocalResourceController extends BaseLocalResourceController {
}
}
@NotNull
@Override
public RMProject updateProject(@NotNull String projectId, @NotNull RMProjectInfo projectInfo) throws DBException {
validateProjectName(projectId, projectInfo.getName());
try (var projectLock = lockController.lock(projectId, "updateProject")) {
try (var ignoredLock = lockController.lock(projectId, "updateProject")) {
RMLocalProject project = getWebProject(projectId, false);
Path targetPath = getProjectPath(projectId);
if (!Files.exists(targetPath)) {
@@ -328,26 +332,27 @@ public class LocalResourceController extends BaseLocalResourceController {
@Override
public void deleteProject(@NotNull String projectId) throws DBException {
try (var projectLock = lockController.lock(projectId, "deleteProject")) {
RMProject project = makeProjectFromId(projectId, false);
try (var ignoredLock = lockController.lock(projectId, "deleteProject")) {
Path targetPath = getProjectPath(projectId);
if (!Files.exists(targetPath)) {
log.debug(MessageFormat.format("Project folder ''{0}'' is not found", projectId));
log.error(MessageFormat.format("Project folder ''{0}'' is not found", projectId));
return;
}
try {
log.debug("Deleting project '" + projectId + "'");
log.debug("Deleting project '" + projectId + "' in persistence storage");
getSecurityController().deleteObject(projectId, projectId, SMObjectType.project);
log.debug("Deleting project '" + projectId + "' folder");
IOUtils.deleteDirectory(targetPath);
getSecurityController().deleteAllObjectPermissions(projectId, SMObjectType.project);
synchronized (projectRegistries) {
projectRegistries.remove(projectId);
}
} catch (IOException e) {
throw new DBException("Error deleting project '" + project.getName() + "'", e);
throw new DBException("Error deleting project '" + projectId + "'", e);
}
}
}
@Nullable
@Override
public RMProject getProject(@NotNull String projectId, boolean readResources, boolean readProperties) throws DBException {
RMProject project = makeProjectFromId(projectId, true);
@@ -407,6 +412,15 @@ public class LocalResourceController extends BaseLocalResourceController {
@Override
public void deleteProjectDataSources(@NotNull String projectId, @NotNull String[] dataSourceIds) throws DBException {
super.deleteProjectDataSources(projectId, dataSourceIds);
log.debug("Delete datasources '" + Arrays.toString(dataSourceIds) + "' from security controller");
for (String dsId : dataSourceIds) {
try {
getSecurityController().deleteObject(projectId, dsId, SMObjectType.datasource);
} catch (DBException e) {
log.error("Error deleting datasource '" + dsId + "' from database");
}
}
if (credentialsProvider.getActiveUserCredentials() != null && dataSourceIds.length > 0) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDataSourceEvent.delete(
@@ -954,7 +968,7 @@ public class LocalResourceController extends BaseLocalResourceController {
}
@Nullable
protected RMProject makeProjectFromId(String projectId, boolean loadPermissions) throws DBException {
protected RMProject makeProjectFromId(@NotNull String projectId, boolean loadPermissions) throws DBException {
var projectName = WebRMUtils.parseProjectName(projectId);
var projectPath = getProjectPath(projectId);
if (!Files.exists(projectPath)) {
@@ -1015,7 +1029,7 @@ public class LocalResourceController extends BaseLocalResourceController {
}
}
protected <T> T doProjectOperation(String projectId, RMFileOperation<T> operation) throws DBException {
protected <T> T doProjectOperation(@NotNull String projectId, @NotNull RMFileOperation<T> operation) throws DBException {
for (RMFileOperationHandler fileHandler : fileHandlers) {
try {
fileHandler.projectOpened(projectId);
@@ -1033,7 +1047,7 @@ public class LocalResourceController extends BaseLocalResourceController {
return operation.doOperation();
}
protected <T> T doFileReadOperation(String projectId, Path file, RMFileOperation<T> operation) throws DBException {
protected <T> T doFileReadOperation(@NotNull String projectId, @NotNull Path file, @NotNull RMFileOperation<T> operation) throws DBException {
for (RMFileOperationHandler fileHandler : fileHandlers) {
try {
fileHandler.beforeFileRead(projectId, file);
@@ -1052,7 +1066,11 @@ public class LocalResourceController extends BaseLocalResourceController {
return operation.doOperation();
}
protected <T> T doFileWriteOperation(String projectId, Path file, RMFileOperation<T> operation) throws DBException {
protected <T> T doFileWriteOperation(
@NotNull String projectId,
@NotNull Path file,
@NotNull RMFileOperation<T> operation
) throws DBException {
for (RMFileOperationHandler fileHandler : fileHandlers) {
fileHandler.beforeFileChange(projectId, file);
}
@@ -1071,7 +1089,7 @@ public class LocalResourceController extends BaseLocalResourceController {
return result;
}
protected Path getProjectPath(String projectId) throws DBException {
protected Path getProjectPath(@NotNull String projectId) throws DBException {
RMProjectName project = WebRMUtils.parseProjectName(projectId);
RMProjectType type = project.getType();
String projectName = project.getName();
@@ -1183,7 +1201,7 @@ public class LocalResourceController extends BaseLocalResourceController {
public static Builder builder(
SMCredentialsProvider credentialsProvider,
DBPWorkspace workspace,
Supplier<SMController> smControllerSupplier
Supplier<SMAdminController> smControllerSupplier
) {
return new Builder(workspace, credentialsProvider, smControllerSupplier);
}
@@ -1195,7 +1213,7 @@ public class LocalResourceController extends BaseLocalResourceController {
public static class Builder {
protected final SMCredentialsProvider credentialsProvider;
protected final Supplier<SMController> smController;
protected final Supplier<SMAdminController> smController;
protected final DBPWorkspace workspace;
protected Path rootPath;
@@ -1204,7 +1222,7 @@ public class LocalResourceController extends BaseLocalResourceController {
protected Builder(
DBPWorkspace workspace, SMCredentialsProvider credentialsProvider,
Supplier<SMController> smControllerSupplier
Supplier<SMAdminController> smControllerSupplier
) {
this.workspace = workspace;
this.credentialsProvider = credentialsProvider;
@@ -41,6 +41,7 @@ public class WebAuthInfo implements SMSessionPrincipal, WebUserAuthToken {
private final WebSession session;
private final WebUser user;
@NotNull
private final WebAuthProviderDescriptor authProvider;
private String authProviderConfigurationId;
@NotNull
@@ -71,6 +72,7 @@ public class WebAuthInfo implements SMSessionPrincipal, WebUserAuthToken {
this.loginTime = loginTime;
}
@NotNull
@Property
public OffsetDateTime getLoginTime() {
return loginTime;
@@ -91,16 +93,19 @@ public class WebAuthInfo implements SMSessionPrincipal, WebUserAuthToken {
return new WebUserOriginInfo(session, user, authProvider);
}
@NotNull
@Property
public String getUserId() {
return userIdentity.getId();
}
@NotNull
@Property
public String getDisplayName() {
return userIdentity.getDisplayName();
}
@NotNull
@Property
public String getAuthProvider() {
return authProvider.getId();
@@ -138,13 +143,11 @@ public class WebAuthInfo implements SMSessionPrincipal, WebUserAuthToken {
}
void closeAuth() {
if (authProvider != null && authSession != null) {
try {
SMAuthProvider authProviderInstance = this.authProvider.getInstance();
authProviderInstance.closeSession(session, authSession);
} catch (Exception e) {
log.error(e);
}
try {
SMAuthProvider authProviderInstance = this.authProvider.getInstance();
authProviderInstance.closeSession(session, authSession);
} catch (Exception e) {
log.error(e);
}
}
@@ -51,6 +51,7 @@ public class WebHttpRequestInfo {
this.lastRemoteUserAgent = lastRemoteUserAgent;
}
@Nullable
public String getId() {
return id;
}
@@ -30,7 +30,6 @@ import io.cloudbeaver.service.sql.WebSQLConstants;
import io.cloudbeaver.utils.CBModelConstants;
import io.cloudbeaver.utils.WebDataSourceUtils;
import io.cloudbeaver.utils.WebEventUtils;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.jkiss.code.NotNull;
@@ -38,6 +37,7 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBFileController;
import org.jkiss.dbeaver.model.DBPAdaptable;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.DBPEventListener;
import org.jkiss.dbeaver.model.access.DBAAuthCredentials;
@@ -45,7 +45,6 @@ import org.jkiss.dbeaver.model.access.DBACredentialsProvider;
import org.jkiss.dbeaver.model.auth.*;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.fs.DBFFileSystemManager;
import org.jkiss.dbeaver.model.meta.Association;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.navigator.DBNModel;
@@ -61,6 +60,8 @@ import org.jkiss.dbeaver.model.websocket.event.MessageType;
import org.jkiss.dbeaver.model.websocket.event.WSSessionLogUpdatedEvent;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.function.ThrowableConsumer;
import org.jkiss.utils.function.ThrowableFunction;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Path;
@@ -68,7 +69,6 @@ import java.time.Instant;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Web session.
@@ -76,16 +76,16 @@ import java.util.stream.Collectors;
*/
//TODO: split to authenticated and non authenticated context
public class WebSession extends BaseWebSession
implements SMSessionWithAuth, SMCredentialsProvider, DBACredentialsProvider, IAdaptable {
implements SMSessionWithAuth, SMCredentialsProvider, DBACredentialsProvider, DBPAdaptable {
private static final Log log = Log.getLog(WebSession.class);
public static final SMSessionType CB_SESSION_TYPE = new SMSessionType("CloudBeaver");
private static final String WEB_SESSION_AUTH_CONTEXT_TYPE = "web-session";
private static final String ATTR_LOCALE = "locale";
private static final AtomicInteger TASK_ID = new AtomicInteger();
public static String RUNTIME_PARAM_AUTH_INFOS = "auth-infos";
public static String RUNTIME_PARAM_CLIENT_ORIGIN = "client-origin";
private final AtomicInteger taskCount = new AtomicInteger();
private final String lastRemoteAddr;
@@ -93,27 +93,29 @@ public class WebSession extends BaseWebSession
private String locale;
private boolean cacheExpired;
private String clientOrigin;
protected WebSessionGlobalProjectImpl globalProject;
private final List<WebServerMessage> sessionMessages = new ArrayList<>();
private final Map<String, WebAsyncTaskInfo> asyncTasks = new HashMap<>();
private final Map<String, Function<Object, Object>> attributeDisposers = new HashMap<>();
private final Map<String, ThrowableConsumer<Object, Exception>> attributeDisposers = new HashMap<>();
// Map of auth tokens. Key is authentication provider
private final List<WebAuthInfo> authTokens = new ArrayList<>();
private DBNModel navigatorModel;
private final DBRProgressMonitor progressMonitor = new SessionProgressMonitor();
private final Map<String, DBWSessionHandler> sessionHandlers;
private final Map<String, DBWSessionHandler<WebSession>> sessionHandlers;
private final WebDataSourceConnectEventListener connectListener = new WebDataSourceConnectEventListener(this);
public WebSession(
@NotNull WebHttpRequestInfo requestInfo,
@NotNull ServletAuthApplication application,
@NotNull Map<String, DBWSessionHandler> sessionHandlers
@NotNull Map<String, DBWSessionHandler<WebSession>> sessionHandlers
) throws DBException {
this(requestInfo.getId(),
this(
Objects.requireNonNull(requestInfo.getId()),
CommonUtils.toString(requestInfo.getLocale()),
application,
sessionHandlers,
@@ -126,8 +128,8 @@ public class WebSession extends BaseWebSession
@NotNull String id,
@Nullable String locale,
@NotNull ServletApplication application,
@NotNull Map<String, DBWSessionHandler> sessionHandlers,
@NotNull String remoteAddr
@NotNull Map<String, DBWSessionHandler<WebSession>> sessionHandlers,
@Nullable String remoteAddr
) throws DBException {
super(id, application);
if (CommonUtils.isEmpty(remoteAddr)) {
@@ -152,7 +154,7 @@ public class WebSession extends BaseWebSession
if (authTokens.isEmpty()) {
return null;
}
return authTokens.get(0);
return authTokens.getFirst();
}
}
@@ -255,23 +257,33 @@ public class WebSession extends BaseWebSession
}
// Note: for admin use only
public synchronized void resetUserState() throws DBException {
public synchronized void resetUserState(boolean needResetUserCache) throws DBException {
clearAuthTokens();
try {
resetSessionCache();
} catch (DBCException e) {
addSessionError(e);
log.error(e);
if (needResetUserCache) {
try {
resetSessionCache();
} catch (DBCException e) {
addSessionError(e);
log.error(e);
}
}
refreshUserData();
clearSessionContext();
}
public synchronized void resetUserState() throws DBException {
resetUserState(true);
}
@NotNull
public DBPEventListener getDataSourceConnectListener() {
return connectListener;
}
public void updateClientOrigin(@NotNull String originFromRequest) {
this.clientOrigin = originFromRequest;
}
private void initNavigatorModel() {
// Cleanup current data
@@ -310,7 +322,7 @@ public class WebSession extends BaseWebSession
anonymousProject.setInMemory(true);
}
if (workspace.getActiveProject() == null && !workspace.getProjects().isEmpty()) {
workspace.setActiveProject(workspace.getProjects().get(0));
workspace.setActiveProject(workspace.getProjects().getFirst());
}
} catch (DBException e) {
addSessionError(e);
@@ -325,7 +337,6 @@ public class WebSession extends BaseWebSession
} else {
sessionProject = createSessionProject(project);
}
sessionProject.refreshProjectSettings();
// do not load data sources for anonymous project
if (project.getType() == RMProjectType.USER && userContext.getUser() == null) {
sessionProject.setInMemory(true);
@@ -334,10 +345,6 @@ public class WebSession extends BaseWebSession
if (!project.isShared() || application.isConfigurationMode()) {
getWorkspace().setActiveProject(sessionProject);
}
log.info(String.format(
"Project created: [ID=%s, Name=%s, Type=%s, Creator=%s]",
project.getId(), project.getName(), project.getType(), project.getCreator()
));
return sessionProject;
}
@@ -359,9 +366,14 @@ public class WebSession extends BaseWebSession
private void resetSessionCache() throws DBCException {
// Clear attributes
synchronized (attributes) {
for (Map.Entry<String, Function<Object, Object>> attrDisposer : attributeDisposers.entrySet()) {
for (Map.Entry<String, ThrowableConsumer<Object, Exception>> attrDisposer : attributeDisposers.entrySet()) {
Object attrValue = attributes.get(attrDisposer.getKey());
attrDisposer.getValue().apply(attrValue);
try {
attrDisposer.getValue().accept(attrValue);
} catch (Exception e) {
log.error("Error disposing attribute '" + attrDisposer.getKey() + "'", e);
}
}
attributeDisposers.clear();
// Remove all non-persistent attributes
@@ -441,7 +453,7 @@ public class WebSession extends BaseWebSession
}
public synchronized void updateInfo(boolean isOldHttpSessionUsed) {
log.debug("Update session lifetime " + getSessionId() + " for user " + getUserId());
log.trace("Update session lifetime " + getSessionId() + " for user " + getUserId());
touchSession();
if (isOldHttpSessionUsed) {
try {
@@ -560,6 +572,10 @@ public class WebSession extends BaseWebSession
if (job instanceof CustomCancelableJob cancelableJob) {
cancelableJob.cancelJob(this, taskInfo);
}
CompletableFuture<?> future = getAttribute(getTaskConfirmationAttributeName(taskId));
if (future != null) {
future.cancel(false);
}
if (job != null) {
job.cancel();
}
@@ -569,8 +585,7 @@ public class WebSession extends BaseWebSession
public WebAsyncTaskInfo createAsyncTask(@NotNull String taskName) {
int taskId = TASK_ID.incrementAndGet();
WebAsyncTaskInfo asyncTask = getAsyncTask(String.valueOf(taskId), taskName, true);
return asyncTask;
return getAsyncTask(String.valueOf(taskId), taskName, true);
}
public List<WebAsyncTaskInfo> findTasksByJob(@NotNull Class<? extends AbstractJob> jobClass) {
@@ -592,8 +607,9 @@ public class WebSession extends BaseWebSession
public WebAsyncTaskInfo runAsyncTask(@NotNull WebAsyncTaskInfo asyncTask, @NotNull WebAsyncTaskProcessor<?> runnable) {
AbstractJob job = new AbstractJob(asyncTask.getName()) {
@NotNull
@Override
protected IStatus run(DBRProgressMonitor monitor) {
protected IStatus run(@NotNull DBRProgressMonitor monitor) {
int curTaskCount = taskCount.incrementAndGet();
DBRProgressMonitor taskMonitor = new TaskProgressMonitor(monitor, WebSession.this, asyncTask);
@@ -688,7 +704,11 @@ public class WebSession extends BaseWebSession
}
}
public <T> T getAttribute(String name, Function<T, T> creator, Function<T, T> disposer) {
public <T, E extends Exception> T getAttribute(
String name,
ThrowableFunction<String, T, E> creator,
ThrowableConsumer<T, E> disposer
) throws E {
synchronized (attributes) {
Object value = attributes.get(name);
if (value instanceof PersistentAttribute persistentAttribute) {
@@ -699,7 +719,7 @@ public class WebSession extends BaseWebSession
if (value != null) {
attributes.put(name, value);
if (disposer != null) {
attributeDisposers.put(name, (Function<Object, Object>) disposer);
attributeDisposers.put(name, (ThrowableConsumer<Object, Exception>) disposer);
}
}
}
@@ -724,7 +744,7 @@ public class WebSession extends BaseWebSession
}
return null;
}
return authTokens.isEmpty() ? null : authTokens.get(0);
return authTokens.isEmpty() ? null : authTokens.getFirst();
}
}
@@ -767,10 +787,7 @@ public class WebSession extends BaseWebSession
if (oldAuthInfo != null) {
removeAuthInfo(oldAuthInfo);
}
SMSession authSession = authInfo.getAuthSession();
if (authSession != null) {
getSessionContext().addSession(authSession);
}
getSessionContext().addSession(authInfo.getAuthSession());
}
synchronized (authTokens) {
Collections.addAll(authTokens, tokens);
@@ -834,6 +851,7 @@ public class WebSession extends BaseWebSession
contextCredentialsProvider.provideAuthParameters(monitor, dataSourceContainer, configuration);
}
configuration.setRuntimeAttribute(RUNTIME_PARAM_AUTH_INFOS, getAllAuthInfo());
configuration.setRuntimeAttribute(RUNTIME_PARAM_CLIENT_ORIGIN, this.clientOrigin);
WebSessionProjectImpl project = getProjectById(dataSourceContainer.getProject().getId());
if (project != null) {
@@ -846,7 +864,7 @@ public class WebSession extends BaseWebSession
// uncommented because we had the problem with non-native auth models
// (for example, can't connect to DynamoDB if credentials are not saved)
DBAAuthCredentials credentials = configuration.getAuthModel().loadCredentials(dataSourceContainer, configuration);
WebDataSourceUtils.updateCredentialsFromProperties(credentials, configuration.getAuthProperties());
WebDataSourceUtils.updateCredentialsFromProperties(this.progressMonitor, credentials, configuration.getAuthProperties());
configuration.getAuthModel().provideCredentials(dataSourceContainer, configuration, credentials);
} catch (DBException e) {
@@ -864,7 +882,7 @@ public class WebSession extends BaseWebSession
// May be called to extract auth information from session
@Override
public <T> T getAdapter(Class<T> adapter) {
public <T> T getAdapter(@NotNull Class<T> adapter) {
synchronized (authTokens) {
for (WebAuthInfo authInfo : authTokens) {
if (isAuthInfoInstanceOf(authInfo, adapter)) {
@@ -886,7 +904,7 @@ public class WebSession extends BaseWebSession
}
private <T> boolean isAuthInfoInstanceOf(WebAuthInfo authInfo, Class<T> adapter) {
if (authInfo != null && authInfo.getAuthSession() != null) {
if (authInfo != null) {
return adapter.isInstance(authInfo.getAuthSession());
}
return false;
@@ -1004,15 +1022,6 @@ public class WebSession extends BaseWebSession
deleteSessionProject(project);
}
@NotNull
public DBFFileSystemManager getFileSystemManager(String projectId) throws DBException {
var project = getProjectById(projectId);
if (project == null) {
throw new DBException("Project not found: " + projectId);
}
return project.getFileSystemManager();
}
@NotNull
public WebSessionPreferenceStore getUserPreferenceStore() {
return getUserContext().getPreferenceStore();
@@ -1028,7 +1037,7 @@ public class WebSession extends BaseWebSession
boolean confirmed,
boolean skipConfirmations
) {
String attributeName = WebSQLConstants.TASK_CONFIRMATION_ATTR_PREFIX + taskId;
String attributeName = getTaskConfirmationAttributeName(taskId);
if (confirmed && skipConfirmations) {
setAttribute(WebSQLConstants.SKIP_TASK_CONFIRMATIONS_ATTR, Boolean.TRUE);
}
@@ -1042,14 +1051,31 @@ public class WebSession extends BaseWebSession
}
}
public void handleTaskConfirmationWithParameters(@NotNull String taskId, @NotNull Map<String, Object> parameters) {
String attributeName = getTaskConfirmationAttributeName(taskId);
CompletableFuture<Map<String, Object>> confirmationFuture = getAttribute(attributeName);
if (confirmationFuture != null) {
confirmationFuture.complete(parameters);
removeAttribute(attributeName);
} else {
log.error("Received unexpected confirmation event for taskId: " + taskId);
}
}
@NotNull
private String getTaskConfirmationAttributeName(@NotNull String taskId) {
return WebSQLConstants.TASK_CONFIRMATION_ATTR_PREFIX + taskId;
}
private class SessionProgressMonitor extends BaseProgressMonitor {
@Override
public void beginTask(String name, int totalWork) {
public void beginTask(@NotNull String name, int totalWork) {
addInfoMessage(name);
}
@Override
public void subTask(String name) {
public void subTask(@NotNull String name) {
addInfoMessage(name);
}
}
@@ -74,7 +74,7 @@ public class WebSessionAuthProcessor {
}
} catch (Exception e) {
if (resetUserStateOnError) {
webSession.resetUserState();
webSession.resetUserState(false);
}
throw new DBException(e.getMessage(), e);
}
@@ -20,6 +20,7 @@ import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.impl.preferences.AbstractUserPreferenceStore;
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
import org.jkiss.utils.CommonUtils;
import java.io.IOException;
import java.util.Map;
@@ -85,4 +86,13 @@ public class WebSessionPreferenceStore extends AbstractUserPreferenceStore {
public void save() throws IOException {
throw new RuntimeException("Not implemented");
}
public boolean getUserPreferenceBoolean(@NotNull String name, boolean fallbackValue) {
String value = CommonUtils.toString(userPreferences.get(name));
if (value.isEmpty()) {
return fallbackValue;
} else {
return toBoolean(value);
}
}
}
@@ -109,7 +109,7 @@ public class WebSessionWorkspace implements DBPWorkspace {
@Nullable
@Override
public WebSessionProjectImpl getProjectById(@NotNull String projectId) {
public WebSessionProjectImpl getProjectById(@Nullable String projectId) {
if (projectId == null) {
return activeProject;
}
@@ -63,11 +63,14 @@ public class WebUserContext implements SMCredentialsProvider {
private SMAdminController adminSecurityController;
private DBSSecretController secretController;
private RMController rmController;
private DBFileController fileController;
private Set<String> accessibleProjectIds = new HashSet<>();
private final DBFileController fileController;
private final Set<String> accessibleProjectIds = new HashSet<>();
private final WebSessionPreferenceStore preferenceStore;
public WebUserContext(ServletApplication application, DBPWorkspace workspace) throws DBException {
public WebUserContext(
@NotNull ServletApplication application,
@NotNull DBPWorkspace workspace
) throws DBException {
this.application = application;
this.workspace = workspace;
this.securityController = application.createSecurityController(this);
@@ -84,7 +87,7 @@ public class WebUserContext implements SMCredentialsProvider {
* @return - true if context changed
* @throws DBException - if user already authorized and new token come from another user
*/
public synchronized boolean refresh(SMAuthInfo smAuthInfo) throws DBException {
public synchronized boolean refresh(@NotNull SMAuthInfo smAuthInfo) throws DBException {
if (smAuthInfo.getAuthPermissions() == null && !isAuthorizedInSecurityManager()) {
throw new DBCException("Required information about session permissions is missing");
}
@@ -242,10 +245,12 @@ public class WebUserContext implements SMCredentialsProvider {
return smSessionId;
}
@Nullable
private Set<String> getDefaultPermissions() {
return application.isAnonymousAccessEnabled() ? null : Set.of();
}
@NotNull
public RMController getRmController() {
return rmController;
}
@@ -40,14 +40,14 @@ public class TaskProgressMonitor extends ProxyProgressMonitor {
}
@Override
public void beginTask(String name, int totalWork) {
public void beginTask(@NotNull String name, int totalWork) {
super.beginTask(name, totalWork);
asyncTask.setStatus(name);
WebEventUtils.sendAsyncTaskEvent(webSession, asyncTask);
}
@Override
public void subTask(String name) {
public void subTask(@NotNull String name) {
super.subTask(name);
asyncTask.setStatus(name);
WebEventUtils.sendAsyncTaskEvent(webSession, asyncTask);
@@ -17,12 +17,18 @@
package io.cloudbeaver.registry;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.DBPConditionalProperty;
import org.jkiss.dbeaver.model.impl.LocalizedPropertyDescriptor;
import org.jkiss.dbeaver.utils.RuntimeUtils;
import org.osgi.framework.Bundle;
public class WebAuthProviderProperty extends LocalizedPropertyDescriptor implements DBPConditionalProperty {
private static final String WEB_AUTH_PROPERTY_PREFIX = "prop.auth.model.";
private final String[] requiredFeatures;
@Nullable
private final String type;
@@ -30,6 +36,7 @@ public class WebAuthProviderProperty extends LocalizedPropertyDescriptor impleme
private final String authProviderId;
private final String hideExpr;
private final String readOnlyExpr;
private final Bundle bundle;
public WebAuthProviderProperty(String category, IConfigurationElement config, String authProviderId) {
super(category, config);
@@ -39,6 +46,7 @@ public class WebAuthProviderProperty extends LocalizedPropertyDescriptor impleme
this.type = config.getAttribute("type");
this.hideExpr = config.getAttribute("hideExpr");
this.readOnlyExpr = config.getAttribute("readOnlyExpr");
this.bundle = extractBundle(config);
}
@NotNull
@@ -63,12 +71,34 @@ public class WebAuthProviderProperty extends LocalizedPropertyDescriptor impleme
return readOnlyExpr;
}
@NotNull
@Override
public String getPropertyId() {
public String getLocalizedName(@NotNull String locale) {
return RuntimeUtils.getBundleLocalization(bundle, locale).getString(getPropertyId());
}
@Nullable
@Override
public String getLocalizedDescription(@NotNull String locale) {
return RuntimeUtils.getBundleLocalization(bundle, locale).getString(getPropertyId() + "." + ATTR_DESCRIPTION);
}
private String getPropertyId() {
if (authProviderId != null) {
return "prop.auth.model." + authProviderId + "." + this.getId();
return WEB_AUTH_PROPERTY_PREFIX + authProviderId + "." + this.getId();
} else {
return "prop.auth.model." + this.getId();
return WEB_AUTH_PROPERTY_PREFIX + this.getId();
}
}
@NotNull
private Bundle extractBundle(@NotNull IConfigurationElement config) {
final Bundle bundle;
String bundleName = config.getContributor().getName();
bundle = Platform.getBundle(bundleName);
if (bundle == null) {
throw new IllegalStateException("Bundle '" + bundleName + "' not found");
}
return bundle;
}
}
@@ -41,7 +41,7 @@ public abstract class BaseServletPlatform extends BasePlatformImpl {
if (tempFolder == null) {
synchronized (this) {
if (tempFolder == null) {
initTempFolder(monitor);
initTempFolder();
}
}
}
@@ -59,9 +59,8 @@ public abstract class BaseServletPlatform extends BasePlatformImpl {
@NotNull
public abstract ServletApplication getApplication();
private void initTempFolder(@NotNull DBRProgressMonitor monitor) {
private void initTempFolder() {
// Make temp folder
monitor.subTask("Create temp folder");
String sysTempFolder = System.getProperty(StandardConstants.ENV_TMP_DIR);
if (CommonUtils.isNotEmpty(sysTempFolder)) {
tempFolder = Path.of(sysTempFolder).resolve(BASE_TEMP_DIR).resolve(DBWConstants.WORK_DATA_FOLDER_NAME);
@@ -69,9 +68,11 @@ public abstract class BaseServletPlatform extends BasePlatformImpl {
//we do not use workspace because it can be in external file system
tempFolder = getApplication().getHomeDirectory().resolve(DBWConstants.WORK_DATA_FOLDER_NAME);
}
log.debug("Temp folder: " + tempFolder);
}
public synchronized void dispose() {
super.dispose();
// Remove temp folder
if (tempFolder != null) {
@@ -47,5 +47,5 @@ public class WebSQLConstants {
public static final String TASK_CONFIRMATION_ATTR_PREFIX = "session_task_confirmation_";
public static final String SKIP_TASK_CONFIRMATIONS_ATTR = "skip_session_task_confirmations";
public static final int TASK_CONFIRMATION_TIMEOUT_SECONDS = 30;
public static final int TASK_CONFIRMATION_TIMEOUT_SECONDS = 60;
}
@@ -21,6 +21,7 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
import org.jkiss.dbeaver.model.data.DBDDataFilter;
import org.jkiss.dbeaver.model.data.DBDRowIdentifier;
import org.jkiss.dbeaver.model.exec.trace.DBCTrace;
import org.jkiss.dbeaver.model.struct.*;
@@ -42,6 +43,8 @@ public class WebSQLResultsInfo {
// TODO: find a way to remove isSingleRow and use virtual keys for reading BLOB and string cell values.
private boolean isSingleRow;
private DBCTrace trace;
@Nullable
private DBDDataFilter dataFilter;
private String queryText;
public WebSQLResultsInfo(@NotNull DBSDataContainer dataContainer, @NotNull String id) {
@@ -151,4 +154,13 @@ public class WebSQLResultsInfo {
public void setSingleRow(boolean singleRow) {
isSingleRow = singleRow;
}
@Nullable
public DBDDataFilter getDataFilter() {
return dataFilter;
}
public void setDataFilter(@Nullable DBDDataFilter dataFilter) {
this.dataFilter = dataFilter;
}
}
@@ -29,7 +29,6 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
@@ -70,9 +69,18 @@ public class WebGQLClient {
* @param variables GraphQL query variables
* @return GraphQL response
*/
@NotNull
@Nullable
public <T> T sendQuery(@NotNull String query, @Nullable Map<String, Object> variables) throws Exception {
return sendQueryWithHeaders(query, variables, List.of());
return sendQueryWithHeaders(query, variables, Map.of());
}
@NotNull
public Map<String, Object> sendQueryAndGetHeaders(
@NotNull String query,
@Nullable Map<String, Object> variables,
@NotNull Map<String, String> requestHeaders
) throws Exception {
return executeGQLRequest(query, variables, requestHeaders);
}
/**
@@ -87,15 +95,26 @@ public class WebGQLClient {
public <T> T sendQueryWithHeaders(
@NotNull String query,
@Nullable Map<String, Object> variables,
@NotNull List<String> headers
@NotNull Map<String, String> headers
) throws Exception {
Map<String, Object> parsed = executeGQLRequest(query, variables, headers);
// graphql response will be in "data" key
return (T) parsed.get("data");
}
@NotNull
private Map<String, Object> executeGQLRequest(
@NotNull String query,
@Nullable Map<String, Object> variables,
@NotNull Map<String, String> requestHeaders
) throws Exception {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(apiUrl))
.POST(HttpRequest.BodyPublishers.ofString(makeGQLRequest(query, variables)))
.setHeader("TE-Client-Version", GeneralUtils.getMajorVersion())
.header("Content-Type", "application/json");
if (!headers.isEmpty()) {
requestBuilder.headers(headers.toArray(String[]::new));
if (!requestHeaders.isEmpty()) {
requestHeaders.forEach(requestBuilder::header);
}
HttpRequest request = requestBuilder.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
@@ -107,8 +126,10 @@ public class WebGQLClient {
String message = JSONUtils.getString(JSONUtils.getObjectList(body, "errors").get(0), "message");
throw new DBException(message);
}
// graphql response will be in "data" key
return (T) JSONUtils.getObject(body, "data").get("result");
Map<String, Object> parsed = new LinkedHashMap<>();
parsed.put("data", JSONUtils.getObject(body, "data").get("result"));
parsed.put("headers", response.headers().map());
return parsed;
}
@NotNull
@@ -16,10 +16,6 @@
*/
package io.cloudbeaver.utils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.Strictness;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebSessionProjectImpl;
@@ -34,7 +30,6 @@ import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBConstants;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.access.DBAAuthCredentials;
import org.jkiss.dbeaver.model.access.DBAAuthCredentialsWithComplexProperties;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
@@ -43,7 +38,9 @@ import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNativeCredentials;
import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration;
import org.jkiss.dbeaver.model.net.ssh.SSHConstants;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceDisconnectEvent;
import org.jkiss.dbeaver.registry.DataSourceUtils;
import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor;
import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry;
import org.jkiss.dbeaver.runtime.DBWorkbench;
@@ -211,31 +208,30 @@ public class WebDataSourceUtils {
return webSession.getAccessibleProjectById(projectId).getWebConnectionInfo(connectionId);
}
public static void updateCredentialsFromProperties(@NotNull DBAAuthCredentials credentials, @NotNull Map<String, ?> properties) {
InstanceCreator<DBAAuthCredentials> credTypeAdapter = type -> credentials;
Gson credGson = new GsonBuilder()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(credentials.getClass(), credTypeAdapter)
.create();
if (credentials instanceof DBAAuthCredentialsWithComplexProperties complexProperties) {
complexProperties.updateCredentialsFromComplexProperties(properties);
public static void updateCredentialsFromProperties(
@NotNull DBRProgressMonitor progressMonitor,
@NotNull DBAAuthCredentials credentials,
@Nullable Map<String, ?> properties
) {
if (properties == null) {
return;
}
credGson.fromJson(credGson.toJsonTree(properties), credentials.getClass());
DataSourceUtils.updateCredentialsFromProperties(progressMonitor, credentials, properties);
}
public static void saveAuthProperties(
@NotNull DBRProgressMonitor progressMonitor,
@NotNull DBPDataSourceContainer dataSourceContainer,
@NotNull DBPConnectionConfiguration configuration,
@Nullable Map<String, Object> authProperties,
boolean saveCredentials,
boolean sharedCredentials
) {
saveAuthProperties(dataSourceContainer, configuration, authProperties, saveCredentials, sharedCredentials, false);
saveAuthProperties(progressMonitor, dataSourceContainer, configuration, authProperties, saveCredentials, sharedCredentials, false);
}
public static void saveAuthProperties(
@NotNull DBRProgressMonitor progressMonitor,
@NotNull DBPDataSourceContainer dataSourceContainer,
@NotNull DBPConnectionConfiguration configuration,
@Nullable Map<String, Object> authProperties,
@@ -271,7 +267,7 @@ public class WebDataSourceUtils {
configuration.setAuthProperties(currentAuthProps);
}
if (!authProperties.isEmpty()) {
updateCredentialsFromProperties(credentials, authProperties);
updateCredentialsFromProperties(progressMonitor, credentials, authProperties);
}
configuration.getAuthModel().saveCredentials(dataSourceContainer, configuration, credentials);
@@ -18,6 +18,7 @@ package io.cloudbeaver.utils;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.websocket.event.task.WSSessionTaskInfoEvent;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.app.DBPProject;
@@ -30,7 +31,6 @@ import org.jkiss.dbeaver.model.websocket.event.datasource.WSDatasourceFolderEven
import org.jkiss.dbeaver.model.websocket.event.permissions.WSSubjectPermissionEvent;
import org.jkiss.dbeaver.model.websocket.event.resource.WSResourceProperty;
import org.jkiss.dbeaver.model.websocket.event.resource.WSResourceUpdatedEvent;
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionTaskInfoEvent;
import java.util.List;
@@ -17,8 +17,11 @@
package io.cloudbeaver.utils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.utils.GeneralUtils;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
@@ -39,4 +42,30 @@ public class WebTestUtils {
return false;
}
}
public static void setPrivateField(
@NotNull Object target,
@NotNull String fieldName,
@Nullable Object value
) throws Exception {
Class<?> cls = target.getClass();
Field f = null;
while (cls != null) {
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
if (field.getName().equals(fieldName)) {
f = field;
break;
}
}
cls = cls.getSuperclass();
}
if (f == null) {
throw new NoSuchFieldException(fieldName);
}
boolean isAccessible = f.canAccess(target);
f.setAccessible(true);
f.set(target, value);
f.setAccessible(isAccessible);
}
}
@@ -0,0 +1,28 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.client;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
public class WSSessionPingClientEvent extends WSClientEvent {
public static final String ID = "cb_client_session_ping";
public WSSessionPingClientEvent(@Nullable String topicId) {
super(ID, topicId);
}
}
@@ -0,0 +1,53 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.client;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
public class WSSessionTaskConfirmationEvent extends WSClientEvent {
public static final String ID = "cb_client_session_task_confirmation";
private final String taskId;
private final boolean confirmed;
private final boolean skipConfirmations;
public WSSessionTaskConfirmationEvent(
@NotNull String taskId,
boolean confirmed,
boolean skipConfirmations
) {
super(ID, WSConstants.TOPIC_SESSION_TASK);
this.taskId = taskId;
this.confirmed = confirmed;
this.skipConfirmations = skipConfirmations;
}
public String getTaskId() {
return taskId;
}
public boolean isConfirmed() {
return confirmed;
}
public boolean isSkipConfirmations() {
return skipConfirmations;
}
}
@@ -0,0 +1,47 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.client;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
import java.util.Map;
public class WSSessionTaskWithParametersConfirmationEvent extends WSClientEvent {
public static final String ID = "cb_client_session_task_with_parameters_confirmation";
@NotNull
private final String taskId;
@NotNull
private final Map<String, Object> parameters;
protected WSSessionTaskWithParametersConfirmationEvent(@NotNull String taskId, @NotNull Map<String, Object> parameters) {
super(ID, WSConstants.TOPIC_SESSION_TASK);
this.taskId = taskId;
this.parameters = parameters;
}
@NotNull
public String getTaskId() {
return taskId;
}
@NotNull
public Map<String, Object> getParameters() {
return parameters;
}
}
@@ -0,0 +1,30 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.client;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
/**
* Subscribe on event topic
*/
public class WSSubscribeOnTopicClientEvent extends WSClientEvent {
public static final String ID = "cb_client_topic_subscribe";
public WSSubscribeOnTopicClientEvent(String topicId) {
super(ID, topicId);
}
}
@@ -0,0 +1,30 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.client;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
/**
* Unsubscribe from event topic
*/
public class WSUnsubscribeFromTopicClientEvent extends WSClientEvent {
public static final String ID = "cb_client_topic_unsubscribe";
public WSUnsubscribeFromTopicClientEvent(String topicId) {
super(ID, topicId);
}
}
@@ -0,0 +1,43 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.client;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
import java.util.Set;
/**
* Subscribe on event topic
*/
public class WSUpdateActiveProjectsClientEvent extends WSClientEvent {
public static final String ID = "cb_client_projects_active";
@NotNull
private final Set<String> projectIds;
public WSUpdateActiveProjectsClientEvent(@NotNull Set<String> projectIds) {
super(ID, WSConstants.TOPIC_PROJECTS);
this.projectIds = projectIds;
}
@NotNull
public Set<String> getProjectIds() {
return projectIds;
}
}
@@ -0,0 +1,57 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.task;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.session.WSAbstractSessionEvent;
public abstract class WSAbstractSessionTaskEvent extends WSAbstractSessionEvent {
@NotNull
private final String taskId;
@NotNull
private final String title;
@NotNull
private final String message;
public WSAbstractSessionTaskEvent(
@NotNull String eventId,
@NotNull String taskId,
@NotNull String title,
@NotNull String message
) {
super(eventId, WSConstants.TOPIC_SESSION_TASK);
this.taskId = taskId;
this.title = title;
this.message = message;
}
@NotNull
public String getTaskId() {
return taskId;
}
@NotNull
public String getTitle() {
return title;
}
@NotNull
public String getMessage() {
return message;
}
}
@@ -0,0 +1,31 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.task;
import org.jkiss.code.NotNull;
public class WSSessionTaskConfirmationRequestEvent extends WSAbstractSessionTaskEvent {
private static final String ID = "cb_session_task_confirmation_request";
public WSSessionTaskConfirmationRequestEvent(
@NotNull String taskId,
@NotNull String title,
@NotNull String message
) {
super(ID, taskId, title, message);
}
}
@@ -0,0 +1,58 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.task;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.session.WSAbstractSessionEvent;
/**
* Async task info event.
*/
public class WSSessionTaskInfoEvent extends WSAbstractSessionEvent {
@NotNull
private final String taskId;
@Nullable
private final String statusName;
private final boolean running;
public WSSessionTaskInfoEvent(
@NotNull String taskId,
@Nullable String statusName,
boolean running
) {
super("cb_session_task_info_updated", WSConstants.TOPIC_SESSION_TASK);
this.taskId = taskId;
this.statusName = statusName;
this.running = running;
}
@NotNull
public String getTaskId() {
return taskId;
}
@Nullable
public String getStatusName() {
return statusName;
}
public boolean isRunning() {
return running;
}
}
@@ -0,0 +1,42 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.task;
import org.jkiss.code.NotNull;
public class WSSessionTaskQueryConfirmationRequestEvent extends WSAbstractSessionTaskEvent {
private static final String ID = "cb_session_task_query_confirmation_request";
@NotNull
private final String query;
public WSSessionTaskQueryConfirmationRequestEvent(
@NotNull String taskId,
@NotNull String title,
@NotNull String message,
@NotNull String query
) {
super(ID, taskId, title, message);
this.query = query;
}
@NotNull
public String getQuery() {
return query;
}
}
@@ -0,0 +1,51 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.task;
import org.jkiss.code.NotNull;
import java.util.Map;
public class WSSessionTaskQueryParamsConfirmationEvent extends WSAbstractSessionTaskEvent {
private static final String ID = "cb_session_task_query_params_confirmation_request";
@NotNull
private final String query;
@NotNull
private final Map<String, Object> parameters;
public WSSessionTaskQueryParamsConfirmationEvent(
@NotNull String taskId,
@NotNull String title,
@NotNull String message,
@NotNull String query,
@NotNull Map<String, Object> parameters
) {
super(ID, taskId, title, message);
this.query = query;
this.parameters = parameters;
}
@NotNull
public String getQuery() {
return query;
}
@NotNull
public Map<String, Object> getParameters() {
return parameters;
}
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Community Product
Bundle-SymbolicName: io.cloudbeaver.product.ce;singleton:=true
Bundle-Version: 25.2.5.qualifier
Bundle-Release-Date: 20251117
Bundle-Version: 25.3.3.qualifier
Bundle-Release-Date: 20260119
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce</artifactId>
<version>25.2.5-SNAPSHOT</version>
<version>25.3.3-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -2,8 +2,8 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Cloudbeaver Base JDBC drivers
Bundle-SymbolicName: io.cloudbeaver.resources.drivers.base;singleton:=true
Bundle-Version: 1.0.134.qualifier
Bundle-Release-Date: 20251117
Bundle-Version: 1.0.138.qualifier
Bundle-Release-Date: 20260119
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
<version>1.0.134-SNAPSHOT</version>
<version>1.0.138-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,12 +3,13 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver CE Server
Bundle-SymbolicName: io.cloudbeaver.server.ce;singleton:=true
Bundle-Version: 25.2.5.qualifier
Bundle-Release-Date: 20251117
Bundle-Version: 25.3.3.qualifier
Bundle-Release-Date: 20260119
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
Require-Bundle: io.cloudbeaver.server;visibility:=reexport,
io.cloudbeaver.model.cli;visibility:=reexport,
com.google.guava
Export-Package: io.cloudbeaver,
io.cloudbeaver.model,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server.ce</artifactId>
<version>25.2.5-SNAPSHOT</version>
<version>25.3.3-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -86,10 +86,12 @@ public class CBServerConfig implements WebServerConfiguration {
return contentRoot;
}
@NotNull
public String getRootURI() {
return rootURI;
}
@NotNull
public String getServicesURI() {
return serviceURI;
}
@@ -24,6 +24,7 @@ import io.cloudbeaver.model.app.BaseServletApplication;
import io.cloudbeaver.model.app.ServletAuthApplication;
import io.cloudbeaver.model.app.ServletAuthConfiguration;
import io.cloudbeaver.model.app.ServletSystemInformationCollector;
import io.cloudbeaver.model.cli.CloudBeaverInstanceServer;
import io.cloudbeaver.model.config.CBAppConfig;
import io.cloudbeaver.model.config.CBServerConfig;
import io.cloudbeaver.registry.WebDriverRegistry;
@@ -32,6 +33,7 @@ import io.cloudbeaver.registry.WebServiceRegistry;
import io.cloudbeaver.server.jetty.CBJettyServer;
import io.cloudbeaver.service.DBWServiceInitializer;
import io.cloudbeaver.service.DBWServiceServerConfigurator;
import io.cloudbeaver.service.security.CBEmbeddedSecurityController;
import io.cloudbeaver.service.session.CBSessionManager;
import io.cloudbeaver.utils.WebDataSourceUtils;
import org.eclipse.core.runtime.Platform;
@@ -48,7 +50,6 @@ import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.impl.app.BaseApplicationImpl;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.security.SMAdminController;
import org.jkiss.dbeaver.model.security.SMConstants;
import org.jkiss.dbeaver.model.security.SMObjectType;
@@ -86,7 +87,6 @@ public abstract class CBApplication<T extends CBServerConfig>
*/
private static final long CONFIGURATION_MODE_SESSION_IDLE_TIME = 60 * 60 * 1000 * 24 * 7;
static {
Log.setDefaultDebugStream(System.out);
}
@@ -119,6 +119,7 @@ public abstract class CBApplication<T extends CBServerConfig>
this.homeDirectory = new File(initHomeFolder());
}
@Nullable
public String getServerURL() {
return getServerConfiguration().getServerURL();
}
@@ -138,15 +139,18 @@ public abstract class CBApplication<T extends CBServerConfig>
return getServerConfiguration().getServerName();
}
@NotNull
public String getRootURI() {
return getServerConfiguration().getRootURI();
}
@NotNull
public String getServicesURI() {
return getServerConfiguration().getServicesURI();
}
@NotNull
public Path getHomeDirectory() {
return homeDirectory.toPath();
}
@@ -170,10 +174,12 @@ public abstract class CBApplication<T extends CBServerConfig>
* @return max session idle time from server configuration, may differ from {@link #getMaxSessionIdleTime()}
*/
@NotNull
public CBAppConfig getAppConfiguration() {
return getServerConfigurationController().getAppConfiguration();
}
@NotNull
public T getServerConfiguration() {
return getServerConfigurationController().getServerConfiguration();
}
@@ -199,6 +205,11 @@ public abstract class CBApplication<T extends CBServerConfig>
@Override
protected void startServer() {
try {
createInstanceServer();
} catch (Exception e) {
log.error("Error initializing instance server", e);
}
try {
if (!loadServerConfiguration()) {
return;
@@ -227,10 +238,7 @@ public abstract class CBApplication<T extends CBServerConfig>
Location instanceLoc = Platform.getInstanceLocation();
try {
if (!instanceLoc.isSet()) { // always false?
URL wsLocationURL = new URL(
"file", //$NON-NLS-1$
null,
getWorkspaceDirectory().toAbsolutePath().toString());
URL wsLocationURL = getWorkspaceDirectory().toUri().toURL();
instanceLoc.set(wsLocationURL, true);
}
} catch (Exception e) {
@@ -273,7 +281,8 @@ public abstract class CBApplication<T extends CBServerConfig>
determineLocalAddresses();
log.debug("\tLocal host addresses:");
for (InetAddress ia : localInetAddresses) {
log.debug("\t\t" + ia.getHostAddress() + " (" + ia.getCanonicalHostName() + ")");
log.debug("\t\t" + ia.getHostAddress() +
(Objects.equals(ia.getHostAddress(), ia.getCanonicalHostName()) ? "" : (" (" + ia.getCanonicalHostName() + ")")));
}
}
{
@@ -292,7 +301,8 @@ public abstract class CBApplication<T extends CBServerConfig>
try {
initializeServer();
} catch (DBException e) {
log.error("Error initializing server", e);
log.error("Error initializing " + systemInformationCollector.getProductName(), e);
shutdown();
return;
}
@@ -300,6 +310,7 @@ public abstract class CBApplication<T extends CBServerConfig>
initializeSecurityController();
} catch (Exception e) {
log.error("Error initializing database", e);
shutdown();
return;
}
@@ -341,6 +352,7 @@ public abstract class CBApplication<T extends CBServerConfig>
getAppConfiguration().setEnabledFeatures(enabledFeatures.toArray(new String[0]));
}
@NotNull
protected ServletSystemInformationCollector<?> createSystemInformationCollector() {
return new ServletSystemInformationCollector<>(this);
}
@@ -494,10 +506,20 @@ public abstract class CBApplication<T extends CBServerConfig>
protected void shutdown() {
log.debug("Cloudbeaver Server is stopping"); //$NON-NLS-1$
try {
if (securityController instanceof CBEmbeddedSecurityController<?> embeddedSecurityController) {
embeddedSecurityController.shutdown();
}
} catch (Exception e) {
log.error(e);
}
eventController.scheduleCheckJob();
}
@Override
public String getInfoDetails(DBRProgressMonitor monitor) {
public String getInfoDetails() {
return "";
}
@@ -519,6 +541,7 @@ public abstract class CBApplication<T extends CBServerConfig>
return getServerConfigurationController().getLocalHostAddress();
}
@NotNull
public List<InetAddress> getLocalInetAddresses() {
return localInetAddresses;
}
@@ -667,6 +690,7 @@ public abstract class CBApplication<T extends CBServerConfig>
return null;
}
@NotNull
public CBSessionManager getSessionManager() {
if (sessionManager == null) {
sessionManager = createSessionManager();
@@ -691,6 +715,7 @@ public abstract class CBApplication<T extends CBServerConfig>
return List.of();
}
@NotNull
@Override
public WSEventController getEventController() {
return eventController;
@@ -772,17 +797,18 @@ public abstract class CBApplication<T extends CBServerConfig>
initActions.remove(actionId);
}
@NotNull
public Map<String, String> getInitActions() {
return Map.copyOf(initActions);
}
@NotNull
@Override
public WebServerConfig getWebServerConfig() {
return new CBWebServerConfig(this);
}
@NotNull
@Override
public ServletSystemInformationCollector<?> getSystemInformationCollector() {
return systemInformationCollector;
}
@@ -795,4 +821,8 @@ public abstract class CBApplication<T extends CBServerConfig>
public <T> T getApplicationContextValue(@NotNull String key) {
return (T) applicationContext.get(key);
}
protected CloudBeaverInstanceServer createInstanceServer() throws IOException {
return new CloudBeaverInstanceServer();
}
}
@@ -17,8 +17,6 @@
package io.cloudbeaver.server;
import io.cloudbeaver.auth.NoAuthCredentialsProvider;
import io.cloudbeaver.model.CBWebServerConfig;
import io.cloudbeaver.model.WebServerConfig;
import io.cloudbeaver.model.config.CBServerConfig;
import io.cloudbeaver.model.rm.local.LocalResourceController;
import io.cloudbeaver.service.security.CBEmbeddedSecurityController;
@@ -79,6 +77,7 @@ public class CBApplicationCE extends CBApplication<CBServerConfig> {
@NotNull
@Override
public RMController createResourceController(@NotNull SMCredentialsProvider credentialsProvider,
@NotNull DBPWorkspace workspace) throws DBException {
@@ -96,17 +95,6 @@ public class CBApplicationCE extends CBApplication<CBServerConfig> {
return serverConfigController;
}
protected void shutdown() {
try {
if (securityController instanceof CBEmbeddedSecurityController<?> embeddedSecurityController) {
embeddedSecurityController.shutdown();
}
} catch (Exception e) {
log.error(e);
}
super.shutdown();
}
protected void finishSecurityServiceConfiguration(
@NotNull String adminName,
@Nullable String adminPassword,
@@ -20,14 +20,13 @@ package io.cloudbeaver.server;
import io.cloudbeaver.server.jobs.SessionStateJob;
import io.cloudbeaver.server.jobs.WebDataSourceMonitorJob;
import io.cloudbeaver.server.jobs.WebSessionMonitorJob;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.app.DBPRegistryListener;
import org.jkiss.dbeaver.model.impl.app.BaseApplicationImpl;
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
import org.jkiss.dbeaver.model.runtime.AbstractJob;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.runtime.LoggingProgressMonitor;
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.utils.IOUtils;
@@ -38,12 +37,11 @@ import java.io.IOException;
*/
public class CBPlatform extends BaseWebPlatform {
// The plug-in ID
public static final String PLUGIN_ID = "io.cloudbeaver.server"; //$NON-NLS-1$
private static final Log log = Log.getLog(CBPlatform.class);
private WebServerPreferenceStore preferenceStore;
private DBPRegistryListener driverRegistryListener;
private boolean isShutdown;
public static CBPlatform getInstance() {
return (CBPlatform) DBWorkbench.getPlatform();
@@ -55,10 +53,19 @@ public class CBPlatform extends BaseWebPlatform {
@Override
protected synchronized void initialize() {
long startTime = System.currentTimeMillis();
log.info("Initialize web platform...: ");
log.info("Initialize web platform " + getClass().getSimpleName());
this.preferenceStore = new WebServerPreferenceStore(WebPlatformActivator.getInstance().getPreferences());
super.initialize();
scheduleServerJobs();
this.scheduleServerJobs();
this.driverRegistryListener = new DBPRegistryListener() {
@Override
public void handleRegistryReload() {
WebAppUtils.getWebApplication().getDriverRegistry().refreshApplicableDrivers();
}
};
DataSourceProviderRegistry.getInstance().addDataSourceRegistryListener(driverRegistryListener);
log.info("Web platform initialized (" + (System.currentTimeMillis() - startTime) + "ms)");
}
@@ -73,23 +80,24 @@ public class CBPlatform extends BaseWebPlatform {
new WebDataSourceMonitorJob(this, getApplication().getSessionManager())
.scheduleMonitor();
new AbstractJob("Delete temp folder") {
@Override
protected IStatus run(DBRProgressMonitor monitor) {
try {
IOUtils.deleteDirectory(getTempFolder(monitor, TEMP_FILE_FOLDER));
IOUtils.deleteDirectory(getTempFolder(monitor, TEMP_FILE_IMPORT_FOLDER));
} catch (IOException e) {
throw new RuntimeException(e);
}
return Status.OK_STATUS;
}
}.schedule();
try {
LoggingProgressMonitor monitor = new LoggingProgressMonitor(log);
IOUtils.deleteDirectory(getTempFolder(monitor, TEMP_FILE_FOLDER));
IOUtils.deleteDirectory(getTempFolder(monitor, TEMP_FILE_IMPORT_FOLDER));
} catch (IOException e) {
log.error(e);
}
}
public synchronized void dispose() {
long startTime = System.currentTimeMillis();
log.debug("Shutdown Core...");
isShutdown = true;
log.debug("Shutdown web platform");
if (driverRegistryListener != null) {
DataSourceProviderRegistry.getInstance().removeDataSourceRegistryListener(driverRegistryListener);
driverRegistryListener = null;
}
super.dispose();
@@ -112,7 +120,7 @@ public class CBPlatform extends BaseWebPlatform {
@Override
public boolean isShuttingDown() {
return false;
return isShutdown;
}
}
@@ -26,6 +26,7 @@ import io.cloudbeaver.server.graphql.GraphQLEndpoint;
import io.cloudbeaver.server.servlets.CBImageServlet;
import io.cloudbeaver.server.servlets.CBStaticServlet;
import io.cloudbeaver.server.servlets.WebStatusServlet;
import io.cloudbeaver.server.websockets.CBEventsLongPollingServlet;
import io.cloudbeaver.server.websockets.CBEventsWebSocket;
import io.cloudbeaver.server.websockets.CBWebSocketServerConfigurator;
import io.cloudbeaver.service.DBWServiceBindingServlet;
@@ -47,11 +48,13 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class CBJettyServer {
private static final Log log = Log.getLog(CBJettyServer.class);
static {
// Set Jetty log level to WARN
System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StdErrLog");
@@ -118,6 +121,9 @@ public class CBJettyServer {
servletContextHandler.addServlet(new ServletHolder("status", new WebStatusServlet()), "/status");
ServletHolder eventsServletHolder = new ServletHolder("events", new CBEventsLongPollingServlet());
servletContextHandler.addServlet(eventsServletHolder, serverConfiguration.getServicesURI() + "events/*");
GraphQLEndpoint endpoint = new GraphQLEndpoint(new ServerConfigurationTimeLimitFilter(application));
application.addApplicationContextValue(GraphQL.class.getName(), endpoint.getGraphQL());
String gqlServletPath = serverConfiguration.getServicesURI() + "gql/*";
@@ -195,11 +201,15 @@ public class CBJettyServer {
log.debug("\t" + sm.getServletName() + ": " + Arrays.toString(sm.getPathSpecs())); //$NON-NLS-1$
}
log.debug("Active websocket mappings:");
for (String mapping : webSocketContext.getMappings()) {
log.debug("\t" + mapping);
List<String> wsMappings = webSocketContext.getMappings();
if (!wsMappings.isEmpty()) {
log.debug("Active websocket mappings:");
for (String mapping : wsMappings) {
log.debug("\t" + mapping);
}
} else {
log.debug("No websocket mappings");
}
}
boolean forwardProxy = application.getAppConfiguration().isEnabledForwardProxy();
@@ -17,6 +17,7 @@
package io.cloudbeaver.service;
import io.cloudbeaver.server.CBApplication;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
/**
@@ -24,6 +25,6 @@ import org.jkiss.dbeaver.DBException;
*/
public interface DBWServiceInitializer extends DBWServiceBinding {
void initializeService(CBApplication<?> application) throws DBException;
void initializeService(@NotNull CBApplication<?> application) throws DBException;
}
@@ -26,6 +26,7 @@ import io.cloudbeaver.server.CBConstants;
import io.cloudbeaver.server.WebAppSessionManager;
import io.cloudbeaver.server.events.WSWebUtils;
import io.cloudbeaver.service.DBWSessionHandler;
import io.cloudbeaver.utils.ServletAppUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
@@ -49,10 +50,10 @@ public class CBSessionManager implements WebAppSessionManager {
private static final Log log = Log.getLog(CBSessionManager.class);
private final CBApplication application;
private final CBApplication<?> application;
private final Map<String, BaseWebSession> sessionMap = new HashMap<>();
public CBSessionManager(CBApplication application) {
public CBSessionManager(CBApplication<?> application) {
this.application = application;
}
@@ -88,7 +89,7 @@ public class CBSessionManager implements WebAppSessionManager {
return null;
}
protected CBApplication getApplication() {
protected CBApplication<?> getApplication() {
return application;
}
@@ -165,6 +166,8 @@ public class CBSessionManager implements WebAppSessionManager {
validateSessionIp(request, webSession);
webSession.updateClientOrigin(ServletAppUtils.getOriginFromRequest(request));
return webSession;
}
@@ -264,7 +267,7 @@ public class CBSessionManager implements WebAppSessionManager {
}
@NotNull
protected Map<String, DBWSessionHandler> getSessionHandlers() {
protected Map<String, DBWSessionHandler<WebSession>> getSessionHandlers() {
return WebHandlerRegistry.getInstance().getSessionHandlers()
.stream()
.collect(Collectors.toMap(WebSessionHandlerDescriptor::getId, WebSessionHandlerDescriptor::getInstance));
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Server
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
Bundle-Version: 25.2.5.qualifier
Bundle-Release-Date: 20251117
Bundle-Version: 25.3.3.qualifier
Bundle-Release-Date: 20260119
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.eclipse.core.runtime;visibility:=reexport,
@@ -18,6 +18,7 @@ Require-Bundle: org.eclipse.core.runtime;visibility:=reexport,
org.jkiss.bundle.apache.dbcp,
org.jkiss.dbeaver.net.ssh,
io.cloudbeaver.model;visibility:=reexport,
io.cloudbeaver.model.cli,
io.cloudbeaver.service.security;visibility:=reexport
Export-Package: io.cloudbeaver,
io.cloudbeaver.model,
@@ -60,6 +60,9 @@
<eventHandler class="io.cloudbeaver.server.events.WSEventHandlerWorkspaceConfigUpdate">
<topic id="cb_workspace_configuration"/>
</eventHandler>
<eventHandler class="io.cloudbeaver.server.events.WSObjectSettingsEventHandler">
<topic id="cb_object_settings"/>
</eventHandler>
</extension>
<extension point="org.jkiss.dbeaver.settings">
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>25.2.5-SNAPSHOT</version>
<version>25.3.3-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -595,6 +595,8 @@ type NavigatorSettings {
hideFolders: Boolean!
hideSchemas: Boolean!
hideVirtualModel: Boolean!
userSettings: Boolean! @since(version: "25.3.2")
}
type RMResourceType {
@@ -637,6 +639,8 @@ input NavigatorSettingsInput {
hideFolders: Boolean!
hideSchemas: Boolean!
hideVirtualModel: Boolean!
userSettings: Boolean @since(version: "25.3.2")
}
input NetworkHandlerConfigInput {
@@ -816,10 +820,10 @@ extend type Mutation {
"Test connection configuration. Returns remote server version"
testConnection( config: ConnectionConfig!, projectId: ID): ConnectionInfo!
"Test network handler"
"Test network handler connectivity"
testNetworkHandler( config: NetworkHandlerConfigInput! ): NetworkEndpointInfo!
"Initiate existing connection"
"Initiate database connection. User credentials may be (optionally) override default settings."
initConnection(
id: ID!,
projectId: ID,
@@ -830,11 +834,13 @@ extend type Mutation {
selectedSecretId:String
): ConnectionInfo!
"Disconnect from database"
"Disconnect from the database"
closeConnection( id: ID!, projectId: ID ): ConnectionInfo!
"Change navigator settings for connection"
"Change navigator settings for connection. It can change default settings or user settings depending on userSettings property."
setConnectionNavigatorSettings( id: ID!, projectId: ID, settings: NavigatorSettingsInput!): ConnectionInfo!
"Sets to default navigator settings for connection. Resets all user navigator settings for this connection."
clearConnectionNavigatorSettings(projectId: ID!, id: ID! ): ConnectionInfo! @since(version: "25.3.2")
#### Generic async functions
@@ -71,8 +71,12 @@ enum CBServerEventId {
cb_server_notification@since(version: "25.2.1")
"Session task confirmations"
cb_session_task_confirmation_request@since(version: "25.2.2")
cb_session_task_query_confirmation_request@since(version: "25.2.2")
cb_session_task_confirmation_request @since(version: "25.2.2")
cb_session_task_query_confirmation_request @since(version: "25.2.2")
cb_session_task_query_params_confirmation_request @since(version: "25.2.5")
"Open URL request in client"
cb_open_url @since(version: "25.3.1")
}
"Events sent by client"
@@ -81,7 +85,8 @@ enum CBClientEventId {
cb_client_topic_unsubscribe,
cb_client_projects_active,
cb_client_session_ping,
cb_client_session_task_confirmation@since(version: "25.2.2")
cb_client_session_task_confirmation @since(version: "25.2.2")
cb_client_session_task_with_parameters_confirmation @since(version: "25.2.5")
}
"Client subscribes on topic to receive only related events"
@@ -106,6 +111,7 @@ enum CBEventTopic {
cb_workspace_configuration @since(version: "25.1.1")
cb_ai @since(version: "25.1.1")
cb_notification @since(version: "25.2.1")
cb_session_action @since(version: "25.3.1")
}
"Base server event interface"
@@ -299,6 +305,13 @@ type WSServerNotificationEvent implements CBServerEvent @since(version: "25.2.1"
notificationType: WSServerNotificationEventType!
}
"Event for opening URL in client application"
type WSOpenUrlEvent implements CBServerEvent @since(version: "25.3.1") {
id: CBServerEventId!
topicId: CBEventTopic!
url: String!
}
type WSSessionTaskConfirmationRequestEvent implements CBServerEvent @since(version: "25.2.2") {
id: CBServerEventId!
topicId: CBEventTopic!
@@ -324,6 +337,23 @@ type WSSessionTaskConfirmationEvent implements CBClientEvent @since(version: "25
skipConfirmations: Boolean!
}
type WSSessionTaskQueryParamsConfirmationEvent implements CBServerEvent @since(version: "25.2.5") {
id: CBServerEventId!
topicId: CBEventTopic!
taskId: ID!
title: String!
message: String!
query: String!
parameters: Object!
}
type WSSessionTaskWithParametersConfirmationEvent implements CBClientEvent @since(version: "25.2.5") {
id: CBClientEventId!
topicId: CBEventTopic
taskId: ID!
parameters: Object!
}
extend type Query {
emptyEvent: Boolean
}
@@ -158,8 +158,6 @@ type SQLExecuteInfo {
duration: Int!
# Actual conditions applied to query
filterText: String
# # original sql query without SQLDataFilter
# originalQuery: String
# Full query that was executed, contains all used filters
fullQuery: String
# Results
@@ -339,7 +337,21 @@ extend type Query {
columnNames: [String!],
functions: [String!],
showDuplicatesOnly: Boolean
): String!
): String! @deprecated (reason: "use sqlGropingResultSet (25.3.1)")
"Creates async task to get grouped SQL results"
sqlGropingResultSet(
projectId: ID,
contextId: ID!,
connectionId: ID!,
resultsId: ID!,
columnNames: [String!],
functions: [String!],
showDuplicatesOnly: Boolean,
filter: SQLDataFilter,
dataFormat: ResultDataFormat,
isInteractive: Boolean
): AsyncTaskInfo! @since(version: "25.3.1")
}
extend type Mutation {
@@ -0,0 +1,31 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks a field or method as sensitive, so its value won't be logged.
*/
@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface WebParameterSecure {
}
@@ -39,7 +39,10 @@ import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.navigator.*;
import org.jkiss.dbeaver.model.navigator.DBNContainer;
import org.jkiss.dbeaver.model.navigator.DBNDataSource;
import org.jkiss.dbeaver.model.navigator.DBNNode;
import org.jkiss.dbeaver.model.navigator.DBNProject;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings;
@@ -68,7 +71,7 @@ public class WebServiceUtils extends WebCommonUtils {
return WebServiceUtils.class.getClassLoader().getResourceAsStream(path);
}
public static DBNBrowseSettings parseNavigatorSettings(Map<String, Object> settingsMap) {
public static DataSourceNavigatorSettings parseNavigatorSettings(Map<String, Object> settingsMap) {
return gson.fromJson(
gson.toJsonTree(settingsMap), DataSourceNavigatorSettings.class);
}
@@ -20,7 +20,6 @@ import io.cloudbeaver.utils.ServletAppUtils;
import org.eclipse.core.runtime.IProduct;
import org.eclipse.core.runtime.Platform;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.CommonUtils;
@@ -75,7 +74,7 @@ public class WebProductInfo {
@Property
public String getLicenseInfo() {
return provideSensitiveInformation
? ServletAppUtils.getServletApplication().getInfoDetails(new VoidProgressMonitor())
? ServletAppUtils.getServletApplication().getInfoDetails()
: "";
}
@@ -17,6 +17,7 @@
package io.cloudbeaver.registry;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.DBWSessionHandler;
import org.eclipse.core.runtime.IConfigurationElement;
import org.jkiss.code.NotNull;
@@ -31,7 +32,7 @@ public class WebSessionHandlerDescriptor extends AbstractContextDescriptor {
public static final String EXTENSION_ID = "io.cloudbeaver.servlet.handler"; //$NON-NLS-1$
private final String id;
private final DBWSessionHandler instance;
private final DBWSessionHandler<WebSession> instance;
public WebSessionHandlerDescriptor(IConfigurationElement config) throws DBException {
super(config);
@@ -45,7 +46,7 @@ public class WebSessionHandlerDescriptor extends AbstractContextDescriptor {
return id;
}
public DBWSessionHandler getInstance() {
public DBWSessionHandler<WebSession> getInstance() {
return instance;
}
@@ -31,25 +31,32 @@ import java.util.Map;
* Base interface for applications with web ui
*/
public interface WebApplication extends ServletApplication {
@NotNull
WebServerConfiguration getServerConfiguration();
@NotNull
WebAppSessionManager getSessionManager();
@NotNull
WebDriverRegistry getDriverRegistry();
@NotNull
WebAppConfiguration getAppConfiguration();
@NotNull
Map<String, Object> getProductConfiguration();
@NotNull
List<InetAddress> getLocalInetAddresses();
@NotNull
Map<String, String> getInitActions();
boolean isLicenseValid();
String getLicenseStatus();
@NotNull
WebServerConfig getWebServerConfig();
}
@@ -0,0 +1,92 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server.events;
import io.cloudbeaver.WebSessionProjectImpl;
import io.cloudbeaver.model.session.BaseWebSession;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.security.SMObjectSettings;
import org.jkiss.dbeaver.model.websocket.event.WSObjectSettingsEvent;
import org.jkiss.utils.CommonUtils;
import java.util.List;
public class WSObjectSettingsEventHandler extends WSDefaultEventHandler<WSObjectSettingsEvent> {
private static final Log log = Log.getLog(WSObjectSettingsEventHandler.class);
@Override
protected void updateSessionData(@NotNull BaseWebSession activeUserSession, @NotNull WSObjectSettingsEvent event) {
String projectId = event.getProjectId();
if (projectId == null) {
// Right now we handle only project-specific object settings
return;
}
if (activeUserSession instanceof WebSession webSession) {
WebSessionProjectImpl project = webSession.getProjectById(projectId);
if (project == null) {
log.warn("Project with id '" + projectId + "' not found in session '" + webSession.getSessionId() + "'");
return;
}
handleEventInWebSessionProject(webSession, project, event);
}
super.updateSessionData(activeUserSession, event);
}
private void handleEventInWebSessionProject(
@NotNull WebSession webSession,
@NotNull WebSessionProjectImpl project,
@NotNull WSObjectSettingsEvent event
) {
String objectId = event.getObjectId();
if (WSObjectSettingsEvent.UPDATED.equals(event.getId())) {
{
try {
List<SMObjectSettings> settings = webSession.getSecurityController().getObjectSettings(
project.getId(),
event.getSmObjectType(),
objectId,
event.getSettingIds().toArray(new String[0])
);
for (SMObjectSettings setting : settings) {
project.getProjectSettings().updateObjectSettingsCache(
setting.objectType(),
setting.objectId(),
setting.settings()
);
}
} catch (DBException e) {
log.error("Error fetching updated object settings", e);
}
}
} else if (WSObjectSettingsEvent.DELETED.equals(event.getId())) {
project.getProjectSettings().deleteObjectSettingsCache(
event.getSmObjectType(),
objectId,
event.getSettingIds()
);
}
}
@Override
protected boolean isAcceptableInSession(@NotNull BaseWebSession activeUserSession, @NotNull WSObjectSettingsEvent event) {
return super.isAcceptableInSession(activeUserSession, event) &&
CommonUtils.equalObjects(activeUserSession.getUserContext().getUserId(), event.getUserId());
}
}
@@ -44,6 +44,7 @@ import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.utils.MimeTypes;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.HttpConstants;
import org.jkiss.utils.IOUtils;
@@ -211,6 +212,7 @@ public class GraphQLEndpoint extends HttpServlet {
if (path.contentEquals("/schema.json") && develMode) {
executeQuery(request, response, GraphQLConstants.SCHEMA_READ_QUERY, null, null);
} else if (path.contentEquals("/console") && develMode) {
response.setContentType(MimeTypes.TEXT_HTML);
try (InputStream consolePageStream = WebServiceUtils.openStaticResource("static/graphiql/index.html")) {
IOUtils.copyStream(consolePageStream, response.getOutputStream());
}
@@ -246,14 +248,7 @@ public class GraphQLEndpoint extends HttpServlet {
if (operationName != null) {
contextBuilder.operationName(operationName);
}
String sessionId = GraphQLLoggerUtil.getSmSessionId(request);
String userId = GraphQLLoggerUtil.getUserId(request);
String loggerMessage = GraphQLLoggerUtil.buildLoggerMessage(sessionId, userId, variables);
if (operationName != null) {
log.debug("API > " + operationName + loggerMessage);
} else if (DEBUG) {
log.debug("API > " + query + loggerMessage);
}
LocalDateTime startTime = LocalDateTime.now();
ExecutionInput executionInput = contextBuilder.build();
ExecutionResult executionResult = null;
@@ -16,35 +16,48 @@
*/
package io.cloudbeaver.server.graphql;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.cloudbeaver.WebParameterSecure;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.WebAppUtils;
import io.cloudbeaver.server.WebApplication;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.qm.QMConstants;
import org.jkiss.utils.CommonUtils;
import java.util.Map;
import java.util.Set;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.StringJoiner;
public class GraphQLLoggerUtil {
public static final String LOG_API_GRAPHQL_DEBUG_PARAMETER = "log.api.graphql.debug";
private static final Set<String> PROHIBITED_VARIABLES =
Set.of("password", "config", "parameters", "settings", "licenseText", "credentials", "username");
public static final Gson GSON = new GsonBuilder().create();
public static final String MASK_STRING = "****";
public static String getUserId(HttpServletRequest request) {
@Nullable
public static String getUserId(@NotNull HttpServletRequest request) {
WebSession session = getWebSession(request);
if (session == null) {
return null;
}
String userId = session.getUserContext().getUserId();
if (userId == null && session.getUserContext().isAuthorizedInSecurityManager()) {
return "anonymous";
if (userId == null) {
if (session.getUserContext().isAuthorizedInSecurityManager()) {
return QMConstants.QM_ANONYMOUS_DOMAIN;
}
return QMConstants.QM_UNAUTHORIZED_DOMAIN;
}
return userId;
}
public static String getSmSessionId(HttpServletRequest request) {
@Nullable
public static String getSmSessionId(@NotNull HttpServletRequest request) {
WebSession session = getWebSession(request);
if (session == null) {
return null;
@@ -53,7 +66,7 @@ public class GraphQLLoggerUtil {
}
@Nullable
public static WebSession getWebSession(HttpServletRequest request) {
public static WebSession getWebSession(@NotNull HttpServletRequest request) {
if (request.getSession() == null) {
return null;
}
@@ -63,43 +76,74 @@ public class GraphQLLoggerUtil {
.findWebSession(request);
}
public static String buildLoggerMessage(String sessionId, String userId, Map<String, Object> variables) {
StringBuilder loggerMessage = new StringBuilder(" [user: ").append(userId)
.append(", sessionId: ").append(sessionId).append("]");
@NotNull
public static String buildLoggerMessage(
@NotNull String sessionId,
@NotNull String userId,
@NotNull Method method,
@NotNull Object[] args
) {
StringBuilder sb = new StringBuilder(64)
.append(" [").append(userId)
.append(", session: ").append(sessionId)
.append("]");
if (WebAppUtils.getWebPlatform().getPreferenceStore().getBoolean(LOG_API_GRAPHQL_DEBUG_PARAMETER)
&& variables != null
) {
loggerMessage.append(" [variables] ");
String parsedVariables = parseVarialbes(variables);
if (CommonUtils.isNotEmpty(parsedVariables)) {
loggerMessage.append(parseVarialbes(variables));
if (WebAppUtils.getWebPlatform().getPreferenceStore().getBoolean(LOG_API_GRAPHQL_DEBUG_PARAMETER)) {
sb.append('(');
String text = maskArgsToString(method, args);
if (CommonUtils.isNotEmpty(text)) {
sb.append(text);
}
sb.append(')');
}
return loggerMessage.toString();
return sb.toString();
}
private static String parseVarialbes(Map<String, Object> map) {
StringBuilder result = new StringBuilder();
@NotNull
public static String maskArgsToString(@NotNull Method method, @Nullable Object[] args) {
Parameter[] params = method.getParameters();
if (params.length == 0 || args == null || args.length == 0) {
return "";
}
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
int limit = Math.min(args.length, params.length);
StringJoiner joiner = new StringJoiner(", ");
boolean isProhibited = PROHIBITED_VARIABLES.stream()
.anyMatch(prohibitedKey -> key.toLowerCase().contains(prohibitedKey.toLowerCase()));
if (isProhibited) {
result.append(key).append(": ").append("******** ");
for (int i = 0; i < limit; i++) {
Object value = args[i];
if (value instanceof WebSession || value instanceof ServletRequest || value instanceof ServletResponse) {
//we already log sessionId
continue;
}
if (params[i].isAnnotationPresent(WebParameterSecure.class)) {
joiner.add(MASK_STRING);
continue;
}
if (value instanceof String sv && CommonUtils.isEmpty(sv)) {
continue;
}
if (value instanceof Map) {
result.append(parseVarialbes((Map<String, Object>) value));
if (value != null && !isSimple(value.getClass())) {
String stringValue;
try {
stringValue = GSON.toJson(value);
} catch (Exception e) {
stringValue = value.toString();
}
joiner.add(stringValue);
} else {
result.append(key).append(": ").append(value).append(" ");
joiner.add(String.valueOf(value));
}
}
return result.toString().trim();
return joiner.toString();
}
private static boolean isSimple(Class<?> cls) {
return cls.isPrimitive()
|| Number.class.isAssignableFrom(cls)
|| CharSequence.class.isAssignableFrom(cls)
|| Boolean.class.equals(cls)
|| Enum.class.isAssignableFrom(cls);
}
}
@@ -18,12 +18,18 @@ package io.cloudbeaver.server.jetty;
import io.cloudbeaver.server.WebApplication;
import org.eclipse.jetty.ee10.servlet.SessionHandler;
import org.jkiss.code.NotNull;
public class CBSessionHandler extends SessionHandler {
static final int ONE_MINUTE = 60;
private final WebApplication application;
public CBSessionHandler(WebApplication application) {
public CBSessionHandler(@NotNull WebApplication application) {
this.application = application;
}
@Override
public String toString() {
return "CBSessionHandler-" + super.hashCode();
}
}
@@ -66,8 +66,9 @@ public class SqlOutputLogReaderJob extends AbstractJob {
this.contextInfoId = contextInfoId;
}
@NotNull
@Override
protected IStatus run(DBRProgressMonitor monitor) {
protected IStatus run(@NotNull DBRProgressMonitor monitor) {
if (!DBWorkbench.getPlatform().isShuttingDown()) {
try {
if (!dbcStatement.isStatementClosed()) {
@@ -0,0 +1,100 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server.websockets;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.session.BaseWebSession;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.websocket.event.client.*;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.websocket.WSUtils;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
import org.jkiss.utils.CommonUtils;
public class CBClientEventProcessor {
private static final Log log = Log.getLog(CBClientEventProcessor.class);
final BaseWebSession webSession;
public CBClientEventProcessor(@NotNull BaseWebSession webSession) {
this.webSession = webSession;
}
public void process(@Nullable String message) {
if (CommonUtils.isEmpty(message)) {
return;
}
WSClientEvent clientEvent;
try {
clientEvent = WSUtils.clientGson.fromJson(message, WSClientEvent.class);
} catch (Exception e) {
log.error("Error parsing event: " + e.getMessage(), e);
webSession.addSessionError(new DBWebException("Invalid event: " + e.getMessage()));
return;
}
switch (clientEvent.getId()) {
case WSSubscribeOnTopicClientEvent.ID: {
webSession.getEventsFilter().subscribeOnEventTopic(clientEvent.getTopicId());
break;
}
case WSUnsubscribeFromTopicClientEvent.ID: {
webSession.getEventsFilter().unsubscribeFromEventTopic(clientEvent.getTopicId());
break;
}
case WSUpdateActiveProjectsClientEvent.ID: {
var projectEvent = (WSUpdateActiveProjectsClientEvent) clientEvent;
webSession.getEventsFilter().setSubscribedProjects(projectEvent.getProjectIds());
break;
}
case WSSessionPingClientEvent.ID: {
if (webSession instanceof WebSession session) {
session.updateInfo(true);
}
break;
}
case WSSessionTaskConfirmationEvent.ID: {
if (webSession instanceof WebSession session) {
var taskConfirmationEvent = (WSSessionTaskConfirmationEvent) clientEvent;
session.handleTaskConfirmation(
taskConfirmationEvent.getTaskId(),
taskConfirmationEvent.isConfirmed(),
taskConfirmationEvent.isSkipConfirmations()
);
}
break;
}
case WSSessionTaskWithParametersConfirmationEvent.ID: {
if (webSession instanceof WebSession session) {
var taskParamsConfirmationEvent = (WSSessionTaskWithParametersConfirmationEvent) clientEvent;
session.handleTaskConfirmationWithParameters(
taskParamsConfirmationEvent.getTaskId(),
taskParamsConfirmationEvent.getParameters()
);
}
break;
}
default:
var e = new DBWebException("Unknown client event: " + clientEvent.getId());
log.error(e.getMessage(), e);
webSession.addSessionError(e);
}
}
}
@@ -0,0 +1,118 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server.websockets;
import io.cloudbeaver.model.session.BaseWebSession;
import io.cloudbeaver.model.session.WebHeadlessSession;
import io.cloudbeaver.websocket.CBWebSessionEventHandler;
import io.cloudbeaver.websocket.event.client.WSSessionPingClientEvent;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.websocket.WSUtils;
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class CBEventsLongPolling implements CBWebSessionEventHandler {
private static final Log log = Log.getLog(CBEventsLongPolling.class);
private static final String PING = WSUtils.clientGson.toJson(new WSSessionPingClientEvent("cb_session"));
private static final int QUEUE_CAPACITY = 1000;
private final BaseWebSession webSession;
private final BlockingQueue<WSEvent> queue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
private final CBClientEventProcessor processor;
private volatile long lastPoll;
public CBEventsLongPolling(@NotNull BaseWebSession webSession) {
this.webSession = webSession;
this.lastPoll = System.currentTimeMillis();
this.webSession.addEventHandler(this);
this.processor = new CBClientEventProcessor(this.webSession);
}
@NotNull
public BaseWebSession webSession() {
return webSession;
}
public long lastPoll() {
return lastPoll;
}
public void onPoll() {
lastPoll = System.currentTimeMillis();
if (webSession instanceof WebHeadlessSession) {
webSession.touchSession();
processor.process(PING);
}
}
public void onUserActivity() {
lastPoll = System.currentTimeMillis();
webSession.touchSession();
}
@NotNull
public List<WSEvent> pollEvents(long timeoutSec) throws InterruptedException {
WSEvent first = queue.poll(timeoutSec, TimeUnit.SECONDS);
if (first == null) {
return List.of();
}
List<WSEvent> result = new ArrayList<>();
result.add(first);
queue.drainTo(result);
return result;
}
@Override
public void handleWebSessionEvent(@NotNull WSEvent event) {
if (!queue.offer(event)) {
log.warn("Event queue overflow: sid=" + webSession.getSessionId() +
", eventId=" + event.getId());
}
}
public void onMessage(@Nullable String message) {
processor.process(message);
}
@Override
public void close() {
webSession.removeEventHandler(this);
queue.clear();
}
@Override
public String toString() {
return "CBEventsLongPolling{" +
"sid=" + webSession.getSessionId() +
", size=" + queue.size() +
", lastPoll=" + lastPoll +
'}';
}
}
@@ -0,0 +1,333 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server.websockets;
import io.cloudbeaver.model.session.BaseWebSession;
import io.cloudbeaver.model.session.WebHttpRequestInfo;
import io.cloudbeaver.server.CBConstants;
import io.cloudbeaver.server.WebAppSessionManager;
import io.cloudbeaver.server.WebAppUtils;
import io.cloudbeaver.utils.ServletAppUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.security.exception.SMAccessTokenExpiredException;
import org.jkiss.dbeaver.model.security.exception.SMException;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.WSUtils;
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
import org.jkiss.dbeaver.model.websocket.event.session.WSSocketConnectedEvent;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.HttpConstants;
import org.jkiss.utils.rest.RpcConstants;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class CBEventsLongPollingServlet extends HttpServlet {
private static final Log log = Log.getLog(CBEventsLongPollingServlet.class);
private static final int POLL_TIMEOUT_SEC = 25;
private static final int SESSION_IDLE_TIMEOUT_SEC = 60;
private volatile boolean running = false;
private final Map<String, CBEventsLongPolling> sessions = new ConcurrentHashMap<>();
private final List<SessionResolver> resolvers = List.of(
new HeadlessSessionResolver(),
new CookieSessionResolver()
);
@Override
public void init() throws ServletException {
super.init();
running = true;
new LongPollingMonitorJob(DBWorkbench.getPlatform(), this).scheduleMonitor();
}
@Override
protected void doOptions(@NotNull HttpServletRequest req, @NotNull HttpServletResponse resp) {
addCorsHeaders(req, resp);
resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
@Override
protected void doGet(@NotNull HttpServletRequest req, @NotNull HttpServletResponse resp) throws IOException {
addCorsHeaders(req, resp);
try {
final BaseWebSession ws = resolveSessionOrSendError(req, resp);
if (ws == null) {
return;
}
CBEventsLongPolling ps = getOrCreatePollSession(ws);
ps.onPoll();
List<WSEvent> events = ps.pollEvents(POLL_TIMEOUT_SEC);
if (events.isEmpty()) {
resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
return;
}
resp.setContentType(CBConstants.APPLICATION_JSON);
WSUtils.clientGson.toJson(Map.of("events", events), resp.getWriter());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.debug("Long-poll interrupted (likely client disconnected)", e);
resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
}
@Override
protected void doPost(@NotNull HttpServletRequest req, @NotNull HttpServletResponse resp) throws IOException {
addCorsHeaders(req, resp);
final BaseWebSession ws = resolveSessionOrSendError(req, resp);
if (ws == null) {
return;
}
CBEventsLongPolling ps = getOrCreatePollSession(ws);
ps.onUserActivity();
String json = new String(req.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
if (CommonUtils.isEmpty(json)) {
sendError(resp, HttpServletResponse.SC_BAD_REQUEST, "Empty payload");
return;
}
ps.onMessage(json);
resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
@Override
public void destroy() {
super.destroy();
running = false;
sessions.clear();
log.debug("HTTP Long-Polling transport stopped");
}
public void cleanupIdleSessions() {
long now = System.currentTimeMillis();
sessions.entrySet().removeIf(entry -> {
CBEventsLongPolling ps = entry.getValue();
long idleMillis = now - ps.lastPoll();
if (idleMillis > SESSION_IDLE_TIMEOUT_SEC * 1000L) {
try {
ps.close();
} catch (Exception e) {
log.debug("Failed to remove event handler for " + entry.getKey(), e);
}
return true;
}
return false;
});
}
public boolean isRunning() {
return running;
}
@Nullable
protected BaseWebSession resolveSessionOrSendError(
@NotNull HttpServletRequest req,
@NotNull HttpServletResponse resp
) {
try {
BaseWebSession ws = resolveSession(req);
resp.setHeader(WSConstants.WS_SESSION_HEADER, ws.getSessionId());
ServletAppUtils.addResponseCookie(
req,
resp,
CBConstants.CB_SESSION_COOKIE_NAME,
ws.getSessionId(),
-1
);
return ws;
} catch (SMException e) {
log.debug("LP request rejected: access token expired");
sendError(resp, HttpConstants.CODE_TOKEN_EXPIRED, e.getMessage());
} catch (SecurityException e) {
log.warn("LP session resolve failed", e);
sendError(resp, HttpConstants.CODE_UNAUTHORIZED, e.getMessage());
}
return null;
}
private void addCorsHeaders(@NotNull HttpServletRequest req, @NotNull HttpServletResponse resp) {
String origin = req.getHeader("Origin");
boolean develMode = ServletAppUtils.getServletApplication().getServerConfiguration().isDevelMode();
if (!develMode || origin == null) {
return;
}
resp.setHeader("Access-Control-Allow-Origin", origin);
resp.setHeader("Access-Control-Allow-Credentials", "true");
String reqHeaders = req.getHeader("Access-Control-Request-Headers");
if (reqHeaders != null) {
resp.setHeader("Access-Control-Allow-Headers", reqHeaders);
}
resp.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
}
@Nullable
protected BaseWebSession resolveSession(@NotNull HttpServletRequest req) throws SMException {
for (SessionResolver r : resolvers) {
BaseWebSession s = r.resolve(req);
if (s != null) {
return s;
}
}
throw new SecurityException("No web session found for long-poll request");
}
@NotNull
private CBEventsLongPolling getOrCreatePollSession(@NotNull BaseWebSession ws) {
final String sid = ws.getSessionId();
return sessions.compute(sid, (key, existing) -> {
if (existing != null) {
return existing;
}
CBEventsLongPolling ps = new CBEventsLongPolling(ws);
ps.handleWebSessionEvent(new WSSocketConnectedEvent(ws.getApplication().getApplicationRunId()));
log.debug("HTTP Long-Poll channel opened for session " + sid);
return ps;
});
}
public static void sendError(
@NotNull HttpServletResponse resp,
int status,
@NotNull String message
) {
try {
resp.setStatus(status);
resp.setContentType(CBConstants.APPLICATION_JSON);
Map<String, Object> error = Map.of(
"error", message,
"status", status
);
RpcConstants.COMPACT_GSON.toJson(error, resp.getWriter());
} catch (IOException e) {
log.warn("Failed to send error response: " + status + " / " + message, e);
}
}
@NotNull
protected static WebHttpRequestInfo createRequestInfo(
@Nullable String sessionId, @NotNull HttpServletRequest req
) {
return new WebHttpRequestInfo(
sessionId,
req.getAttribute("locale"),
req.getRemoteAddr(),
req.getHeader(HttpConstants.HEADER_USER_AGENT)
);
}
/**
* Resolves a {@link BaseWebSession} from an incoming HTTP request.
* <p>
* Implementations may use different resolution strategies
* (e.g. cookies, headers, tokens).
* </p>
*
*/
public interface SessionResolver {
/**
* @return session or null if resolver is not applicable
*/
@Nullable
BaseWebSession resolve(@NotNull HttpServletRequest req) throws SMException;
}
public static class CookieSessionResolver implements SessionResolver {
@Nullable
@Override
public BaseWebSession resolve(@NotNull HttpServletRequest req) {
String sid = ServletAppUtils.getRequestCookie(req, CBConstants.CB_SESSION_COOKIE_NAME);
if (CommonUtils.isEmpty(sid)) {
return null;
}
WebHttpRequestInfo info = CBEventsLongPollingServlet.createRequestInfo(sid, req);
WebAppSessionManager sm = WebAppUtils.getWebApplication().getSessionManager();
return sm.getOrRestoreWebSession(info);
}
}
public static class HeadlessSessionResolver implements SessionResolver {
@Nullable
@Override
public BaseWebSession resolve(@NotNull HttpServletRequest req) throws SMException {
String sid = req.getHeader(WSConstants.WS_SESSION_HEADER);
String token = req.getHeader(WSConstants.WS_AUTH_HEADER);
if (CommonUtils.isEmpty(sid) && CommonUtils.isEmpty(token)) {
return null;
}
WebHttpRequestInfo info = CBEventsLongPollingServlet.createRequestInfo(sid, req);
try {
WebAppSessionManager sm = WebAppUtils.getWebApplication().getSessionManager();
return sm.getHeadlessSession(token, info, true);
} catch (SMAccessTokenExpiredException te) {
throw te;
} catch (Exception e) {
log.warn("Failed to resolve headless session", e);
return null;
}
}
}
}
@@ -16,19 +16,15 @@
*/
package io.cloudbeaver.server.websockets;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.session.BaseWebSession;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.websocket.CBWebSessionEventHandler;
import jakarta.websocket.*;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
import org.jkiss.dbeaver.model.websocket.event.client.*;
import org.jkiss.dbeaver.model.websocket.event.session.WSAccessTokenExpiredEvent;
import org.jkiss.dbeaver.model.websocket.event.session.WSSocketConnectedEvent;
import org.jkiss.utils.CommonUtils;
import java.time.Duration;
@@ -52,77 +48,22 @@ public class CBEventsWebSocket extends CBAbstractWebSocket implements CBWebSessi
log.debug("EventWebSocket connected to the " + webSession.getSessionId() + " session");
session.setMaxIdleTimeout(Duration.ofMinutes(5).toMillis());
session.addMessageHandler(String.class, new FromUserEventHandler());
session.addMessageHandler(String.class, new FromUserEventHandler(webSession));
session.addMessageHandler(PongMessage.class, new WebSocketPingPongCallback(webSession));
CBJettyWebSocketManager.registerWebSocket(webSession.getSessionId(), this);
}
}
private class FromUserEventHandler implements MessageHandler.Whole<String> {
private static class FromUserEventHandler extends CBClientEventProcessor implements MessageHandler.Whole<String> {
private FromUserEventHandler(@NotNull BaseWebSession webSession) {
super(webSession);
}
@Override
public void onMessage(String message) {
if (CommonUtils.isEmpty(message)) {
return;
}
if (webSession == null) {
log.warn("No web session for browser event");
return;
}
WSClientEvent clientEvent;
try {
clientEvent = CBAbstractWebSocket.gson.fromJson(message, WSClientEvent.class);
} catch (Exception e) {
if (webSession != null) {
webSession.addSessionError(
new DBWebException("Invalid websocket event: " + e.getMessage())
);
}
log.error("Error parsing websocket event: " + e.getMessage(), e);
return;
}
if (clientEvent.getId() == null) {
webSession.addSessionError(
new DBWebException("Invalid websocket event: " + message)
);
return;
}
switch (clientEvent.getId()) {
case WSSubscribeOnTopicClientEvent.ID: {
webSession.getEventsFilter().subscribeOnEventTopic(clientEvent.getTopicId());
break;
}
case WSUnsubscribeFromTopicClientEvent.ID: {
webSession.getEventsFilter().unsubscribeFromEventTopic(clientEvent.getTopicId());
break;
}
case WSUpdateActiveProjectsClientEvent.ID: {
var projectEvent = (WSUpdateActiveProjectsClientEvent) clientEvent;
webSession.getEventsFilter().setSubscribedProjects(projectEvent.getProjectIds());
break;
}
case WSSessionPingClientEvent.ID: {
if (webSession instanceof WebSession session) {
session.updateInfo(true);
}
break;
}
case WSSessionTaskConfirmationEvent.ID: {
if (webSession instanceof WebSession session) {
var taskConfirmationEvent = (WSSessionTaskConfirmationEvent) clientEvent;
session.handleTaskConfirmation(
taskConfirmationEvent.getTaskId(),
taskConfirmationEvent.isConfirmed(),
taskConfirmationEvent.isSkipConfirmations()
);
}
break;
}
default:
var e = new DBWebException("Unknown websocket client event: " + clientEvent.getId());
log.error(e.getMessage(), e);
webSession.addSessionError(e);
}
public void onMessage(@Nullable String message) {
process(message);
}
}
@@ -0,0 +1,54 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server.websockets;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.app.DBPPlatform;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.runtime.PeriodicJob;
import java.time.Duration;
public class LongPollingMonitorJob extends PeriodicJob {
private static final Log log = Log.getLog(LongPollingMonitorJob.class);
private static final Duration INTERVAL = Duration.ofSeconds(30);
private final CBEventsLongPollingServlet longPolling;
public LongPollingMonitorJob(
@NotNull DBPPlatform platform,
@NotNull CBEventsLongPollingServlet longPolling
) {
super("LongPolling monitor", platform, INTERVAL);
this.longPolling = longPolling;
}
@Override
protected void doJob(@NotNull DBRProgressMonitor monitor) {
if (!longPolling.isRunning()) {
return;
}
try {
longPolling.cleanupIdleSessions();
} catch (Throwable t) {
log.error("Error during cleanup of long-polling sessions", t);
}
}
}
@@ -19,6 +19,7 @@ package io.cloudbeaver.server.websockets;
import io.cloudbeaver.server.BaseWebPlatform;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.runtime.AbstractJob;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
@@ -38,8 +39,9 @@ public class WebSocketPingPongJob extends AbstractJob {
setSystem(true);
}
@NotNull
@Override
protected IStatus run(DBRProgressMonitor monitor) {
protected IStatus run(@NotNull DBRProgressMonitor monitor) {
if (platform.isShuttingDown()) {
return Status.OK_STATUS;
}
@@ -16,6 +16,7 @@
*/
package io.cloudbeaver.service;
import graphql.GraphQLContext;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
@@ -27,9 +28,11 @@ import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.session.WebSessionProvider;
import io.cloudbeaver.server.WebAppUtils;
import io.cloudbeaver.server.graphql.GraphQLEndpoint;
import io.cloudbeaver.server.graphql.GraphQLLoggerUtil;
import io.cloudbeaver.service.security.SMUtils;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
@@ -161,7 +164,7 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
}
@NotNull
public static WebConnectionInfo getWebConnection(@NotNull WebSession session, @NotNull String projectId, @NotNull String connectionId) throws DBWebException {
public static WebConnectionInfo getWebConnection(@NotNull WebSession session, @Nullable String projectId, @NotNull String connectionId) throws DBWebException {
return WebDataSourceUtils.getWebConnectionInfo(session, projectId, connectionId);
}
@@ -315,7 +318,7 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
// Check license
if (application.isLicenseRequired() && !application.isLicenseValid()) {
if (!ArrayUtils.contains(reqPermissions, DBWConstants.PERMISSION_ADMIN)) {
String errorMessage = "Invalid server license";
String errorMessage = "Server license is missing";
String licenseStatus = application.getLicenseStatus();
if (licenseStatus != null) {
errorMessage = errorMessage + ": " + licenseStatus;
@@ -340,16 +343,27 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
}
}
}
// Perform any checks before action call
protected void beforeWebActionCall(WebAction webAction, Method method, Object[] args) throws DBException {
}
GraphQLContext graphQlContext = this.env.getGraphQlContext();
HttpServletRequest request = graphQlContext.get("request");
if (request == null) {
return;
}
String sessionId = GraphQLLoggerUtil.getSmSessionId(request);
String userId = GraphQLLoggerUtil.getUserId(request);
String loggerMessage = GraphQLLoggerUtil.buildLoggerMessage(sessionId, userId, method, args);
// Perform any checks before action call
protected void beforeWebActionCall(WebAction webAction, Method method, Object[] args) throws DBException {
setLogContext(method, args);
}
log.debug("API > " + method.getName() + loggerMessage);
setLogContext(method, args);
}
protected void afterWebActionCall(WebAction webAction, Method method, Object[] args) throws DBException {
Log.setContext(null);
}
protected void afterWebActionCall(WebAction webAction, Method method, Object[] args) throws DBException {
Log.setContext(null);
}
protected void setLogContext(Method method, @Nullable Object[] args) {
@@ -16,10 +16,7 @@
*/
package io.cloudbeaver.service.core;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebAction;
import io.cloudbeaver.WebObjectId;
import io.cloudbeaver.WebProjectAction;
import io.cloudbeaver.*;
import io.cloudbeaver.model.*;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.DBWService;
@@ -27,8 +24,8 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
import org.jkiss.dbeaver.model.rm.RMConstants;
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings;
import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor;
import java.util.List;
@@ -119,7 +116,7 @@ public interface DBWServiceCore extends DBWService {
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull String connectionId,
@Nullable Map<String, Object> authProperties,
@WebParameterSecure @Nullable Map<String, Object> authProperties,
@Nullable List<WebNetworkHandlerConfigInput> networkCredentials,
boolean saveCredentials,
boolean sharedCredentials,
@@ -130,7 +127,7 @@ public interface DBWServiceCore extends DBWService {
WebConnectionInfo createConnection(
@NotNull WebSession webSession,
@Nullable @WebObjectId String projectId,
@NotNull Map<String, Object> connectionConfig
@WebParameterSecure @NotNull Map<String, Object> connectionConfig
) throws DBWebException;
@WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_DATASOURCES_EDIT})
@@ -203,7 +200,18 @@ public interface DBWServiceCore extends DBWService {
@WebAction
WebConnectionInfo setConnectionNavigatorSettings(
WebSession webSession, @Nullable String projectId, String id, DBNBrowseSettings settings) throws DBWebException;
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull String id,
@NotNull DataSourceNavigatorSettings settings
) throws DBWebException;
@WebAction
WebConnectionInfo clearConnectionNavigatorSettings(
@NotNull WebSession webSession,
@NotNull String projectId,
@NotNull String id
) throws DBWebException;
///////////////////////////////////////////
// Async tasks
@@ -149,9 +149,16 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
.dataFetcher("setConnectionNavigatorSettings", env -> getService(env).setConnectionNavigatorSettings(
getWebSession(env),
getProjectReference(env),
getArgument(env, "id"),
getArgumentVal(env, "id"),
WebServiceUtils.parseNavigatorSettings(getArgument(env, "settings"))
))
.dataFetcher(
"clearConnectionNavigatorSettings", env -> getService(env).clearConnectionNavigatorSettings(
getWebSession(env),
getProjectReference(env),
getArgumentVal(env, "id")
)
)
.dataFetcher("asyncTaskInfo", env -> getService(env).getAsyncTaskInfo(
getWebSession(env),
@@ -46,7 +46,6 @@ import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.exec.DBCConnectException;
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
import org.jkiss.dbeaver.model.navigator.DBNDataSource;
import org.jkiss.dbeaver.model.navigator.DBNModel;
import org.jkiss.dbeaver.model.navigator.DBNNode;
@@ -60,6 +59,8 @@ import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.secret.DBSSecretController;
import org.jkiss.dbeaver.model.secret.DBSSecretValue;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings;
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettingsUtils;
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor;
import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry;
@@ -373,6 +374,7 @@ public class WebServiceCore implements DBWServiceCore {
if (saveCredentials) {
// Save all passed credentials in the datasource container
WebDataSourceUtils.saveAuthProperties(
webSession.getProgressMonitor(),
dataSourceContainer,
dataSourceContainer.getConnectionConfiguration(),
authProperties,
@@ -496,6 +498,8 @@ public class WebServiceCore implements DBWServiceCore {
WebSessionProjectImpl project = getProjectById(webSession, projectId);
WebConnectionConfig configInput = project.getConnectionConfigInput(connectionConfig);
configInput.setSaveCredentials(true); // It is used in createConnectionFromConfig
DataSourceDescriptor dataSource = (DataSourceDescriptor) WebDataSourceUtils.getLocalOrGlobalDataSource(
webSession, projectId, configInput.getConnectionId());
@@ -527,6 +531,7 @@ public class WebServiceCore implements DBWServiceCore {
}
}
WebDataSourceUtils.saveAuthProperties(
webSession.getProgressMonitor(),
testDataSource,
testDataSource.getConnectionConfiguration(),
configInput.getCredentials(),
@@ -535,7 +540,7 @@ public class WebServiceCore implements DBWServiceCore {
true
);
} else {
testDataSource = project.getDataSourceContainerFromInput(connectionConfig);
testDataSource = project.getDataSourceContainerFromInput(configInput);
}
validateDriverLibrariesPresence(testDataSource);
webSession.provideAuthParameters(
@@ -716,12 +721,40 @@ public class WebServiceCore implements DBWServiceCore {
@Override
public WebConnectionInfo setConnectionNavigatorSettings(
WebSession webSession, @Nullable String projectId, String id, DBNBrowseSettings settings
@NotNull WebSession webSession,
@Nullable String projectId,
@NotNull String id,
@NotNull DataSourceNavigatorSettings settings
) throws DBWebException {
WebConnectionInfo connectionInfo = WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, id);
DataSourceDescriptor dataSourceDescriptor = ((DataSourceDescriptor) connectionInfo.getDataSourceContainer());
dataSourceDescriptor.setNavigatorSettings(settings);
dataSourceDescriptor.persistConfiguration();
try {
if (settings.isUserSettings()) {
DataSourceNavigatorSettingsUtils.updateCustomNavigatorSettings(dataSourceDescriptor, settings);
} else {
// If user has no permissions to save it will cause error
dataSourceDescriptor.setNavigatorSettings(settings);
dataSourceDescriptor.persistConfiguration();
}
} catch (DBException e) {
throw new DBWebException("Error saving custom navigator settings", e);
}
return connectionInfo;
}
@Override
public WebConnectionInfo clearConnectionNavigatorSettings(
@NotNull WebSession webSession,
@NotNull String projectId,
@NotNull String id
) throws DBWebException {
WebConnectionInfo connectionInfo = WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, id);
DataSourceDescriptor dataSourceDescriptor = ((DataSourceDescriptor) connectionInfo.getDataSourceContainer());
try {
DataSourceNavigatorSettingsUtils.clearCustomNavigatorSettings(dataSourceDescriptor);
} catch (DBException e) {
throw new DBWebException("Error deleting custom navigator settings", e);
}
return connectionInfo;
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@ package io.cloudbeaver.service.navigator;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.fs.FSUtils;
import io.cloudbeaver.model.fs.WebFSUtils;
import io.cloudbeaver.model.rm.DBNResourceManagerResource;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.registry.WebObjectFeatureProviderDescriptor;
@@ -208,7 +208,7 @@ public class WebNavigatorNodeInfo {
if (node instanceof DBNPathBase dbnPath) {
return DBFUtils.getUriFromPath(dbnPath.getPath()).toString();
} else if (node instanceof DBNFileSystem dbnFs) {
return FSUtils.makeUniqueFsId(dbnFs.getFileSystem());
return WebFSUtils.makeUniqueFsId(dbnFs.getFileSystem());
}
return null;
}
@@ -17,10 +17,7 @@
package io.cloudbeaver.service.navigator.impl;
import io.cloudbeaver.BaseWebProjectImpl;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.*;
import io.cloudbeaver.model.WebCommandContext;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.rm.DBNAbstractResourceManagerNode;
@@ -111,6 +108,14 @@ public class WebServiceNavigator implements DBWServiceNavigator {
Set<String> nodeIds = new HashSet<>(); // filter duplicate node ids
for (DBNNode node : nodeChildren) {
// TODO: find a better way to filter out empty folder nodes for non-admin users
if (node instanceof DBNLocalFolder localFolderNode &&
localFolderNode.getDataSourceRegistry() instanceof WebGlobalProjectRegistryProxy globalProjectRegistryProxy
) {
if (!globalProjectRegistryProxy.getAllFolders().contains(localFolderNode.getFolder())) {
continue;
}
}
if (node instanceof DBNDatabaseFolder folderNode && CommonUtils.isEmpty(folderNode.getMeta().getChildren(null))) {
// Skip empty folders. Folder may become empty if their nested elements are provided by UI plugins.
continue;
@@ -201,12 +201,25 @@ public interface DBWServiceSQL extends DBWService {
WebSQLQueryInfo parseSqlQuery(@NotNull WebConnectionInfo connectionInfo, @NotNull String sqlScript, int cursorPosition) throws DBWebException;
@WebAction
@Deprecated
String generateGroupByQuery(@NotNull WebSQLContextInfo contextInfo,
@NotNull String resultsId,
@NotNull List<String> columnsList,
@Nullable List<String> functions,
@Nullable Boolean showDuplicatesOnly) throws DBWebException;
@WebAction
WebAsyncTaskInfo getGroupingSqlResultSet(
@NotNull WebSQLContextInfo contextInfo,
@NotNull String resultsId,
@NotNull List<String> columnsList,
@Nullable List<String> functions,
@Nullable Boolean showDuplicatesOnly,
@Nullable WebSQLDataFilter filter,
@Nullable WebDataFormat dataFormat,
boolean isInteractive
) throws DBException;
@WebAction
WebAsyncTaskInfo getRowDataCount(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo, @NotNull String resultsId) throws DBWebException;
@@ -69,7 +69,7 @@ public class WebSQLContextInfo implements WebSessionProvider {
private final transient WebSQLProcessor processor;
private final String id;
private final String projectId;
private final Map<String, WebSQLResultsInfo> resultInfoMap = new HashMap<>();
private final transient Map<String, WebSQLResultsInfo> resultInfoMap = new HashMap<>();
private final AtomicInteger resultId = new AtomicInteger();
@@ -71,7 +71,7 @@ public class WebSQLExecuteInfo {
@Property
public String getOriginalQuery() {
//TODO implement
return null;
return null; // should return the original query without data filters
}
@Property
@@ -0,0 +1,98 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.service.sql;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.sql.messages.WebSQLMessages;
import io.cloudbeaver.websocket.event.task.WSSessionTaskQueryParamsConfirmationEvent;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.DBDDataReceiver;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.sql.SQLParametersProviderBase;
import org.jkiss.dbeaver.model.sql.SQLQuery;
import org.jkiss.dbeaver.model.sql.SQLQueryParameter;
import org.jkiss.dbeaver.model.sql.SQLScriptContext;
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
public class WebSQLParametersProvider extends SQLParametersProviderBase {
private static final Log log = Log.getLog(WebSQLParametersProvider.class);
@NotNull
private final WebSession webSession;
@NotNull
private final WebAsyncTaskInfo taskInfo;
public WebSQLParametersProvider(@NotNull WebSession webSession, @NotNull WebAsyncTaskInfo taskInfo) {
this.webSession = webSession;
this.taskInfo = taskInfo;
}
@Nullable
@Override
protected Boolean collectAndAssignVariables(
@NotNull SQLScriptContext scriptContext,
@NotNull SQLQuery sqlStatement,
@NotNull List<SQLQueryParameter> parameters,
@NotNull Supplier<DBDDataReceiver> dataReceiverSupplier
) {
Map<String, Object> unsetParams = new LinkedHashMap<>();
for (SQLQueryParameter param : parameters) {
if (!param.isVariableSet()) {
// Empty value for now, maybe in future we can set some defaults here
unsetParams.put(param.getName(), "");
}
}
if (unsetParams.isEmpty()) {
return true;
}
CompletableFuture<Map<String, Object>> completableFuture = new CompletableFuture<>();
WSEvent event = new WSSessionTaskQueryParamsConfirmationEvent(
taskInfo.getId(),
WebSQLMessages.model_web_dialog_sql_param_title,
"",
sqlStatement.getText(),
unsetParams
);
try {
Map<String, Object> response = WebSQLUtils.requestConfirmation(webSession, taskInfo, event, completableFuture);
// Save values back to script context
for (SQLQueryParameter param : parameters) {
if (response != null && response.containsKey(param.getName())) {
String strValue = JSONUtils.getString(response, param.getName());
param.setValue(strValue);
param.setVariableSet(true);
scriptContext.setVariable(param.getName(), strValue);
}
}
return true;
} catch (DBWebException e) {
log.error("Error getting SQL parameter values: " + e.getMessage(), e);
return false;
}
}
}
@@ -20,10 +20,13 @@ import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.session.WebSessionPreferenceStore;
import io.cloudbeaver.model.session.WebSessionProvider;
import io.cloudbeaver.server.WebAppUtils;
import io.cloudbeaver.server.jobs.SqlOutputLogReaderJob;
import io.cloudbeaver.service.sql.messages.WebSQLMessages;
import io.cloudbeaver.websocket.event.task.WSSessionTaskConfirmationRequestEvent;
import io.cloudbeaver.websocket.event.task.WSSessionTaskQueryConfirmationRequestEvent;
import org.eclipse.jface.text.Document;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
@@ -52,8 +55,6 @@ import org.jkiss.dbeaver.model.sql.parser.SQLScriptParser;
import org.jkiss.dbeaver.model.struct.*;
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
import org.jkiss.dbeaver.model.websocket.event.WSTransactionalCountEvent;
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionTaskConfirmationRequestEvent;
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionTaskQueryConfirmationRequestEvent;
import org.jkiss.dbeaver.registry.confirmation.ConfirmationConstants;
import org.jkiss.dbeaver.registry.confirmation.ConfirmationDescriptor;
import org.jkiss.dbeaver.registry.confirmation.ConfirmationRegistry;
@@ -62,15 +63,12 @@ import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@@ -189,7 +187,9 @@ public class WebSQLProcessor implements WebSessionProvider {
long startTime = System.currentTimeMillis();
WebSQLExecuteInfo executeInfo = new WebSQLExecuteInfo();
var dataContainer = new WebSQLQueryDataContainer(connection.getDataSource(), syntaxManager, sql);
WebSQLParametersProvider parametersProvider = new WebSQLParametersProvider(webSession, asyncTask);
var dataContainer = new WebSQLQueryDataContainer(connection.getDataSource(), syntaxManager, sql, parametersProvider);
DBCExecutionContext context = getExecutionContext(dataContainer);
@@ -228,10 +228,15 @@ public class WebSQLProcessor implements WebSessionProvider {
}
}
if (element instanceof SQLQuery mainQuery) {
if (useEvents) {
boolean isConfirmed = confirmQueryIfNeeded(mainQuery.getScriptElements(), asyncTask, isGenerated);
// fill query with parameters
mainQuery.setParameters(SQLScriptParser.parseParametersAndVariables(parserContext, 0, mainQuery.getLength()));
boolean isConfirmed =
dataContainer.getScriptContext().fillQueryParameters(mainQuery, () -> null, true) &&
confirmDangerousQueryIfNeeded(mainQuery.getScriptElements(), asyncTask, isGenerated);
if (!isConfirmed) {
throw new DBWebException("Query execution cancelled by user");
throw new DBWebException("Query execution was cancelled by user");
}
}
@@ -292,8 +297,6 @@ public class WebSQLProcessor implements WebSessionProvider {
sqlOutputLogReaderJob.join();
}
fillQueryResults(contextInfo, dataContainer, dbStat, hasResultSet, executeInfo, webDataFilter, dataFilter, dataFormat);
} catch (DBException e) {
throw new InvocationTargetException(e);
}
}
}
@@ -335,7 +338,9 @@ public class WebSQLProcessor implements WebSessionProvider {
DBDDataFilter dataFilter = filter.makeDataFilter((resultId == null ? null : contextInfo.getResults(resultId)));
DBExecUtils.tryExecuteRecover(monitor, connection.getDataSource(), param -> {
try (DBCSession session = executionContext.openSession(monitor, resolveQueryPurpose(dataFilter), "Read data from container")) {
try (WebSQLQueryDataReceiver dataReceiver = new WebSQLQueryDataReceiver(contextInfo, dataContainer, dataFormat)) {
try (
WebSQLQueryDataReceiver dataReceiver = new WebSQLQueryDataReceiver(contextInfo, dataContainer, dataFormat, dataFilter)
) {
DBCStatistics statistics = dataContainer.readData(
new WebExecutionSource(dataContainer, executionContext, this),
session,
@@ -352,14 +357,12 @@ public class WebSQLProcessor implements WebSessionProvider {
results.setResultSet(resultSet);
executeInfo.setResults(new WebSQLQueryResults[]{results});
setResultFilterText(dataContainer, session.getDataSource(), executeInfo, dataFilter);
setResultFilterText(session.getDataSource(), executeInfo, dataFilter);
executeInfo.setFullQuery(statistics.getQueryText());
if (resultSet != null && resultSet.getRowsWithMetaData() != null && resultSet.getResultsInfo() != null) {
resultSet.getResultsInfo().setQueryText(statistics.getQueryText());
executeInfo.setStatusMessage(resultSet.getRowsWithMetaData().size() + " row(s) fetched");
}
} catch (DBException e) {
throw new InvocationTargetException(e);
}
}
});
@@ -399,7 +402,7 @@ public class WebSQLProcessor implements WebSessionProvider {
for (var rowIdentifier : rowIdentifierList) {
Map<DBSDataManipulator.ExecuteBatch, Object[]> resultBatches = new LinkedHashMap<>();
DBSDataManipulator dataManipulator = generateUpdateResultsDataBatch(
monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, dataFormat, resultBatches, keyReceiver);
monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, resultBatches, keyReceiver);
DBCExecutionContext executionContext = getExecutionContext(dataManipulator);
try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Update data in container")) {
@@ -500,7 +503,7 @@ public class WebSQLProcessor implements WebSessionProvider {
@NotNull Set<WebSQLQueryResultSetRow> newResultSetRows,
@Nullable WebDataFormat dataFormat,
@NotNull DBRProgressMonitor monitor)
throws DBCException {
throws DBException {
try (DBCSession session = getExecutionContext().openSession(
monitor,
DBCExecutionPurpose.UTIL,
@@ -576,9 +579,8 @@ public class WebSQLProcessor implements WebSessionProvider {
@NotNull String resultsId,
@Nullable List<WebSQLResultsRow> updatedRows,
@Nullable List<WebSQLResultsRow> deletedRows,
@Nullable List<WebSQLResultsRow> addedRows,
@Nullable WebDataFormat dataFormat) throws DBException
{
@Nullable List<WebSQLResultsRow> addedRows
) throws DBException {
Map<DBSDataManipulator.ExecuteBatch, Object[]> resultBatches = new LinkedHashMap<>();
@@ -594,7 +596,7 @@ public class WebSQLProcessor implements WebSessionProvider {
StringBuilder sqlBuilder = new StringBuilder();
for (var rowIdentifier : rowIdentifierList) {
DBSDataManipulator dataManipulator = generateUpdateResultsDataBatch(
monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, dataFormat, resultBatches, null);
monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, resultBatches, null);
List<DBEPersistAction> actions = new ArrayList<>();
@@ -620,7 +622,6 @@ public class WebSQLProcessor implements WebSessionProvider {
@Nullable List<WebSQLResultsRow> updatedRows,
@Nullable List<WebSQLResultsRow> deletedRows,
@Nullable List<WebSQLResultsRow> addedRows,
@Nullable WebDataFormat dataFormat,
@NotNull Map<DBSDataManipulator.ExecuteBatch, Object[]> resultBatches,
@Nullable DBDDataReceiver keyReceiver)
throws DBException
@@ -880,7 +881,6 @@ public class WebSQLProcessor implements WebSessionProvider {
@NotNull
public WebSQLExecutionPlan explainExecutionPlan(
@NotNull DBRProgressMonitor monitor,
@NotNull WebSQLContextInfo contextInfo,
@NotNull String sql,
@NotNull Map<String, Object> configuration) throws DBWebException {
@@ -905,8 +905,6 @@ public class WebSQLProcessor implements WebSessionProvider {
DBCQueryPlannerConfiguration planConfig = new DBCQueryPlannerConfiguration();
planConfig.getParameters().putAll(configuration);
dbcPlan[0] = planner.planQueryExecution(session, sql, planConfig);
} catch (DBException e) {
throw new InvocationTargetException(e);
}
});
} catch (DBException e) {
@@ -946,16 +944,14 @@ public class WebSQLProcessor implements WebSessionProvider {
private void readCellDataValue(
@NotNull DBRProgressMonitor monitor,
@NotNull WebSQLResultsInfo resultsInfo,
@Nullable WebSQLResultsRow row,
@NotNull WebSQLResultsRow row,
@NotNull WebSQLCellValueReceiver dataReceiver
) throws DBException {
DBSDataContainer dataContainer = resultsInfo.getDataContainer();
DBCExecutionContext executionContext = getExecutionContext(dataContainer);
try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Generate data update batches")) {
DBDDataFilter dataFilter = new DBDDataFilter();
if (!resultsInfo.isSingleRow()) {
addKeyAttributes(resultsInfo, row, dataContainer, session, dataFilter);
}
addKeyAttributes(monitor, resultsInfo, row, dataContainer, session, dataFilter);
WebExecutionSource executionSource = new WebExecutionSource(dataContainer, executionContext, this);
dataContainer.readData(
executionSource, session, dataReceiver, dataFilter,
@@ -964,13 +960,22 @@ public class WebSQLProcessor implements WebSessionProvider {
}
private void addKeyAttributes(
@NotNull DBRProgressMonitor monitor,
@NotNull WebSQLResultsInfo resultsInfo,
@Nullable WebSQLResultsRow row,
@NotNull WebSQLResultsRow row,
@NotNull DBSDataContainer dataContainer,
@NotNull DBCSession session,
@NotNull DBDDataFilter dataFilter
) throws DBException {
DBDAttributeBinding[] keyAttributes = resultsInfo.getDefaultRowIdentifier().getAttributes().toArray(new DBDAttributeBinding[0]);
if (resultsInfo.isSingleRow()) {
long rowCount = DBUtils.readRowCount(monitor, session.getExecutionContext(), dataContainer, null, this);
if (rowCount == 1) {
return;
}
}
DBDRowIdentifier rowIdentifier = resultsInfo.getDefaultRowIdentifier();
checkRowIdentifier(resultsInfo, rowIdentifier);
DBDAttributeBinding[] keyAttributes = rowIdentifier.getAttributes().toArray(new DBDAttributeBinding[0]);
Object[] rowValues = new Object[keyAttributes.length];
List<DBDAttributeConstraint> constraints = new ArrayList<>();
for (int i = 0; i < keyAttributes.length; i++) {
@@ -1074,10 +1079,16 @@ public class WebSQLProcessor implements WebSessionProvider {
if (resultSet == null) {
break;
}
try (WebSQLQueryDataReceiver dataReceiver = new WebSQLQueryDataReceiver(contextInfo, dataContainer, dataFormat)) {
try (
WebSQLQueryDataReceiver dataReceiver = new WebSQLQueryDataReceiver(
contextInfo,
dataContainer,
dataFormat,
dataFilter
)
) {
readResultSet(dbStat.getSession(), resultSet, webDataFilter, dataReceiver);
results.setResultSet(dataReceiver.getResultSet());
dataReceiver.getResultSet().getResultsInfo().setQueryText(resultSet.getSourceStatement().getQueryString());
}
}
resultList.add(results);
@@ -1097,11 +1108,15 @@ public class WebSQLProcessor implements WebSessionProvider {
}
executeInfo.setResults(resultList.toArray(new WebSQLQueryResults[0]));
setResultFilterText(dataContainer, dbStat.getSession().getDataSource(), executeInfo, dataFilter);
setResultFilterText(dbStat.getSession().getDataSource(), executeInfo, dataFilter);
executeInfo.setFullQuery(dbStat.getQueryString());
}
private void setResultFilterText(@NotNull DBSDataContainer dataContainer, @NotNull DBPDataSource dataSource, @NotNull WebSQLExecuteInfo executeInfo, @NotNull DBDDataFilter filter) throws DBException {
private void setResultFilterText(
@NotNull DBPDataSource dataSource,
@NotNull WebSQLExecuteInfo executeInfo,
@NotNull DBDDataFilter filter
) throws DBException {
if (!filter.getConstraints().isEmpty() || !CommonUtils.isEmpty(filter.getWhere())) {
StringBuilder where = new StringBuilder();
SQLUtils.appendConditionString(
@@ -1114,8 +1129,13 @@ public class WebSQLProcessor implements WebSessionProvider {
}
}
private void readResultSet(@NotNull DBCSession session, @NotNull DBCResultSet dbResult, @NotNull WebSQLDataFilter filter, @NotNull WebSQLQueryDataReceiver dataReceiver) throws DBCException {
dataReceiver.fetchStart(session, dbResult, filter.getOffset(), filter.getLimit());
private void readResultSet(
@NotNull DBCSession session,
@NotNull DBCResultSet dbResult,
@NotNull WebSQLDataFilter filter,
@NotNull WebSQLQueryDataReceiver dataReceiver
) throws DBException {
DBDDataReceiver.startFetchWorkflow(dataReceiver, session, dbResult, filter.getOffset(), filter.getLimit());
int rowCount = 0;
while (dbResult.nextRow()) {
if (rowCount > filter.getLimit()) {
@@ -1125,7 +1145,6 @@ public class WebSQLProcessor implements WebSessionProvider {
dataReceiver.fetchRow(session, dbResult);
rowCount++;
}
dataReceiver.fetchEnd(session, dbResult);
}
/**
@@ -1163,7 +1182,6 @@ public class WebSQLProcessor implements WebSessionProvider {
if (keyValue == null) {
continue;
}
boolean updated = false;
if (!CommonUtils.isEmpty(keyAttribute.getName())) {
DBDAttributeBinding binding = DBUtils.findObject(resultsAttributes, keyAttribute.getName());
if (binding != null) {
@@ -1234,8 +1252,7 @@ public class WebSQLProcessor implements WebSessionProvider {
private Object setCellRowValue(Object cellRow, WebSession webSession, DBCSession dbcSession, DBDAttributeBinding allAttributes, boolean withoutExecution)
throws DBException {
if (cellRow instanceof Map<?, ?>) {
Map<String, Object> variables = (Map<String, Object>) cellRow;
if (cellRow instanceof Map<?, ?> variables) {
if (variables.get(FILE_ID) != null) {
Path path = WebAppUtils.getWebPlatform()
.getTempFolder(webSession.getProgressMonitor(), TEMP_FILE_FOLDER)
@@ -1253,8 +1270,7 @@ public class WebSQLProcessor implements WebSessionProvider {
return convertInputCellValue(dbcSession, allAttributes, cellRow, withoutExecution);
}
// TODO: Refactor to unify with desktop when confirmation settings will be added
private boolean confirmQueryIfNeeded(
private boolean confirmDangerousQueryIfNeeded(
@NotNull List<SQLScriptElement> scriptElements,
@NotNull WebAsyncTaskInfo asyncTask,
boolean isGenerated
@@ -1281,27 +1297,33 @@ public class WebSQLProcessor implements WebSessionProvider {
.map(SQLScriptElement::getText)
.collect(Collectors.joining("\n\n"));
} else {
WebSessionPreferenceStore store = webSession.getUserPreferenceStore();
boolean confirmDangerousQueries = store.getUserPreferenceBoolean(ConfirmationConstants.CONFIRM_DANGER_SQL_KEY, true);
boolean confirmDropQueries = store.getUserPreferenceBoolean(ConfirmationConstants.CONFIRM_DROP_SQL_KEY, true);
for (SQLScriptElement scriptElement : scriptElements) {
if (scriptElement instanceof SQLQuery sqlQuery) {
if (sqlQuery.isDeleteUpdateDangerous()) {
if (confirmDangerousQueries && sqlQuery.isDeleteUpdateDangerous()) {
hasDangerousUpdates = true;
ConfirmationDescriptor descriptor = ConfirmationRegistry.getInstance()
.getConfirmation(ConfirmationConstants.CONFIRM_DANGER_SQL);
.getConfirmation(ConfirmationConstants.CONFIRM_DANGER_SQL_ID);
title = descriptor.getLocalizedTitle(webSession.getLocale());
var entityMetadata = sqlQuery.getEntityMetadata(false);
message = MessageFormat.format(
descriptor.getLocalizedMessage(webSession.getLocale()),
sqlQuery.getType().name(),
entityMetadata != null ? entityMetadata.getEntityName() : null
entityMetadata != null ? entityMetadata.getEntityName() : "multiple tables"
);
break;
}
if (sqlQuery.isDropTableDangerous()) {
if (confirmDropQueries && sqlQuery.isDropDangerous()) {
hasDropStatement = true;
ConfirmationDescriptor descriptor = ConfirmationRegistry.getInstance()
.getConfirmation(ConfirmationConstants.CONFIRM_DROP_SQL);
.getConfirmation(ConfirmationConstants.CONFIRM_DROP_SQL_ID);
title = descriptor.getLocalizedTitle(webSession.getLocale());
message = descriptor.getLocalizedMessage(webSession.getLocale());
message = MessageFormat.format(
descriptor.getLocalizedMessage(webSession.getLocale()),
sqlQuery.getText()
);
break;
}
}
@@ -1311,31 +1333,9 @@ public class WebSQLProcessor implements WebSessionProvider {
if (!hasGeneratedUpdates && !hasDangerousUpdates && !hasDropStatement) {
return true;
} else {
return requestConfirmation(asyncTask, queryPreview, title, message);
}
}
private boolean requestConfirmation(
@NotNull WebAsyncTaskInfo asyncTask,
@Nullable String query,
@NotNull String title,
@NotNull String message
) throws DBWebException {
String attributeName = WebSQLConstants.TASK_CONFIRMATION_ATTR_PREFIX + asyncTask.getId();
CompletableFuture<Boolean> confirmationFuture = new CompletableFuture<>();
webSession.setAttribute(attributeName, confirmationFuture);
webSession.addSessionEvent(createConfirmationEvent(asyncTask, query, title, message));
try {
Boolean isConfirmed = confirmationFuture.get(WebSQLConstants.TASK_CONFIRMATION_TIMEOUT_SECONDS, TimeUnit.SECONDS);
return isConfirmed != null && isConfirmed;
} catch (TimeoutException e) {
throw new DBWebException("Query confirmation timeout");
} catch (Exception e) {
throw new DBWebException("Error when processing confirmation response", e);
} finally {
webSession.removeAttribute(attributeName);
WSEvent confirmationEvent = createConfirmationEvent(asyncTask, queryPreview, title, message);
CompletableFuture<Boolean> confirmationFuture = new CompletableFuture<>();
return CommonUtils.toBoolean(WebSQLUtils.requestConfirmation(webSession, asyncTask, confirmationEvent, confirmationFuture));
}
}
@@ -26,6 +26,7 @@ import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.DBDDataFilter;
import org.jkiss.dbeaver.model.data.DBDDataReceiver;
import org.jkiss.dbeaver.model.exec.*;
import org.jkiss.dbeaver.model.sql.SQLParametersProvider;
import org.jkiss.dbeaver.model.sql.SQLQuery;
import org.jkiss.dbeaver.model.sql.SQLScriptContext;
import org.jkiss.dbeaver.model.sql.SQLSyntaxManager;
@@ -48,13 +49,19 @@ public class WebSQLQueryDataContainer implements DBSDataContainer, DBPContextPro
private final String query;
private final SQLQueryDataContainer queryDataContainer;
public WebSQLQueryDataContainer(DBPDataSource dataSource, SQLSyntaxManager syntaxManager, String query) {
public WebSQLQueryDataContainer(
@NotNull DBPDataSource dataSource,
@NotNull SQLSyntaxManager syntaxManager,
@NotNull String query,
@NotNull SQLParametersProvider parametersProvider
) {
this.dataSource = dataSource;
this.syntaxManager = syntaxManager;
this.query = query;
SQLScriptContext scriptContext = new SQLScriptContext(null,
this, null, new PrintWriter(System.err, true), null);
this, null, new PrintWriter(System.err, true), parametersProvider
);
queryDataContainer = new SQLQueryDataContainer(this, new SQLQuery(dataSource, query), scriptContext, log);
}
@@ -104,14 +111,25 @@ public class WebSQLQueryDataContainer implements DBSDataContainer, DBPContextPro
long maxRows,
long flags,
int fetchSize
) throws DBCException {
) throws DBException {
return queryDataContainer.readData(source, session, dataReceiver, dataFilter, firstRow, maxRows, flags, fetchSize);
}
@Override
public long countData(@NotNull DBCExecutionSource source, @NotNull DBCSession session, @Nullable DBDDataFilter dataFilter, long flags) throws DBCException {
try {
SQLQuery countQuery = new SQLQueryTransformerCount().transformQuery(dataSource, syntaxManager, new SQLQuery(dataSource, query));
String filteredQuery = dataFilter == null ? query : getDataSource().getSQLDialect().addFiltersToQuery(
session.getProgressMonitor(),
getDataSource(),
query,
dataFilter
);
SQLQuery countQuery = new SQLQueryTransformerCount().transformQuery(
dataSource,
syntaxManager,
new SQLQuery(dataSource, filteredQuery)
);
return DBUtils.countDataFromQuery(source, session, countQuery);
} catch (DBException e) {
throw new DBCException("Error executing row count", e);

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