/**
* 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;
import com.liferay.portal.fabric.netty.codec.serialization.AnnotatedObjectDecoder;
import com.liferay.portal.fabric.netty.codec.serialization.AnnotatedObjectEncoder;
import com.liferay.portal.fabric.netty.fileserver.handlers.FileRequestChannelHandler;
import com.liferay.portal.fabric.netty.fileserver.handlers.FileResponseChannelHandler;
import com.liferay.portal.fabric.netty.fileserver.handlers.FileServerTestUtil;
import com.liferay.portal.kernel.concurrent.AsyncBroker;
import com.liferay.portal.kernel.util.NamedThreadFactory;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.EventExecutorGroup;
import java.net.BindException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* @author Shuyang Zhou
*/
public class FileServerTest {
@Before
public void setUp() throws Exception {
_port = _startServer();
_connectClient();
}
@After
public void tearDown() throws Exception {
_disconnectClient();
_stopServer();
FileServerTestUtil.cleanUp();
}
@Test
public void testFileTransfer() throws Exception {
doTestFileTransfer(true);
doTestFileTransfer(false);
}
@Test
public void testFileTransferNotFound() throws Exception {
_sourceFilePath = FileServerTestUtil.createNotExistFile(
Paths.get("testFile"));
Future<FileResponse> future = _asyncBroker.post(_sourceFilePath);
_clientChannel.writeAndFlush(
new FileRequest(_sourceFilePath, 0, false));
FileResponse fileResponse = future.get(_TIME_OUT, TimeUnit.MINUTES);
Assert.assertTrue(fileResponse.isFileNotFound());
}
@Test
public void testFileTransferNotModified() throws Exception {
_sourceFilePath = FileServerTestUtil.createFileWithData(
Paths.get("testMissingFile"));
Future<FileResponse> future = _asyncBroker.post(_sourceFilePath);
FileTime fileTime = Files.getLastModifiedTime(_sourceFilePath);
_clientChannel.writeAndFlush(
new FileRequest(_sourceFilePath, fileTime.toMillis(), false));
FileResponse fileResponse = future.get(_TIME_OUT, TimeUnit.MINUTES);
Assert.assertTrue(fileResponse.isFileNotModified());
}
@Test
public void testFolderTransfer() throws Exception {
doTestFolderTransfer(true);
doTestFolderTransfer(false);
}
protected void doTestFileTransfer(boolean deleteAfterFetch)
throws Exception {
_sourceFilePath = FileServerTestUtil.createFileWithData(
Paths.get("testFile"));
FileTime sourceFileTime = Files.getLastModifiedTime(_sourceFilePath);
byte[] data = Files.readAllBytes(_sourceFilePath);
Future<FileResponse> future = _asyncBroker.post(_sourceFilePath);
_clientChannel.writeAndFlush(
new FileRequest(_sourceFilePath, 0, deleteAfterFetch));
FileResponse fileResponse = future.get(_TIME_OUT, TimeUnit.MINUTES);
_destFile = fileResponse.getLocalFile();
Assert.assertTrue(Files.exists(_destFile));
if (deleteAfterFetch) {
Assert.assertTrue(Files.notExists(_sourceFilePath));
}
FileTime destFileTime = Files.getLastModifiedTime(_destFile);
Assert.assertEquals(sourceFileTime.toMillis(), destFileTime.toMillis());
Assert.assertArrayEquals(data, Files.readAllBytes(_destFile));
}
protected void doTestFolderTransfer(boolean deleteAfterFetch)
throws Exception {
_sourceFilePath = FileServerTestUtil.createFolderWithFiles(
Paths.get("testFolder"));
FileTime sourceFileTime = Files.getLastModifiedTime(_sourceFilePath);
Future<FileResponse> future = _asyncBroker.post(_sourceFilePath);
_clientChannel.writeAndFlush(
new FileRequest(_sourceFilePath, 0, deleteAfterFetch));
FileResponse fileResponse = future.get(_TIME_OUT, TimeUnit.MINUTES);
_destFile = fileResponse.getLocalFile();
Assert.assertTrue(Files.exists(_destFile));
if (deleteAfterFetch) {
Assert.assertTrue(Files.notExists(_sourceFilePath));
}
FileTime destFileTime = Files.getLastModifiedTime(_destFile);
Assert.assertEquals(sourceFileTime.toMillis(), destFileTime.toMillis());
if (!deleteAfterFetch) {
FileServerTestUtil.assertFileEquals(_sourceFilePath, _destFile);
}
}
private void _connectClient() throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(_nioEventLoopGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(
new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
ChannelPipeline channelPipeline = socketChannel.pipeline();
channelPipeline.addLast(
AnnotatedObjectEncoder.NAME,
AnnotatedObjectEncoder.INSTANCE);
channelPipeline.addLast(new AnnotatedObjectDecoder());
channelPipeline.addLast(
new FileResponseChannelHandler(
_asyncBroker, _fileServerEventExecutorGroup));
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", _port);
channelFuture.sync();
_clientChannel = channelFuture.channel();
}
private void _disconnectClient() throws InterruptedException {
ChannelFuture channelFuture = _clientChannel.close();
channelFuture.sync();
}
private int _startServer() throws Exception {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(_nioEventLoopGroup);
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childHandler(
new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
ChannelPipeline channelPipeline = socketChannel.pipeline();
channelPipeline.addLast(
AnnotatedObjectEncoder.NAME,
AnnotatedObjectEncoder.INSTANCE);
channelPipeline.addLast(new AnnotatedObjectDecoder());
channelPipeline.addLast(
_fileServerEventExecutorGroup,
FileRequestChannelHandler.NAME,
new FileRequestChannelHandler(
CompressionLevel.BEST_SPEED));
}
});
int port = _START_PORT;
while (port < 65536) {
try {
ChannelFuture channelFuture = serverBootstrap.bind(
"127.0.0.1", port);
channelFuture.sync();
_serverChannel = channelFuture.channel();
return port;
}
catch (Exception e) {
if (!(e instanceof BindException)) {
throw e;
}
System.err.println(
"Unable to bind to " + (port++) + ", trying " + port);
}
}
throw new IllegalStateException("Unable to start server");
}
private void _stopServer() throws InterruptedException {
try {
ChannelFuture channelFuture = _serverChannel.close();
channelFuture.sync();
}
finally {
_nioEventLoopGroup.shutdownGracefully();
_fileServerEventExecutorGroup.shutdownGracefully();
}
}
private static final int _START_PORT = 12758;
private static final long _TIME_OUT = 10;
private final AsyncBroker<Path, FileResponse> _asyncBroker =
new AsyncBroker<>();
private Channel _clientChannel;
private Path _destFile;
private final EventExecutorGroup _fileServerEventExecutorGroup =
new NioEventLoopGroup(
1,
new NamedThreadFactory(
"FileServer-EventLoop", Thread.MAX_PRIORITY,
FileServerTest.class.getClassLoader()));
private final EventLoopGroup _nioEventLoopGroup = new NioEventLoopGroup(
1,
new NamedThreadFactory(
"IO-EventLoop", Thread.MAX_PRIORITY,
FileServerTest.class.getClassLoader()));
private int _port;
private Channel _serverChannel;
private Path _sourceFilePath;
}