Continue setting up tests (compiler server).
[idea/community.git] / jps / jps-builders / src / org / jetbrains / jps / server / ServerMessageHandler.java
1 package org.jetbrains.jps.server;
2
3 import com.intellij.openapi.diagnostic.Logger;
4 import com.intellij.openapi.util.Pair;
5 import com.intellij.openapi.util.Ref;
6 import org.jboss.netty.channel.*;
7 import org.jetbrains.annotations.NotNull;
8 import org.jetbrains.annotations.Nullable;
9 import org.jetbrains.jps.api.*;
10 import org.jetbrains.jps.incremental.MessageHandler;
11 import org.jetbrains.jps.incremental.messages.*;
12
13 import java.io.ByteArrayOutputStream;
14 import java.io.File;
15 import java.io.PrintStream;
16 import java.util.*;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.ExecutorService;
19 import java.util.concurrent.RunnableFuture;
20
21 /**
22  * @author Eugene Zhuravlev
23  *         Date: 8/11/11
24  */
25 class ServerMessageHandler extends SimpleChannelHandler {
26   private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.server.ServerMessageHandler");
27
28   private final Map<String, SequentialTaskExecutor> myTaskExecutors = new HashMap<String, SequentialTaskExecutor>();
29   private final List<Pair<RunnableFuture, CompilationTask>> myBuildsInProgress = Collections.synchronizedList(new LinkedList<Pair<RunnableFuture, CompilationTask>>());
30   private final ExecutorService myBuildsExecutor;
31   private final Server myServer;
32
33   public ServerMessageHandler(ExecutorService buildsExecutor, Server server) {
34     myBuildsExecutor = buildsExecutor;
35     myServer = server;
36   }
37
38   public void messageReceived(final ChannelHandlerContext ctx, MessageEvent e) throws Exception {
39     final JpsRemoteProto.Message message = (JpsRemoteProto.Message)e.getMessage();
40     final UUID sessionId = ProtoUtil.fromProtoUUID(message.getSessionId());
41
42     JpsRemoteProto.Message reply = null;
43
44     if (message.getMessageType() != JpsRemoteProto.Message.Type.REQUEST) {
45       reply = ProtoUtil.toMessage(sessionId, ProtoUtil.createFailure("Cannot handle message " + message.toString()));
46     }
47     else if (!message.hasRequest()) {
48       reply = ProtoUtil.toMessage(sessionId, ProtoUtil.createFailure("No request in message: " + message.toString()));
49     }
50     else {
51       final JpsRemoteProto.Message.Request request = message.getRequest();
52       final JpsRemoteProto.Message.Request.Type requestType = request.getRequestType();
53       final ServerState facade = ServerState.getInstance();
54       switch (requestType) {
55         case COMPILE_REQUEST :
56           reply = startBuild(sessionId, ctx, request.getCompileRequest());
57           break;
58         case RELOAD_PROJECT_COMMAND:
59           final JpsRemoteProto.Message.Request.ReloadProjectCommand reloadProjectCommand = request.getReloadProjectCommand();
60           facade.clearProjectCache(reloadProjectCommand.getProjectIdList());
61           reply = ProtoUtil.toMessage(sessionId, ProtoUtil.createCommandCompletedEvent(null));
62           break;
63         case CANCEL_BUILD_COMMAND:
64           final JpsRemoteProto.Message.Request.CancelBuildCommand cancelCommand = request.getCancelBuildCommand();
65           final UUID targetSessionId = ProtoUtil.fromProtoUUID(cancelCommand.getTargetSessionId());
66           synchronized (myBuildsInProgress) {
67             for (Iterator<Pair<RunnableFuture, CompilationTask>> it = myBuildsInProgress.iterator(); it.hasNext(); ) {
68               final Pair<RunnableFuture, CompilationTask> pair = it.next();
69               final CompilationTask task = pair.second;
70               if (task.getSessionId().equals(targetSessionId)) {
71                 it.remove();
72                 task.cancel();
73                 pair.first.cancel(true);
74                 break;
75               }
76             }
77           }
78           reply = ProtoUtil.toMessage(sessionId, ProtoUtil.createCommandCompletedEvent(null));
79           break;
80         case SETUP_COMMAND:
81           final Map<String, String> pathVars = new HashMap<String, String>();
82           final JpsRemoteProto.Message.Request.SetupCommand setupCommand = request.getSetupCommand();
83           for (JpsRemoteProto.Message.Request.SetupCommand.PathVariable variable : setupCommand.getPathVariableList()) {
84             pathVars.put(variable.getName(), variable.getValue());
85           }
86           final List<GlobalLibrary> libs = new ArrayList<GlobalLibrary>();
87           for (JpsRemoteProto.Message.Request.SetupCommand.GlobalLibrary library : setupCommand.getGlobalLibraryList()) {
88             libs.add(
89               library.hasHomePath()?
90               new SdkLibrary(library.getName(), library.getHomePath(), library.getPathList()) :
91               new GlobalLibrary(library.getName(), library.getPathList())
92             );
93           }
94           final String globalEncoding = setupCommand.isInitialized()? setupCommand.getGlobalEncoding() : null;
95           facade.setGlobals(libs, pathVars, globalEncoding);
96           reply = ProtoUtil.toMessage(sessionId, ProtoUtil.createCommandCompletedEvent(null));
97           break;
98
99         case SHUTDOWN_COMMAND :
100           // todo pay attention to policy
101           myBuildsExecutor.submit(new Runnable() {
102             public void run() {
103               final List<RunnableFuture> futures = new ArrayList<RunnableFuture>();
104
105               synchronized (myBuildsInProgress) {
106                 for (Iterator<Pair<RunnableFuture, CompilationTask>> it = myBuildsInProgress.iterator(); it.hasNext(); ) {
107                   final Pair<RunnableFuture, CompilationTask> pair = it.next();
108                   it.remove();
109                   pair.second.cancel();
110                   final RunnableFuture future = pair.first;
111                   futures.add(future);
112                   future.cancel(true);
113                 }
114               }
115
116               facade.clearCahedState();
117
118               // wait until really stopped
119               for (RunnableFuture future : futures) {
120                 try {
121                   future.get();
122                 }
123                 catch (InterruptedException ignored) {
124                 }
125                 catch (ExecutionException ignored) {
126                 }
127               }
128
129               myServer.stop();
130             }
131           });
132           break;
133         case FS_EVENT:
134           final JpsRemoteProto.Message.Request.FSEvent fsEvent = request.getFsEvent();
135           final String projectId = fsEvent.getProjectId();
136           final ProjectDescriptor pd = facade.getProjectDescriptor(projectId);
137           if (pd != null) {
138             try {
139               for (String path : fsEvent.getChangedPathsList()) {
140                 facade.notifyFileChanged(pd, new File(path));
141               }
142               for (String path : fsEvent.getDeletedPathsList()) {
143                 facade.notifyFileDeleted(pd, new File(path));
144               }
145             }
146             finally {
147               pd.release();
148             }
149           }
150           reply = ProtoUtil.toMessage(sessionId, ProtoUtil.createCommandCompletedEvent(null));
151           break;
152         default:
153           reply = ProtoUtil.toMessage(sessionId, ProtoUtil.createFailure("Unknown request: " + message));
154       }
155     }
156     if (reply != null) {
157       Channels.write(ctx.getChannel(), reply);
158     }
159   }
160
161   @Nullable
162   private JpsRemoteProto.Message startBuild(UUID sessionId, final ChannelHandlerContext channelContext, JpsRemoteProto.Message.Request.CompilationRequest compileRequest) {
163     if (!compileRequest.hasProjectId()) {
164       return ProtoUtil.toMessage(sessionId, ProtoUtil.createFailure("No project specified"));
165     }
166
167     final String projectId = compileRequest.getProjectId();
168     final JpsRemoteProto.Message.Request.CompilationRequest.Type compileType = compileRequest.getCommandType();
169
170     switch (compileType) {
171       // todo
172       case MAKE:
173       case FORCED_COMPILATION:
174       case REBUILD: {
175         final BuildType buildType = convertCompileType(compileType);
176         final CompilationTask task = new CompilationTask(
177           sessionId, channelContext, projectId, buildType, compileRequest.getModuleNameList(), compileRequest.getFilePathList()
178         );
179         final RunnableFuture future = getCompileTaskExecutor(projectId).submit(task);
180         myBuildsInProgress.add(new Pair<RunnableFuture, CompilationTask>(future, task));
181         return null;
182       }
183
184       default:
185         return ProtoUtil.toMessage(sessionId, ProtoUtil.createFailure("Unsupported command: '" + compileType + "'"));
186     }
187   }
188
189   @NotNull
190   private SequentialTaskExecutor getCompileTaskExecutor(String projectId) {
191     synchronized (myTaskExecutors) {
192       SequentialTaskExecutor executor = myTaskExecutors.get(projectId);
193       if (executor == null) {
194         executor = new SequentialTaskExecutor(new SequentialTaskExecutor.AsyncTaskExecutor() {
195           @Override
196           public void submit(Runnable runnable) {
197             myBuildsExecutor.submit(runnable);
198           }
199         });
200         myTaskExecutors.put(projectId, executor);
201       }
202       return executor;
203     }
204   }
205
206   public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
207     if (this == ctx.getPipeline().getLast()) {
208       LOG.error(e);
209     }
210     ctx.sendUpstream(e);
211   }
212
213   private class CompilationTask implements Runnable, CanceledStatus {
214
215     private final UUID mySessionId;
216     private final ChannelHandlerContext myChannelContext;
217     private final String myProjectPath;
218     private final BuildType myBuildType;
219     private final Collection<String> myPaths;
220     private final Set<String> myModules;
221     private volatile boolean myCanceled = false;
222
223     public CompilationTask(UUID sessionId,
224                            ChannelHandlerContext channelContext,
225                            String projectId,
226                            BuildType buildType,
227                            Collection<String> modules,
228                            Collection<String> paths) {
229       mySessionId = sessionId;
230       myChannelContext = channelContext;
231       myProjectPath = projectId;
232       myBuildType = buildType;
233       myPaths = paths;
234       myModules = new HashSet<String>(modules);
235     }
236
237     public UUID getSessionId() {
238       return mySessionId;
239     }
240
241     public boolean isCanceled() {
242       return myCanceled;
243     }
244
245     public void run() {
246       Channels.write(myChannelContext.getChannel(), ProtoUtil.toMessage(mySessionId, ProtoUtil.createBuildStartedEvent("build started")));
247       Throwable error = null;
248       final Ref<Boolean> hasErrors = new Ref<Boolean>(false);
249       final Ref<Boolean> markedFilesUptodate = new Ref<Boolean>(false);
250       try {
251         ServerState.getInstance().startBuild(myProjectPath, myBuildType, myModules, myPaths, new MessageHandler() {
252           public void processMessage(BuildMessage buildMessage) {
253             final JpsRemoteProto.Message.Response response;
254             if (buildMessage instanceof FileGeneratedEvent) {
255               final Collection<Pair<String, String>> paths = ((FileGeneratedEvent)buildMessage).getPaths();
256               response = !paths.isEmpty()? ProtoUtil.createFileGeneratedEvent(paths) : null;
257             }
258             else if (buildMessage instanceof UptoDateFilesSavedEvent) {
259               markedFilesUptodate.set(true);
260               response = null;
261             }
262             else if (buildMessage instanceof CompilerMessage) {
263               markedFilesUptodate.set(true);
264               final CompilerMessage compilerMessage = (CompilerMessage)buildMessage;
265               final String text = compilerMessage.getCompilerName() + ": " + compilerMessage.getMessageText();
266               final BuildMessage.Kind kind = compilerMessage.getKind();
267               if (kind == BuildMessage.Kind.ERROR) {
268                 hasErrors.set(true);
269               }
270               response = ProtoUtil.createCompileMessageResponse(
271                 kind, text, compilerMessage.getSourcePath(),
272                 compilerMessage.getProblemBeginOffset(), compilerMessage.getProblemEndOffset(),
273                 compilerMessage.getProblemLocationOffset(), compilerMessage.getLine(), compilerMessage.getColumn(),
274                 -1.0f);
275             }
276             else {
277               float done = -1.0f;
278               if (buildMessage instanceof ProgressMessage) {
279                 done = ((ProgressMessage)buildMessage).getDone();
280               }
281               response = ProtoUtil.createCompileProgressMessageResponse(buildMessage.getMessageText(), done);
282             }
283             if (response != null) {
284               Channels.write(myChannelContext.getChannel(), ProtoUtil.toMessage(mySessionId, response));
285             }
286           }
287         }, this);
288       }
289       catch (Throwable e) {
290         error = e;
291       }
292       finally {
293         finishBuild(error, hasErrors.get(), markedFilesUptodate.get());
294       }
295     }
296
297     private void finishBuild(@Nullable Throwable error, boolean hadBuildErrors, boolean markedUptodateFiles) {
298       JpsRemoteProto.Message lastMessage = null;
299       try {
300         if (error != null) {
301           Throwable cause = error.getCause();
302           if (cause == null) {
303             cause = error;
304           }
305           final ByteArrayOutputStream out = new ByteArrayOutputStream();
306           cause.printStackTrace(new PrintStream(out));
307
308           final StringBuilder messageText = new StringBuilder();
309           messageText.append("JPS Internal error: (").append(cause.getClass().getName()).append(") ").append(cause.getMessage());
310           final String trace = out.toString();
311           if (!trace.isEmpty()) {
312             messageText.append("\n").append(trace);
313           }
314           lastMessage = ProtoUtil.toMessage(mySessionId, ProtoUtil.createFailure(messageText.toString(), cause));
315         }
316         else {
317           JpsRemoteProto.Message.Response.BuildEvent.Status status = JpsRemoteProto.Message.Response.BuildEvent.Status.SUCCESS;
318           if (myCanceled) {
319             status = JpsRemoteProto.Message.Response.BuildEvent.Status.CANCELED;
320           }
321           else if (hadBuildErrors) {
322             status = JpsRemoteProto.Message.Response.BuildEvent.Status.ERRORS;
323           }
324           else if (!markedUptodateFiles){
325             status = JpsRemoteProto.Message.Response.BuildEvent.Status.UP_TO_DATE;
326           }
327           lastMessage = ProtoUtil.toMessage(mySessionId, ProtoUtil.createBuildCompletedEvent("build completed", status));
328         }
329       }
330       catch (Throwable e) {
331         lastMessage = ProtoUtil.toMessage(mySessionId, ProtoUtil.createFailure(e.getMessage(), e));
332       }
333       finally {
334         Channels.write(myChannelContext.getChannel(), lastMessage).addListener(new ChannelFutureListener() {
335           public void operationComplete(ChannelFuture future) throws Exception {
336             final UUID sessionId = getSessionId();
337             synchronized (myBuildsInProgress) {
338               for (Iterator<Pair<RunnableFuture, CompilationTask>> it = myBuildsInProgress.iterator(); it.hasNext(); ) {
339                 final CompilationTask task = it.next().second;
340                 if (sessionId.equals(task.getSessionId())) {
341                   it.remove();
342                   break;
343                 }
344               }
345             }
346           }
347         });
348       }
349     }
350
351     public void cancel() {
352       myCanceled = true;
353     }
354   }
355
356   private static BuildType convertCompileType(JpsRemoteProto.Message.Request.CompilationRequest.Type compileType) {
357     switch (compileType) {
358       case CLEAN: return BuildType.CLEAN;
359       case MAKE: return BuildType.MAKE;
360       case REBUILD: return BuildType.PROJECT_REBUILD;
361       case FORCED_COMPILATION: return BuildType.FORCED_COMPILATION;
362     }
363     return BuildType.MAKE; // use make by default
364   }
365 }