/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.greencheek.elasticacheconfig.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import org.greencheek.elasticacheconfig.annotations.ConfigMessage;
import org.greencheek.elasticacheconfig.annotations.DelayConfigResponse;
import org.greencheek.elasticacheconfig.annotations.SendAllMessages;
import org.junit.rules.ExternalResource;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;
/**
* Echoes back any received data from a client.
*/
public final class StringServer implements TestRule {
private final String[] message;
private final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
private final EventLoopGroup workerGroup = new NioEventLoopGroup();
private volatile int port;
private final TimeUnit startDelayUnit;
private final long startDelay;
public StringServer(String msg) {
this(msg,-1,TimeUnit.SECONDS);
}
public StringServer(String msg, long startDelay, TimeUnit startDelayUnit) {
this.message = new String[]{msg};
this.startDelay = startDelay;
this.startDelayUnit = startDelayUnit;
}
public StringServer(String[] msg, long startDelay, TimeUnit startDelayUnit) {
this.message = msg;
this.startDelay = startDelay;
this.startDelayUnit = startDelayUnit;
}
public int getPort() {
return port;
}
/**
* Override to set up your specific external resource.
*
* @throws if setup fails (which will disable {@code after}
*/
public void before(final String[] message,
final TimeUnit delayUnit,
final long delay,
boolean sendAllMessages) throws Throwable {
final ServerSocket socket = findFreePort();
final ChannelHandler sharedHandler = new StringBasedServerHandler(message,delayUnit,delay,sendAllMessages);
// do nothing
try {
final ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.option(ChannelOption.SO_SNDBUF, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new ConfigGetClusterDecoder());
p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(sharedHandler);
}
});
// Start the server.
if(startDelay>0) {
port = getPortNoClose(socket);
workerGroup.schedule(new Runnable() {
@Override
public void run() {
try {
port = getPort(socket);
b.bind(port).sync();
outputStartedMessage();
} catch (InterruptedException e) {
}
}
},startDelay,startDelayUnit);
} else {
port = getPort(socket);
b.bind(port).sync();
outputStartedMessage();
}
} finally {
}
}
private void outputStartedMessage() {
System.out.println("===========");
System.out.println("ElastiCache Config Server started on port: " + port);
System.out.println("===========");
System.out.flush();
}
private void outputShutdownMessage() {
System.out.println("===========");
System.out.println("ElastiCache Config Server shutdown on port: " + port);
System.out.println("===========");
System.out.flush();
}
/**
* Override to tear down your specific external resource.
*/
public void after() {
// do nothing
// Shut down all event loops to terminate all threads.
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
outputShutdownMessage();
}
private ServerSocket findFreePort() throws IOException {
ServerSocket server = new ServerSocket(0,1, InetAddress.getLoopbackAddress());
server.setReuseAddress(true);
return server;
}
private int getPortNoClose(ServerSocket server) {
return server.getLocalPort();
}
private int getPort(ServerSocket server) {
int port = server.getLocalPort();
try {
server.close();
} catch (IOException e) {
}
return port;
}
public Statement apply(final Statement base, final Description description) {
final ConfigMessage message = description.getAnnotation(ConfigMessage.class);
final DelayConfigResponse delayBy = description.getAnnotation(DelayConfigResponse.class);
final SendAllMessages sendAll = description.getAnnotation(SendAllMessages.class);
final boolean sendAllMessages = sendAll == null ? false : true;
final TimeUnit delayUnit;
final long delayedFor;
final String[] messageToSend;
if(message == null) {
messageToSend = this.message;
} else {
String[] msg = message.message();
if (msg == null || msg.length == 0) {
messageToSend = this.message;
} else {
messageToSend = msg;
}
}
if(delayBy == null) {
delayUnit = TimeUnit.SECONDS;
delayedFor = -1;
} else {
delayUnit = delayBy.delayedForTimeUnit();
delayedFor = delayBy.delayFor();
}
return new Statement() {
@Override
public void evaluate() throws Throwable {
before(messageToSend,delayUnit,delayedFor,sendAllMessages);
try {
base.evaluate();
} finally {
after();
}
}
};
}
}