/*
* Copyright (C) 2014 Red Hat, inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.jboss.as.repository;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.jboss.as.repository.HashUtil.emptyStream;
import static org.jboss.as.repository.PathUtil.deleteRecursively;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.hamcrest.CoreMatchers;
import org.jboss.as.protocol.StreamUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
*
* @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();
}
}
}