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;
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;
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;
30 public class MavenServerConnector implements @NotNull Disposable {
33 private final RemoteMavenServerLogger myLogger = new RemoteMavenServerLogger();
34 private final RemoteMavenServerDownloadListener
35 myDownloadListener = new RemoteMavenServerDownloadListener();
37 private final Project myProject;
38 private final MavenServerManager myManager;
39 private final Integer myDebugPort;
41 private boolean myLoggerExported;
42 private boolean myDownloadListenerExported;
43 private final Sdk myJdk;
44 private final MavenDistribution myDistribution;
45 private final String myVmOptions;
47 private MavenServerRemoteProcessSupport mySupport;
48 private MavenServer myMavenServer;
51 public MavenServerConnector(@NotNull Project project,
52 @NotNull MavenServerManager manager,
53 @NotNull MavenWorkspaceSettings settings,
55 @Nullable Integer debugPort) {
59 myDebugPort = debugPort;
60 myDistribution = findMavenDistribution(project, settings);
61 settings.generalSettings.setMavenHome(myDistribution.getMavenHome().getAbsolutePath());
62 myVmOptions = readVmOptions(project, settings);
67 public MavenServerConnector(@NotNull Project project,
68 @NotNull MavenServerManager manager,
69 @NotNull MavenWorkspaceSettings settings,
71 this(project, manager, settings, jdk, null);
74 public boolean isSettingsStillValid(MavenWorkspaceSettings settings) {
75 String baseDir = myProject.getBasePath();
76 if (baseDir == null) { //for default projects and unit tests backward-compatibility
79 String distributionUrl = MavenWrapperSupport.getWrapperDistributionUrl(LocalFileSystem.getInstance().findFileByPath(baseDir));
80 if (distributionUrl != null && !distributionUrl.equals(myDistribution.getName())) { //new maven url in maven-wrapper.properties
83 String newVmOptions = readVmOptions(myProject, settings);
84 return StringUtil.equals(newVmOptions, myVmOptions);
87 private static String readVmOptions(Project project, MavenWorkspaceSettings settings) {
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();
96 return new String(jdkOpts.contentsToByteArray(true), CharsetToolkit.UTF8_CHARSET);
98 catch (IOException e) {
100 return settings.importingSettings.getVmOptionsForImporter();
105 private static @Nullable String getWrapperDistributionUrl(Project project) {
106 VirtualFile baseDir = project.getBaseDir();
107 if (baseDir == null) {
110 return MavenWrapperSupport.getWrapperDistributionUrl(baseDir);
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();
128 console.startWrapperResolving();
129 MavenDistribution distribution = new MavenWrapperSupport().downloadAndInstallMaven(distributionUrl, console.progressIndicatorForWrapper());
130 console.finishWrapperResolving(null);
133 catch (RuntimeException | IOException e) {
134 MavenLog.LOG.info(e);
135 console.finishWrapperResolving(e);
136 return MavenServerManager.resolveEmbeddedMavenHome();
141 private void connect() {
142 if (mySupport != null || myMavenServer != null) {
143 throw new IllegalStateException("Already connected");
146 if (myDebugPort != null) {
147 //simple connection using JavaDebuggerConsoleFilterProvider
148 //noinspection UseOfSystemOutOrSystemErr
149 System.out.println("Listening for transport dt_socket at address: " + myDebugPort);
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");
157 myDownloadListenerExported = MavenRemoteObjectWrapper.doWrapAndExport(myDownloadListener) != null;
158 if (!myDownloadListenerExported) throw new RemoteException("Cannot export download listener object");
160 myMavenServer.set(myLogger, myDownloadListener, MavenRemoteObjectWrapper.ourToken);
162 catch (Exception e) {
163 throw new RuntimeException("Cannot start maven service", e);
167 private void cleanUp() {
168 if (myLoggerExported) {
170 UnicastRemoteObject.unexportObject(myLogger, true);
172 catch (RemoteException e) {
173 MavenLog.LOG.warn(e);
175 myLoggerExported = false;
177 if (myDownloadListenerExported) {
179 UnicastRemoteObject.unexportObject(myDownloadListener, true);
181 catch (RemoteException e) {
182 MavenLog.LOG.warn(e);
184 myDownloadListenerExported = false;
186 myMavenServer = null;
190 public MavenServerEmbedder createEmbedder(MavenEmbedderSettings settings) throws RemoteException {
191 return myMavenServer.createEmbedder(settings, MavenRemoteObjectWrapper.ourToken);
194 public MavenServerIndexer createIndexer() throws RemoteException {
195 return myMavenServer.createIndexer(MavenRemoteObjectWrapper.ourToken);
199 public void addDownloadListener(MavenServerDownloadListener listener) {
200 myDownloadListener.myListeners.add(listener);
203 public void removeDownloadListener(MavenServerDownloadListener listener) {
204 myDownloadListener.myListeners.remove(listener);
208 public MavenModel interpolateAndAlignModel(final MavenModel model, final File basedir) {
209 return perform(() -> myMavenServer.interpolateAndAlignModel(model, basedir, MavenRemoteObjectWrapper.ourToken));
212 public MavenModel assembleInheritance(final MavenModel model, final MavenModel parentModel) {
213 return perform(() -> myMavenServer.assembleInheritance(model, parentModel, MavenRemoteObjectWrapper.ourToken));
216 public ProfileApplicationResult applyProfiles(final MavenModel model,
218 final MavenExplicitProfiles explicitProfiles,
219 final Collection<String> alwaysOnProfiles) {
221 () -> myMavenServer.applyProfiles(model, basedir, explicitProfiles, alwaysOnProfiles, MavenRemoteObjectWrapper.ourToken));
224 public void shutdown(boolean wait) {
225 myManager.unregisterConnector(this);
226 if (mySupport != null) {
227 mySupport.stopAll(wait);
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++) {
238 catch (RemoteException e) {
239 MavenServerRemoteProcessSupport processSupport = mySupport;
240 if (processSupport != null) {
241 processSupport.stopAll(false);
247 throw new RuntimeException("Cannot reconnect.", last);
252 public void dispose() {
257 public Sdk getJdk() {
261 public MavenDistribution getMavenDistribution() {
262 return myDistribution;
265 public String getVMOptions() {
270 private static class RemoteMavenServerLogger extends MavenRemoteObject implements MavenServerLogger {
272 public void info(Throwable e) {
273 MavenLog.LOG.info(e);
277 public void warn(Throwable e) {
278 MavenLog.LOG.warn(e);
282 public void error(Throwable e) {
283 MavenLog.LOG.error(e);
287 public void print(String s) {
288 //noinspection UseOfSystemOutOrSystemErr
289 System.out.println(s);
293 private static class RemoteMavenServerDownloadListener extends MavenRemoteObject implements MavenServerDownloadListener {
294 private final List<MavenServerDownloadListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
297 public void artifactDownloaded(File file, String relativePath) throws RemoteException {
298 for (MavenServerDownloadListener each : myListeners) {
299 each.artifactDownloaded(file, relativePath);