/* * 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.stream; import com.navercorp.pinpoint.rpc.PinpointSocketException; import com.navercorp.pinpoint.rpc.packet.PacketType; import com.navercorp.pinpoint.rpc.packet.stream.*; import com.navercorp.pinpoint.rpc.util.AssertUtils; import com.navercorp.pinpoint.rpc.util.IDGenerator; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * @author koo.taejin */ public class StreamChannelManager { private static final LoggingStreamChannelStateChangeEventHandler LOGGING_STATE_CHANGE_HANDLER = new LoggingStreamChannelStateChangeEventHandler(); private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final Channel channel; private final IDGenerator idGenerator; private final ServerStreamChannelMessageListener streamChannelMessageListener; private final ConcurrentMap<Integer, StreamChannelContext> channelMap = new ConcurrentHashMap<Integer, StreamChannelContext>(); public StreamChannelManager(Channel channel, IDGenerator idGenerator) { this(channel, idGenerator, DisabledServerStreamChannelMessageListener.INSTANCE); } public StreamChannelManager(Channel channel, IDGenerator idGenerator, ServerStreamChannelMessageListener serverStreamChannelMessageListener) { AssertUtils.assertNotNull(channel, "Channel may not be null."); AssertUtils.assertNotNull(idGenerator, "IDGenerator may not be null."); AssertUtils.assertNotNull(serverStreamChannelMessageListener, "ServerStreamChannelMessageListener may not be null."); this.channel = channel; this.idGenerator = idGenerator; this.streamChannelMessageListener = serverStreamChannelMessageListener; } public void close() { Set<Integer> keySet = channelMap.keySet(); for (Integer key : keySet) { clearResourceAndSendClose(key, StreamCode.STATE_CLOSED); } } public ClientStreamChannelContext openStream(byte[] payload, ClientStreamChannelMessageListener messageListener) { return openStream(payload, messageListener, LOGGING_STATE_CHANGE_HANDLER); } public ClientStreamChannelContext openStream(byte[] payload, ClientStreamChannelMessageListener messageListener, StreamChannelStateChangeEventHandler<ClientStreamChannel> stateChangeListener) { logger.info("Open streamChannel initialization started. Channel:{} ", channel); final int streamChannelId = idGenerator.generate(); ClientStreamChannel newStreamChannel = new ClientStreamChannel(channel, streamChannelId, this); if (stateChangeListener != null) { newStreamChannel.addStateChangeEventHandler(stateChangeListener); } else { newStreamChannel.addStateChangeEventHandler(LOGGING_STATE_CHANGE_HANDLER); } newStreamChannel.changeStateOpen(); ClientStreamChannelContext newStreamChannelContext = new ClientStreamChannelContext(newStreamChannel, messageListener); StreamChannelContext old = channelMap.put(streamChannelId, newStreamChannelContext); if (old != null) { throw new PinpointSocketException("already streamChannelId exist:" + streamChannelId + " streamChannel:" + old); } // the order of below code is very important. newStreamChannel.changeStateConnectAwait(); newStreamChannel.sendCreate(payload); newStreamChannel.awaitOpen(3000); if (newStreamChannel.checkState(StreamChannelStateCode.CONNECTED)) { logger.info("Open streamChannel initialization completed. Channel:{}, StreamChannelContext:{} ", channel, newStreamChannelContext); } else { newStreamChannel.changeStateClose(); channelMap.remove(streamChannelId); newStreamChannelContext.setCreateFailPacket(new StreamCreateFailPacket(streamChannelId, StreamCode.CONNECTION_TIMEOUT)); } return newStreamChannelContext; } public void messageReceived(StreamPacket packet) { final int streamChannelId = packet.getStreamChannelId(); final short packetType = packet.getPacketType(); logger.debug("StreamChannel message received. (Channel:{}, StreamId:{}, Packet:{}).", channel, streamChannelId, packet); if (PacketType.APPLICATION_STREAM_CREATE == packetType) { handleCreate((StreamCreatePacket) packet); return; } StreamChannelContext context = findStreamChannel(streamChannelId); if (context == null) { if (!(PacketType.APPLICATION_STREAM_CLOSE == packetType)) { clearResourceAndSendClose(streamChannelId, StreamCode.ID_NOT_FOUND); } } else { if (isServerStreamChannelContext(context)) { messageReceived((ServerStreamChannelContext) context, packet); } else if (isClientStreamChannelContext(context)) { messageReceived((ClientStreamChannelContext) context, packet); } else { clearResourceAndSendClose(streamChannelId, StreamCode.UNKNWON_ERROR); } } } private void messageReceived(ServerStreamChannelContext context, StreamPacket packet) { final short packetType = packet.getPacketType(); final int streamChannelId = packet.getStreamChannelId(); switch (packetType) { case PacketType.APPLICATION_STREAM_CLOSE: handleStreamClose(context, (StreamClosePacket)packet); break; case PacketType.APPLICATION_STREAM_PING: handlePing(context, (StreamPingPacket) packet); break; case PacketType.APPLICATION_STREAM_PONG: // handlePong((StreamPongPacket) packet); break; default: clearResourceAndSendClose(streamChannelId, StreamCode.PACKET_UNKNOWN); logger.info("Unknown StreamPacket received Channel:{}, StreamId:{}, Packet;{}.", channel, streamChannelId, packet); } } private void messageReceived(ClientStreamChannelContext context, StreamPacket packet) { final short packetType = packet.getPacketType(); final int streamChannelId = packet.getStreamChannelId(); switch (packetType) { case PacketType.APPLICATION_STREAM_CREATE_SUCCESS: handleCreateSuccess(context, (StreamCreateSuccessPacket) packet); break; case PacketType.APPLICATION_STREAM_CREATE_FAIL: handleCreateFail(context, (StreamCreateFailPacket) packet); break; case PacketType.APPLICATION_STREAM_RESPONSE: handleStreamResponse(context, (StreamResponsePacket) packet); break; case PacketType.APPLICATION_STREAM_CLOSE: handleStreamClose(context, (StreamClosePacket) packet); break; case PacketType.APPLICATION_STREAM_PING: handlePing(context, (StreamPingPacket) packet); break; case PacketType.APPLICATION_STREAM_PONG: // handlePong((StreamPongPacket) packet); break; default: clearResourceAndSendClose(streamChannelId, StreamCode.PACKET_UNKNOWN); logger.info("Unknown StreamPacket received Channel:{}, StreamId:{}, Packet;{}.", channel, streamChannelId, packet); } } private void handleCreate(StreamCreatePacket packet) { final int streamChannelId = packet.getStreamChannelId(); StreamCode code = StreamCode.OK; ServerStreamChannel streamChannel = new ServerStreamChannel(this.channel, streamChannelId, this); ServerStreamChannelContext streamChannelContext = new ServerStreamChannelContext(streamChannel); code = registerStreamChannel(streamChannelContext); if (code == StreamCode.OK) { code = streamChannelMessageListener.handleStreamCreate(streamChannelContext, packet); if (code == StreamCode.OK) { streamChannel.changeStateConnected(); streamChannel.sendCreateSuccess(); } } if (code != StreamCode.OK) { clearResourceAndSendCreateFail(streamChannelId, code); } } private StreamCode registerStreamChannel(ServerStreamChannelContext streamChannelContext) { int streamChannelId = streamChannelContext.getStreamId(); ServerStreamChannel streamChannel = streamChannelContext.getStreamChannel(); streamChannel.changeStateOpen(); if (channelMap.putIfAbsent(streamChannelId, streamChannelContext) != null) { streamChannel.changeStateClose(); return StreamCode.ID_DUPLICATED; } if (!streamChannel.changeStateConnectArrived()) { streamChannel.changeStateClose(); channelMap.remove(streamChannelId); return StreamCode.STATE_ERROR; } return StreamCode.OK; } private void handleCreateSuccess(ClientStreamChannelContext streamChannelContext, StreamCreateSuccessPacket packet) { StreamChannel streamChannel = streamChannelContext.getStreamChannel(); streamChannel.changeStateConnected(); } private void handleCreateFail(ClientStreamChannelContext streamChannelContext, StreamCreateFailPacket packet) { streamChannelContext.setCreateFailPacket(packet); clearStreamChannelResource(streamChannelContext.getStreamId()); } private void handleStreamResponse(ClientStreamChannelContext context, StreamResponsePacket packet) { int streamChannelId = packet.getStreamChannelId(); StreamChannel streamChannel = context.getStreamChannel(); StreamChannelStateCode currentCode = streamChannel.getCurrentState(); if (StreamChannelStateCode.CONNECTED == currentCode) { context.getClientStreamChannelMessageListener().handleStreamData(context, packet); } else if (StreamChannelStateCode.CONNECT_AWAIT == currentCode) { // may happen in the timing } else { clearResourceAndSendClose(streamChannelId, StreamCode.STATE_NOT_CONNECTED); } } private void handleStreamClose(ClientStreamChannelContext context, StreamClosePacket packet) { context.getClientStreamChannelMessageListener().handleStreamClose(context, packet); clearStreamChannelResource(context.getStreamId()); } private void handleStreamClose(ServerStreamChannelContext context, StreamClosePacket packet) { streamChannelMessageListener.handleStreamClose(context, packet); clearStreamChannelResource(context.getStreamId()); } private void handlePing(StreamChannelContext streamChannelContext, StreamPingPacket packet) { int streamChannelId = packet.getStreamChannelId(); StreamChannel streamChannel = streamChannelContext.getStreamChannel(); if (!streamChannel.checkState(StreamChannelStateCode.CONNECTED)) { clearResourceAndSendClose(streamChannelId, StreamCode.STATE_NOT_CONNECTED); return; } streamChannel.sendPong(packet.getRequestId()); } public StreamChannelContext findStreamChannel(int channelId) { StreamChannelContext streamChannelContext = this.channelMap.get(channelId); return streamChannelContext; } private ChannelFuture clearResourceAndSendCreateFail(int streamChannelId, StreamCode code) { clearStreamChannelResource(streamChannelId); return sendCreateFail(streamChannelId, code); } protected ChannelFuture clearResourceAndSendClose(int streamChannelId, StreamCode code) { clearStreamChannelResource(streamChannelId); return sendClose(streamChannelId, code); } private void clearStreamChannelResource(int streamId) { StreamChannelContext streamChannelContext = channelMap.remove(streamId); if (streamChannelContext != null) { streamChannelContext.getStreamChannel().changeStateClose(); } } private ChannelFuture sendCreateFail(int streamChannelId, StreamCode code) { StreamCreateFailPacket packet = new StreamCreateFailPacket(streamChannelId, code); return this.channel.write(packet); } private ChannelFuture sendClose(int streamChannelId, StreamCode code) { if (channel.isConnected()) { StreamClosePacket packet = new StreamClosePacket(streamChannelId, code); return this.channel.write(packet); } else { return null; } } private boolean isServerStreamChannelContext(StreamChannelContext context) { if (context == null || !(context instanceof ServerStreamChannelContext)) { return false; } return true; } private boolean isClientStreamChannelContext(StreamChannelContext context) { if (context == null || !(context instanceof ClientStreamChannelContext)) { return false; } return true; } public boolean isSupportServerMode() { return streamChannelMessageListener != DisabledServerStreamChannelMessageListener.INSTANCE; } }