/** @author Emmanuel Hugonnet (c) 2016 Red Hat, inc. */
public class ContentRepositoryTest {

  private static final boolean IS_WINDOWS =
      AccessController.doPrivileged(
          (PrivilegedAction<Boolean>)
              () ->
                  System.getProperty("os.name", null)
                      .toLowerCase(Locale.ENGLISH)
                      .contains("windows"));
  private static final FileTime time = FileTime.from(Instant.parse("2007-12-03T10:15:30.00Z"));

  private ContentRepository repository;
  private final File rootDir = new File("target", "repository");
  private final File tmpRootDir = new File("target", "tmp");

  public ContentRepositoryTest() {}

  @Before
  public void createRepository() throws IOException {
    if (rootDir.exists()) {
      deleteRecursively(rootDir.toPath());
    }
    rootDir.mkdirs();
    if (tmpRootDir.exists()) {
      deleteRecursively(tmpRootDir.toPath());
    }
    tmpRootDir.mkdirs();
    repository = ContentRepository.Factory.create(rootDir, tmpRootDir, 0L);
  }

  @After
  public void destroyRepository() throws IOException {
    deleteRecursively(rootDir.toPath());
    deleteRecursively(tmpRootDir.toPath());
    repository = null;
  }

  private String readFileContent(Path path) throws Exception {
    try (InputStream in = getFileInputStream(path)) {
      return readFileContent(in);
    }
  }

