From 00df0608a88b5459008aef49c496a7d8bfe9e661 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Fri, 9 Aug 2013 19:16:47 +0400 Subject: [PATCH] IDEA-111831 External system: Make it possible to communicate with external system at the ide process --- .../util/ExternalSystemConstants.java | 2 + ... => AbstractExternalSystemFacadeImpl.java} | 233 ++++------------ .../ExternalSystemCommunicationManager.java | 62 +++++ .../service/ExternalSystemFacadeManager.java | 222 +++------------ ...essExternalSystemCommunicationManager.java | 53 ++++ .../InProcessExternalSystemFacadeImpl.java | 44 +++ ...oteExternalSystemCommunicationManager.java | 257 ++++++++++++++++++ .../RemoteExternalSystemFacadeImpl.java | 188 +++++++++++++ .../wrapper/ExternalSystemFacadeWrapper.java | 5 + .../src/misc/registry.properties | 2 + .../src/META-INF/ExternalSystemExtensions.xml | 2 + 11 files changed, 708 insertions(+), 362 deletions(-) rename platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/{ExternalSystemFacadeImpl.java => AbstractExternalSystemFacadeImpl.java} (55%) create mode 100644 platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ExternalSystemCommunicationManager.java create mode 100644 platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/InProcessExternalSystemCommunicationManager.java create mode 100644 platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/InProcessExternalSystemFacadeImpl.java create mode 100644 platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/RemoteExternalSystemCommunicationManager.java create mode 100644 platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/RemoteExternalSystemFacadeImpl.java diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemConstants.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemConstants.java index 80795f45afe3..659a32d16728 100644 --- a/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemConstants.java +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemConstants.java @@ -33,6 +33,8 @@ public class ExternalSystemConstants { @NonNls @NotNull public static final String TOOL_WINDOW_PLACE = "ExternalSystem.ToolWindow"; @NonNls @NotNull public static final String TREE_CONTEXT_MENU_PLACE = "ExternalSystem.Tree.Context.Menu"; + @NotNull @NonNls public static final String USE_IN_PROCESS_COMMUNICATION_REGISTRY_KEY = "external.system.in.process"; + @NotNull public static final String DEBUG_RUNNER_ID = "ExternalSystemTaskDebugRunner"; @NotNull public static final String RUNNER_ID = "ExternalSystemTaskRunner"; diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ExternalSystemFacadeImpl.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/AbstractExternalSystemFacadeImpl.java similarity index 55% rename from platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ExternalSystemFacadeImpl.java rename to platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/AbstractExternalSystemFacadeImpl.java index d61abed5faa2..98deeacde3b5 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ExternalSystemFacadeImpl.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/AbstractExternalSystemFacadeImpl.java @@ -1,106 +1,66 @@ package com.intellij.openapi.externalSystem.service; import com.intellij.execution.rmi.RemoteServer; -import com.intellij.openapi.externalSystem.task.ExternalSystemTaskManager; import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings; import com.intellij.openapi.externalSystem.model.task.*; import com.intellij.openapi.externalSystem.service.project.ExternalSystemProjectResolver; import com.intellij.openapi.externalSystem.service.remote.*; -import com.intellij.util.Alarm; +import com.intellij.openapi.externalSystem.task.ExternalSystemTaskManager; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtilRt; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintStream; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; import java.rmi.RemoteException; -import java.rmi.server.UnicastRemoteObject; import java.util.*; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; /** * @author Denis Zhdanov * @since 8/8/11 12:51 PM */ -public class ExternalSystemFacadeImpl extends RemoteServer +public abstract class AbstractExternalSystemFacadeImpl extends RemoteServer implements RemoteExternalSystemFacade { - private static final long DEFAULT_REMOTE_GRADLE_PROCESS_TTL_IN_MS = TimeUnit.MILLISECONDS.convert(3, TimeUnit.MINUTES); - private final ConcurrentMap, RemoteExternalSystemService> myRemotes = ContainerUtil.newConcurrentMap(); private final AtomicReference mySettings = new AtomicReference(); - private final AtomicLong myTtlMs = new AtomicLong(DEFAULT_REMOTE_GRADLE_PROCESS_TTL_IN_MS); - private final Alarm myShutdownAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); - private final AtomicInteger myCallsInProgressNumber = new AtomicInteger(); - private final AtomicReference myNotificationListener = new AtomicReference(new ExternalSystemTaskNotificationListenerAdapter() {}); @NotNull private final RemoteExternalSystemProjectResolverImpl myProjectResolver; @NotNull private final RemoteExternalSystemTaskManagerImpl myTaskManager; - private volatile boolean myStdOutputConfigured; - public ExternalSystemFacadeImpl(@NotNull Class> projectResolverClass, - @NotNull Class> buildManagerClass) + public AbstractExternalSystemFacadeImpl(@NotNull Class> projectResolverClass, + @NotNull Class> buildManagerClass) throws IllegalAccessException, InstantiationException { myProjectResolver = new RemoteExternalSystemProjectResolverImpl(projectResolverClass.newInstance()); myTaskManager = new RemoteExternalSystemTaskManagerImpl(buildManagerClass.newInstance()); - updateAutoShutdownTime(); } - @SuppressWarnings("unchecked") - public static void main(String[] args) throws Exception { - if (args.length < 1) { - throw new IllegalArgumentException( - "Can't create external system facade. Reason: given arguments don't contain information about external system resolver to use"); - } - final Class> resolverClass = (Class>)Class.forName(args[0]); - if (!ExternalSystemProjectResolver.class.isAssignableFrom(resolverClass)) { - throw new IllegalArgumentException(String.format( - "Can't create external system facade. Reason: given external system resolver class (%s) must be IS-A '%s'", - resolverClass, - ExternalSystemProjectResolver.class)); - } - - if (args.length < 2) { - throw new IllegalArgumentException( - "Can't create external system facade. Reason: given arguments don't contain information about external system build manager to use" - ); - } - final Class> buildManagerClass = (Class>)Class.forName(args[1]); - if (!ExternalSystemProjectResolver.class.isAssignableFrom(resolverClass)) { - throw new IllegalArgumentException(String.format( - "Can't create external system facade. Reason: given external system build manager (%s) must be IS-A '%s'", - buildManagerClass, ExternalSystemTaskManager.class - )); - } - - ExternalSystemFacadeImpl facade = new ExternalSystemFacadeImpl(resolverClass, buildManagerClass); - facade.init(); - start(facade); - } - - private void init() throws RemoteException { + protected void init() throws RemoteException { applyProgressManager(RemoteExternalSystemProgressNotificationManager.NULL_OBJECT); } + @Nullable + protected S getSettings() { + return mySettings.get(); + } + + @NotNull + protected ExternalSystemTaskNotificationListener getNotificationListener() { + return myNotificationListener.get(); + } + @SuppressWarnings("unchecked") @NotNull @Override public RemoteExternalSystemProjectResolver getResolver() throws RemoteException, IllegalStateException { try { - return getRemote(RemoteExternalSystemProjectResolver.class, myProjectResolver); + return getService(RemoteExternalSystemProjectResolver.class, myProjectResolver); } catch (Exception e) { throw new IllegalStateException(String.format("Can't create '%s' service", RemoteExternalSystemProjectResolverImpl.class.getName()), @@ -113,13 +73,48 @@ public class ExternalSystemFacadeImpl @Override public RemoteExternalSystemTaskManager getTaskManager() throws RemoteException { try { - return getRemote(RemoteExternalSystemTaskManager.class, myTaskManager); + return getService(RemoteExternalSystemTaskManager.class, myTaskManager); } catch (Exception e) { throw new IllegalStateException(String.format("Can't create '%s' service", ExternalSystemTaskManager.class.getName()), e); } } + @SuppressWarnings({"unchecked", "IOResourceOpenedButNotSafelyClosed", "UseOfSystemOutOrSystemErr"}) + private , C extends I> I getService(@NotNull Class interfaceClass, + @NotNull final C impl) + throws ClassNotFoundException, IllegalAccessException, InstantiationException, RemoteException + { + Object cachedResult = myRemotes.get(interfaceClass); + if (cachedResult != null) { + return (I)cachedResult; + } + S settings = getSettings(); + if (settings != null) { + impl.setNotificationListener(getNotificationListener()); + impl.setSettings(settings); + } + impl.setNotificationListener(getNotificationListener()); + try { + I created = createService(interfaceClass, impl); + I stored = (I)myRemotes.putIfAbsent(interfaceClass, created); + return stored == null ? created : stored; + } + catch (RemoteException e) { + Object raceResult = myRemotes.get(interfaceClass); + if (raceResult != null) { + // Race condition occurred + return (I)raceResult; + } + else { + throw new IllegalStateException( + String.format("Can't prepare remote service for interface '%s', implementation '%s'", interfaceClass, impl), + e + ); + } + } + } + /** * Generic method to retrieve exposed implementations of the target interface. *

@@ -136,59 +131,9 @@ public class ExternalSystemFacadeImpl * @throws RemoteException */ @SuppressWarnings({"unchecked", "IOResourceOpenedButNotSafelyClosed", "UseOfSystemOutOrSystemErr"}) - private , C extends I> I getRemote(@NotNull Class interfaceClass, - @NotNull final C impl) - throws ClassNotFoundException, IllegalAccessException, InstantiationException, RemoteException - { - Object cachedResult = myRemotes.get(interfaceClass); - if (cachedResult != null) { - return (I)cachedResult; - } - - if (!myStdOutputConfigured) { - myStdOutputConfigured = true; - System.setOut(new LineAwarePrintStream(System.out)); - System.setErr(new LineAwarePrintStream(System.err)); - } - - S settings = mySettings.get(); - if (settings != null) { - impl.setNotificationListener(myNotificationListener.get()); - impl.setSettings(settings); - } - impl.setNotificationListener(myNotificationListener.get()); - I proxy = (I)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { interfaceClass }, new InvocationHandler() { - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - myCallsInProgressNumber.incrementAndGet(); - try { - return method.invoke(impl, args); - } - finally { - myCallsInProgressNumber.decrementAndGet(); - updateAutoShutdownTime(); - } - } - }); - try { - I stub = (I)UnicastRemoteObject.exportObject(proxy, 0); - I stored = (I)myRemotes.putIfAbsent(interfaceClass, stub); - return stored == null ? stub : stored; - } - catch (RemoteException e) { - Object raceResult = myRemotes.get(interfaceClass); - if (raceResult != null) { - // Race condition occurred - return (I)raceResult; - } - else { - throw new IllegalStateException( - String.format("Can't prepare remote service for interface '%s', implementation '%s'", interfaceClass, impl), - e - ); - } - } - } + protected abstract , C extends I> I createService(@NotNull Class interfaceClass, + @NotNull final C impl) + throws ClassNotFoundException, IllegalAccessException, InstantiationException, RemoteException; @Override public boolean isTaskInProgress(@NotNull ExternalSystemTaskId id) throws RemoteException { @@ -229,10 +174,6 @@ public class ExternalSystemFacadeImpl @Override public void applySettings(@NotNull S settings) throws RemoteException { mySettings.set(settings); - long ttl = settings.getRemoteProcessIdleTtlInMs(); - if (ttl > 0) { - myTtlMs.set(ttl); - } List> services = ContainerUtilRt.newArrayList(myRemotes.values()); for (RemoteExternalSystemService service : services) { service.setSettings(settings); @@ -247,26 +188,6 @@ public class ExternalSystemFacadeImpl myTaskManager.setNotificationListener(listener); } - /** - * Schedules automatic process termination in {@code #REMOTE_GRADLE_PROCESS_TTL_IN_MS} milliseconds. - *

- * Rationale: it's possible that IJ user performs gradle related activity (e.g. import from gradle) when the works purely - * at IJ. We don't want to keep remote process that communicates with the gradle api then. - */ - private void updateAutoShutdownTime() { - myShutdownAlarm.cancelAllRequests(); - myShutdownAlarm.addRequest(new Runnable() { - @Override - public void run() { - if (myCallsInProgressNumber.get() > 0) { - updateAutoShutdownTime(); - return; - } - System.exit(0); - } - }, (int)myTtlMs.get()); - } - private static class SwallowingNotificationListener implements ExternalSystemTaskNotificationListener { @NotNull private final RemoteExternalSystemProgressNotificationManager myManager; @@ -319,46 +240,4 @@ public class ExternalSystemFacadeImpl } } } - - @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") - private static class LineAwarePrintStream extends PrintStream { - private LineAwarePrintStream(@NotNull final PrintStream delegate) { - super(new OutputStream() { - - @NotNull private final StringBuilder myBuffer = new StringBuilder(); - - @Override - public void write(int b) throws IOException { - char c = (char)b; - myBuffer.append(Character.toString(c)); - if (c == '\n') { - doFlush(); - } - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - int start = off; - int maxOffset = off + len; - for (int i = off; i < maxOffset; i++) { - if (b[i] == '\n') { - myBuffer.append(new String(b, start, i - start + 1)); - doFlush(); - start = i + 1; - } - } - - if (start < maxOffset) { - myBuffer.append(new String(b, start, maxOffset - start)); - } - } - - private void doFlush() { - delegate.print(myBuffer.toString()); - delegate.flush(); - myBuffer.setLength(0); - } - }); - } - } } diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ExternalSystemCommunicationManager.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ExternalSystemCommunicationManager.java new file mode 100644 index 000000000000..3b74bcffbde1 --- /dev/null +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ExternalSystemCommunicationManager.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.externalSystem.service; + +import com.intellij.openapi.externalSystem.model.ProjectSystemId; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * External system integration consists of common 'external system' functionality and external system-specific code. There are at + * least two approaches how to work with that external system-specific code: + *

+ * 
    + *
  • use it from the ide process;
  • + *
  • create a slave process and perform external system-specific actions there;
  • + *
+ *
+ *

+ * E.g. when we work with particular external system api it might worth to do that at a separate process in order to avoid bad stuff + * like memory leaks to happen at the ide process. However, when external system-specific communication just starts new external + * system-process, there is no point in creating intermediate mediator process just to launch new process. + *

+ * That's why that stuff is covered by the current interface, i.e. different implementations are supposed to provide + * different 'in process' modes. + * + * @author Denis Zhdanov + * @since 8/9/13 3:21 PM + */ +public interface ExternalSystemCommunicationManager { + + /** + * Creates new external system facade for the given arguments. + * + * @param id if for which new facade is to be created + * @param externalSystemId target external system id + * @return newly created facade for the given arguments (if it was possible to create one) + * @throws Exception in case something goes wrong + */ + @Nullable + RemoteExternalSystemFacade acquire(@NotNull String id, @NotNull ProjectSystemId externalSystemId) + throws Exception; + + boolean isAlive(@NotNull RemoteExternalSystemFacade facade); + + /** + * Disposes all resources acquired by the current manager. + */ + void clear(); +} diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ExternalSystemFacadeManager.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ExternalSystemFacadeManager.java index 7136ca7fcfa6..5043bd3116e5 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ExternalSystemFacadeManager.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ExternalSystemFacadeManager.java @@ -1,53 +1,23 @@ package com.intellij.openapi.externalSystem.service; -import com.intellij.CommonBundle; -import com.intellij.execution.DefaultExecutionResult; -import com.intellij.execution.ExecutionException; -import com.intellij.execution.ExecutionResult; -import com.intellij.execution.Executor; -import com.intellij.execution.configurations.CommandLineState; -import com.intellij.execution.configurations.GeneralCommandLine; -import com.intellij.execution.configurations.RunProfileState; -import com.intellij.execution.configurations.SimpleJavaParameters; -import com.intellij.execution.process.OSProcessHandler; -import com.intellij.execution.process.ProcessHandler; -import com.intellij.execution.process.ProcessTerminatedListener; -import com.intellij.execution.rmi.RemoteProcessSupport; -import com.intellij.execution.runners.ProgramRunner; -import com.intellij.ide.actions.OpenProjectFileChooserDescriptor; import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.externalSystem.ExternalSystemManager; import com.intellij.openapi.externalSystem.model.ProjectSystemId; import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId; -import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener; import com.intellij.openapi.externalSystem.service.notification.ExternalSystemProgressNotificationManager; -import com.intellij.openapi.externalSystem.service.remote.ExternalSystemProgressNotificationManagerImpl; import com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemProgressNotificationManager; import com.intellij.openapi.externalSystem.service.remote.wrapper.ExternalSystemFacadeWrapper; import com.intellij.openapi.externalSystem.settings.ExternalSystemSettingsManager; import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; +import com.intellij.openapi.externalSystem.util.ExternalSystemConstants; import com.intellij.openapi.externalSystem.util.IntegrationKey; import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.projectRoots.JavaSdkType; -import com.intellij.openapi.projectRoots.JdkUtil; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.projectRoots.SimpleJavaSdkType; -import com.intellij.openapi.roots.DependencyScope; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.ShutDownTracker; -import com.intellij.psi.PsiBundle; -import com.intellij.util.Alarm; +import com.intellij.openapi.util.registry.Registry; import com.intellij.util.Consumer; -import com.intellij.util.PathUtil; -import com.intellij.util.SystemProperties; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtilRt; import org.jetbrains.annotations.NotNull; @@ -57,14 +27,11 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.nio.charset.Charset; import java.rmi.RemoteException; -import java.rmi.server.UnicastRemoteObject; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -79,10 +46,6 @@ import java.util.concurrent.locks.ReentrantLock; */ public class ExternalSystemFacadeManager { - private static final Logger LOG = Logger.getInstance("#" + ExternalSystemFacadeManager.class.getName()); - - private static final String MAIN_CLASS_NAME = ExternalSystemFacadeImpl.class.getName(); - private static final int REMOTE_FAIL_RECOVERY_ATTEMPTS_NUMBER = 3; private final ConcurrentMap myFacadeWrappers = ContainerUtil.newConcurrentMap(); @@ -90,43 +53,30 @@ public class ExternalSystemFacadeManager { private final Map> myRemoteFacades = ContainerUtil.newConcurrentMap(); - @NotNull private final Lock myLock = new ReentrantLock(); + @NotNull private final Lock myLock = new ReentrantLock(); + @NotNull private final AtomicBoolean myInProcessCommunication = new AtomicBoolean(); - private final AtomicReference myExportedNotificationManager - = new AtomicReference(); + @NotNull private final AtomicReference myCommunicationManager = + new AtomicReference(); - @NotNull private final ExternalSystemProgressNotificationManagerImpl myProgressManager; - @NotNull private final ExternalSystemSettingsManager mySettingsManager; - @NotNull private final RemoteProcessSupport mySupport; - @NotNull private final ThreadLocal myTargetExternalSystemId = new ThreadLocal(); + @NotNull private final ExternalSystemSettingsManager mySettingsManager; + @NotNull private final RemoteExternalSystemProgressNotificationManager myProgressManager; + @NotNull private final RemoteExternalSystemCommunicationManager myRemoteCommunicationManager; + @NotNull private final InProcessExternalSystemCommunicationManager myInProcessCommunicationManager; public ExternalSystemFacadeManager(@NotNull ExternalSystemSettingsManager settingsManager, - @NotNull ExternalSystemProgressNotificationManager notificationManager) + @NotNull ExternalSystemProgressNotificationManager notificationManager, + @NotNull RemoteExternalSystemCommunicationManager remoteCommunicationManager, + @NotNull InProcessExternalSystemCommunicationManager inProcessCommunicationManager) { mySettingsManager = settingsManager; - myProgressManager = (ExternalSystemProgressNotificationManagerImpl)notificationManager; - mySupport = new RemoteProcessSupport(RemoteExternalSystemFacade.class) { - @Override - protected void fireModificationCountChanged() { - } - - @Override - protected String getName(Object o) { - return RemoteExternalSystemFacade.class.getName(); - } - - @Override - protected RunProfileState getRunProfileState(Object o, String configuration, Executor executor) throws ExecutionException { - return createRunProfileState(); - } - }; - - ShutDownTracker.getInstance().registerShutdownTask(new Runnable() { - public void run() { - shutdown(false); - } - }); + myProgressManager = (RemoteExternalSystemProgressNotificationManager)notificationManager; + myRemoteCommunicationManager = remoteCommunicationManager; + myInProcessCommunicationManager = inProcessCommunicationManager; + boolean inProcessCommunication = Registry.is(ExternalSystemConstants.USE_IN_PROCESS_COMMUNICATION_REGISTRY_KEY, false); + myInProcessCommunication.set(inProcessCommunication); + myCommunicationManager.set(inProcessCommunication ? myInProcessCommunicationManager : myRemoteCommunicationManager); } @NotNull @@ -140,93 +90,6 @@ public class ExternalSystemFacadeManager { return projectManager.getDefaultProject(); } - private RunProfileState createRunProfileState() { - return new CommandLineState(null) { - private SimpleJavaParameters createJavaParameters() throws ExecutionException { - - final SimpleJavaParameters params = new SimpleJavaParameters(); - params.setJdk(new SimpleJavaSdkType().createJdk("tmp", SystemProperties.getJavaHome())); - - params.setWorkingDirectory(PathManager.getBinPath()); - final List classPath = ContainerUtilRt.newArrayList(); - - // IDE jars. - classPath.addAll(PathManager.getUtilClassPath()); - ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(ProjectBundle.class), classPath); - ExternalSystemApiUtil.addBundle(params.getClassPath(), "messages.ProjectBundle", ProjectBundle.class); - ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(PsiBundle.class), classPath); - ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(Alarm.class), classPath); - ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(DependencyScope.class), classPath); - ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(ExtensionPointName.class), classPath); - ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(OpenProjectFileChooserDescriptor.class), classPath); - ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(ExternalSystemTaskNotificationListener.class), classPath); - - // External system module jars - ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(getClass()), classPath); - ExternalSystemApiUtil.addBundle(params.getClassPath(), "messages.CommonBundle", CommonBundle.class); - params.getClassPath().addAll(classPath); - - params.setMainClass(MAIN_CLASS_NAME); - params.getVMParametersList().addParametersString("-Djava.awt.headless=true"); - - // It may take a while for gradle api to resolve external dependencies. Default RMI timeout - // is 15 seconds (http://download.oracle.com/javase/6/docs/technotes/guides/rmi/sunrmiproperties.html#connectionTimeout), - // we don't want to get EOFException because of that. - params.getVMParametersList().addParametersString( - "-Dsun.rmi.transport.connectionTimeout=" + String.valueOf(TimeUnit.HOURS.toMillis(1)) - ); -// params.getVMParametersList().addParametersString("-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5009"); - - ProjectSystemId externalSystemId = myTargetExternalSystemId.get(); - if (externalSystemId != null) { - ExternalSystemManager manager = ExternalSystemApiUtil.getManager(externalSystemId); - if (manager != null) { - params.getClassPath().add(PathUtil.getJarPathForClass(manager.getProjectResolverClass().getClass())); - params.getProgramParametersList().add(manager.getProjectResolverClass().getName()); - params.getProgramParametersList().add(manager.getTaskManagerClass().getName()); - manager.enhanceParameters(params); - } - } - - return params; - } - - @Override - @NotNull - public ExecutionResult execute(@NotNull Executor executor, @NotNull ProgramRunner runner) throws ExecutionException { - ProcessHandler processHandler = startProcess(); - return new DefaultExecutionResult(null, processHandler, AnAction.EMPTY_ARRAY); - } - - @NotNull - protected OSProcessHandler startProcess() throws ExecutionException { - SimpleJavaParameters params = createJavaParameters(); - Sdk sdk = params.getJdk(); - if (sdk == null) { - throw new ExecutionException("No sdk is defined. Params: " + params); - } - - final GeneralCommandLine commandLine = JdkUtil.setupJVMCommandLine( - ((JavaSdkType)sdk.getSdkType()).getVMExecutablePath(sdk), - params, - false - ); - final OSProcessHandler processHandler = new OSProcessHandler(commandLine.createProcess(), commandLine.getCommandLineString()) { - @Override - public Charset getCharset() { - return commandLine.getCharset(); - } - }; - ProcessTerminatedListener.attach(processHandler); - return processHandler; - } - }; - } - - public synchronized void shutdown(boolean wait) { - mySupport.stopAll(wait); - } - public void onProjectRename(@NotNull String oldName, @NotNull String newName) { onProjectRename(myFacadeWrappers, oldName, newName); onProjectRename(myRemoteFacades, oldName, newName); @@ -254,9 +117,9 @@ public class ExternalSystemFacadeManager { } } } - + /** - * @return gradle api facade to use + * @return gradle api facade to use * @throws Exception in case of inability to return the facade */ @NotNull @@ -271,7 +134,8 @@ public class ExternalSystemFacadeManager { final RemoteExternalSystemFacade facade = myFacadeWrappers.get(key); if (facade == null) { final RemoteExternalSystemFacade newFacade = (RemoteExternalSystemFacade)Proxy.newProxyInstance( - ExternalSystemFacadeManager.class.getClassLoader(), new Class[]{RemoteExternalSystemFacade.class, Consumer.class}, new MyHandler(key) + ExternalSystemFacadeManager.class.getClassLoader(), new Class[]{RemoteExternalSystemFacade.class, Consumer.class}, + new MyHandler(key) ); myFacadeWrappers.putIfAbsent(key, newFacade); } @@ -299,6 +163,12 @@ public class ExternalSystemFacadeManager { @SuppressWarnings("ConstantConditions") @NotNull private RemoteExternalSystemFacade doGetFacade(@NotNull IntegrationKey key, @NotNull Project project) throws Exception { + boolean currentInProcess = Registry.is(ExternalSystemConstants.USE_IN_PROCESS_COMMUNICATION_REGISTRY_KEY, false); + if (myInProcessCommunication.getAndSet(currentInProcess) != currentInProcess) { + myCommunicationManager.get().clear(); + myCommunicationManager.set(currentInProcess ? myInProcessCommunicationManager : myRemoteCommunicationManager); + } + ExternalSystemManager manager = ExternalSystemApiUtil.getManager(key.getExternalSystemId()); if (project.isDisposed() || manager == null) { return RemoteExternalSystemFacade.NULL_OBJECT; @@ -315,7 +185,7 @@ public class ExternalSystemFacadeManager { return pair.first; } if (pair != null) { - mySupport.stopAll(true); + myCommunicationManager.get().clear(); myFacadeWrappers.clear(); myRemoteFacades.clear(); } @@ -329,16 +199,14 @@ public class ExternalSystemFacadeManager { @SuppressWarnings("unchecked") @NotNull private RemoteExternalSystemFacade doCreateFacade(@NotNull IntegrationKey key, @NotNull Project project) throws Exception { - myTargetExternalSystemId.set(key.getExternalSystemId()); - final RemoteExternalSystemFacade facade = mySupport.acquire(this, project.getName()); - myTargetExternalSystemId.set(null); + final RemoteExternalSystemFacade facade = myCommunicationManager.get().acquire(project.getName(), key.getExternalSystemId()); if (facade == null) { - throw new IllegalStateException("Can't obtain facade to working with gradle api at the remote process. Project: " + project); + throw new IllegalStateException("Can't obtain facade to working with external api at the remote process. Project: " + project); } Disposer.register(project, new Disposable() { @Override public void dispose() { - mySupport.stopAll(true); + myCommunicationManager.get().clear(); myFacadeWrappers.clear(); myRemoteFacades.clear(); } @@ -349,22 +217,6 @@ public class ExternalSystemFacadeManager { Pair newPair = Pair.create(result, settings); myRemoteFacades.put(key, newPair); result.applySettings(newPair.second); - RemoteExternalSystemProgressNotificationManager exported = myExportedNotificationManager.get(); - if (exported == null) { - try { - exported = (RemoteExternalSystemProgressNotificationManager)UnicastRemoteObject.exportObject(myProgressManager, 0); - myExportedNotificationManager.set(exported); - } - catch (RemoteException e) { - exported = myExportedNotificationManager.get(); - } - } - if (exported == null) { - LOG.warn("Can't export progress manager"); - } - else { - result.applyProgressManager(exported); - } return result; } @@ -373,10 +225,10 @@ public class ExternalSystemFacadeManager { @NotNull IntegrationKey key, @NotNull Pair pair) { - // Check if remote process is alive. + if (!myCommunicationManager.get().isAlive(pair.first)) { + return false; + } try { - pair.first.getResolver(); - ExternalSystemExecutionSettings currentSettings = mySettingsManager.getExecutionSettings(project, key.getExternalProjectConfigPath(), key.getExternalSystemId()); if (!currentSettings.equals(pair.second)) { diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/InProcessExternalSystemCommunicationManager.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/InProcessExternalSystemCommunicationManager.java new file mode 100644 index 000000000000..87405e077bbc --- /dev/null +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/InProcessExternalSystemCommunicationManager.java @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.externalSystem.service; + +import com.intellij.openapi.externalSystem.ExternalSystemManager; +import com.intellij.openapi.externalSystem.model.ProjectSystemId; +import com.intellij.openapi.externalSystem.service.remote.wrapper.ExternalSystemFacadeWrapper; +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Denis Zhdanov + * @since 8/9/13 4:00 PM + */ +public class InProcessExternalSystemCommunicationManager implements ExternalSystemCommunicationManager { + + @SuppressWarnings("unchecked") + @Nullable + @Override + public RemoteExternalSystemFacade acquire(@NotNull String id, @NotNull ProjectSystemId externalSystemId) throws Exception { + ExternalSystemManager manager = ExternalSystemApiUtil.getManager(externalSystemId); + assert manager != null; + return new InProcessExternalSystemFacadeImpl(manager.getProjectResolverClass(), manager.getTaskManagerClass()); + } + + @Override + public boolean isAlive(@NotNull RemoteExternalSystemFacade facade) { + RemoteExternalSystemFacade toCheck = facade; + if (facade instanceof ExternalSystemFacadeWrapper) { + toCheck = ((ExternalSystemFacadeWrapper)facade).getDelegate(); + + } + return toCheck instanceof InProcessExternalSystemFacadeImpl; + } + + @Override + public void clear() { + } +} diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/InProcessExternalSystemFacadeImpl.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/InProcessExternalSystemFacadeImpl.java new file mode 100644 index 000000000000..a0b0141f8a1c --- /dev/null +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/InProcessExternalSystemFacadeImpl.java @@ -0,0 +1,44 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.externalSystem.service; + +import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings; +import com.intellij.openapi.externalSystem.service.project.ExternalSystemProjectResolver; +import com.intellij.openapi.externalSystem.task.ExternalSystemTaskManager; +import org.jetbrains.annotations.NotNull; + +import java.rmi.RemoteException; + +/** + * @author Denis Zhdanov + * @since 8/9/13 5:42 PM + */ +public class InProcessExternalSystemFacadeImpl extends AbstractExternalSystemFacadeImpl { + + public InProcessExternalSystemFacadeImpl(@NotNull Class> projectResolverClass, + @NotNull Class> buildManagerClass) + throws IllegalAccessException, InstantiationException + { + super(projectResolverClass, buildManagerClass); + } + + @Override + protected , C extends I> I createService(@NotNull Class interfaceClass, @NotNull C impl) + throws ClassNotFoundException, IllegalAccessException, InstantiationException, RemoteException + { + return impl; + } +} diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/RemoteExternalSystemCommunicationManager.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/RemoteExternalSystemCommunicationManager.java new file mode 100644 index 000000000000..894831feb78d --- /dev/null +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/RemoteExternalSystemCommunicationManager.java @@ -0,0 +1,257 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.externalSystem.service; + +import com.intellij.CommonBundle; +import com.intellij.execution.DefaultExecutionResult; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.ExecutionResult; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.CommandLineState; +import com.intellij.execution.configurations.GeneralCommandLine; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.configurations.SimpleJavaParameters; +import com.intellij.execution.process.OSProcessHandler; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.process.ProcessTerminatedListener; +import com.intellij.execution.rmi.RemoteProcessSupport; +import com.intellij.execution.runners.ProgramRunner; +import com.intellij.ide.actions.OpenProjectFileChooserDescriptor; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.externalSystem.ExternalSystemManager; +import com.intellij.openapi.externalSystem.model.ProjectSystemId; +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener; +import com.intellij.openapi.externalSystem.service.notification.ExternalSystemProgressNotificationManager; +import com.intellij.openapi.externalSystem.service.remote.ExternalSystemProgressNotificationManagerImpl; +import com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemProgressNotificationManager; +import com.intellij.openapi.externalSystem.service.remote.wrapper.ExternalSystemFacadeWrapper; +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.projectRoots.JavaSdkType; +import com.intellij.openapi.projectRoots.JdkUtil; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.SimpleJavaSdkType; +import com.intellij.openapi.roots.DependencyScope; +import com.intellij.openapi.util.ShutDownTracker; +import com.intellij.psi.PsiBundle; +import com.intellij.util.Alarm; +import com.intellij.util.PathUtil; +import com.intellij.util.SystemProperties; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.ContainerUtilRt; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.nio.charset.Charset; +import java.rmi.RemoteException; +import java.rmi.server.UnicastRemoteObject; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author Denis Zhdanov + * @since 8/9/13 3:37 PM + */ +public class RemoteExternalSystemCommunicationManager implements ExternalSystemCommunicationManager { + + private static final Logger LOG = Logger.getInstance("#" + RemoteExternalSystemCommunicationManager.class.getName()); + + private static final String MAIN_CLASS_NAME = RemoteExternalSystemFacadeImpl.class.getName(); + + private final AtomicReference myExportedNotificationManager + = new AtomicReference(); + + @NotNull private final ThreadLocal myTargetExternalSystemId = new ThreadLocal(); + + @NotNull private final ExternalSystemProgressNotificationManagerImpl myProgressManager; + @NotNull private final RemoteProcessSupport mySupport; + + public RemoteExternalSystemCommunicationManager(@NotNull ExternalSystemProgressNotificationManager notificationManager) { + myProgressManager = (ExternalSystemProgressNotificationManagerImpl)notificationManager; + mySupport = new RemoteProcessSupport(RemoteExternalSystemFacade.class) { + @Override + protected void fireModificationCountChanged() { + } + + @Override + protected String getName(Object o) { + return RemoteExternalSystemFacade.class.getName(); + } + + @Override + protected RunProfileState getRunProfileState(Object o, String configuration, Executor executor) throws ExecutionException { + return createRunProfileState(); + } + }; + + ShutDownTracker.getInstance().registerShutdownTask(new Runnable() { + public void run() { + shutdown(false); + } + }); + } + + public synchronized void shutdown(boolean wait) { + mySupport.stopAll(wait); + } + + private RunProfileState createRunProfileState() { + return new CommandLineState(null) { + private SimpleJavaParameters createJavaParameters() throws ExecutionException { + + final SimpleJavaParameters params = new SimpleJavaParameters(); + params.setJdk(new SimpleJavaSdkType().createJdk("tmp", SystemProperties.getJavaHome())); + + params.setWorkingDirectory(PathManager.getBinPath()); + final List classPath = ContainerUtilRt.newArrayList(); + + // IDE jars. + classPath.addAll(PathManager.getUtilClassPath()); + ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(ProjectBundle.class), classPath); + ExternalSystemApiUtil.addBundle(params.getClassPath(), "messages.ProjectBundle", ProjectBundle.class); + ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(PsiBundle.class), classPath); + ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(Alarm.class), classPath); + ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(DependencyScope.class), classPath); + ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(ExtensionPointName.class), classPath); + ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(OpenProjectFileChooserDescriptor.class), classPath); + ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(ExternalSystemTaskNotificationListener.class), classPath); + + // External system module jars + ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(getClass()), classPath); + ExternalSystemApiUtil.addBundle(params.getClassPath(), "messages.CommonBundle", CommonBundle.class); + params.getClassPath().addAll(classPath); + + params.setMainClass(MAIN_CLASS_NAME); + params.getVMParametersList().addParametersString("-Djava.awt.headless=true"); + + // It may take a while for gradle api to resolve external dependencies. Default RMI timeout + // is 15 seconds (http://download.oracle.com/javase/6/docs/technotes/guides/rmi/sunrmiproperties.html#connectionTimeout), + // we don't want to get EOFException because of that. + params.getVMParametersList().addParametersString( + "-Dsun.rmi.transport.connectionTimeout=" + String.valueOf(TimeUnit.HOURS.toMillis(1)) + ); +// params.getVMParametersList().addParametersString("-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5009"); + + ProjectSystemId externalSystemId = myTargetExternalSystemId.get(); + if (externalSystemId != null) { + ExternalSystemManager manager = ExternalSystemApiUtil.getManager(externalSystemId); + if (manager != null) { + params.getClassPath().add(PathUtil.getJarPathForClass(manager.getProjectResolverClass().getClass())); + params.getProgramParametersList().add(manager.getProjectResolverClass().getName()); + params.getProgramParametersList().add(manager.getTaskManagerClass().getName()); + manager.enhanceParameters(params); + } + } + + return params; + } + + @Override + @NotNull + public ExecutionResult execute(@NotNull Executor executor, @NotNull ProgramRunner runner) throws ExecutionException { + ProcessHandler processHandler = startProcess(); + return new DefaultExecutionResult(null, processHandler, AnAction.EMPTY_ARRAY); + } + + @NotNull + protected OSProcessHandler startProcess() throws ExecutionException { + SimpleJavaParameters params = createJavaParameters(); + Sdk sdk = params.getJdk(); + if (sdk == null) { + throw new ExecutionException("No sdk is defined. Params: " + params); + } + + final GeneralCommandLine commandLine = JdkUtil.setupJVMCommandLine( + ((JavaSdkType)sdk.getSdkType()).getVMExecutablePath(sdk), + params, + false + ); + final OSProcessHandler processHandler = new OSProcessHandler(commandLine.createProcess(), commandLine.getCommandLineString()) { + @Override + public Charset getCharset() { + return commandLine.getCharset(); + } + }; + ProcessTerminatedListener.attach(processHandler); + return processHandler; + } + }; + } + + @Nullable + @Override + public RemoteExternalSystemFacade acquire(@NotNull String id, @NotNull ProjectSystemId externalSystemId) + throws Exception + { + myTargetExternalSystemId.set(externalSystemId); + final RemoteExternalSystemFacade facade; + try { + facade = mySupport.acquire(this, id); + } + finally { + myTargetExternalSystemId.set(null); + } + if (facade == null) { + return null; + } + + RemoteExternalSystemProgressNotificationManager exported = myExportedNotificationManager.get(); + if (exported == null) { + try { + exported = (RemoteExternalSystemProgressNotificationManager)UnicastRemoteObject.exportObject(myProgressManager, 0); + myExportedNotificationManager.set(exported); + } + catch (RemoteException e) { + exported = myExportedNotificationManager.get(); + } + } + if (exported == null) { + LOG.warn("Can't export progress manager"); + } + else { + facade.applyProgressManager(exported); + } + return facade; + } + + @Override + public boolean isAlive(@NotNull RemoteExternalSystemFacade facade) { + RemoteExternalSystemFacade toCheck = facade; + if (facade instanceof ExternalSystemFacadeWrapper) { + toCheck = ((ExternalSystemFacadeWrapper)facade).getDelegate(); + + } + if (toCheck instanceof InProcessExternalSystemFacadeImpl) { + return false; + } + try { + facade.getResolver(); + return true; + } + catch (RemoteException e) { + return false; + } + } + + @Override + public void clear() { + mySupport.stopAll(true); + } +} diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/RemoteExternalSystemFacadeImpl.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/RemoteExternalSystemFacadeImpl.java new file mode 100644 index 000000000000..16d181ea7bc3 --- /dev/null +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/RemoteExternalSystemFacadeImpl.java @@ -0,0 +1,188 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.externalSystem.service; + +import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings; +import com.intellij.openapi.externalSystem.service.project.ExternalSystemProjectResolver; +import com.intellij.openapi.externalSystem.task.ExternalSystemTaskManager; +import com.intellij.util.Alarm; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.rmi.RemoteException; +import java.rmi.server.UnicastRemoteObject; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * @author Denis Zhdanov + * @since 8/9/13 4:28 PM + */ +public class RemoteExternalSystemFacadeImpl extends AbstractExternalSystemFacadeImpl { + + private static final long DEFAULT_REMOTE_PROCESS_TTL_IN_MS = TimeUnit.MILLISECONDS.convert(3, TimeUnit.MINUTES); + + private final AtomicInteger myCallsInProgressNumber = new AtomicInteger(); + private final Alarm myShutdownAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); + private final AtomicLong myTtlMs = new AtomicLong(DEFAULT_REMOTE_PROCESS_TTL_IN_MS); + + private volatile boolean myStdOutputConfigured; + + public RemoteExternalSystemFacadeImpl(@NotNull Class> projectResolverClass, + @NotNull Class> buildManagerClass) + throws IllegalAccessException, InstantiationException + { + super(projectResolverClass, buildManagerClass); + updateAutoShutdownTime(); + } + + @SuppressWarnings("unchecked") + public static void main(String[] args) throws Exception { + if (args.length < 1) { + throw new IllegalArgumentException( + "Can't create external system facade. Reason: given arguments don't contain information about external system resolver to use"); + } + final Class> resolverClass = (Class>)Class.forName(args[0]); + if (!ExternalSystemProjectResolver.class.isAssignableFrom(resolverClass)) { + throw new IllegalArgumentException(String.format( + "Can't create external system facade. Reason: given external system resolver class (%s) must be IS-A '%s'", + resolverClass, + ExternalSystemProjectResolver.class)); + } + + if (args.length < 2) { + throw new IllegalArgumentException( + "Can't create external system facade. Reason: given arguments don't contain information about external system build manager to use" + ); + } + final Class> buildManagerClass = (Class>)Class.forName(args[1]); + if (!ExternalSystemProjectResolver.class.isAssignableFrom(resolverClass)) { + throw new IllegalArgumentException(String.format( + "Can't create external system facade. Reason: given external system build manager (%s) must be IS-A '%s'", + buildManagerClass, ExternalSystemTaskManager.class + )); + } + + RemoteExternalSystemFacadeImpl facade = new RemoteExternalSystemFacadeImpl(resolverClass, buildManagerClass); + facade.init(); + start(facade); + } + + @SuppressWarnings({"IOResourceOpenedButNotSafelyClosed", "unchecked", "UseOfSystemOutOrSystemErr"}) + @Override + protected , C extends I> I createService(@NotNull Class interfaceClass, @NotNull final C impl) + throws ClassNotFoundException, IllegalAccessException, InstantiationException, RemoteException + { + if (!myStdOutputConfigured) { + myStdOutputConfigured = true; + System.setOut(new LineAwarePrintStream(System.out)); + System.setErr(new LineAwarePrintStream(System.err)); + } + + I proxy = (I)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + myCallsInProgressNumber.incrementAndGet(); + try { + return method.invoke(impl, args); + } + finally { + myCallsInProgressNumber.decrementAndGet(); + updateAutoShutdownTime(); + } + } + }); + return (I)UnicastRemoteObject.exportObject(proxy, 0); + } + + @Override + public void applySettings(@NotNull S settings) throws RemoteException { + super.applySettings(settings); + long ttl = settings.getRemoteProcessIdleTtlInMs(); + if (ttl > 0) { + myTtlMs.set(ttl); + } + } + + /** + * Schedules automatic process termination in {@code #REMOTE_GRADLE_PROCESS_TTL_IN_MS} milliseconds. + *

+ * Rationale: it's possible that IJ user performs gradle related activity (e.g. import from gradle) when the works purely + * at IJ. We don't want to keep remote process that communicates with the gradle api then. + */ + private void updateAutoShutdownTime() { + myShutdownAlarm.cancelAllRequests(); + myShutdownAlarm.addRequest(new Runnable() { + @Override + public void run() { + if (myCallsInProgressNumber.get() > 0) { + updateAutoShutdownTime(); + return; + } + System.exit(0); + } + }, (int)myTtlMs.get()); + } + + @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") + private static class LineAwarePrintStream extends PrintStream { + private LineAwarePrintStream(@NotNull final PrintStream delegate) { + super(new OutputStream() { + + @NotNull private final StringBuilder myBuffer = new StringBuilder(); + + @Override + public void write(int b) throws IOException { + char c = (char)b; + myBuffer.append(Character.toString(c)); + if (c == '\n') { + doFlush(); + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + int start = off; + int maxOffset = off + len; + for (int i = off; i < maxOffset; i++) { + if (b[i] == '\n') { + myBuffer.append(new String(b, start, i - start + 1)); + doFlush(); + start = i + 1; + } + } + + if (start < maxOffset) { + myBuffer.append(new String(b, start, maxOffset - start)); + } + } + + private void doFlush() { + delegate.print(myBuffer.toString()); + delegate.flush(); + myBuffer.setLength(0); + } + }); + } + } + +} diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/wrapper/ExternalSystemFacadeWrapper.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/wrapper/ExternalSystemFacadeWrapper.java index 3aed54aa7c25..81ddcdca9405 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/wrapper/ExternalSystemFacadeWrapper.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/wrapper/ExternalSystemFacadeWrapper.java @@ -35,6 +35,11 @@ public class ExternalSystemFacadeWrapper getDelegate() { + return myDelegate; + } + @NotNull @Override public RemoteExternalSystemProjectResolver getResolver() throws RemoteException, IllegalStateException { diff --git a/platform/platform-resources-en/src/misc/registry.properties b/platform/platform-resources-en/src/misc/registry.properties index 816fab1042fe..876919883124 100644 --- a/platform/platform-resources-en/src/misc/registry.properties +++ b/platform/platform-resources-en/src/misc/registry.properties @@ -309,3 +309,5 @@ linux.native.menu=false linux.native.menu.description=Enables native menu on Ubuntu windows.jumplist=false windows.jumplist.description=Enables JumpLists on Windows + +external.system.in.process=false \ No newline at end of file diff --git a/platform/platform-resources/src/META-INF/ExternalSystemExtensions.xml b/platform/platform-resources/src/META-INF/ExternalSystemExtensions.xml index 77257eb660af..f2f4435534ed 100644 --- a/platform/platform-resources/src/META-INF/ExternalSystemExtensions.xml +++ b/platform/platform-resources/src/META-INF/ExternalSystemExtensions.xml @@ -5,6 +5,8 @@ + +