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