  private String readFileContent(InputStream in) throws Exception {
    try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
      byte[] buffer = new byte[8];
      int length = 8;
      while ((length = in.read(buffer, 0, length)) > 0) {
        out.write(buffer, 0, length);
      }
      return out.toString("UTF-8");
    }
  }

  /** Test of addContent method, of class ContentRepository. */
  @Test
  public void testAddContent() throws Exception {
    try (InputStream stream = getResourceAsStream("overlay.xhtml")) {
      String expResult = "0c40ffacd15b0f66d5081a93407d3ff5e3c65a71";
      byte[] result = repository.addContent(stream);
      assertThat(result, is(notNullValue()));
      assertThat(HashUtil.bytesToHexString(result), is(expResult));
    }
  }

  /** Test of explodeContent method, of class ContentRepository. */
  @Test
  public void testExplodeContent() throws Exception {
    byte[] archive = createArchive(Collections.singletonList("overlay.xhtml"));
    try (ByteArrayInputStream stream = new ByteArrayInputStream(archive)) {
      byte[] hash = repository.explodeContent(repository.addContent(stream));
      String expResult = "b1f18e286615dda0643633ec31f1a17d90e48875";
      // hash is different from the simple overlay.xhtml as we add the content folder name in the
      // computation
      assertThat(hash, is(notNullValue()));
      Path content = repository.getContent(hash).getPhysicalFile().toPath();
      String contentHtml = readFileContent(content.resolve("overlay.xhtml"));
      String expectedContentHtml = readFileContent(getResourceAsStream("overlay.xhtml"));
      assertThat(contentHtml, is(expectedContentHtml));
      assertThat(HashUtil.bytesToHexString(hash), is(expResult));
    }
  }

  /** Test of explodeContent method, of class ContentRepository. */
  @Test
  public void testExplodeSubContent() throws Exception {
    byte[] archive =
        createMultiLevelArchive(Collections.singletonList("overlay.xhtml"), "test/archive.zip");
    try (ByteArrayInputStream stream = new ByteArrayInputStream(archive)) {
      byte[] originalHash = repository.addContent(stream);
      assertThat(originalHash, is(notNullValue()));
      assertThat(
          HashUtil.bytesToHexString(originalHash), is("f11be1883895957b06f7e46d784cad60dd015d71"));
      try {
        repository.explodeSubContent(originalHash, "test/archive.zip");
        fail("Shouldn't be able to explode sub content of unexploded content");
      } catch (ExplodedContentException ex) {
      }
      byte[] hash = repository.explodeContent(originalHash);
      // hash is different from the simple overlay.xhtml as we add the content folder name in the
      // computation
      assertThat(hash, is(notNullValue()));
      assertThat(HashUtil.bytesToHexString(hash), is("5ab326c763fadad903d0e9bbfecbb42e69a1b8b4"));
      Path content = repository.getContent(hash).getPhysicalFile().toPath();
      String contentHtml = readFileContent(content.resolve("overlay.xhtml"));
      String expectedContentHtml = readFileContent(getResourceAsStream("overlay.xhtml"));
      assertThat(contentHtml, is(expectedContentHtml));
      Path archiveFile = content.resolve("test").resolve("archive.zip");
      assertTrue(Files.exists(archiveFile));
      assertTrue(PathUtil.isArchive(archiveFile));
      byte[] fullyExplodedHash = repository.explodeSubContent(hash, "test/archive.zip");
      assertThat(fullyExplodedHash, is(notNullValue()));
      assertThat(
          HashUtil.bytesToHexString(fullyExplodedHash),
          is("231f4d042711f017d7f8c45aa4affcccbd4d67f4"));
      content =
          repository
              .getContent(repository.explodeSubContent(hash, "test/archive.zip"))
              .getPhysicalFile()
              .toPath();
      Path directory = content.resolve("test").resolve("archive.zip");
      assertTrue("Should not be a zip file", Files.isDirectory(directory));
      assertThat(contentHtml, is(expectedContentHtml));
    }
  }

  private byte[] createMultiLevelArchive(List<String> resources, String archivePath)
      throws IOException {
    try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
      try (ZipOutputStream out = new ZipOutputStream(buffer)) {
        for (String resourcePath : resources) {
          ZipEntry entry = new ZipEntry(resourcePath);
          entry.setLastModifiedTime(time);
          out.putNextEntry(entry);
          try (InputStream in = getResourceAsStream(resourcePath)) {
            StreamUtils.copyStream(in, out);
          }
          out.closeEntry();
        }
        ZipEntry entry = new ZipEntry("test/");
        entry.setLastModifiedTime(time);
        out.putNextEntry(entry);
        out.closeEntry();
        entry = new ZipEntry(archivePath);
        entry.setLastModifiedTime(time);
        out.putNextEntry(entry);
        try (InputStream in = new ByteArrayInputStream(createArchive(resources))) {
          StreamUtils.copyStream(in, out);
        }
        out.closeEntry();
      }
      return buffer.toByteArray();
    }
  }

  private byte[] createArchive(List<String> resources) throws IOException {
    try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
      try (ZipOutputStream out = new ZipOutputStream(buffer)) {
        for (String resourcePath : resources) {
          ZipEntry entry = new ZipEntry(resourcePath);
          entry.setLastModifiedTime(time);
          out.putNextEntry(entry);
          try (InputStream in = getResourceAsStream(resourcePath)) {
            StreamUtils.copyStream(in, out);
          }
          out.closeEntry();
        }
      }
      return buffer.toByteArray();
    }
  }

  private byte[] createContentArchive() throws IOException {
    try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
      try (ZipOutputStream out = new ZipOutputStream(buffer)) {
        ZipEntry entry = new ZipEntry("overlay.xhtml");
        entry.setLastModifiedTime(time);
        out.putNextEntry(entry);
        try (InputStream in = getResourceAsStream("overlay.xhtml")) {
          StreamUtils.copyStream(in, out);
        }
        out.closeEntry();
        entry = new ZipEntry("test.jsp");
        entry.setLastModifiedTime(time);
        out.putNextEntry(entry);
        try (InputStream in =
            new ByteArrayInputStream("this is a test".getBytes(StandardCharsets.UTF_8))) {
          StreamUtils.copyStream(in, out);
        }
        out.closeEntry();
        entry = new ZipEntry("empty-dir/");
        entry.setLastModifiedTime(time);
        out.putNextEntry(entry);
        out.closeEntry();
        assertTrue(entry.isDirectory());
        entry = new ZipEntry("test/");
        entry.setLastModifiedTime(time);
        out.putNextEntry(entry);
        out.closeEntry();
        assertTrue(entry.isDirectory());
        entry = new ZipEntry("test/empty-file.txt");
        entry.setLastModifiedTime(time);
        out.putNextEntry(entry);
        try (InputStream in = HashUtil.emptyStream()) {
          StreamUtils.copyStream(in, out);
        }
        out.closeEntry();
      }
      return buffer.toByteArray();
    }
  }

  /** Test of explodeContent method, of class ContentRepository. */
  @Test
  public void testChangeExplodedContent() throws Exception {
    byte[] archive = createArchive(Collections.singletonList("overlay.xhtml"));
    try (ByteArrayInputStream stream = new ByteArrayInputStream(archive)) {
      byte[] hash = repository.explodeContent(repository.addContent(stream));
      String expResult = "b1f18e286615dda0643633ec31f1a17d90e48875";
      // hash is different from the simple overlay.xhtml as we add the content folder name in the
      // computation
      assertThat(hash, is(notNullValue()));
      Path content = repository.getContent(hash).getPhysicalFile().toPath();
      String contentHtml = readFileContent(content.resolve("overlay.xhtml"));
      String expectedContentHtml = readFileContent(getResourceAsStream("overlay.xhtml"));
      assertThat(contentHtml, is(expectedContentHtml));
      assertThat(HashUtil.bytesToHexString(hash), is(expResult));
      String updatedExpectedResult = "161a2c95b16d5ffede0721c2cec984ca51009082";
      hash =
          repository.addContentToExploded(
              hash,
              Collections.singletonList(
                  new ExplodedContent(
                      "test.jsp",
                      new ByteArrayInputStream("this is a test".getBytes(StandardCharsets.UTF_8)))),
              true);
      assertThat(hash, is(notNullValue()));
      assertThat(HashUtil.bytesToHexString(hash), is(updatedExpectedResult));
      try (InputStream addedContent = repository.readContent(hash, "test.jsp")) {
        assertThat(addedContent, is(notNullValue()));
        assertThat(readFileContent(addedContent), is("this is a test"));
      }
      content = repository.getContent(hash).getPhysicalFile().toPath();
      assertThat(content.toFile().list().length, is(2));
      hash = repository.removeContentFromExploded(hash, Collections.singletonList("test.jsp"));
      assertThat(hash, is(notNullValue()));
      assertThat(HashUtil.bytesToHexString(hash), is(expResult));
      updatedExpectedResult = "a44921155d75009d885db3357005b85b435cf59f";
      hash =
          repository.addContentToExploded(
              hash,
              Collections.singletonList(
                  new ExplodedContent(
                      "test.jsp",
                      new ByteArrayInputStream(
                          "this is an overwrite test".getBytes(StandardCharsets.UTF_8)))),
              true);
      assertThat(hash, is(notNullValue()));
      assertThat(HashUtil.bytesToHexString(hash), is(updatedExpectedResult));
      try (InputStream addedContent = repository.readContent(hash, "test.jsp")) {
        assertThat(addedContent, is(notNullValue()));
        assertThat(readFileContent(addedContent), is("this is an overwrite test"));
      }
      try {
        hash =
            repository.addContentToExploded(
                hash,
                Collections.singletonList(
                    new ExplodedContent(
                        "test.jsp",
                        new ByteArrayInputStream(
                            "this is a failure test".getBytes(StandardCharsets.UTF_8)))),
                false);
        fail("Overwritting shouldn't work");
      } catch (ExplodedContentException ex) {
      }
    }
  }

  @Test
  public void testListContents() throws Exception {
    byte[] archive = createArchive(Collections.singletonList("overlay.xhtml"));
    try (ByteArrayInputStream stream = new ByteArrayInputStream(archive)) {
      byte[] hash = repository.explodeContent(repository.addContent(stream));
      String expResult = "b1f18e286615dda0643633ec31f1a17d90e48875";
      // hash is different from the simple overlay.xhtml as we add the content folder name in the
      // computation
      assertThat(hash, is(notNullValue()));
      Path content = repository.getContent(hash).getPhysicalFile().toPath();
      String contentHtml = readFileContent(content.resolve("overlay.xhtml"));
      String expectedContentHtml = readFileContent(getResourceAsStream("overlay.xhtml"));
      assertThat(contentHtml, is(expectedContentHtml));
      assertThat(HashUtil.bytesToHexString(hash), is(expResult));
      String updatedExpectedResult = "161a2c95b16d5ffede0721c2cec984ca51009082";
      hash =
          repository.addContentToExploded(
              hash,
              Collections.singletonList(
                  new ExplodedContent(
                      "test.jsp",
                      new ByteArrayInputStream("this is a test".getBytes(StandardCharsets.UTF_8)))),
              true);
      assertThat(hash, is(notNullValue()));
      assertThat(HashUtil.bytesToHexString(hash), is(updatedExpectedResult));
      List<String> contents =
          repository
              .listContent(hash, "", ContentFilter.Factory.createContentFilter(-1, false))
              .stream()
              .map(ContentRepositoryElement::getPath)
              .collect(Collectors.toList());
      assertThat(contents.size(), is(2));
      assertThat(contents, CoreMatchers.hasItems("test.jsp", "overlay.xhtml"));
      hash =
          repository.addContentToExploded(
              hash,
              Collections.singletonList(new ExplodedContent("test/empty-file.txt", emptyStream())),
              true);
      hash =
          repository.addContentToExploded(
              hash, Collections.singletonList(new ExplodedContent("empty-dir", null)), true);
      contents =
          repository
              .listContent(hash, "", ContentFilter.Factory.createContentFilter(-1, false))
              .stream()
              .map(ContentRepositoryElement::getPath)
              .collect(Collectors.toList());
      assertThat(contents, is(notNullValue()));
      assertThat(contents.size(), is(5));
      assertThat(
          contents,
          CoreMatchers.hasItems(
              "test.jsp", "overlay.xhtml", "test/empty-file.txt", "test/", "empty-dir/"));
      hash = repository.removeContentFromExploded(hash, Collections.singletonList("test.jsp"));
      contents =
          repository
              .listContent(hash, "", ContentFilter.Factory.createFileFilter(-1, false))
              .stream()
              .map(ContentRepositoryElement::getPath)
              .collect(Collectors.toList());
      assertThat(contents, is(notNullValue()));
      assertThat(contents.size(), is(2));
      assertThat(contents, CoreMatchers.hasItems("overlay.xhtml", "test/empty-file.txt"));
    }
  }

  @Test
  public void testListArchiveContents() throws Exception {
    byte[] archive = createContentArchive();
    try (ByteArrayInputStream stream = new ByteArrayInputStream(archive)) {
      byte[] hash = repository.addContent(stream);
      // hash is different from the simple overlay.xhtml as we add the content folder name in the
      // computation
      assertThat(hash, is(notNullValue()));
      List<String> contents =
          repository
              .listContent(hash, "", ContentFilter.Factory.createContentFilter(-1, false))
              .stream()
              .map(ContentRepositoryElement::getPath)
              .collect(Collectors.toList());
      assertThat(contents.size(), is(5));
      assertThat(
          contents,
          CoreMatchers.hasItems(
              "test.jsp", "overlay.xhtml", "test/empty-file.txt", "test/", "empty-dir/"));
      hash =
          repository.addContentToExploded(
              hash,
              Collections.singletonList(new ExplodedContent("test/empty-file.txt", emptyStream())),
              true);
      hash =
          repository.addContentToExploded(
              hash, Collections.singletonList(new ExplodedContent("empty-dir", null)), true);
      contents =
          repository
              .listContent(hash, "", ContentFilter.Factory.createContentFilter(1, false))
              .stream()
              .map(ContentRepositoryElement::getPath)
              .collect(Collectors.toList());
      assertThat(contents, is(notNullValue()));
      assertThat(contents.size(), is(4));
      assertThat(
          contents, CoreMatchers.hasItems("test.jsp", "overlay.xhtml", "test/", "empty-dir/"));
      contents =
          repository
              .listContent(hash, "", ContentFilter.Factory.createFileFilter(-1, false))
              .stream()
              .map(ContentRepositoryElement::getPath)
              .collect(Collectors.toList());
      assertThat(contents, is(notNullValue()));
      assertThat(contents.size(), is(3));
      assertThat(
          contents, CoreMatchers.hasItems("test.jsp", "overlay.xhtml", "test/empty-file.txt"));
    }
  }

  /** Test of addContentReference method, of class ContentRepository. */
  @Test
  public void testAddContentReference() throws Exception {
    try (InputStream stream = getResourceAsStream("overlay.xhtml")) {
      String expResult = "0c40ffacd15b0f66d5081a93407d3ff5e3c65a71";
      byte[] result = repository.addContent(stream);
      assertThat(result, is(notNullValue()));
      assertThat(HashUtil.bytesToHexString(result), is(expResult));
      ContentReference reference = new ContentReference("contentReferenceIdentifier", result);
      repository.addContentReference(reference);
    }
  }

  /** Test of getContent method, of class ContentRepository. */
  @Test
  public void testGetContent() throws Exception {
    try (InputStream stream = getResourceAsStream("overlay.xhtml")) {
      String expResult = "0c40ffacd15b0f66d5081a93407d3ff5e3c65a71";
      byte[] result = repository.addContent(stream);
      assertThat(result, is(notNullValue()));
      assertThat(HashUtil.bytesToHexString(result), is(expResult));
      Path content = repository.getContent(result).getPhysicalFile().toPath();
      String contentHtml = readFileContent(content);
      String expectedContentHtml = readFileContent(getResourceAsStream("overlay.xhtml"));
      assertThat(contentHtml, is(expectedContentHtml));
    }
  }

  /** Test of hasContent method, of class ContentRepository. */
  @Test
  public void testHasContent() throws Exception {
    String expResult = "0c40ffacd15b0f66d5081a93407d3ff5e3c65a71";
    try (InputStream stream = getResourceAsStream("overlay.xhtml")) {
      assertThat(repository.hasContent(HashUtil.hexStringToByteArray(expResult)), is(false));
      byte[] result = repository.addContent(stream);
      assertThat(result, is(notNullValue()));
      assertThat(HashUtil.bytesToHexString(result), is(expResult));
      assertThat(repository.hasContent(HashUtil.hexStringToByteArray(expResult)), is(true));
    }
  }

  /** Test of removeContent method, of class ContentRepository. */
  @Test
  public void testRemoveContent() throws Exception {
    String expResult = "0c40ffacd15b0f66d5081a93407d3ff5e3c65a71";
    Path grandparent = rootDir.toPath().resolve("0c");
    Path parent = grandparent.resolve("40ffacd15b0f66d5081a93407d3ff5e3c65a71");
    Path expectedContent = parent.resolve("content");
    assertFalse(expectedContent + " shouldn't exist", Files.exists(expectedContent));
    assertFalse(expectedContent.getParent() + " shouldn't exist", Files.exists(parent));
    assertFalse(
        expectedContent.getParent().getParent() + " shouldn't exist", Files.exists(grandparent));
    byte[] result;
    try (InputStream stream = getResourceAsStream("overlay.xhtml")) {
      assertThat(repository.hasContent(HashUtil.hexStringToByteArray(expResult)), is(false));
      result = repository.addContent(stream);
    }
    assertThat(result, is(notNullValue()));
    assertThat(HashUtil.bytesToHexString(result), is(expResult));
    assertThat(repository.hasContent(HashUtil.hexStringToByteArray(expResult)), is(true));
    assertTrue(expectedContent + " should have been created", Files.exists(expectedContent));
    assertTrue(parent + " should have been created", Files.exists(parent));
    assertTrue(grandparent + " should have been created", Files.exists(grandparent));
    repository.removeContent(new ContentReference("overlay.xhtml", expResult));
    assertThat(repository.hasContent(HashUtil.hexStringToByteArray(expResult)), is(false));
    assertFalse(expectedContent + " should have been deleted", Files.exists(expectedContent));
    assertFalse(parent.toAbsolutePath() + " should have been deleted", Files.exists(parent));
    assertFalse(grandparent + " should have been deleted", Files.exists(grandparent));
    Path content = repository.getContent(result).getPhysicalFile().toPath();
    assertFalse(Files.exists(content));
  }

  /** Test that an empty dir will be removed during cleaning. */
  @Test
  public void testCleanEmptyParentDir() throws Exception {
    File emptyGrandParent = new File(rootDir, "ae");
    emptyGrandParent.mkdir();
    File emptyParent = new File(emptyGrandParent, "ffacd15b0f66d5081a93407d3ff5e3c65a71");
    emptyParent.mkdir();
    assertThat(emptyGrandParent.exists(), is(true));
    assertThat(emptyParent.exists(), is(true));
    Map<String, Set<String>> result =
        repository.cleanObsoleteContent(); // To mark content for deletion
    assertThat(result.get(ContentRepository.MARKED_CONTENT).size(), is(1));
    assertThat(result.get(ContentRepository.DELETED_CONTENT).size(), is(0));
    assertThat(
        result.get(ContentRepository.MARKED_CONTENT).contains(emptyParent.getAbsolutePath()),
        is(true));
    Thread.sleep(10);
    result = repository.cleanObsoleteContent();
    assertThat(emptyGrandParent.exists(), is(false));
    assertThat(emptyParent.exists(), is(false));
    assertThat(result.get(ContentRepository.MARKED_CONTENT).size(), is(0));
    assertThat(result.get(ContentRepository.DELETED_CONTENT).size(), is(1));
    assertThat(
        result.get(ContentRepository.DELETED_CONTENT).contains(emptyParent.getAbsolutePath()),
        is(true));
  }

  /** Test that an empty dir will be removed during cleaning. */
  @Test
  public void testCleanEmptyGrandParentDir() throws Exception {
    File emptyGrandParent = new File(rootDir, "ae");
    emptyGrandParent.mkdir();
    assertThat(emptyGrandParent.exists(), is(true));
    Map<String, Set<String>> result =
        repository.cleanObsoleteContent(); // Mark content for deletion
    assertThat(result.get(ContentRepository.MARKED_CONTENT).size(), is(1));
    assertThat(result.get(ContentRepository.DELETED_CONTENT).size(), is(0));
    assertThat(
        result.get(ContentRepository.MARKED_CONTENT).contains(emptyGrandParent.getAbsolutePath()),
        is(true));
    Thread.sleep(10);
    result = repository.cleanObsoleteContent();
    assertThat(emptyGrandParent.exists(), is(false));
    assertThat(result.get(ContentRepository.DELETED_CONTENT).size(), is(1));
    assertThat(result.get(ContentRepository.MARKED_CONTENT).size(), is(0));
    assertThat(
        result.get(ContentRepository.DELETED_CONTENT).contains(emptyGrandParent.getAbsolutePath()),
        is(true));
  }

  /**
   * Test that an empty dir with a system metadata file .DS_Store will be removed during cleaning.
   */
  @Test
  public void testCleanEmptyParentDirWithSystemMetaDataFile() throws Exception {
    File emptyGrandParent = new File(rootDir, "ae");
    emptyGrandParent.mkdir();
    File metaDataFile = new File(emptyGrandParent, ".DS_Store");
    metaDataFile.createNewFile();
    assertThat(emptyGrandParent.exists(), is(true));
    assertThat(metaDataFile.exists(), is(true));
    Map<String, Set<String>> result =
        repository.cleanObsoleteContent(); // To mark content for deletion
    assertThat(result.get(ContentRepository.MARKED_CONTENT).size(), is(1));
    assertThat(result.get(ContentRepository.DELETED_CONTENT).size(), is(0));
    assertThat(
        result.get(ContentRepository.MARKED_CONTENT).contains(metaDataFile.getAbsolutePath()),
        is(true));
    Thread.sleep(10);
    result = repository.cleanObsoleteContent();
    assertThat(emptyGrandParent.exists(), is(false));
    assertThat(metaDataFile.exists(), is(false));
    assertThat(result.get(ContentRepository.MARKED_CONTENT).size(), is(0));
    assertThat(result.get(ContentRepository.DELETED_CONTENT).size(), is(1));
    assertThat(
        result.get(ContentRepository.DELETED_CONTENT).contains(metaDataFile.getAbsolutePath()),
        is(true));
  }

  /** Test that an empty dir will be removed during cleaning. */
  @Test
  public void testCleanNotEmptyGrandParentDir() throws Exception {
    String expResult = "0c40ffacd15b0f66d5081a93407d3ff5e3c65a71";
    Path grandparent = rootDir.toPath().resolve("0c");
    Path parent = grandparent.resolve("40ffacd15b0f66d5081a93407d3ff5e3c65a71");
    Path other = grandparent.resolve("40ffacd15b0f66d5081a93407d3ff5e3c65a81");
    Files.createDirectories(other);
    Path expectedContent = parent.resolve("content");
    assertFalse(expectedContent + " shouldn't exist", Files.exists(expectedContent));
    assertFalse(parent + " shouldn't exist", Files.exists(parent));
    byte[] result;
    try (InputStream stream = getResourceAsStream("overlay.xhtml")) {
      assertThat(repository.hasContent(HashUtil.hexStringToByteArray(expResult)), is(false));
      result = repository.addContent(stream);
    }
    assertThat(result, is(notNullValue()));
    assertThat(HashUtil.bytesToHexString(result), is(expResult));
    assertThat(repository.hasContent(HashUtil.hexStringToByteArray(expResult)), is(true));
    assertTrue(expectedContent + " should have been created", Files.exists(expectedContent));
    assertTrue(parent + " should have been created", Files.exists(parent));
    repository.removeContent(new ContentReference("overlay.xhtml", expResult));
    assertFalse(repository.hasContent(HashUtil.hexStringToByteArray(expResult)));
    assertFalse(expectedContent + " should have been deleted", Files.exists(expectedContent));
    assertFalse(parent.toAbsolutePath() + " should have been deleted", Files.exists(parent));
    assertTrue(other + " should not have been deleted", Files.exists(other));
    assertTrue(grandparent + " should not have been deleted", Files.exists(grandparent));
    Path content = repository.getContent(result).getPhysicalFile().toPath();
    assertFalse(Files.exists(content));
  }

  /** Test that an dir not empty with no content will not be removed during cleaning. */
  @Test
  public void testNotEmptyDir() throws Exception {
    Path parentDir = rootDir.toPath().resolve("ae").resolve("ffacd15b0f66d5081a93407d3ff5e3c65a71");
    Path overlay = parentDir.resolve("overlay.xhtml");
    Path content = parentDir.resolve("content");
    Files.createDirectories(overlay.getParent());
    try (InputStream stream = getResourceAsStream("overlay.xhtml")) {
      Files.copy(stream, overlay);
      Files.copy(overlay, content);
      assertThat(Files.exists(content), is(true));
      assertThat(Files.exists(overlay), is(true));
      Map<String, Set<String>> result =
          repository.cleanObsoleteContent(); // Mark content for deletion
      assertThat(result.get(ContentRepository.MARKED_CONTENT).size(), is(1));
      assertThat(result.get(ContentRepository.DELETED_CONTENT).size(), is(0));
      assertThat(
          result
              .get(ContentRepository.MARKED_CONTENT)
              .contains(parentDir.toFile().getAbsolutePath()),
          is(true));
      Thread.sleep(10);
      result = repository.cleanObsoleteContent();
      assertThat(Files.exists(content), is(false));
      assertThat(Files.exists(overlay), is(true));
      assertThat(result.get(ContentRepository.MARKED_CONTENT).size(), is(0));
      assertThat(result.get(ContentRepository.DELETED_CONTENT).size(), is(1));
      assertThat(
          result
              .get(ContentRepository.DELETED_CONTENT)
              .contains(parentDir.toFile().getAbsolutePath()),
          is(true));
    } finally {
      Files.deleteIfExists(overlay);
      Files.deleteIfExists(overlay.getParent());
      Files.deleteIfExists(overlay.getParent().getParent());
    }
  }

  private InputStream getResourceAsStream(final String name) throws IOException {
    final InputStream result = getClass().getClassLoader().getResourceAsStream(name);
    // If we're on Windows we want to replace the stream with one that ignores \r
    if (result != null && IS_WINDOWS) {
      return new CarriageReturnRemovalInputStream(result);
    }
    return result;
  }

  private InputStream getFileInputStream(final File file) throws IOException {
    if (IS_WINDOWS) {
      return new CarriageReturnRemovalInputStream(new FileInputStream(file));
    }
    return new FileInputStream(file);
  }

  private InputStream getFileInputStream(final Path path) throws IOException {
    if (IS_WINDOWS) {
      return new CarriageReturnRemovalInputStream(Files.newInputStream(path));
    }
    return Files.newInputStream(path);
  }

  private static class CarriageReturnRemovalInputStream extends InputStream {

    private final InputStream delegate;

    private CarriageReturnRemovalInputStream(final InputStream delegate) {
      this.delegate = delegate;
    }

    @Override
    public int read() throws IOException {
      int result = delegate.read();
      if (result == '\r') {
        result = delegate.read();
      }
      return result;
    }

    @Override
    public int read(final byte[] b) throws IOException {
      Objects.nonNull(b);
      int result = 0;
      while (result > -1 && result < b.length) {
        int c = read();
        if (c == -1) {
          return result == 0 ? -1 : result;
        }
        b[result++] = (byte) c;
      }
      return result;
    }

    @Override
    public int read(final byte[] b, final int off, final int len) throws IOException {
      Objects.nonNull(b);
      if (off < 0 || len < 0 || len > b.length - off) {
        throw new IndexOutOfBoundsException();
      } else if (len == 0) {
        return 0;
      }
      int result = 0;
      while (result > -1 && result < len) {
        int c = read();
        if (c == -1) {
          return result == 0 ? -1 : result;
        }
        b[off + result++] = (byte) c;
      }
      return result;
    }

    @Override
    public long skip(final long n) throws IOException {
      return delegate.skip(n);
    }

    @Override
    public int available() throws IOException {
      return delegate.available();
    }

    @Override
    public void close() throws IOException {
      delegate.close();
    }

    @Override
    public void mark(final int readlimit) {
      delegate.mark(readlimit);
    }

    @Override
    public void reset() throws IOException {
      delegate.reset();
    }

    @Override
    public boolean markSupported() {
      return delegate.markSupported();
    }
  }
}
Example #2
0
 /** Converts "standard Unix time"(in seconds, UTC/GMT) to FileTime */
 public static final FileTime unixTimeToFileTime(long utime) {
   return FileTime.from(utime, TimeUnit.SECONDS);
 }
