blob: d324a620155b506d02d88cbd123e287b905383be [file] [log] [blame]
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* 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.intellij.compiler.server;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.containers.ConcurrentHashSet;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.io.SimpleChannelInboundHandlerAdapter;
import org.jetbrains.jps.api.CmdlineProtoUtil;
import org.jetbrains.jps.api.CmdlineRemoteProto;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Eugene Zhuravlev
* Date: 4/25/12
*/
@ChannelHandler.Sharable
class BuildMessageDispatcher extends SimpleChannelInboundHandlerAdapter<CmdlineRemoteProto.Message> {
private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.server.BuildMessageDispatcher");
private static final AttributeKey<SessionData> SESSION_DATA = AttributeKey.valueOf("BuildMessageDispatcher.sessionData");
private final Map<UUID, SessionData> myMessageHandlers = new ConcurrentHashMap<UUID, SessionData>(16, 0.75f, 1);
private final Set<UUID> myCanceledSessions = new ConcurrentHashSet<UUID>();
public void registerBuildMessageHandler(UUID sessionId,
BuilderMessageHandler handler,
CmdlineRemoteProto.Message.ControllerMessage params) {
myMessageHandlers.put(sessionId, new SessionData(sessionId, handler, params));
}
@Nullable
public BuilderMessageHandler unregisterBuildMessageHandler(UUID sessionId) {
myCanceledSessions.remove(sessionId);
final SessionData data = myMessageHandlers.remove(sessionId);
return data != null? data.handler : null;
}
public void cancelSession(UUID sessionId) {
if (myCanceledSessions.add(sessionId)) {
final Channel channel = getConnectedChannel(sessionId);
if (channel != null) {
channel.writeAndFlush(CmdlineProtoUtil.toMessage(sessionId, CmdlineProtoUtil.createCancelCommand()));
}
}
}
@Nullable
public Channel getConnectedChannel(final UUID sessionId) {
final Channel channel = getAssociatedChannel(sessionId);
return channel != null && channel.isActive()? channel : null;
}
@Nullable
public Channel getAssociatedChannel(final UUID sessionId) {
final SessionData data = myMessageHandlers.get(sessionId);
return data != null? data.channel : null;
}
@Override
protected void messageReceived(ChannelHandlerContext context, CmdlineRemoteProto.Message message) throws Exception {
SessionData sessionData = context.attr(SESSION_DATA).get();
UUID sessionId;
if (sessionData == null) {
// this is the first message for this session, so fill session data with missing info
final CmdlineRemoteProto.Message.UUID id = message.getSessionId();
sessionId = new UUID(id.getMostSigBits(), id.getLeastSigBits());
sessionData = myMessageHandlers.get(sessionId);
if (sessionData != null) {
sessionData.channel = context.channel();
context.attr(SESSION_DATA).set(sessionData);
}
if (myCanceledSessions.contains(sessionId)) {
context.channel().writeAndFlush(CmdlineProtoUtil.toMessage(sessionId, CmdlineProtoUtil.createCancelCommand()));
}
}
else {
sessionId = sessionData.sessionId;
}
final BuilderMessageHandler handler = sessionData != null? sessionData.handler : null;
if (handler == null) {
// todo
LOG.info("No message handler registered for session " + sessionId);
return;
}
final CmdlineRemoteProto.Message.Type messageType = message.getType();
switch (messageType) {
case FAILURE:
handler.handleFailure(sessionId, message.getFailure());
break;
case BUILDER_MESSAGE:
final CmdlineRemoteProto.Message.BuilderMessage builderMessage = message.getBuilderMessage();
final CmdlineRemoteProto.Message.BuilderMessage.Type msgType = builderMessage.getType();
if (msgType == CmdlineRemoteProto.Message.BuilderMessage.Type.PARAM_REQUEST) {
final CmdlineRemoteProto.Message.ControllerMessage params = sessionData.params;
if (params != null) {
handler.buildStarted(sessionId);
sessionData.params = null;
context.writeAndFlush(CmdlineProtoUtil.toMessage(sessionId, params));
}
else {
cancelSession(sessionId);
}
}
else {
handler.handleBuildMessage(context.channel(), sessionId, builderMessage);
}
break;
default:
LOG.info("Unsupported message type " + messageType);
break;
}
}
@Override
public void channelInactive(ChannelHandlerContext context) throws Exception {
try {
super.channelInactive(context);
}
finally {
final SessionData sessionData = context.attr(SESSION_DATA).get();
if (sessionData != null) {
final BuilderMessageHandler handler = unregisterBuildMessageHandler(sessionData.sessionId);
if (handler != null) {
// notify the handler only if it has not been notified yet
handler.sessionTerminated(sessionData.sessionId);
}
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext context, Throwable cause) throws Exception {
if (cause != null) {
LOG.info(cause);
}
}
private static final class SessionData {
final UUID sessionId;
final BuilderMessageHandler handler;
volatile CmdlineRemoteProto.Message.ControllerMessage params;
volatile Channel channel;
private SessionData(UUID sessionId, BuilderMessageHandler handler, CmdlineRemoteProto.Message.ControllerMessage params) {
this.sessionId = sessionId;
this.handler = handler;
this.params = params;
}
}
}