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 80d89ceeb2..f461124595 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 @@ -438,7 +438,8 @@ public class LocalResourceController implements RMController { public String setResourceContents( @NotNull String projectId, @NotNull String resourcePath, - @NotNull byte[] data) throws DBException + @NotNull byte[] data, + boolean forceOverwrite) throws DBException { validateResourcePath(resourcePath); Number fileSizeLimit = WebAppUtils.getWebApplication() @@ -452,6 +453,9 @@ public class LocalResourceController implements RMController { data.length); } Path targetPath = getTargetPath(projectId, resourcePath); + if (!forceOverwrite && Files.exists(targetPath)) { + throw new DBException("Resource '" + resourcePath + "' exists"); + } if (!Files.exists(targetPath.getParent())) { throw new DBException("Parent folder '" + targetPath.getParent().getFileName() + "' doesn't exist"); } diff --git a/server/bundles/io.cloudbeaver.service.rm/schema/service.rm.graphqls b/server/bundles/io.cloudbeaver.service.rm/schema/service.rm.graphqls index 64cd93c62d..1746a11e6e 100644 --- a/server/bundles/io.cloudbeaver.service.rm/schema/service.rm.graphqls +++ b/server/bundles/io.cloudbeaver.service.rm/schema/service.rm.graphqls @@ -57,7 +57,8 @@ extend type Mutation { rmWriteResourceStringContent( projectId: String!, resourcePath: String!, - data: String!): String! + data: String!, + forceOverwrite: Boolean!): String! rmCreateProject( projectName: String!, diff --git a/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/DBWServiceRM.java b/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/DBWServiceRM.java index 5cd13bf3c4..9c0f7805c5 100644 --- a/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/DBWServiceRM.java +++ b/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/DBWServiceRM.java @@ -80,7 +80,8 @@ public interface DBWServiceRM extends DBWService { @NotNull WebSession webSession, @NotNull @WebObjectId String projectId, @NotNull String resourcePath, - @NotNull String data) throws DBException; + @NotNull String data, + boolean forceOverwrite) throws DBException; @WebAction(requirePermissions = {DBWConstants.PERMISSION_ADMIN, RMConstants.PERMISSION_RM_ADMIN}) WebProjectInfo createProject( diff --git a/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/WebServiceBindingRM.java b/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/WebServiceBindingRM.java index 37554a7734..f1175d08ad 100644 --- a/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/WebServiceBindingRM.java +++ b/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/WebServiceBindingRM.java @@ -64,7 +64,8 @@ public class WebServiceBindingRM extends WebServiceBindingBase { env -> getService(env).writeResourceStringContent(getWebSession(env), env.getArgument("projectId"), env.getArgument("resourcePath"), - env.getArgument("data"))) + env.getArgument("data"), + env.getArgument("forceOverwrite"))) .dataFetcher("rmCreateProject", env -> getService(env).createProject( getWebSession(env), diff --git a/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/impl/WebServiceRM.java b/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/impl/WebServiceRM.java index 414fcac1ae..92ef72c058 100644 --- a/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/impl/WebServiceRM.java +++ b/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/impl/WebServiceRM.java @@ -89,11 +89,17 @@ public class WebServiceRM implements DBWServiceRM { @NotNull @Override - public String writeResourceStringContent(@NotNull WebSession webSession, @NotNull String projectId, @NotNull String resourcePath, @NotNull String data) throws DBException { + public String writeResourceStringContent( + @NotNull WebSession webSession, + @NotNull String projectId, + @NotNull String resourcePath, + @NotNull String data, + boolean forceOverwrite + ) throws DBException { checkIsRmEnabled(webSession); try { byte[] bytes = data.getBytes(StandardCharsets.UTF_8); - return getResourceController(webSession).setResourceContents(projectId, resourcePath, bytes); + return getResourceController(webSession).setResourceContents(projectId, resourcePath, bytes, forceOverwrite); } catch (Exception e) { throw new DBWebException("Error writing resource '" + resourcePath + "' data", e); } diff --git a/server/test/io.cloudbeaver.test.platform/META-INF/MANIFEST.MF b/server/test/io.cloudbeaver.test.platform/META-INF/MANIFEST.MF index 22573dad98..60b470f15f 100644 --- a/server/test/io.cloudbeaver.test.platform/META-INF/MANIFEST.MF +++ b/server/test/io.cloudbeaver.test.platform/META-INF/MANIFEST.MF @@ -22,5 +22,6 @@ Require-Bundle: org.eclipse.core.runtime, io.cloudbeaver.server, io.cloudbeaver.resources.drivers.base, io.cloudbeaver.product.ce, - io.cloudbeaver.service.auth + io.cloudbeaver.service.auth, + io.cloudbeaver.service.rm Export-Package: io.cloudbeaver.test.platform diff --git a/server/test/io.cloudbeaver.test.platform/src/io/cloudbeaver/test/platform/CEServerTestSuite.java b/server/test/io.cloudbeaver.test.platform/src/io/cloudbeaver/test/platform/CEServerTestSuite.java index c4a36f14b3..a0b6874783 100644 --- a/server/test/io.cloudbeaver.test.platform/src/io/cloudbeaver/test/platform/CEServerTestSuite.java +++ b/server/test/io.cloudbeaver.test.platform/src/io/cloudbeaver/test/platform/CEServerTestSuite.java @@ -29,7 +29,7 @@ import java.net.http.HttpClient; import java.nio.file.Path; @RunWith(Suite.class) -@Suite.SuiteClasses({PlatformTest.class, AuthenticationTest.class }) +@Suite.SuiteClasses({PlatformTest.class, AuthenticationTest.class, ResourceManagerTest.class }) public class CEServerTestSuite { public static final String GQL_API_URL = "http://localhost:18978/api/gql"; diff --git a/server/test/io.cloudbeaver.test.platform/src/io/cloudbeaver/test/platform/ResourceManagerTest.java b/server/test/io.cloudbeaver.test.platform/src/io/cloudbeaver/test/platform/ResourceManagerTest.java new file mode 100644 index 0000000000..28e12ff90c --- /dev/null +++ b/server/test/io.cloudbeaver.test.platform/src/io/cloudbeaver/test/platform/ResourceManagerTest.java @@ -0,0 +1,77 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2022 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.test.platform; + +import io.cloudbeaver.model.rm.local.LocalResourceController; +import io.cloudbeaver.server.CBApplication; +import io.cloudbeaver.utils.WebTestUtils; +import org.jkiss.dbeaver.model.auth.SMAuthStatus; +import org.jkiss.dbeaver.model.data.json.JSONUtils; +import org.jkiss.utils.CommonUtils; +import org.junit.Assert; +import org.junit.Test; + +import java.net.CookieManager; +import java.net.http.HttpClient; +import java.util.Map; + +public class ResourceManagerTest { + + public static final String GQL_TEMPLATE_RM_WRITE_RESOURCE = "rmWriteResource.json"; + public static final String GQL_TEMPLATE_RM_DELETE_RESOURCE = "navDeleteNode.json"; + + @Test + public void createDeleteResourceTest() throws Exception { + Assert.assertTrue(CBApplication.getInstance().getAppConfiguration().isResourceManagerEnabled()); + HttpClient client = HttpClient.newBuilder() + .cookieHandler(new CookieManager()) + .version(HttpClient.Version.HTTP_2) + .build(); + Map authInfo = WebTestUtils.authenticateUser( + client, CEServerTestSuite.getScriptsPath(), CEServerTestSuite.GQL_API_URL); + Assert.assertEquals(SMAuthStatus.SUCCESS.name(), JSONUtils.getString(authInfo, "authStatus")); + Assert.assertTrue(createResource(client, false)); + Assert.assertFalse(createResource(client, false)); + Assert.assertTrue(createResource(client, true)); + Assert.assertEquals(1, deleteResource(client)); + } + + + private boolean createResource(HttpClient client, boolean forceOverwrite) throws Exception { + String input = WebTestUtils.readScriptTemplate( + GQL_TEMPLATE_RM_WRITE_RESOURCE, CEServerTestSuite.getScriptsPath() + ).replaceAll("\\{forceOverwrite}", CommonUtils.toString(forceOverwrite)); + Map map = WebTestUtils.doPost(CEServerTestSuite.GQL_API_URL, input, client); + Map data = JSONUtils.getObjectOrNull(map, "data"); + if (data != null) { + return LocalResourceController.DEFAULT_CHANGE_ID.equals(JSONUtils.getString(data, "rmWriteResourceStringContent")); + } + return false; + } + + private int deleteResource(HttpClient client) throws Exception { + String input = WebTestUtils.readScriptTemplate(GQL_TEMPLATE_RM_DELETE_RESOURCE, CEServerTestSuite.getScriptsPath()); + Map map = WebTestUtils.doPost(CEServerTestSuite.GQL_API_URL, input, client); + Map data = JSONUtils.getObjectOrNull(map, "data"); + if (data != null) { + return JSONUtils.getInteger(data, "navDeleteNodes"); + } + return -1; + } + +} diff --git a/server/test/io.cloudbeaver.test.platform/workspace/gql_scripts/navDeleteNode.json b/server/test/io.cloudbeaver.test.platform/workspace/gql_scripts/navDeleteNode.json new file mode 100644 index 0000000000..7302133c3d --- /dev/null +++ b/server/test/io.cloudbeaver.test.platform/workspace/gql_scripts/navDeleteNode.json @@ -0,0 +1,9 @@ +{ + "query": "\n mutation navDeleteNodes($nodePaths: [ID!]!) {\n navDeleteNodes(nodePaths: $nodePaths)\n}\n ", + "variables": { + "nodePaths": [ + "ext://resources/u_test/testScript.sql" + ] + }, + "operationName": "navDeleteNodes" +} \ No newline at end of file diff --git a/server/test/io.cloudbeaver.test.platform/workspace/gql_scripts/rmWriteResource.json b/server/test/io.cloudbeaver.test.platform/workspace/gql_scripts/rmWriteResource.json new file mode 100644 index 0000000000..1652331d3b --- /dev/null +++ b/server/test/io.cloudbeaver.test.platform/workspace/gql_scripts/rmWriteResource.json @@ -0,0 +1,10 @@ +{ + "query": "\n mutation writeContent($projectId: String!, $resourcePath: String!, $data: String!, $forceOverwrite: Boolean!) {\n rmWriteResourceStringContent(\n projectId: $projectId\n resourcePath: $resourcePath\n data: $data\n forceOverwrite: $forceOverwrite\n )\n}\n ", + "variables": { + "projectId": "u_test", + "resourcePath": "testScript.sql", + "data": "TEST SCRIPT;", + "forceOverwrite": {forceOverwrite} + }, + "operationName": "writeContent" +} \ No newline at end of file diff --git a/webapp/packages/core-sdk/src/queries/resource-manager/writeResourceContent.gql b/webapp/packages/core-sdk/src/queries/resource-manager/writeResourceContent.gql index f60c24cd4b..40fe89cb8c 100644 --- a/webapp/packages/core-sdk/src/queries/resource-manager/writeResourceContent.gql +++ b/webapp/packages/core-sdk/src/queries/resource-manager/writeResourceContent.gql @@ -1,3 +1,13 @@ -mutation writeResourceContent($projectId: String!, $resourcePath: String!, $data: String!) { - rmWriteResourceStringContent(projectId: $projectId, resourcePath: $resourcePath, data: $data) +mutation writeResourceContent( + $projectId: String!, + $resourcePath: String!, + $data: String!, + $forceOverwrite: Boolean! +) { + rmWriteResourceStringContent( + projectId: $projectId, + resourcePath: $resourcePath, + data: $data, + forceOverwrite: $forceOverwrite + ) } \ No newline at end of file diff --git a/webapp/packages/core-sdk/src/sdk.ts b/webapp/packages/core-sdk/src/sdk.ts index d88f4e2e26..5ae56d4f47 100644 --- a/webapp/packages/core-sdk/src/sdk.ts +++ b/webapp/packages/core-sdk/src/sdk.ts @@ -563,6 +563,7 @@ export interface MutationRmMoveResourceArgs { export interface MutationRmWriteResourceStringContentArgs { data: Scalars['String']; + forceOverwrite: Scalars['Boolean']; projectId: Scalars['String']; resourcePath: Scalars['String']; } @@ -2446,6 +2447,7 @@ export type WriteResourceContentMutationVariables = Exact<{ projectId: Scalars['String']; resourcePath: Scalars['String']; data: Scalars['String']; + forceOverwrite: Scalars['Boolean']; }>; @@ -3907,11 +3909,12 @@ export const ReadResourceDocument = ` } `; export const WriteResourceContentDocument = ` - mutation writeResourceContent($projectId: String!, $resourcePath: String!, $data: String!) { + mutation writeResourceContent($projectId: String!, $resourcePath: String!, $data: String!, $forceOverwrite: Boolean!) { rmWriteResourceStringContent( projectId: $projectId resourcePath: $resourcePath data: $data + forceOverwrite: $forceOverwrite ) } `; diff --git a/webapp/packages/plugin-resource-manager/src/NavResourceNodeService.ts b/webapp/packages/plugin-resource-manager/src/NavResourceNodeService.ts index abbda5a013..772c8d5026 100644 --- a/webapp/packages/plugin-resource-manager/src/NavResourceNodeService.ts +++ b/webapp/packages/plugin-resource-manager/src/NavResourceNodeService.ts @@ -40,7 +40,7 @@ export class NavResourceNodeService { async saveScript(resourceData: IResourceData, name: string, script: string): Promise { const resourcePath = createPath(resourceData.resourcePath, name); // await this.resourceManagerResource.createResource(resourceData.key.projectId, resourcePath, false); - await this.resourceManagerResource.writeResource(resourceData.key.projectId, resourcePath, script); + await this.resourceManagerResource.writeResource(resourceData.key.projectId, resourcePath, script, false); await this.navTreeResource.refreshTree(resourceData.nodeId); return createPath(RESOURCES_NODE_PATH, resourceData.key.projectId, resourcePath); @@ -57,7 +57,12 @@ export class NavResourceNodeService { } async write(resourceData: IResourceData, value: string) { - await this.resourceManagerResource.writeResource(resourceData.key.projectId, resourceData.resourcePath, value); + await this.resourceManagerResource.writeResource( + resourceData.key.projectId, + resourceData.resourcePath, + value, + true + ); } getResourceData(nodeId: string): IResourceData | undefined { diff --git a/webapp/packages/plugin-resource-manager/src/ResourceManagerResource.ts b/webapp/packages/plugin-resource-manager/src/ResourceManagerResource.ts index f018e21fa7..7459f92ec5 100644 --- a/webapp/packages/plugin-resource-manager/src/ResourceManagerResource.ts +++ b/webapp/packages/plugin-resource-manager/src/ResourceManagerResource.ts @@ -59,11 +59,12 @@ export class ResourceManagerResource extends CachedMapResource