/**
* 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.NettyTestUtil;
import com.liferay.portal.fabric.netty.fileserver.CompressionLevel;
import com.liferay.portal.fabric.netty.fileserver.FileHelperUtil;
import com.liferay.portal.fabric.netty.fileserver.FileResponse;
import com.liferay.portal.kernel.concurrent.AsyncBroker;
import com.liferay.portal.kernel.concurrent.NoticeableFuture;
import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream;
import com.liferay.portal.kernel.nio.FileChannelWrapper;
import com.liferay.portal.kernel.test.CaptureHandler;
import com.liferay.portal.kernel.test.JDKLoggerTestUtil;
import com.liferay.portal.kernel.test.ReflectionTestUtil;
import com.liferay.portal.kernel.test.rule.AggregateTestRule;
import com.liferay.portal.kernel.test.rule.CodeCoverageAssertor;
import com.liferay.portal.kernel.test.rule.NewEnv;
import com.liferay.portal.kernel.util.Time;
import com.liferay.portal.test.rule.AdviseWith;
import com.liferay.portal.test.rule.AspectJNewEnvTestRule;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoop;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.EventExecutor;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.junit.After;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
/**
* @author Shuyang Zhou
*/
public class FileUploadChannelHandlerTest {
@ClassRule
@Rule
public static final AggregateTestRule aggregateTestRule =
new AggregateTestRule(
CodeCoverageAssertor.INSTANCE, AspectJNewEnvTestRule.INSTANCE);
@After
public void tearDown() {
FileServerTestUtil.cleanUp();
}
@Test
public void testConstructor() throws IOException {
FileResponse fileResponse = new FileResponse(
Paths.get("testFile"), 1, -1, false);
FileUploadChannelHandler fileUploadChannelHandler =
new FileUploadChannelHandler(
_asyncBroker, fileResponse, _embeddedChannel.eventLoop());
Assert.assertSame(_asyncBroker, fileUploadChannelHandler.asyncBroker);
Assert.assertSame(fileResponse, fileUploadChannelHandler.fileResponse);
Assert.assertSame(
_embeddedChannel.eventLoop(),
fileUploadChannelHandler.eventExecutor);
Path file = FileServerTestUtil.registerForCleanUp(
fileResponse.getLocalFile());
Assert.assertTrue(Files.isRegularFile(file));
try (FileChannel fileChannel = fileUploadChannelHandler.fileChannel) {
Assert.assertTrue(fileChannel.isOpen());
}
}
@Test
public void testConstructorParameterValidation() throws IOException {
try {
new FileUploadChannelHandler(null, null, null);
Assert.fail();
}
catch (NullPointerException npe) {
Assert.assertEquals("Async broker is null", npe.getMessage());
}
try {
new FileUploadChannelHandler(_asyncBroker, null, null);
Assert.fail();
}
catch (NullPointerException npe) {
Assert.assertEquals("File response is null", npe.getMessage());
}
FileResponse fileResponse = new FileResponse(
Paths.get("testFile"), FileResponse.FILE_NOT_FOUND, -1, false);
try {
new FileUploadChannelHandler(_asyncBroker, fileResponse, null);
Assert.fail();
}
catch (NullPointerException npe) {
Assert.assertEquals("Event executor is null", npe.getMessage());
}
try {
new FileUploadChannelHandler(
_asyncBroker, fileResponse, _embeddedChannel.eventLoop());
Assert.fail();
}
catch (IllegalArgumentException iae) {
Assert.assertEquals(
"File response has no content for uploading", iae.getMessage());
}
}
@Test
public void testFileUpload() throws Exception {
doTestFileUpload(false, false, false);
doTestFileUpload(false, false, true);
doTestFileUpload(false, true, false);
doTestFileUpload(false, true, true);
doTestFileUpload(true, false, false);
doTestFileUpload(true, false, true);
doTestFileUpload(true, true, false);
doTestFileUpload(true, true, true);
}
@AdviseWith(adviceClasses = FileHelperUtilAdvice.class)
@NewEnv(type = NewEnv.Type.CLASSLOADER)
@Test
public void testFolderUpload() throws Exception {
doTestFolderUpload(false, false, false);
doTestFolderUpload(false, false, true);
doTestFolderUpload(false, true, false);
doTestFolderUpload(false, true, true);
doTestFolderUpload(true, false, false);
doTestFolderUpload(true, false, true);
doTestFolderUpload(true, true, false);
doTestFolderUpload(true, true, true);
}
@Test
public void testReceive() throws IOException {
byte[] data = FileServerTestUtil.createRandomData(20);
FileResponse fileResponse = new FileResponse(
Paths.get("testFile"), data.length, -1, false);
FileUploadChannelHandler fileUploadChannelHandler =
new FileUploadChannelHandler(
_asyncBroker, fileResponse, _embeddedChannel.eventLoop());
FileServerTestUtil.registerForCleanUp(fileResponse.getLocalFile());
final UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
new UnsyncByteArrayOutputStream();
ReflectionTestUtil.setFieldValue(
fileUploadChannelHandler, "fileChannel",
new FileChannelWrapper(fileUploadChannelHandler.fileChannel) {
@Override
public long position() {
return unsyncByteArrayOutputStream.size();
}
@Override
public int write(ByteBuffer byteBuffer) {
unsyncByteArrayOutputStream.write(byteBuffer.get());
return 1;
}
});
ByteBuf byteBuf = FileServerTestUtil.wrapFirstHalf(data);
Assert.assertEquals(1, byteBuf.refCnt());
Assert.assertFalse(fileUploadChannelHandler.receive(byteBuf));
Assert.assertEquals(0, byteBuf.refCnt());
byteBuf = Unpooled.buffer();
byteBuf.writeBytes(FileServerTestUtil.wrapSecondHalf(data));
byteBuf.writeBytes(data);
Assert.assertEquals(1, byteBuf.refCnt());
Assert.assertTrue(fileUploadChannelHandler.receive(byteBuf));
Assert.assertEquals(1, byteBuf.refCnt());
Assert.assertArrayEquals(
data, unsyncByteArrayOutputStream.toByteArray());
Assert.assertEquals(Unpooled.wrappedBuffer(data), byteBuf);
}
@Aspect
public static class FileHelperUtilAdvice {
@Around(
"execution(public static java.nio.file.Path " +
"com.liferay.portal.fabric.netty.fileserver.FileHelperUtil." +
"unzip(java.nio.file.Path, java.nio.file.Path))"
)
public Object unzip(ProceedingJoinPoint proceedingJoinPoint)
throws Throwable {
if (_throwException) {
_throwException = false;
throw new IOException("Forced Exception");
}
return proceedingJoinPoint.proceed();
}
private static boolean _throwException;
}
protected void doTestFileUpload(
boolean inEventLoop, boolean fail, boolean postAsyncBroker)
throws Exception {
byte[] data = FileServerTestUtil.createRandomData(1024);
long lastModified = FileServerTestUtil.getFileSystemTime(
System.currentTimeMillis() - Time.DAY);
Path file = doTestUpload(
data, lastModified, false, inEventLoop, fail, postAsyncBroker);
if (!fail) {
Assert.assertArrayEquals(data, Files.readAllBytes(file));
}
}
protected void doTestFolderUpload(
boolean inEventLoop, boolean fail, boolean postAsyncBroker)
throws Exception {
Path testFolder = FileServerTestUtil.createFolderWithFiles(
Paths.get("testFolder"));
long lastModified = FileServerTestUtil.getFileSystemTime(
System.currentTimeMillis() - Time.DAY);
Files.setLastModifiedTime(
testFolder, FileTime.fromMillis(lastModified));
Path zipFile = FileHelperUtil.zip(
testFolder, FileHelperUtil.TEMP_DIR_PATH,
CompressionLevel.BEST_SPEED);
try {
Path folder = doTestUpload(
Files.readAllBytes(zipFile), lastModified, true, inEventLoop,
fail, postAsyncBroker);
if (!fail) {
FileServerTestUtil.assertFileEquals(testFolder, folder);
}
}
finally {
FileHelperUtil.delete(zipFile);
}
}
protected Path doTestUpload(
byte[] data, long lastModified, boolean folder, boolean inEventloop,
boolean fail, boolean postAsyncBroker)
throws Exception {
FileResponse fileResponse = new FileResponse(
Paths.get("testFile"), data.length, lastModified, folder);
NoticeableFuture<FileResponse> noticeableFuture = null;
if (postAsyncBroker) {
noticeableFuture = _asyncBroker.post(fileResponse.getPath());
}
final FileUploadChannelHandler fileUploadChannelHandler =
new FileUploadChannelHandler(
_asyncBroker, fileResponse, getEventLoop(inEventloop));
if (folder) {
FileServerTestUtil.registerForCleanUp(fileResponse.getLocalFile());
}
final ChannelPipeline channelPipeline = _embeddedChannel.pipeline();
channelPipeline.addFirst(
FileUploadChannelHandler.class.getName(), fileUploadChannelHandler);
if (fail) {
if (folder) {
FileHelperUtilAdvice._throwException = true;
}
else {
try (FileChannel fileChannel =
fileUploadChannelHandler.fileChannel) {
Assert.assertTrue(fileChannel.isOpen());
}
}
}
else if (inEventloop) {
channelPipeline.addAfter(
FileUploadChannelHandler.class.getName(),
"Assert Auto Removal Channel Handler",
new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(
ChannelHandlerContext channelHandlerContext,
Object object) {
Assert.assertSame(this, channelPipeline.first());
channelHandlerContext.fireChannelRead(object);
channelPipeline.removeFirst();
}
});
}
try (CaptureHandler captureHandler =
JDKLoggerTestUtil.configureJDKLogger(
FileUploadChannelHandler.class.getName(), Level.SEVERE)) {
try {
if (inEventloop) {
_embeddedChannel.writeInbound(
FileServerTestUtil.wrapFirstHalf(data),
Unpooled.copiedBuffer(
FileServerTestUtil.wrapSecondHalf(data),
Unpooled.wrappedBuffer(data)));
if (!fail) {
Queue<Object> queue =
_embeddedChannel.inboundMessages();
Assert.assertEquals(1, queue.size());
Assert.assertEquals(
Unpooled.wrappedBuffer(data), queue.poll());
}
}
else {
fileUploadChannelHandler.channelRead(
channelPipeline.firstContext(),
FileServerTestUtil.wrapFirstHalf(data));
fileUploadChannelHandler.channelRead(
channelPipeline.firstContext(),
FileServerTestUtil.wrapSecondHalf(data));
}
}
catch (Exception e) {
fileUploadChannelHandler.exceptionCaught(
channelPipeline.firstContext(), e);
}
if (postAsyncBroker) {
if (fail) {
try {
noticeableFuture.get();
Assert.fail();
}
catch (ExecutionException ee) {
Throwable throwable = ee.getCause();
if (folder) {
Assert.assertEquals(
"Forced Exception", throwable.getMessage());
}
else {
Assert.assertTrue(
throwable instanceof ClosedChannelException);
}
}
}
else {
Assert.assertSame(fileResponse, noticeableFuture.get());
}
}
shutdown(inEventloop, fileUploadChannelHandler.eventExecutor);
List<LogRecord> logRecords = captureHandler.getLogRecords();
if (fail) {
LogRecord logRecord = logRecords.remove(0);
Assert.assertEquals(
"File upload failure", logRecord.getMessage());
Throwable throwable = logRecord.getThrown();
if (folder) {
Assert.assertEquals(
"Forced Exception", throwable.getMessage());
}
else {
Assert.assertTrue(
throwable instanceof ClosedChannelException);
}
}
if (!postAsyncBroker) {
LogRecord logRecord = logRecords.remove(0);
if (fail) {
Assert.assertEquals(
"Unable to place exception because no future exists " +
"with ID " + fileResponse.getPath(),
logRecord.getMessage());
Throwable throwable = logRecord.getThrown();
if (folder) {
Assert.assertEquals(
"Forced Exception", throwable.getMessage());
}
else {
Assert.assertTrue(
throwable instanceof ClosedChannelException);
}
}
else {
Assert.assertEquals(
"Unable to place result " + fileResponse +
" because no future exists with ID " +
fileResponse.getPath(),
logRecord.getMessage());
}
}
Assert.assertTrue(logRecords.isEmpty());
Assert.assertSame(channelPipeline.first(), channelPipeline.last());
}
Path file = FileServerTestUtil.registerForCleanUp(
fileResponse.getLocalFile());
if (!fail) {
FileTime fileTime = Files.getLastModifiedTime(file);
Assert.assertEquals(lastModified, fileTime.toMillis());
}
return file;
}
protected EventLoop getEventLoop(boolean inEventloop) {
if (inEventloop) {
return _embeddedChannel.eventLoop();
}
NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup(1);
return nioEventLoopGroup.next();
}
protected void shutdown(boolean inEventloop, EventExecutor eventExecutor)
throws Exception {
if (inEventloop) {
_embeddedChannel.runPendingTasks();
}
else {
Future<?> future = eventExecutor.shutdownGracefully();
future.get();
}
}
private final AsyncBroker<Path, FileResponse> _asyncBroker =
new AsyncBroker<>();
private final EmbeddedChannel _embeddedChannel =
NettyTestUtil.createEmptyEmbeddedChannel();
}