/*
* Copyright 2014 NAVER Corp.
*
* Licensed 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 com.navercorp.pinpoint.rpc.server;
import com.navercorp.pinpoint.common.util.StringUtils;
import com.navercorp.pinpoint.rpc.ChannelWriteFailListenableFuture;
import com.navercorp.pinpoint.rpc.Future;
import com.navercorp.pinpoint.rpc.ResponseMessage;
import com.navercorp.pinpoint.rpc.client.RequestManager;
import com.navercorp.pinpoint.rpc.client.WriteFailFutureListener;
import com.navercorp.pinpoint.rpc.cluster.ClusterOption;
import com.navercorp.pinpoint.rpc.cluster.Role;
import com.navercorp.pinpoint.rpc.common.CyclicStateChecker;
import com.navercorp.pinpoint.rpc.common.SocketStateChangeResult;
import com.navercorp.pinpoint.rpc.common.SocketStateCode;
import com.navercorp.pinpoint.rpc.control.ProtocolException;
import com.navercorp.pinpoint.rpc.packet.*;
import com.navercorp.pinpoint.rpc.packet.stream.StreamPacket;
import com.navercorp.pinpoint.rpc.server.handler.DoNothingChannelStateEventHandler;
import com.navercorp.pinpoint.rpc.server.handler.ServerStateChangeEventHandler;
import com.navercorp.pinpoint.rpc.stream.*;
import com.navercorp.pinpoint.rpc.util.AssertUtils;
import com.navercorp.pinpoint.rpc.util.ClassUtils;
import com.navercorp.pinpoint.rpc.util.ControlMessageEncodingUtils;
import com.navercorp.pinpoint.rpc.util.IDGenerator;
import com.navercorp.pinpoint.rpc.util.ListUtils;
import com.navercorp.pinpoint.rpc.util.MapUtils;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.SocketAddress;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author Taejin Koo
*/
public class DefaultPinpointServer implements PinpointServer {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final Channel channel;
private final RequestManager requestManager;
private final DefaultPinpointServerState state;
private final CyclicStateChecker stateChecker;
private final ServerMessageListener messageListener;
private final List<ServerStateChangeEventHandler> stateChangeEventListeners;
private final StreamChannelManager streamChannelManager;
private final AtomicReference<Map<Object, Object>> properties = new AtomicReference<Map<Object, Object>>();
private final String objectUniqName;
private final ClusterOption localClusterOption;
private ClusterOption remoteClusterOption;
private final ChannelFutureListener serverCloseWriteListener;
private final ChannelFutureListener responseWriteFailListener;
private final WriteFailFutureListener pongWriteFutureListener = new WriteFailFutureListener(logger, "pong write fail.", "pong write success.");
public DefaultPinpointServer(Channel channel, PinpointServerConfig serverConfig) {
this(channel, serverConfig, null);
}
public DefaultPinpointServer(Channel channel, PinpointServerConfig serverConfig, ServerStateChangeEventHandler... stateChangeEventListeners) {
this.channel = channel;
this.messageListener = serverConfig.getMessageListener();
StreamChannelManager streamChannelManager = new StreamChannelManager(channel, IDGenerator.createEvenIdGenerator(), serverConfig.getStreamMessageListener());
this.streamChannelManager = streamChannelManager;
this.stateChangeEventListeners = new ArrayList<ServerStateChangeEventHandler>();
List<ServerStateChangeEventHandler> configuredStateChangeEventHandlers = serverConfig.getStateChangeEventHandlers();
if (configuredStateChangeEventHandlers != null) {
for (ServerStateChangeEventHandler configuredStateChangeEventHandler : configuredStateChangeEventHandlers) {
ListUtils.addIfValueNotNull(this.stateChangeEventListeners, configuredStateChangeEventHandler);
}
}
ListUtils.addAllExceptNullValue(this.stateChangeEventListeners, stateChangeEventListeners);
if (this.stateChangeEventListeners.isEmpty()) {
this.stateChangeEventListeners.add(DoNothingChannelStateEventHandler.INSTANCE);
}
RequestManager requestManager = new RequestManager(serverConfig.getRequestManagerTimer(), serverConfig.getDefaultRequestTimeout());
this.requestManager = requestManager;
this.objectUniqName = ClassUtils.simpleClassNameAndHashCodeString(this);
this.serverCloseWriteListener = new WriteFailFutureListener(logger, objectUniqName + " sendClosePacket() write fail.", "serverClosePacket write success");
this.responseWriteFailListener = new WriteFailFutureListener(logger, objectUniqName + " response() write fail.");
this.state = new DefaultPinpointServerState(this, this.stateChangeEventListeners);
this.stateChecker = new CyclicStateChecker(5);
this.localClusterOption = serverConfig.getClusterOption();
}
public void start() {
logger.info("{} start() started. channel:{}.", objectUniqName, channel);
state.toConnected();
state.toRunWithoutHandshake();
logger.info("{} start() completed.", objectUniqName);
}
public void stop() {
logger.info("{} stop() started. channel:{}.", objectUniqName, channel);
stop(false);
logger.info("{} stop() completed.", objectUniqName);
}
public void stop(boolean serverStop) {
try {
SocketStateCode currentStateCode = getCurrentStateCode();
if (SocketStateCode.BEING_CLOSE_BY_SERVER == currentStateCode) {
state.toClosed();
} else if (SocketStateCode.BEING_CLOSE_BY_CLIENT == currentStateCode) {
state.toClosedByPeer();
} else if (SocketStateCode.isRun(currentStateCode) && serverStop) {
state.toUnexpectedClosed();
} else if (SocketStateCode.isRun(currentStateCode)) {
state.toUnexpectedClosedByPeer();
} else if (SocketStateCode.isClosed(currentStateCode)) {
logger.warn("{} stop(). Socket has closed state({}).", objectUniqName, currentStateCode);
} else {
state.toErrorUnknown();
logger.warn("{} stop(). Socket has unexpected state.", objectUniqName, currentStateCode);
}
if (this.channel.isConnected()) {
channel.close();
}
} finally {
streamChannelManager.close();
}
}
@Override
public void send(byte[] payload) {
AssertUtils.assertNotNull(payload, "payload may not be null.");
if (!isEnableDuplexCommunication()) {
throw new IllegalStateException("Send fail. Error: Illegal State. pinpointServer:" + toString());
}
SendPacket send = new SendPacket(payload);
write0(send);
}
@Override
public Future<ResponseMessage> request(byte[] payload) {
AssertUtils.assertNotNull(payload, "payload may not be null.");
if (!isEnableDuplexCommunication()) {
throw new IllegalStateException("Request fail. Error: Illegal State. pinpointServer:" + toString());
}
RequestPacket requestPacket = new RequestPacket(payload);
ChannelWriteFailListenableFuture<ResponseMessage> messageFuture = this.requestManager.register(requestPacket);
write0(requestPacket, messageFuture);
return messageFuture;
}
@Override
public void response(RequestPacket requestPacket, byte[] payload) {
response(requestPacket.getRequestId(), payload);
}
@Override
public void response(int requestId, byte[] payload) {
AssertUtils.assertNotNull(payload, "payload may not be null.");
if (!isEnableCommunication()) {
throw new IllegalStateException("Response fail. Error: Illegal State. pinpointServer:" + toString());
}
ResponsePacket responsePacket = new ResponsePacket(requestId, payload);
write0(responsePacket, responseWriteFailListener);
}
private ChannelFuture write0(Object message) {
return write0(message, null);
}
private ChannelFuture write0(Object message, ChannelFutureListener futureListener) {
ChannelFuture future = channel.write(message);
if (futureListener != null) {
future.addListener(futureListener);
}
return future;
}
public StreamChannelContext getStreamChannel(int channelId) {
return streamChannelManager.findStreamChannel(channelId);
}
@Override
public ClientStreamChannelContext openStream(byte[] payload, ClientStreamChannelMessageListener messageListener) {
return openStream(payload, messageListener, null);
}
@Override
public ClientStreamChannelContext openStream(byte[] payload, ClientStreamChannelMessageListener messageListener, StreamChannelStateChangeEventHandler<ClientStreamChannel> stateChangeListener) {
logger.info("{} createStream() started.", objectUniqName);
ClientStreamChannelContext streamChannel = streamChannelManager.openStream(payload, messageListener, stateChangeListener);
logger.info("{} createStream() completed.", objectUniqName);
return streamChannel;
}
public void closeAllStreamChannel() {
logger.info("{} closeAllStreamChannel() started.", objectUniqName);
streamChannelManager.close();
logger.info("{} closeAllStreamChannel() completed.", objectUniqName);
}
@Override
public Map<Object, Object> getChannelProperties() {
Map<Object, Object> properties = this.properties.get();
return properties == null ? Collections.emptyMap() : properties;
}
public boolean setChannelProperties(Map<Object, Object> value) {
if (value == null) {
return false;
}
return this.properties.compareAndSet(null, Collections.unmodifiableMap(value));
}
@Override
public SocketAddress getRemoteAddress() {
return channel.getRemoteAddress();
}
public ChannelFuture sendClosePacket() {
logger.info("{} sendClosePacket() started.", objectUniqName);
SocketStateChangeResult stateChangeResult = state.toBeingClose();
if (stateChangeResult.isChange()) {
final ChannelFuture writeFuture = this.channel.write(ServerClosePacket.DEFAULT_SERVER_CLOSE_PACKET);
writeFuture.addListener(serverCloseWriteListener);
logger.info("{} sendClosePacket() completed.", objectUniqName);
return writeFuture;
} else {
logger.info("{} sendClosePacket() failed. Error:{}.", objectUniqName, stateChangeResult);
return null;
}
}
@Override
public void messageReceived(Object message) {
if (!isEnableCommunication()) {
// FIXME need change rules.
// as-is : do nothing when state is not run.
// candidate : close channel when state is not run.
logger.warn("{} messageReceived() failed. Error: Illegal state this message({}) will be ignore.", objectUniqName, message);
return;
}
final short packetType = getPacketType(message);
switch (packetType) {
case PacketType.APPLICATION_SEND: {
handleSend((SendPacket) message);
return;
}
case PacketType.APPLICATION_REQUEST: {
handleRequest((RequestPacket) message);
return;
}
case PacketType.APPLICATION_RESPONSE: {
handleResponse((ResponsePacket) message);
return;
}
case PacketType.APPLICATION_STREAM_CREATE:
case PacketType.APPLICATION_STREAM_CLOSE:
case PacketType.APPLICATION_STREAM_CREATE_SUCCESS:
case PacketType.APPLICATION_STREAM_CREATE_FAIL:
case PacketType.APPLICATION_STREAM_RESPONSE:
case PacketType.APPLICATION_STREAM_PING:
case PacketType.APPLICATION_STREAM_PONG:
handleStreamEvent((StreamPacket) message);
return;
case PacketType.CONTROL_HANDSHAKE:
handleHandshake((ControlHandshakePacket) message);
return;
case PacketType.CONTROL_CLIENT_CLOSE: {
handleClosePacket(channel);
return;
}
case PacketType.CONTROL_PING: {
handlePingPacket(channel, (PingPacket) message);
return;
}
default: {
logger.warn("invalid messageReceived msg:{}, connection:{}", message, channel);
}
}
}
private short getPacketType(Object packet) {
if (packet == null) {
return PacketType.UNKNOWN;
}
if (packet instanceof Packet) {
return ((Packet) packet).getPacketType();
}
return PacketType.UNKNOWN;
}
private void handleSend(SendPacket sendPacket) {
messageListener.handleSend(sendPacket, this);
}
private void handleRequest(RequestPacket requestPacket) {
messageListener.handleRequest(requestPacket, this);
}
private void handleResponse(ResponsePacket responsePacket) {
this.requestManager.messageReceived(responsePacket, this);
}
private void handleStreamEvent(StreamPacket streamPacket) {
streamChannelManager.messageReceived(streamPacket);
}
private void handleHandshake(ControlHandshakePacket handshakepacket) {
logger.info("{} handleHandshake() started. Packet:{}", objectUniqName, handshakepacket);
int requestId = handshakepacket.getRequestId();
Map<Object, Object> handshakeData = decodeHandshakePacket(handshakepacket);
HandshakeResponseCode responseCode = messageListener.handleHandshake(handshakeData);
boolean isFirst = setChannelProperties(handshakeData);
if (isFirst) {
if (HandshakeResponseCode.DUPLEX_COMMUNICATION == responseCode) {
this.remoteClusterOption = getClusterOption(handshakeData);
state.toRunDuplex();
} else if (HandshakeResponseCode.SIMPLEX_COMMUNICATION == responseCode || HandshakeResponseCode.SUCCESS == responseCode) {
state.toRunSimplex();
}
}
logger.info("{} handleHandshake(). ResponseCode:{}", objectUniqName, responseCode);
Map<String, Object> responseData = createHandshakeResponse(responseCode, isFirst);
sendHandshakeResponse0(requestId, responseData);
logger.info("{} handleHandshake() completed.", objectUniqName);
}
private ClusterOption getClusterOption(Map handshakeResponse) {
if (handshakeResponse == Collections.EMPTY_MAP) {
return ClusterOption.DISABLE_CLUSTER_OPTION;
}
Map cluster = (Map) handshakeResponse.get(ControlHandshakeResponsePacket.CLUSTER);
if (cluster == null) {
return ClusterOption.DISABLE_CLUSTER_OPTION;
}
String id = MapUtils.getString(cluster, "id", "");
List<Role> roles = getRoles((List) cluster.get("roles"));
if (StringUtils.isEmpty(id)) {
return ClusterOption.DISABLE_CLUSTER_OPTION;
} else {
return new ClusterOption(true, id, roles);
}
}
private List<Role> getRoles(List roleNames) {
List<Role> roles = new ArrayList<Role>();
for (Object roleName : roleNames) {
if (roleName instanceof String && StringUtils.isNotEmpty((String) roleName)) {
roles.add(Role.getValue((String) roleName));
}
}
return roles;
}
private void handleClosePacket(Channel channel) {
logger.info("{} handleClosePacket() started.", objectUniqName);
SocketStateChangeResult stateChangeResult = state.toBeingCloseByPeer();
if (!stateChangeResult.isChange()) {
logger.info("{} handleClosePacket() failed. Error: {}", objectUniqName, stateChangeResult);
} else {
logger.info("{} handleClosePacket() completed.", objectUniqName);
}
}
private void handlePingPacket(Channel channel, PingPacket packet) {
logger.debug("{} handlePingPacket() started. packet:{}", objectUniqName, packet);
SocketStateCode statusCode = state.getCurrentStateCode();
if (statusCode.getId() == packet.getStateCode()) {
stateChecker.unmark();
messageListener.handlePing(packet, this);
PongPacket pongPacket = PongPacket.PONG_PACKET;
ChannelFuture write = channel.write(pongPacket);
write.addListener(pongWriteFutureListener);
} else {
logger.warn("Session state sync failed. channel:{}, packet:{}, server-state:{}", channel, packet, statusCode);
if (stateChecker.markAndCheckCondition()) {
state.toErrorSyncStateSession();
stop();
}
}
}
private Map<String, Object> createHandshakeResponse(HandshakeResponseCode responseCode, boolean isFirst) {
HandshakeResponseCode createdCode = null;
if (isFirst) {
createdCode = responseCode;
} else {
if (HandshakeResponseCode.DUPLEX_COMMUNICATION == responseCode) {
createdCode = HandshakeResponseCode.ALREADY_DUPLEX_COMMUNICATION;
} else if (HandshakeResponseCode.SIMPLEX_COMMUNICATION == responseCode) {
createdCode = HandshakeResponseCode.ALREADY_SIMPLEX_COMMUNICATION;
} else {
createdCode = responseCode;
}
}
Map<String, Object> result = new HashMap<String, Object>();
result.put(ControlHandshakeResponsePacket.CODE, createdCode.getCode());
result.put(ControlHandshakeResponsePacket.SUB_CODE, createdCode.getSubCode());
if (localClusterOption.isEnable()) {
result.put(ControlHandshakeResponsePacket.CLUSTER, localClusterOption.getProperties());
}
return result;
}
private void sendHandshakeResponse0(int requestId, Map<String, Object> data) {
try {
byte[] resultPayload = ControlMessageEncodingUtils.encode(data);
ControlHandshakeResponsePacket packet = new ControlHandshakeResponsePacket(requestId, resultPayload);
channel.write(packet);
} catch (ProtocolException e) {
logger.warn(e.getMessage(), e);
}
}
private Map<Object, Object> decodeHandshakePacket(ControlHandshakePacket message) {
try {
byte[] payload = message.getPayload();
Map<Object, Object> properties = (Map) ControlMessageEncodingUtils.decode(payload);
return properties;
} catch (ProtocolException e) {
logger.warn(e.getMessage(), e);
}
return Collections.EMPTY_MAP;
}
public boolean isEnableCommunication() {
return state.isEnableCommunication();
}
public boolean isEnableDuplexCommunication() {
return state.isEnableDuplexCommunication();
}
String getObjectUniqName() {
return objectUniqName;
}
@Override
public ClusterOption getLocalClusterOption() {
return localClusterOption;
}
@Override
public ClusterOption getRemoteClusterOption() {
return remoteClusterOption;
}
@Override
public SocketStateCode getCurrentStateCode() {
return state.getCurrentStateCode();
}
@Override
public void close() {
stop();
}
@Override
public String toString() {
StringBuilder log = new StringBuilder(32);
log.append(objectUniqName);
log.append("(");
log.append("remote:");
log.append(getRemoteAddress());
log.append(", state:");
log.append(getCurrentStateCode());
log.append(")");
return log.toString();
}
}