1a5b54d85e475e69b151cbb9edb57eb149965172
[idea/community.git] / plugins / maven / src / main / java / org / jetbrains / idea / maven / server / MavenServerConnector.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package org.jetbrains.idea.maven.server;
3
4 import com.intellij.openapi.Disposable;
5 import com.intellij.openapi.project.Project;
6 import com.intellij.openapi.projectRoots.Sdk;
7 import com.intellij.openapi.util.text.StringUtil;
8 import com.intellij.openapi.vfs.CharsetToolkit;
9 import com.intellij.openapi.vfs.LocalFileSystem;
10 import com.intellij.openapi.vfs.VirtualFile;
11 import com.intellij.util.containers.ContainerUtil;
12 import org.jetbrains.annotations.NotNull;
13 import org.jetbrains.annotations.Nullable;
14 import org.jetbrains.idea.maven.buildtool.MavenSyncConsole;
15 import org.jetbrains.idea.maven.execution.SyncBundle;
16 import org.jetbrains.idea.maven.model.MavenExplicitProfiles;
17 import org.jetbrains.idea.maven.model.MavenModel;
18 import org.jetbrains.idea.maven.project.MavenProjectsManager;
19 import org.jetbrains.idea.maven.project.MavenWorkspaceSettings;
20 import org.jetbrains.idea.maven.utils.MavenLog;
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.rmi.RemoteException;
25 import java.rmi.server.UnicastRemoteObject;
26 import java.util.Collection;
27 import java.util.List;
28
29 public class MavenServerConnector implements @NotNull Disposable {
30
31
32   private final RemoteMavenServerLogger myLogger = new RemoteMavenServerLogger();
33   private final RemoteMavenServerDownloadListener
34     myDownloadListener = new RemoteMavenServerDownloadListener();
35
36   private final Project myProject;
37   private final MavenServerManager myManager;
38   private final Integer myDebugPort;
39
40   private boolean myLoggerExported;
41   private boolean myDownloadListenerExported;
42   private final Sdk myJdk;
43   private final MavenDistribution myDistribution;
44   private final String myVmOptions;
45
46   private MavenServerRemoteProcessSupport mySupport;
47   private MavenServer myMavenServer;
48
49
50   public MavenServerConnector(@NotNull Project project,
51                               @NotNull MavenServerManager manager,
52                               @NotNull MavenWorkspaceSettings settings,
53                               @NotNull Sdk jdk,
54                               @Nullable Integer debugPort) {
55
56     myProject = project;
57     myManager = manager;
58     myDebugPort = debugPort;
59     myDistribution = findMavenDistribution(project, settings);
60     settings.generalSettings.setMavenHome(myDistribution.getMavenHome().getAbsolutePath());
61     myVmOptions = readVmOptions(project, settings);
62     myJdk = jdk;
63     connect();
64   }
65
66   public MavenServerConnector(@NotNull Project project,
67                               @NotNull MavenServerManager manager,
68                               @NotNull MavenWorkspaceSettings settings,
69                               @NotNull Sdk jdk) {
70     this(project, manager, settings, jdk, null);
71   }
72
73   public boolean isSettingsStillValid(MavenWorkspaceSettings settings) {
74     String baseDir = myProject.getBasePath();
75     if (baseDir == null) { //for default projects and unit tests backward-compatibility
76       return true;
77     }
78     String distributionUrl = MavenWrapperSupport.getWrapperDistributionUrl(LocalFileSystem.getInstance().findFileByPath(baseDir));
79     if (distributionUrl != null && !distributionUrl.equals(myDistribution.getName())) { //new maven url in maven-wrapper.properties
80       return false;
81     }
82     String newVmOptions = readVmOptions(myProject, settings);
83     return StringUtil.equals(newVmOptions, myVmOptions);
84   }
85
86   private static String readVmOptions(Project project, MavenWorkspaceSettings settings) {
87
88     VirtualFile baseDir = project.getBaseDir();
89     if (baseDir == null) return settings.importingSettings.getVmOptionsForImporter();
90     VirtualFile mvn = baseDir.findChild(".mvn");
91     if (mvn == null) return settings.importingSettings.getVmOptionsForImporter();
92     VirtualFile jdkOpts = mvn.findChild("jvm.config");
93     if (jdkOpts == null) return settings.importingSettings.getVmOptionsForImporter();
94     try {
95       return new String(jdkOpts.contentsToByteArray(true), CharsetToolkit.UTF8_CHARSET);
96     }
97     catch (IOException e) {
98       MavenLog.LOG.warn(e);
99       return settings.importingSettings.getVmOptionsForImporter();
100     }
101   }
102
103
104   private static @Nullable String getWrapperDistributionUrl(Project project) {
105     VirtualFile baseDir = project.getBaseDir();
106     if (baseDir == null) {
107       return null;
108     }
109     return MavenWrapperSupport.getWrapperDistributionUrl(baseDir);
110   }
111
112   private static MavenDistribution findMavenDistribution(Project project, MavenWorkspaceSettings settings) {
113     MavenSyncConsole console = MavenProjectsManager.getInstance(project).getSyncConsole();
114     String distributionUrl = getWrapperDistributionUrl(project);
115     if (distributionUrl == null) {
116       MavenDistribution distribution = new MavenDistributionConverter().fromString(settings.generalSettings.getMavenHome());
117       if (distribution == null) {
118         console.addWarning(SyncBundle.message("cannot.resolve.maven.home"), SyncBundle
119           .message("is.not.correct.maven.home.reverting.to.embedded", settings.generalSettings.getMavenHome()));
120         return MavenServerManager.resolveEmbeddedMavenHome();
121       }
122       return distribution;
123     }
124     else {
125       try {
126
127         console.startWrapperResolving();
128         MavenDistribution distribution = new MavenWrapperSupport().downloadAndInstallMaven(distributionUrl);
129         console.finishWrapperResolving(null);
130         return distribution;
131       }
132       catch (RuntimeException | IOException e) {
133         MavenLog.LOG.info(e);
134         console.finishWrapperResolving(e);
135         return MavenServerManager.resolveEmbeddedMavenHome();
136       }
137     }
138   }
139
140   private void connect() {
141     if (mySupport != null || myMavenServer != null) {
142       throw new IllegalStateException("Already connected");
143     }
144     try {
145       if (myDebugPort != null) {
146         //simple connection using JavaDebuggerConsoleFilterProvider
147         //noinspection UseOfSystemOutOrSystemErr
148         System.out.println("Listening for transport dt_socket at address: " + myDebugPort);
149       }
150
151       mySupport = new MavenServerRemoteProcessSupport(myJdk, myVmOptions, myDistribution, myProject, myDebugPort);
152       myMavenServer = mySupport.acquire(this, "");
153       myLoggerExported = MavenRemoteObjectWrapper.doWrapAndExport(myLogger) != null;
154       if (!myLoggerExported) throw new RemoteException("Cannot export logger object");
155
156       myDownloadListenerExported = MavenRemoteObjectWrapper.doWrapAndExport(myDownloadListener) != null;
157       if (!myDownloadListenerExported) throw new RemoteException("Cannot export download listener object");
158
159       myMavenServer.set(myLogger, myDownloadListener, MavenRemoteObjectWrapper.ourToken);
160     }
161     catch (Exception e) {
162       throw new RuntimeException("Cannot start maven service", e);
163     }
164   }
165
166   private void cleanUp() {
167     if (myLoggerExported) {
168       try {
169         UnicastRemoteObject.unexportObject(myLogger, true);
170       }
171       catch (RemoteException e) {
172         MavenLog.LOG.warn(e);
173       }
174       myLoggerExported = false;
175     }
176     if (myDownloadListenerExported) {
177       try {
178         UnicastRemoteObject.unexportObject(myDownloadListener, true);
179       }
180       catch (RemoteException e) {
181         MavenLog.LOG.warn(e);
182       }
183       myDownloadListenerExported = false;
184     }
185     myMavenServer = null;
186     mySupport = null;
187   }
188
189   public MavenServerEmbedder createEmbedder(MavenEmbedderSettings settings) throws RemoteException {
190     return myMavenServer.createEmbedder(settings, MavenRemoteObjectWrapper.ourToken);
191   }
192
193   public MavenServerIndexer createIndexer() throws RemoteException {
194     return myMavenServer.createIndexer(MavenRemoteObjectWrapper.ourToken);
195   }
196
197
198   public void addDownloadListener(MavenServerDownloadListener listener) {
199     myDownloadListener.myListeners.add(listener);
200   }
201
202   public void removeDownloadListener(MavenServerDownloadListener listener) {
203     myDownloadListener.myListeners.remove(listener);
204   }
205
206   @NotNull
207   public MavenModel interpolateAndAlignModel(final MavenModel model, final File basedir) {
208     return perform(() -> myMavenServer.interpolateAndAlignModel(model, basedir, MavenRemoteObjectWrapper.ourToken));
209   }
210
211   public MavenModel assembleInheritance(final MavenModel model, final MavenModel parentModel) {
212     return perform(() -> myMavenServer.assembleInheritance(model, parentModel, MavenRemoteObjectWrapper.ourToken));
213   }
214
215   public ProfileApplicationResult applyProfiles(final MavenModel model,
216                                                 final File basedir,
217                                                 final MavenExplicitProfiles explicitProfiles,
218                                                 final Collection<String> alwaysOnProfiles) {
219     return perform(
220       () -> myMavenServer.applyProfiles(model, basedir, explicitProfiles, alwaysOnProfiles, MavenRemoteObjectWrapper.ourToken));
221   }
222
223   public void shutdown(boolean wait) {
224     myManager.unregisterConnector(this);
225     if (mySupport != null) {
226       mySupport.stopAll(wait);
227     }
228     cleanUp();
229   }
230
231   protected <R, E extends Exception> R perform(RemoteObjectWrapper.Retriable<R, E> r) throws E {
232     RemoteException last = null;
233     for (int i = 0; i < 2; i++) {
234       try {
235         return r.execute();
236       }
237       catch (RemoteException e) {
238         MavenServerRemoteProcessSupport processSupport = mySupport;
239         if (processSupport != null) {
240           processSupport.stopAll(false);
241         }
242         cleanUp();
243         connect();
244       }
245     }
246     throw new RuntimeException("Cannot reconnect.", last);
247   }
248
249
250   @Override
251   public void dispose() {
252     shutdown(true);
253   }
254
255   @NotNull
256   public Sdk getJdk() {
257     return myJdk;
258   }
259
260   public MavenDistribution getMavenDistribution() {
261     return myDistribution;
262   }
263
264   public String getVMOptions() {
265     return myVmOptions;
266   }
267
268
269   private static class RemoteMavenServerLogger extends MavenRemoteObject implements MavenServerLogger {
270     @Override
271     public void info(Throwable e) {
272       MavenLog.LOG.info(e);
273     }
274
275     @Override
276     public void warn(Throwable e) {
277       MavenLog.LOG.warn(e);
278     }
279
280     @Override
281     public void error(Throwable e) {
282       MavenLog.LOG.error(e);
283     }
284
285     @Override
286     public void print(String s) {
287       //noinspection UseOfSystemOutOrSystemErr
288       System.out.println(s);
289     }
290   }
291
292   private static class RemoteMavenServerDownloadListener extends MavenRemoteObject implements MavenServerDownloadListener {
293     private final List<MavenServerDownloadListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
294
295     @Override
296     public void artifactDownloaded(File file, String relativePath) throws RemoteException {
297       for (MavenServerDownloadListener each : myListeners) {
298         each.artifactDownloaded(file, relativePath);
299       }
300     }
301   }
302 }