]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15580 Migrate api/project_dump/status endpoint to CE + unit tests
authorKlaudio Sinani <klaudio.sinani@sonarsource.com>
Tue, 2 Nov 2021 15:57:40 +0000 (16:57 +0100)
committersonartech <sonartech@sonarsource.com>
Thu, 4 Nov 2021 20:03:25 +0000 (20:03 +0000)
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectexport/util/ProjectExportDumpFSImpl.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectexport/util/ProjectImportDumpFSImpl.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/util/ProjectExportDumpFSImplTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/util/ProjectImportDumpFSImplTest.java
server/sonar-webserver-webapi/build.gradle
server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/projectdump/ProjectExportWsModule.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/projectdump/ws/StatusAction.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/projectdump/ws/StatusActionTest.java [new file with mode: 0644]

index e224587816d6736b411b5b3b6104351e7edaf5a3..158ea66581cd4e3a5ea876e90a23a49aab2f7453 100644 (file)
@@ -48,7 +48,6 @@ public class ProjectExportDumpFSImpl implements ProjectExportDumpFS, Startable {
 
   @Override
   public void start() {
-    Files2.FILES2.createDir(importDir);
     Files2.FILES2.createDir(exportDir);
   }
 
index fadb16f98e9a2c7483b109499bb0d4ad6b734193..67813e731533abd34434138351b05bb89e6353b8 100644 (file)
@@ -49,7 +49,6 @@ public class ProjectImportDumpFSImpl implements ProjectImportDumpFS, Startable {
   @Override
   public void start() {
     Files2.FILES2.createDir(importDir);
-    Files2.FILES2.createDir(exportDir);
   }
 
   @Override
index 64752d4589fd70fc4c32b7bb311b7435f4868655..1dcc55076a2b9ce406dfbda511cc0cc68a95433d 100644 (file)
@@ -65,7 +65,6 @@ public class ProjectExportDumpFSImplTest {
     underTest.start();
 
     assertThat(dataDir).exists().isDirectory();
-    assertThat(importDir).exists().isDirectory();
     assertThat(exportDir).exists().isDirectory();
   }
 
index cd12341368ef3c5a57b7c9fa331a4fe3f790dcea..16480931f43232b674c99424b1b0f542306c5cc2 100644 (file)
@@ -66,7 +66,6 @@ public class ProjectImportDumpFSImplTest {
 
     assertThat(dataDir).exists().isDirectory();
     assertThat(importDir).exists().isDirectory();
-    assertThat(exportDir).exists().isDirectory();
   }
 
   @Test
index d514e24c911aa13d9516e6d44ddafcb20beefac0..3d1dc73f85e35e981f998e4e905e15d1aa480bb3 100644 (file)
@@ -19,7 +19,6 @@ dependencies {
   compile project(':server:sonar-alm-client')
   compile project(':sonar-scanner-protocol')
 
-
   compileOnly 'com.google.code.findbugs:jsr305'
   compileOnly 'javax.servlet:javax.servlet-api'
 
index 86366a9b6bdd2bc35edea8d730bcfc7abb4e2eda..b0ea6422152f14b53b2ee13a81ceb75cc775d672 100644 (file)
@@ -23,6 +23,7 @@ import org.sonar.core.platform.Module;
 import org.sonar.server.projectdump.ws.ExportAction;
 import org.sonar.server.projectdump.ws.ProjectDumpWs;
 import org.sonar.server.projectdump.ws.ProjectDumpWsSupport;
+import org.sonar.server.projectdump.ws.StatusAction;
 
 public class ProjectExportWsModule extends Module {
   @Override
@@ -31,7 +32,8 @@ public class ProjectExportWsModule extends Module {
       ProjectDumpWsSupport.class,
       ProjectDumpWs.class,
       ExportAction.class,
-      ExportSubmitterImpl.class
+      ExportSubmitterImpl.class,
+      StatusAction.class
     );
 
   }
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/projectdump/ws/StatusAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/projectdump/ws/StatusAction.java
new file mode 100644 (file)
index 0000000..28f639e
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.projectdump.ws;
+
+import java.io.File;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import javax.annotation.Nullable;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.user.UserSession;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.apache.commons.lang.StringUtils.isNotBlank;
+import static org.sonar.core.util.Slug.slugify;
+import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
+import static org.sonar.db.DatabaseUtils.closeQuietly;
+import static org.sonar.process.ProcessProperties.Property.PATH_DATA;
+import static org.sonar.server.component.ComponentFinder.ParamNames.ID_AND_KEY;
+import static org.sonar.server.exceptions.BadRequestException.checkRequest;
+
+public class StatusAction implements ProjectDumpAction {
+  private static final String PARAM_PROJECT_KEY = "key";
+  private static final String PARAM_PROJECT_ID = "id";
+  private static final String DUMP_FILE_EXTENSION = ".zip";
+
+  private static final String GOVERNANCE_DIR_NAME = "governance";
+  private static final String PROJECT_DUMPS_DIR_NAME = "project_dumps";
+
+  private final DbClient dbClient;
+  private final UserSession userSession;
+  private final ComponentFinder componentFinder;
+
+  private final String dataPath;
+  private final File importDir;
+  private final File exportDir;
+
+  public StatusAction(DbClient dbClient, UserSession userSession, ComponentFinder componentFinder, Configuration config) {
+    this.dbClient = dbClient;
+    this.userSession = userSession;
+    this.componentFinder = componentFinder;
+    this.dataPath = config.get(PATH_DATA.getKey()).get();
+    this.importDir = this.getProjectDumpDir("import");
+    this.exportDir = this.getProjectDumpDir("export");
+  }
+
+  @Override
+  public void define(WebService.NewController controller) {
+    WebService.NewAction action = controller.createAction("status")
+      .setDescription("Provide the import and export status of a project. Permission 'Administer' is required. " +
+        "The project id or project key must be provided.")
+      .setSince("1.0")
+      .setInternal(true)
+      .setPost(false)
+      .setHandler(this)
+      .setResponseExample(getClass().getResource("example-status.json"));
+    action.createParam(PARAM_PROJECT_ID)
+      .setDescription("Project id")
+      .setExampleValue(UUID_EXAMPLE_01);
+    action.createParam(PARAM_PROJECT_KEY)
+      .setDescription("Project key")
+      .setExampleValue("my_project");
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    String uuid = request.param(PARAM_PROJECT_ID);
+    String key = request.param(PARAM_PROJECT_KEY);
+    checkRequest(uuid == null ^ key == null, "Project id or project key must be provided, not both.");
+
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      ProjectDto project = getProject(dbSession, uuid, key);
+      userSession.checkProjectPermission(UserRole.ADMIN, project);
+
+      WsResponse wsResponse = new WsResponse();
+      checkDumps(project, wsResponse);
+
+      SnapshotsStatus snapshots = checkSnapshots(dbSession, project);
+      if (snapshots.hasLast) {
+        wsResponse.setCanBeExported();
+      } else if (!snapshots.hasAny) {
+        wsResponse.setCanBeImported();
+      }
+      write(response, wsResponse);
+    }
+  }
+
+  private SnapshotsStatus checkSnapshots(DbSession dbSession, ProjectDto project) throws SQLException {
+    PreparedStatement stmt = null;
+    ResultSet rs = null;
+    try {
+      String sql = "select" +
+        " count(*), islast" +
+        " from snapshots" +
+        " where" +
+        " component_uuid = ?" +
+        " group by" +
+        " islast";
+      stmt = dbClient.getMyBatis().newScrollingSelectStatement(dbSession, sql);
+      stmt.setString(1, project.getUuid());
+      rs = stmt.executeQuery();
+      SnapshotsStatus res = new SnapshotsStatus();
+      while (rs.next()) {
+        long count = rs.getLong(1);
+        boolean isLast = rs.getBoolean(2);
+        if (isLast) {
+          res.setHasLast(count > 0);
+        }
+        if (count > 0) {
+          res.setHasAny(true);
+        }
+      }
+      return res;
+    } finally {
+      closeQuietly(rs);
+      closeQuietly(stmt);
+    }
+  }
+
+  private File getProjectDumpDir(String type) {
+    final File governanceDir = new File(this.dataPath, GOVERNANCE_DIR_NAME);
+    final File projectDumpDir = new File(governanceDir, PROJECT_DUMPS_DIR_NAME);
+
+    return new File(projectDumpDir, type);
+  }
+
+  private void checkDumps(ProjectDto project, WsResponse wsResponse) {
+    String fileName = slugify(project.getKey()) + DUMP_FILE_EXTENSION;
+
+    final File importFile = new File(this.importDir, fileName);
+    final File exportFile = new File(this.exportDir, fileName);
+
+    if (importFile.exists() && importFile.isFile()) {
+      wsResponse.setDumpToImport(importFile.toPath().toString());
+    }
+
+    if (exportFile.exists() && exportFile.isFile()) {
+      wsResponse.setExportedDump(exportFile.toPath().toString());
+    }
+  }
+
+  private static void write(Response response, WsResponse wsResponse) {
+    JsonWriter jsonWriter = response.newJsonWriter();
+    jsonWriter
+      .beginObject()
+      .prop("canBeExported", wsResponse.canBeExported)
+      .prop("canBeImported", wsResponse.canBeImported)
+      .prop("exportedDump", wsResponse.exportedDump)
+      .prop("dumpToImport", wsResponse.dumpToImport)
+      .endObject();
+    jsonWriter.close();
+  }
+
+  private static class WsResponse {
+    private String exportedDump = null;
+    private String dumpToImport = null;
+    private boolean canBeExported = false;
+    private boolean canBeImported = false;
+
+    public void setExportedDump(String exportedDump) {
+      checkArgument(isNotBlank(exportedDump), "exportedDump can not be null nor empty");
+      this.exportedDump = exportedDump;
+    }
+
+    public void setDumpToImport(String dumpToImport) {
+      checkArgument(isNotBlank(dumpToImport), "dumpToImport can not be null nor empty");
+      this.dumpToImport = dumpToImport;
+    }
+
+    public void setCanBeExported() {
+      this.canBeExported = true;
+    }
+
+    public void setCanBeImported() {
+      this.canBeImported = true;
+    }
+  }
+
+  private static class SnapshotsStatus {
+    private boolean hasLast = false;
+    private boolean hasAny = false;
+
+    public void setHasLast(boolean hasLast) {
+      this.hasLast = hasLast;
+    }
+
+    public void setHasAny(boolean hasAny) {
+      this.hasAny = hasAny;
+    }
+  }
+
+  private ProjectDto getProject(DbSession dbSession, @Nullable String uuid, @Nullable String key) {
+    return componentFinder.getProjectByUuidOrKey(dbSession, uuid, key, ID_AND_KEY);
+  }
+}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/projectdump/ws/StatusActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/projectdump/ws/StatusActionTest.java
new file mode 100644 (file)
index 0000000..b76c956
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.projectdump.ws;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.util.Slug;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ResourceTypesRule;
+import org.sonar.db.component.SnapshotTesting;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsActionTester;
+
+import static java.util.Comparator.reverseOrder;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+import static org.sonar.test.JsonAssert.assertJson;
+
+public class StatusActionTest {
+
+  private static final String SOME_UUID = "some uuid";
+  private static final String ID_PARAM = "id";
+  private static final String KEY_PARAM = "key";
+  private static final String SOME_KEY = "some key";
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+
+  private final DbClient dbClient = db.getDbClient();
+  private final DbSession dbSession = db.getSession();
+  private final ResourceTypesRule resourceTypes = new ResourceTypesRule().setRootQualifiers(PROJECT);
+
+  private final static String projectDumpsDirectoryPathname = "data/governance/project_dumps/";
+  private final static String importDirectoryPathname = Paths.get(projectDumpsDirectoryPathname, "import").toString();
+  private final static String exportDirectoryPathname = Paths.get(projectDumpsDirectoryPathname,"export").toString();
+
+  private ProjectDto project;
+
+  private WsActionTester underTest;
+
+  private final Configuration config = mock(Configuration.class);
+
+  @Before
+  public void setUp() throws Exception {
+    project = insertProject(SOME_UUID, SOME_KEY);
+    logInAsProjectAdministrator("user");
+
+    when(config.get("sonar.path.data")).thenReturn(Optional.of("data"));
+    underTest = new WsActionTester(new StatusAction(dbClient, userSession, new ComponentFinder(dbClient, resourceTypes), config));
+
+    cleanUpFilesystem();
+  }
+
+  @AfterClass
+  public static void cleanUp() throws Exception {
+    cleanUpFilesystem();
+  }
+
+  @Test
+  public void fails_with_BRE_if_no_param_is_provided() {
+    expectedException.expect(BadRequestException.class);
+    expectedException.expectMessage("Project id or project key must be provided, not both.");
+
+    underTest.newRequest().execute();
+  }
+
+  @Test
+  public void fails_with_BRE_if_both_params_are_provided() {
+    expectedException.expect(BadRequestException.class);
+    expectedException.expectMessage("Project id or project key must be provided, not both.");
+
+    underTest.newRequest()
+      .setParam(ID_PARAM, SOME_UUID).setParam(KEY_PARAM, SOME_KEY)
+      .execute();
+  }
+
+  @Test
+  public void fails_with_NFE_if_component_with_uuid_does_not_exist() {
+    String UNKOWN_UUID = "UNKOWN_UUID";
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("Project '" + UNKOWN_UUID + "' not found");
+
+    underTest.newRequest()
+      .setParam(ID_PARAM, UNKOWN_UUID)
+      .execute();
+  }
+
+  @Test
+  public void fails_with_NFE_if_component_with_key_does_not_exist() {
+    String UNKNOWN_KEY = "UNKNOWN_KEY";
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("Project '" + UNKNOWN_KEY + "' not found");
+
+    underTest.newRequest()
+      .setParam(KEY_PARAM, UNKNOWN_KEY)
+      .execute();
+  }
+
+  @Test
+  public void project_without_snapshot_can_be_imported_but_not_exported() {
+    String response = underTest.newRequest()
+      .setParam(KEY_PARAM, SOME_KEY)
+      .execute()
+      .getInput();
+
+    assertJson(response)
+      .isSimilarTo("{\"canBeExported\":false,\"canBeImported\":true}");
+  }
+
+  @Test
+  public void project_with_snapshots_but_none_is_last_can_neither_be_imported_nor_exported() {
+    insertSnapshot(project, false);
+    insertSnapshot(project, false);
+
+    String response = underTest.newRequest()
+      .setParam(KEY_PARAM, SOME_KEY)
+      .execute()
+      .getInput();
+
+    assertJson(response)
+      .isSimilarTo("{\"canBeExported\":false,\"canBeImported\":false}");
+  }
+
+  @Test
+  public void project_with_is_last_snapshot_can_be_exported_but_not_imported() {
+    insertSnapshot(project, false);
+    insertSnapshot(project, true);
+    insertSnapshot(project, false);
+
+    String response = underTest.newRequest()
+      .setParam(KEY_PARAM, SOME_KEY)
+      .execute()
+      .getInput();
+
+    assertJson(response)
+      .isSimilarTo("{\"canBeExported\":true,\"canBeImported\":false}");
+  }
+
+  @Test
+  public void exportedDump_field_contains_absolute_path_if_file_exists_and_is_regular_file() throws IOException {
+    final String exportDumpFilePath = ensureDumpFileExists(SOME_KEY, false);
+
+    String response = underTest.newRequest()
+      .setParam(KEY_PARAM, SOME_KEY)
+      .execute()
+      .getInput();
+
+    assertJson(response)
+      .isSimilarTo("{\"exportedDump\":\"" + exportDumpFilePath + "\"}");
+  }
+
+  @Test
+  public void exportedDump_field_contains_absolute_path_if_file_exists_and_is_link() throws IOException {
+    final String exportDumpFilePath = ensureDumpFileExists(SOME_KEY, false);
+
+    String response = underTest.newRequest()
+      .setParam(KEY_PARAM, SOME_KEY)
+      .execute()
+      .getInput();
+
+    assertJson(response)
+      .isSimilarTo("{\"exportedDump\":\"" + exportDumpFilePath + "\"}");
+  }
+
+  @Test
+  public void exportedDump_field_not_sent_if_file_is_directory() throws IOException {
+    Files.createDirectories(Paths.get(exportDirectoryPathname));
+
+    String response = underTest.newRequest()
+      .setParam(KEY_PARAM, SOME_KEY)
+      .execute()
+      .getInput();
+
+    assertThat(response)
+      .doesNotContain("exportedDump");
+  }
+
+  @Test
+  public void dumpToImport_field_contains_absolute_path_if_file_exists_and_is_regular_file() throws IOException {
+    final String importDumpFilePath = ensureDumpFileExists(SOME_KEY, true);
+
+    String response = underTest.newRequest()
+      .setParam(KEY_PARAM, SOME_KEY)
+      .execute()
+      .getInput();
+
+    assertJson(response)
+      .isSimilarTo("{\"dumpToImport\":\"" + importDumpFilePath + "\"}");
+  }
+
+  @Test
+  public void dumpToImport_field_contains_absolute_path_if_file_exists_and_is_link() throws IOException {
+    final String importDumpFilePath = ensureDumpFileExists(SOME_KEY, true);
+
+    String response = underTest.newRequest()
+      .setParam(KEY_PARAM, SOME_KEY)
+      .execute()
+      .getInput();
+
+    assertJson(response)
+      .isSimilarTo("{\"dumpToImport\":\"" + importDumpFilePath + "\"}");
+  }
+
+  @Test
+  public void dumpToImport_field_not_sent_if_file_is_directory() throws IOException {
+    Files.createDirectories(Paths.get(importDirectoryPathname));
+
+    String response = underTest.newRequest()
+      .setParam(KEY_PARAM, SOME_KEY)
+      .execute()
+      .getInput();
+
+    assertThat(response)
+      .doesNotContain("dumpToImport");
+  }
+
+  @Test
+  public void fail_when_using_branch_db_key() {
+    ComponentDto project = db.components().insertPublicProject();
+    ComponentDto branch = db.components().insertProjectBranch(project);
+
+    expectedException.expect(NotFoundException.class);
+
+    underTest.newRequest()
+      .setParam(KEY_PARAM, branch.getDbKey())
+      .execute();
+  }
+
+  @Test
+  public void fail_when_using_branch_id() {
+    ComponentDto project = db.components().insertPublicProject();
+    ComponentDto branch = db.components().insertProjectBranch(project);
+
+    expectedException.expect(NotFoundException.class);
+
+    underTest.newRequest()
+      .setParam(ID_PARAM, branch.uuid())
+      .execute();
+  }
+
+  private ProjectDto insertProject(String uuid, String key) {
+    return db.components().insertPrivateProjectDto(c -> c.setProjectUuid(uuid).setUuid(uuid).setDbKey(key));
+  }
+
+  private void insertSnapshot(ProjectDto projectDto, boolean last) {
+    dbClient.snapshotDao().insert(dbSession, SnapshotTesting.newAnalysis(projectDto.getUuid()).setLast(last));
+    dbSession.commit();
+  }
+
+  private void logInAsProjectAdministrator(String login) {
+    userSession.logIn(login).addProjectPermission(UserRole.ADMIN, project);
+  }
+
+  private String ensureDumpFileExists(String projectKey, boolean isImport) throws IOException {
+    final String targetDirectoryPathname = isImport ? importDirectoryPathname : exportDirectoryPathname;
+    final String dumpFilename = Slug.slugify(projectKey) + ".zip";
+    final String dumpFilePathname = Paths.get(targetDirectoryPathname, dumpFilename).toString();
+
+    final Path dumpFilePath = Paths.get(dumpFilePathname);
+
+    File fileToImport = new File(dumpFilePathname);
+
+    fileToImport.getParentFile().mkdirs();
+
+    Files.createFile(dumpFilePath);
+
+    return dumpFilePathname;
+  }
+
+  private static void cleanUpFilesystem() throws IOException {
+    final Path projectDumpsDirectoryPath = Paths.get(projectDumpsDirectoryPathname);
+
+    if (Files.exists(projectDumpsDirectoryPath)) {
+      Files.walk(projectDumpsDirectoryPath)
+        .sorted(reverseOrder())
+        .map(Path::toFile)
+        .forEach(File::delete);
+    }
+  }
+}