/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* 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.
*/
package com.liferay.portal.fabric.netty.fileserver.handlers;
import com.liferay.portal.fabric.netty.fileserver.CompressionLevel;
import com.liferay.portal.fabric.netty.fileserver.FileRequest;
import com.liferay.portal.fabric.netty.fileserver.FileResponse;
import com.liferay.portal.kernel.io.BigEndianCodec;
import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayInputStream;
import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream;
import com.liferay.portal.kernel.test.rule.CodeCoverageAssertor;
import com.liferay.portal.kernel.util.StreamUtil;
import io.netty.channel.DefaultFileRegion;
import io.netty.channel.FileRegion;
import io.netty.channel.embedded.EmbeddedChannel;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.junit.After;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
/**
* @author Shuyang Zhou
*/
public class FileRequestChannelHandlerTest {
@ClassRule
public static final CodeCoverageAssertor codeCoverageAssertor =
CodeCoverageAssertor.INSTANCE;
@After
public void tearDown() {
_embeddedChannel.finish();
FileServerTestUtil.cleanUp();
}
@Test
public void testFileNotFound() {
Path path = FileServerTestUtil.createNotExistFile(
Paths.get("testNotExistFile"));
_embeddedChannel.writeInbound(new FileRequest(path, 0, false));
Queue<Object> queue = _embeddedChannel.outboundMessages();
Assert.assertEquals(1, queue.size());
Assert.assertEquals(
new FileResponse(path, FileResponse.FILE_NOT_FOUND, -1, false),
queue.poll());
}
@Test
public void testFileNotModified() throws IOException {
Path path = FileServerTestUtil.createEmptyFile(
Paths.get("testEmptyFile"));
FileTime fileTime = Files.getLastModifiedTime(path);
_embeddedChannel.writeInbound(
new FileRequest(path, fileTime.toMillis(), false));
Queue<Object> queue = _embeddedChannel.outboundMessages();
Assert.assertEquals(1, queue.size());
Assert.assertEquals(
new FileResponse(path, FileResponse.FILE_NOT_MODIFIED, -1, false),
queue.poll());
}
@Test
public void testFileTransfer() throws IOException {
doTestFileTransfer(true);
doTestFileTransfer(false);
}
@Test
public void testFolderTransfer() throws IOException {
doTestFolderTransfer(true);
doTestFolderTransfer(false);
}
protected void doTestFileTransfer(boolean deleteAfterFetch)
throws IOException {
Path path = FileServerTestUtil.createFileWithData(
Paths.get("testFile"));
FileTime fileTime = Files.getLastModifiedTime(path);
byte[] data = Files.readAllBytes(path);
_embeddedChannel.writeInbound(
new FileRequest(path, 0, deleteAfterFetch));
Queue<Object> queue = _embeddedChannel.outboundMessages();
Assert.assertEquals(2, queue.size());
if (deleteAfterFetch) {
Assert.assertTrue(Files.notExists(path));
}
Assert.assertEquals(
new FileResponse(path, data.length, fileTime.toMillis(), false),
queue.poll());
Assert.assertArrayEquals(
data, _readFileRegion((DefaultFileRegion)queue.poll()));
}
protected void doTestFolderTransfer(boolean deleteAfterFetch)
throws IOException {
Path path = FileServerTestUtil.createFolderWithFiles(
Paths.get("testFolder"));
FileTime fileTime = Files.getLastModifiedTime(path);
_embeddedChannel.writeInbound(
new FileRequest(path, 0, deleteAfterFetch));
Queue<Object> queue = _embeddedChannel.outboundMessages();
Assert.assertEquals(2, queue.size());
if (deleteAfterFetch) {
Assert.assertTrue(Files.notExists(path));
}
FileResponse fileResponse = (FileResponse)queue.poll();
Assert.assertEquals(
new FileResponse(
path, fileResponse.getSize(), fileTime.toMillis(), true),
fileResponse);
if (deleteAfterFetch) {
Assert.assertTrue(queue.poll() instanceof FileRegion);
}
else {
_assertZipStream(
path,
new UnsyncByteArrayInputStream(
_readFileRegion((FileRegion)queue.poll())));
}
}
private void _assertZipStream(
Path expectedRootFolder, InputStream inputStream)
throws IOException {
final List<Path> files = new ArrayList<>();
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
ZipEntry zipEntry = null;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
if (zipEntry.isDirectory()) {
continue;
}
Path expectedFile = expectedRootFolder.resolve(
zipEntry.getName());
Assert.assertTrue(
"Zip entry file " + expectedFile + " does not exist",
Files.exists(expectedFile));
FileTime fileTime = Files.getLastModifiedTime(expectedFile);
Assert.assertEquals(
"Last modified time mismatch", fileTime.toMillis(),
BigEndianCodec.getLong(zipEntry.getExtra(), 0));
Assert.assertEquals(
"File size mismatch", Files.size(expectedFile),
BigEndianCodec.getLong(zipEntry.getExtra(), 8));
Assert.assertArrayEquals(
"File content mismatch", Files.readAllBytes(expectedFile),
_readInputStream(zipInputStream));
files.add(expectedFile);
}
}
Files.walkFileTree(
expectedRootFolder,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(
Path file, BasicFileAttributes attrs) {
Assert.assertTrue(
"Miss file " + file + " from zip stream",
files.contains(file));
return FileVisitResult.CONTINUE;
}
});
}
private byte[] _readFileRegion(FileRegion fileRegion) throws IOException {
try (UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
new UnsyncByteArrayOutputStream();
WritableByteChannel writableByteChannel =
Channels.newChannel(unsyncByteArrayOutputStream)) {
while (fileRegion.transfered() < fileRegion.count()) {
fileRegion.transferTo(
writableByteChannel, fileRegion.transfered());
}
return unsyncByteArrayOutputStream.toByteArray();
}
finally {
fileRegion.release();
}
}
private byte[] _readInputStream(InputStream inputStream)
throws IOException {
try (UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
new UnsyncByteArrayOutputStream()) {
StreamUtil.transfer(
inputStream, unsyncByteArrayOutputStream, false);
return unsyncByteArrayOutputStream.toByteArray();
}
}
private final EmbeddedChannel _embeddedChannel = new EmbeddedChannel(
new FileRequestChannelHandler(CompressionLevel.NO_COMPRESSION));
}