096a113cf7f9b33dabbd19dc5b7a9d2cb0f2fed8
[idea/community.git] / java / compiler / impl / src / com / intellij / compiler / server / BuildMessageDispatcher.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.compiler.server;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.util.containers.ContainerUtil;
20 import io.netty.channel.Channel;
21 import io.netty.channel.ChannelHandler;
22 import io.netty.channel.ChannelHandlerContext;
23 import io.netty.util.AttributeKey;
24 import org.jetbrains.annotations.NotNull;
25 import org.jetbrains.annotations.Nullable;
26 import org.jetbrains.io.SimpleChannelInboundHandlerAdapter;
27 import org.jetbrains.jps.api.CmdlineProtoUtil;
28 import org.jetbrains.jps.api.CmdlineRemoteProto;
29 import org.jetbrains.jps.api.RequestFuture;
30
31 import java.util.Map;
32 import java.util.Set;
33 import java.util.UUID;
34 import java.util.concurrent.ConcurrentHashMap;
35
36 /**
37 * @author Eugene Zhuravlev
38 *         Date: 4/25/12
39 */
40 @ChannelHandler.Sharable
41 class BuildMessageDispatcher extends SimpleChannelInboundHandlerAdapter<CmdlineRemoteProto.Message> {
42   private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.server.BuildMessageDispatcher");
43
44   private static final AttributeKey<SessionData> SESSION_DATA = AttributeKey.valueOf("BuildMessageDispatcher.sessionData");
45
46   private final Map<UUID, SessionData> mySessionDescriptors = new ConcurrentHashMap<UUID, SessionData>(16, 0.75f, 1);
47   private final Set<UUID> myCanceledSessions = ContainerUtil.newConcurrentSet();
48
49   public void registerBuildMessageHandler(@NotNull final RequestFuture<? extends BuilderMessageHandler> future, @Nullable CmdlineRemoteProto.Message.ControllerMessage params) {
50     final BuilderMessageHandler wrappedHandler = new DelegatingMessageHandler() {
51       @Override
52       protected BuilderMessageHandler getDelegateHandler() {
53         return future.getMessageHandler();
54       }
55       
56       @Override
57       public void sessionTerminated(UUID sessionId) {
58         try {
59           super.sessionTerminated(sessionId);
60         }
61         finally {
62           future.setDone();
63         }
64       }
65     };
66     final UUID sessionId = future.getRequestID();
67     mySessionDescriptors.put(sessionId, new SessionData(sessionId, wrappedHandler, params));
68   }
69
70   @Nullable
71   public BuilderMessageHandler unregisterBuildMessageHandler(UUID sessionId) {
72     myCanceledSessions.remove(sessionId);
73     final SessionData data = mySessionDescriptors.remove(sessionId);
74     return data != null? data.handler : null;
75   }
76
77   public void cancelSession(UUID sessionId) {
78     if (myCanceledSessions.add(sessionId)) {
79       final Channel channel = getConnectedChannel(sessionId);
80       if (channel != null) {
81         channel.writeAndFlush(CmdlineProtoUtil.toMessage(sessionId, CmdlineProtoUtil.createCancelCommand()));
82       }
83     }
84   }
85
86   @Nullable
87   public Channel getConnectedChannel(final UUID sessionId) {
88     final Channel channel = getAssociatedChannel(sessionId);
89     return channel != null && channel.isActive()? channel : null;
90   }
91
92   @Nullable
93   public Channel getAssociatedChannel(final UUID sessionId) {
94     final SessionData data = mySessionDescriptors.get(sessionId);
95     return data != null? data.channel : null;
96   }
97
98   public boolean sendBuildParameters(@NotNull final UUID preloadedSessionId, @NotNull CmdlineRemoteProto.Message.ControllerMessage params) {
99     boolean succeeded = false;
100     final SessionData sessionData = mySessionDescriptors.get(preloadedSessionId);
101     if (sessionData != null) {
102       //noinspection SynchronizationOnLocalVariableOrMethodParameter
103       synchronized (sessionData) {
104         if (sessionData.state == SessionData.State.WAITING_PARAMS) {
105           sessionData.state = SessionData.State.RUNNING;
106           final Channel channel = sessionData.channel;
107           if (channel != null && channel.isActive()) {
108             sessionData.handler.buildStarted(preloadedSessionId);
109             channel.writeAndFlush(CmdlineProtoUtil.toMessage(preloadedSessionId, params));
110             succeeded = true;
111           }
112         }
113         else {
114           if (sessionData.state == SessionData.State.INITIAL) {
115             sessionData.params = params;
116             succeeded = true;
117           }
118         }
119       }
120     }
121     return succeeded;
122   }
123   
124   @Override
125   protected void messageReceived(ChannelHandlerContext context, CmdlineRemoteProto.Message message) throws Exception {
126     SessionData sessionData = context.attr(SESSION_DATA).get();
127
128     UUID sessionId;
129     if (sessionData == null) {
130       // this is the first message for this session, so fill session data with missing info
131       final CmdlineRemoteProto.Message.UUID id = message.getSessionId();
132       sessionId = new UUID(id.getMostSigBits(), id.getLeastSigBits());
133
134       sessionData = mySessionDescriptors.get(sessionId);
135       if (sessionData != null) {
136         sessionData.channel = context.channel();
137         context.attr(SESSION_DATA).set(sessionData);
138       }
139       if (myCanceledSessions.contains(sessionId)) {
140         context.channel().writeAndFlush(CmdlineProtoUtil.toMessage(sessionId, CmdlineProtoUtil.createCancelCommand()));
141       }
142     }
143     else {
144       sessionId = sessionData.sessionId;
145     }
146
147     final BuilderMessageHandler handler = sessionData != null? sessionData.handler : null;
148     if (handler == null) {
149       // todo
150       LOG.info("No message handler registered for session " + sessionId);
151       return;
152     }
153
154     final CmdlineRemoteProto.Message.Type messageType = message.getType();
155     switch (messageType) {
156       case FAILURE:
157         handler.handleFailure(sessionId, message.getFailure());
158         break;
159
160       case BUILDER_MESSAGE:
161         final CmdlineRemoteProto.Message.BuilderMessage builderMessage = message.getBuilderMessage();
162         final CmdlineRemoteProto.Message.BuilderMessage.Type msgType = builderMessage.getType();
163         if (msgType == CmdlineRemoteProto.Message.BuilderMessage.Type.PARAM_REQUEST) {
164           //noinspection SynchronizationOnLocalVariableOrMethodParameter
165           synchronized (sessionData) {
166             final CmdlineRemoteProto.Message.ControllerMessage params = sessionData.params;
167             if (params != null) {
168               sessionData.state = SessionData.State.RUNNING;
169               handler.buildStarted(sessionId);
170               sessionData.params = null;
171               context.writeAndFlush(CmdlineProtoUtil.toMessage(sessionId, params));
172             }
173             else {
174               if (sessionData.state == SessionData.State.INITIAL) {
175                 sessionData.state = SessionData.State.WAITING_PARAMS;
176               }
177               else {
178                 // this message is expected to be sent only once.
179                 // To be on the safe side, cancel the session
180                 cancelSession(sessionId);
181               }
182             }
183           }
184         }
185         else {
186           handler.handleBuildMessage(context.channel(), sessionId, builderMessage);
187         }
188         break;
189
190       default:
191         LOG.info("Unsupported message type " + messageType);
192         break;
193     }
194   }
195
196   @Override
197   public void channelInactive(ChannelHandlerContext context) throws Exception {
198     try {
199       super.channelInactive(context);
200     }
201     finally {
202       final SessionData sessionData = context.attr(SESSION_DATA).get();
203       if (sessionData != null) {
204         final BuilderMessageHandler handler = unregisterBuildMessageHandler(sessionData.sessionId);
205         if (handler != null) {
206           // notify the handler only if it has not been notified yet
207           handler.sessionTerminated(sessionData.sessionId);
208         }
209       }
210     }
211   }
212
213   @Override
214   public void exceptionCaught(ChannelHandlerContext context, Throwable cause) throws Exception {
215     if (cause != null) {
216       LOG.info(cause);
217     }
218   }
219
220   private static final class SessionData {
221     enum State {
222       INITIAL, WAITING_PARAMS, RUNNING
223     }
224     
225     @NotNull
226     final UUID sessionId;
227     @NotNull
228     final BuilderMessageHandler handler;
229     volatile CmdlineRemoteProto.Message.ControllerMessage params;
230     volatile Channel channel;
231     State state = State.INITIAL;
232
233     private SessionData(@NotNull UUID sessionId, @NotNull BuilderMessageHandler handler, CmdlineRemoteProto.Message.ControllerMessage params) {
234       this.sessionId = sessionId;
235       this.handler = handler;
236       this.params = params;
237     }
238   }
239 }