From 9c1678c256ae829ea5c98476cbc6bb0c092a90de Mon Sep 17 00:00:00 2001 From: Viktor Kirst Date: Wed, 8 Apr 2026 17:23:38 +0000 Subject: [PATCH] dbeaver/pro#8824 Inject lock manager into CloudBeaver RM controllers (#4242) * dbeaver/pro#8824 Inject lock manager into CloudBeaver RM controllers * dbeaver/pro#8824 Use application lock manager provider in servlet applications * dbeaver/pro#8824 Use default application lock manager * dbeaver/pro#8824 Fix codestyle warnings --------- Co-authored-by: Daria Markaryan <43252954+DariaMarkaryan@users.noreply.github.com> Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com> Co-authored-by: Serge Rider --- .../model/app/BaseServletApplication.java | 20 +++++- .../rm/local/BaseLocalResourceController.java | 18 ++--- .../rm/local/LocalResourceController.java | 67 ++++++++++++------- .../model/rm/local/ProjectsMetadataInfo.java | 15 +++-- .../cloudbeaver/server/CBApplicationCE.java | 15 +++-- 5 files changed, 89 insertions(+), 46 deletions(-) diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseServletApplication.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseServletApplication.java index ce823cb1ef..82c7962fa9 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseServletApplication.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseServletApplication.java @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2025 DBeaver Corp and others + * Copyright (C) 2010-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,10 +25,13 @@ 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.app.DBPLockManagerProvider; 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.data.json.JSONUtils; +import org.jkiss.dbeaver.model.fs.lock.LockManager; +import org.jkiss.dbeaver.model.fs.lock.shared.SharedFileLockManager; import org.jkiss.dbeaver.model.impl.app.ApplicationRegistry; import org.jkiss.dbeaver.model.impl.app.BaseApplicationImpl; import org.jkiss.dbeaver.model.impl.app.BaseWorkspaceImpl; @@ -48,7 +51,7 @@ import java.util.Map; /** * Servlet application */ -public abstract class BaseServletApplication extends BaseApplicationImpl implements ServletApplication { +public abstract class BaseServletApplication extends BaseApplicationImpl implements ServletApplication, DBPLockManagerProvider { public static final String DEFAULT_CONFIG_FILE_PATH = "/etc/cloudbeaver.conf"; public static final String CUSTOM_CONFIG_FOLDER = "custom"; @@ -59,6 +62,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme private String instanceId; + @NotNull @Override public RMController createResourceController( @NotNull SMCredentialsProvider credentialsProvider, @@ -73,6 +77,18 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme throw new IllegalStateException("File controller is not supported by " + getClass().getSimpleName()); } + @NotNull + @Override + public LockManager createLockManager(@NotNull Path metadataFolder) throws DBException { + return new SharedFileLockManager(getApplicationInstanceId(), metadataFolder); + } + + @NotNull + @Override + public LockManager createLockManager() throws DBException { + return new SharedFileLockManager(getApplicationInstanceId()); + } + @Nullable @Override public Path getDefaultWorkingFolder() { diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/BaseLocalResourceController.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/BaseLocalResourceController.java index 48ac3b1b53..3ec793dceb 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/BaseLocalResourceController.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/BaseLocalResourceController.java @@ -26,7 +26,9 @@ import org.jkiss.dbeaver.model.DBPDataSourceFolder; 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.fs.lock.LockManager; +import org.jkiss.dbeaver.model.fs.lock.LockOptions; +import org.jkiss.dbeaver.model.fs.lock.LockTarget; import org.jkiss.dbeaver.model.rm.RMController; import org.jkiss.dbeaver.model.rm.RMEvent; import org.jkiss.dbeaver.model.rm.RMEventManager; @@ -53,11 +55,11 @@ public abstract class BaseLocalResourceController implements RMController { @NotNull protected final DBPWorkspace workspace; @NotNull - protected final FileLockController lockController; + protected final LockManager lockController; protected BaseLocalResourceController( @NotNull DBPWorkspace workspace, - @NotNull FileLockController lockController + @NotNull LockManager lockController ) { this.workspace = workspace; this.lockController = lockController; @@ -157,7 +159,7 @@ public abstract class BaseLocalResourceController implements RMController { @NotNull String configuration, @Nullable List dataSourceIds ) throws DBException { - try (var ignoredLock = lockController.lock(projectId, "updateProjectDataSources")) { + try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("updateProjectDataSources"))) { DBPProject project = getWebProject(projectId, false); return doFileWriteOperation( projectId, project.getMetadataFolder(false), @@ -188,7 +190,7 @@ public abstract class BaseLocalResourceController implements RMController { @NotNull String projectId, @NotNull String[] dataSourceIds ) throws DBException { - try (var ignoredLock = lockController.lock(projectId, "deleteDataSources")) { + try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("deleteDataSources"))) { DBPProject project = getWebProject(projectId, false); doFileWriteOperation(projectId, project.getMetadataFolder(false), () -> { DBPDataSourceRegistry registry = project.getDataSourceRegistry(); @@ -213,7 +215,7 @@ public abstract class BaseLocalResourceController implements RMController { @NotNull String projectId, @NotNull String folderPath ) throws DBException { - try (var ignoredLock = lockController.lock(projectId, "createDatasourceFolder")) { + try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("createDatasourceFolder"))) { DBPProject project = getWebProject(projectId, false); log.debug("Creating data source folder '" + folderPath + "' in project '" + projectId + "'"); doFileWriteOperation(projectId, project.getMetadataFolder(false), @@ -241,7 +243,7 @@ public abstract class BaseLocalResourceController implements RMController { @NotNull String[] folderPaths, boolean dropContents ) throws DBException { - try (var ignoredLock = lockController.lock(projectId, "createDatasourceFolder")) { + try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("createDatasourceFolder"))) { DBPProject project = getWebProject(projectId, false); doFileWriteOperation(projectId, project.getMetadataFolder(false), () -> { @@ -267,7 +269,7 @@ public abstract class BaseLocalResourceController implements RMController { @NotNull String oldPath, @NotNull String newPath ) throws DBException { - try (var ignoredLock = lockController.lock(projectId, "createDatasourceFolder")) { + try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("createDatasourceFolder"))) { DBPProject project = getWebProject(projectId, false); log.debug("Moving data source folder from '" + oldPath + "' to '" + newPath + "' in project '" + projectId + "'"); doFileWriteOperation(projectId, project.getMetadataFolder(false), diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java index 7eff36b2d4..5a0033c300 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2025 DBeaver Corp and others + * Copyright (C) 2010-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,9 @@ import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry; import org.jkiss.dbeaver.model.app.DBPWorkspace; import org.jkiss.dbeaver.model.auth.SMCredentials; import org.jkiss.dbeaver.model.auth.SMCredentialsProvider; -import org.jkiss.dbeaver.model.fs.lock.FileLockController; +import org.jkiss.dbeaver.model.fs.lock.LockManager; +import org.jkiss.dbeaver.model.fs.lock.LockOptions; +import org.jkiss.dbeaver.model.fs.lock.LockTarget; import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl; import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl; import org.jkiss.dbeaver.model.navigator.DBNLocalFolder; @@ -90,9 +92,10 @@ public class LocalResourceController extends BaseLocalResourceController { @NotNull Path rootPath, @NotNull Path userProjectsPath, @NotNull Path sharedProjectsPath, - @NotNull Supplier smControllerSupplier + @NotNull Supplier smControllerSupplier, + @NotNull LockManager lockController ) throws DBException { - super(workspace, new FileLockController(ServletAppUtils.getServletApplication().getApplicationInstanceId())); + super(workspace, lockController); this.credentialsProvider = credentialsProvider; this.rootPath = rootPath; this.userProjectsPath = userProjectsPath; @@ -101,7 +104,7 @@ public class LocalResourceController extends BaseLocalResourceController { this.globalProjectName = DBWorkbench.getPlatform().getApplication().getDefaultProjectName(); this.fileHandlers = RMFileOperationHandlersRegistry.getInstance().getFileHandlers(); - this.sharedProjectsMetadataInfo = new ProjectsMetadataInfo(sharedProjectsPath); + this.sharedProjectsMetadataInfo = new ProjectsMetadataInfo(sharedProjectsPath, lockController); } @NotNull @@ -303,7 +306,7 @@ public class LocalResourceController extends BaseLocalResourceController { @Override public RMProject updateProject(@NotNull String projectId, @NotNull RMProjectInfo projectInfo) throws DBException { validateProjectName(projectId, projectInfo.getName()); - try (var ignoredLock = lockController.lock(projectId, "updateProject")) { + try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("updateProject"))) { RMLocalProject project = getWebProject(projectId, false); Path targetPath = getProjectPath(projectId); if (!Files.exists(targetPath)) { @@ -330,7 +333,7 @@ public class LocalResourceController extends BaseLocalResourceController { @Override public void deleteProject(@NotNull String projectId) throws DBException { - try (var ignoredLock = lockController.lock(projectId, "deleteProject")) { + try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("deleteProject"))) { Path targetPath = getProjectPath(projectId); if (!Files.exists(targetPath)) { log.error(MessageFormat.format("Project folder ''{0}'' is not found", projectId)); @@ -699,7 +702,7 @@ public class LocalResourceController extends BaseLocalResourceController { @NotNull String resourcePath, boolean isFolder ) throws DBException { - try (var ignoredLock = lockController.lock(projectId, "createResource")) { + try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("createResource"))) { validateResourcePath(resourcePath); Path targetPath = getTargetPath(projectId, resourcePath); if (Files.exists(targetPath)) { @@ -731,7 +734,7 @@ public class LocalResourceController extends BaseLocalResourceController { @NotNull String oldResourcePath, @NotNull String newResourcePath ) throws DBException { - try (var ignoredLock = lockController.lock(projectId, "moveResource")) { + try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("moveResource"))) { var normalizedOldResourcePath = CommonUtils.normalizeResourcePath(oldResourcePath); var normalizedNewResourcePath = CommonUtils.normalizeResourcePath(newResourcePath); if (log.isDebugEnabled()) { @@ -803,7 +806,7 @@ public class LocalResourceController extends BaseLocalResourceController { @Override public void deleteResource(@NotNull String projectId, @NotNull String resourcePath, boolean recursive) throws DBException { - try (var ignoredLock = lockController.lock(projectId, "deleteResource")) { + try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("deleteResource"))) { if (log.isDebugEnabled()) { log.debug("Removing resource from '" + resourcePath + "' in project '" + projectId + "'" + (recursive ? " recursive" : "")); } @@ -891,7 +894,7 @@ public class LocalResourceController extends BaseLocalResourceController { @NotNull byte[] data, boolean forceOverwrite ) throws DBException { - try (var ignoredLock = lockController.lock(projectId, "setResourceContents")) { + try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("setResourceContents"))) { validateResourcePath(resourcePath); Number fileSizeLimit = ServletAppUtils.getServletApplication() .getAppConfiguration() @@ -936,7 +939,7 @@ public class LocalResourceController extends BaseLocalResourceController { @NotNull String propertyName, @Nullable Object propertyValue ) throws DBException { - try (var ignoredLock = lockController.lock(projectId, "resourcePropertyUpdate")) { + try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("resourcePropertyUpdate"))) { validateResourcePath(resourcePath); RMLocalProject webProject = getWebProject(projectId, false); doFileWriteOperation(projectId, webProject.getMetadataFilePath(), @@ -957,7 +960,7 @@ public class LocalResourceController extends BaseLocalResourceController { @NotNull String resourcePath, @NotNull Map properties ) throws DBException { - try (var ignoredLock = lockController.lock(projectId, "resourcePropertyUpdate")) { + try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("resourcePropertyUpdate"))) { validateResourcePath(resourcePath); RMLocalProject webProject = getWebProject(projectId, false); doFileWriteOperation(projectId, webProject.getMetadataFilePath(), @@ -1204,12 +1207,14 @@ public class LocalResourceController extends BaseLocalResourceController { return getProjectPath(projectId).toAbsolutePath().relativize(path).toString().replace('\\', IPath.SEPARATOR); } + @NotNull public static Builder builder( - SMCredentialsProvider credentialsProvider, - DBPWorkspace workspace, - Supplier smControllerSupplier + @NotNull SMCredentialsProvider credentialsProvider, + @NotNull DBPWorkspace workspace, + @NotNull LockManager lockController, + @NotNull Supplier smControllerSupplier ) { - return new Builder(workspace, credentialsProvider, smControllerSupplier); + return new Builder(workspace, credentialsProvider, lockController, smControllerSupplier); } @Override @@ -1225,10 +1230,13 @@ public class LocalResourceController extends BaseLocalResourceController { protected Path rootPath; protected Path userProjectsPath; protected Path sharedProjectsPath; + protected LockManager lockController; protected Builder( - DBPWorkspace workspace, SMCredentialsProvider credentialsProvider, - Supplier smControllerSupplier + @NotNull DBPWorkspace workspace, + @NotNull SMCredentialsProvider credentialsProvider, + @NotNull LockManager lockController, + @NotNull Supplier smControllerSupplier ) { this.workspace = workspace; this.credentialsProvider = credentialsProvider; @@ -1236,25 +1244,38 @@ public class LocalResourceController extends BaseLocalResourceController { this.rootPath = RMUtils.getRootPath(); this.userProjectsPath = RMUtils.getUserProjectsPath(); this.sharedProjectsPath = RMUtils.getSharedProjectsPath(); + this.lockController = lockController; } - public Builder setRootPath(Path rootPath) { + @NotNull + public Builder setRootPath(@NotNull Path rootPath) { this.rootPath = rootPath; return this; } - public Builder setUserProjectsPath(Path userProjectsPath) { + @NotNull + public Builder setUserProjectsPath(@NotNull Path userProjectsPath) { this.userProjectsPath = userProjectsPath; return this; } - public Builder setSharedProjectsPath(Path sharedProjectsPath) { + @NotNull + public Builder setSharedProjectsPath(@NotNull Path sharedProjectsPath) { this.sharedProjectsPath = sharedProjectsPath; return this; } + @NotNull public LocalResourceController build() throws DBException { - return new LocalResourceController(workspace, credentialsProvider, rootPath, userProjectsPath, sharedProjectsPath, smController); + return new LocalResourceController( + workspace, + credentialsProvider, + rootPath, + userProjectsPath, + sharedProjectsPath, + smController, + lockController + ); } } diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/ProjectsMetadataInfo.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/ProjectsMetadataInfo.java index 196d26cf35..12c3a4ace1 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/ProjectsMetadataInfo.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/ProjectsMetadataInfo.java @@ -17,14 +17,15 @@ package io.cloudbeaver.model.rm.local; import com.google.gson.reflect.TypeToken; -import io.cloudbeaver.utils.ServletAppUtils; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.app.DBPProject; import org.jkiss.dbeaver.model.data.json.JSONUtils; -import org.jkiss.dbeaver.model.fs.lock.FileLockController; +import org.jkiss.dbeaver.model.fs.lock.LockManager; +import org.jkiss.dbeaver.model.fs.lock.LockOptions; +import org.jkiss.dbeaver.model.fs.lock.LockTarget; import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl; import org.jkiss.dbeaver.model.rm.RMProjectInfo; import org.jkiss.dbeaver.model.rm.RMProjectType; @@ -45,12 +46,12 @@ public class ProjectsMetadataInfo { private final Map projectsInfo = new LinkedHashMap<>(); private final Path projectsPath; - private final FileLockController lockController; + private final LockManager lockController; - public ProjectsMetadataInfo(@NotNull Path projectsPath) throws DBException { + public ProjectsMetadataInfo(@NotNull Path projectsPath, @NotNull LockManager lockController) throws DBException { this.projectsPath = projectsPath; - this.lockController = new FileLockController(ServletAppUtils.getServletApplication().getApplicationInstanceId()); + this.lockController = lockController; readProjectInfos(projectsPath); } @@ -139,7 +140,7 @@ public class ProjectsMetadataInfo { } private void saveProjectsInfo() { - try (var lock = lockController.lock(PROJECTS_INFO_FILE_NAME, "saveProjectsInfo")) { + try (var lock = lockController.lock(LockTarget.of(PROJECTS_INFO_FILE_NAME), LockOptions.of("saveProjectsInfo"))) { log.info("Saving project information"); Files.writeString(projectsPath.resolve(PROJECTS_INFO_FILE_NAME), JSONUtils.GSON.toJson(projectsInfo)); } catch (IOException e) { @@ -148,4 +149,4 @@ public class ProjectsMetadataInfo { log.error("Error locking file " + PROJECTS_INFO_FILE_NAME + ": " + e.getMessage()); } } -} \ No newline at end of file +} diff --git a/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/CBApplicationCE.java b/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/CBApplicationCE.java index 2470917003..98fd423a27 100644 --- a/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/CBApplicationCE.java +++ b/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/CBApplicationCE.java @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2024 DBeaver Corp and others + * Copyright (C) 2010-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -75,13 +75,16 @@ public class CBApplicationCE extends CBApplication { ); } - - @NotNull @Override - public RMController createResourceController(@NotNull SMCredentialsProvider credentialsProvider, - @NotNull DBPWorkspace workspace) throws DBException { - return LocalResourceController.builder(credentialsProvider, workspace, this::getSecurityController).build(); + public RMController createResourceController( + @NotNull SMCredentialsProvider credentialsProvider, + @NotNull DBPWorkspace workspace + ) throws DBException { + var lockManager = createLockManager(); + return LocalResourceController + .builder(credentialsProvider, workspace, lockManager, this::getSecurityController) + .build(); } @NotNull