Example #3
0
 /**
  * @param line structured as SHA256;0000;size;YYYY-MM-DDTHH:MM:SS;name
  * @throws IllegalArgumentException
  */
 public FileInfo(String line) throws IllegalArgumentException {
   if (line == null) {
     throw new IllegalArgumentException("line must not be null");
   }
   String[] fields = line.split(";");
   if (fields.length < 5) {
     throw new IllegalArgumentException(
         String.format("line has %d fields instead of 5", fields.length));
   }
   byte[] hex = null;
   try {
     hex = DatatypeConverter.parseHexBinary(fields[0]);
   } catch (IllegalArgumentException e) {
     // exception handled below
   }
   if (hex == null || hex.length != HASH_BYTES) {
     throw new IllegalArgumentException(
         String.format("%s is not a valid SHA-256 signature", fields[0]));
   }
   hash = fields[0];
   int val = -1;
   try {
     val = Integer.parseInt(fields[1], 16);
   } catch (NumberFormatException e) {
     // exception handled below
   }
   if (val == -1 || fields[1].length() != 4) {
     throw new IllegalArgumentException(
         String.format("%s is not a valid 16 bit status", fields[1]));
   }
   flags = val;
   long len = -1;
   try {
     len = Long.parseLong(fields[2]);
   } catch (NumberFormatException e) {
     // exception handled below
   }
   if (len == -1) {
     throw new IllegalArgumentException(String.format("%s is not a valid size", fields[2]));
   }
   size = len;
   Instant instant = null;
   try {
     instant = Instant.parse(fields[3]);
   } catch (DateTimeParseException e) {
     // exception handled below
   }
   if (instant == null) {
     throw new IllegalArgumentException(String.format("%s is not a valid timestamp", fields[3]));
   }
   lastModif = FileTime.from(instant);
   StringBuffer finLigne = new StringBuffer(fields[4]);
   for (int i = 5; i < fields.length; i++) {
     finLigne.append(";").append(fields[i]);
   }
   if (finLigne.length() == 0) {
     throw new IllegalArgumentException(String.format("file name is empty"));
   }
   name = finLigne.toString();
   initialized = true;
 }
Example #4
0
 /** Converts Windows time (in microseconds, UTC/GMT) time to FileTime. */
 public static final FileTime winTimeToFileTime(long wtime) {
   return FileTime.from(wtime / 10 + WINDOWS_EPOCH_IN_MICROSECONDS, TimeUnit.MICROSECONDS);
 }