package com.atsebak.embeddedlinuxjvm.commandline;
import com.atsebak.embeddedlinuxjvm.console.EmbeddedLinuxJVMConsoleView;
import com.atsebak.embeddedlinuxjvm.console.EmbeddedLinuxJVMOutputForwarder;
import com.atsebak.embeddedlinuxjvm.deploy.DeploymentTarget;
import com.atsebak.embeddedlinuxjvm.localization.EmbeddedLinuxJVMBundle;
import com.atsebak.embeddedlinuxjvm.protocol.ssh.SSHHandlerTarget;
import com.atsebak.embeddedlinuxjvm.protocol.ssh.jsch.EmbeddedSSHClient;
import com.atsebak.embeddedlinuxjvm.runner.conf.EmbeddedLinuxJVMRunConfiguration;
import com.atsebak.embeddedlinuxjvm.runner.data.EmbeddedLinuxJVMRunConfigurationRunnerParameters;
import com.atsebak.embeddedlinuxjvm.services.ClasspathService;
import com.atsebak.embeddedlinuxjvm.utils.FileUtilities;
import com.atsebak.embeddedlinuxjvm.utils.RemoteCommandLineBuilder;
import com.intellij.debugger.DebuggerManagerEx;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.impl.DebuggerSession;
import com.intellij.execution.*;
import com.intellij.execution.configurations.*;
import com.intellij.execution.executors.DefaultDebugExecutor;
import com.intellij.execution.process.*;
import com.intellij.execution.remote.RemoteConfiguration;
import com.intellij.execution.remote.RemoteConfigurationType;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.ui.ConsoleView;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.ui.content.Content;
import com.intellij.util.NotNullFunction;
import com.intellij.util.PathsList;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class AppCommandLineState extends JavaCommandLineState {
@NonNls
private static final String RUN_CONFIGURATION_NAME_PATTERN = "Embedded Device Debugger (%s)";
@NonNls
private static final String DEBUG_TCP_MESSAGE = "Listening for transport dt_socket at address: %s";
@NotNull
private final EmbeddedLinuxJVMRunConfiguration configuration;
@NotNull
private final RunnerSettings runnerSettings;
@NotNull
private final EmbeddedLinuxJVMOutputForwarder outputForwarder;
@NotNull
private final boolean isDebugMode;
@NotNull
private final Project project;
/**
* Command line state when runner is launch
*
* @param environment
* @param configuration
*/
public AppCommandLineState(@NotNull ExecutionEnvironment environment, @NotNull EmbeddedLinuxJVMRunConfiguration configuration) {
super(environment);
this.configuration = configuration;
this.runnerSettings = environment.getRunnerSettings();
this.project = environment.getProject();
this.isDebugMode = runnerSettings instanceof DebuggingRunnerData;
this.outputForwarder = new EmbeddedLinuxJVMOutputForwarder(EmbeddedLinuxJVMConsoleView.getInstance(project));
this.outputForwarder.attachTo(null);
}
/**
* Gets the debug runner
*
* @param debugPort
* @return
*/
@NotNull
public static String getRunConfigurationName(String debugPort) {
return String.format(RUN_CONFIGURATION_NAME_PATTERN, debugPort);
}
/**
* Creates the console view
* @param executor
* @return
* @throws ExecutionException
*/
@Nullable
@Override
protected ConsoleView createConsole(@NotNull Executor executor) throws ExecutionException {
return EmbeddedLinuxJVMConsoleView.getInstance(project).getConsoleView(true);
}
/**
* Creates the command line view
* @return
* @throws ExecutionException
*/
@Override
protected GeneralCommandLine createCommandLine() throws ExecutionException {
return RemoteCommandLineBuilder.createFromJavaParameters(getJavaParameters(), CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext()), true);
}
/**
* Starts console process
* @return
* @throws ExecutionException
*/
@NotNull
@Override
protected OSProcessHandler startProcess() throws ExecutionException {
final OSProcessHandler handler = JavaCommandLineStateUtil.startProcess(createCommandLine());
ProcessTerminatedListener.attach(handler, project, EmbeddedLinuxJVMBundle.message("pi.console.exited"));
handler.addProcessListener(new ProcessAdapter() {
private void closeSSHConnection() {
try {
EmbeddedLinuxJVMConsoleView instance = EmbeddedLinuxJVMConsoleView.getInstance(project);
JavaStatusChecker javaStatusChecker = new JavaStatusChecker(instance);
javaStatusChecker.forceStopJavaApplication(instance.getSession(), configuration.getRunnerParameters().isRunAsRoot(), configuration.getRunnerParameters().getMainclass());
if (isDebugMode) {
//todo fix tcp connection closing issue random error message showing up, its because remote debugger needs to be closed before embedded console
final DebuggerSession debuggerSession = DebuggerManagerEx.getInstanceEx(project).getContext().getDebuggerSession();
if (debuggerSession == null) {
return;
}
final DebugProcessImpl debugProcess = debuggerSession.getProcess();
if (debugProcess.isDetached() || debugProcess.isDetaching()) {
debugProcess.stop(true);
debugProcess.dispose();
debuggerSession.dispose();
}
}
} catch (Exception e) {
}
}
/**
* closes debug session
*/
private void closeDescriptors() {
//todo remove remote debugger console
final Collection<RunContentDescriptor> descriptors =
ExecutionHelper.findRunningConsoleByTitle(project, title -> AppCommandLineState.getRunConfigurationName(configuration.getRunnerParameters().getPort()).equals(title));
// for (RunContentDescriptor descriptor : descriptors) {
// final Content content = descriptor.getAttachedContent();
// }
}
/**
* Called when user clicks the stop button
*
* @param event
* @param willBeDestroyed
*/
@Override
public void processWillTerminate(ProcessEvent event, boolean willBeDestroyed) {
ProgressManager.getInstance().run(new Task.Backgroundable(project, EmbeddedLinuxJVMBundle.message("pi.closingsession"), true) {
@Override
public void run(@NotNull ProgressIndicator progressIndicator) {
closeSSHConnection();
if (isDebugMode) {
closeDescriptors();
}
}
});
super.processWillTerminate(event, willBeDestroyed);
}
@Override
public void onTextAvailable(ProcessEvent event, Key outputType) {
super.onTextAvailable(event, outputType);
}
});
return handler;
}
/**
* Creates the necessary Java parameters for the application.
*
* @return
* @throws ExecutionException
*/
@Override
protected JavaParameters createJavaParameters() throws ExecutionException {
EmbeddedLinuxJVMConsoleView.getInstance(project).clear();
final JavaParameters javaParams = new JavaParameters();
final ProjectRootManager manager = ProjectRootManager.getInstance(project);
javaParams.setJdk(manager.getProjectSdk());
// All modules to use the same things
Module[] modules = ModuleManager.getInstance(project).getModules();
if (modules.length > 0) {
for (Module module : modules) {
javaParams.configureByModule(module, JavaParameters.JDK_AND_CLASSES);
}
}
// setting jvm config
javaParams.getProgramParametersList().add(configuration.getRunnerParameters().getProgramArguments());
javaParams.getVMParametersList().add(configuration.getRunnerParameters().getVmParameters());
javaParams.setMainClass(configuration.getRunnerParameters().getMainclass());
javaParams.setWorkingDirectory(project.getBasePath());
javaParams.getProgramParametersList().addParametersString(configuration.getOutputFilePath());
final CommandLineTarget commandLineTarget = CommandLineTarget.builder()
.embeddedLinuxJVMRunConfiguration(configuration)
.isDebugging(isDebugMode)
.parameters(javaParams).build();
final PathsList classPath = javaParams.getClassPath();
final Application app = ApplicationManager.getApplication();
//deploy on Non-read thread so can execute right away
app.executeOnPooledThread(() -> ApplicationManager.getApplication().runReadAction(() -> {
try {
ClasspathService service = ServiceManager.getService(project, ClasspathService.class);
List<File> hostLibraries = invokeClassPathResolver(classPath.getPathList(), manager.getProjectSdk());
File classpathArchive = FileUtilities.createClasspathArchive(service.invokeFindDeployedJars(hostLibraries, buildTargetHandler()), project);
invokeDeployment(classpathArchive.getPath(), commandLineTarget);
} catch (Exception e) {
EmbeddedLinuxJVMConsoleView.getInstance(project).print(EmbeddedLinuxJVMBundle.message("pi.connection.failed", e.getMessage()) + "\r\n",
ConsoleViewContentType.ERROR_OUTPUT);
JavaStatusChecker javaStatusChecker = new JavaStatusChecker(null, EmbeddedLinuxJVMConsoleView.getInstance(project));
javaStatusChecker.stopApplication(-1);
}
}));
//invoke later because it reads from other threads(debugging executor)
ProgressManager.getInstance().run(new Task.Backgroundable(project, EmbeddedLinuxJVMBundle.message("pi.deploy"), true) {
@Override
public void run(@NotNull ProgressIndicator progressIndicator) {
if (isDebugMode) {
// progressIndicator(true);
final String initializeMsg = String.format(DEBUG_TCP_MESSAGE, configuration.getRunnerParameters().getPort());
//this should wait until the deployment states that it's listening to the port
while (!outputForwarder.toString().contains(initializeMsg)) {
}
app.invokeLater(() -> closeOldSessionAndDebug(project, configuration.getRunnerParameters()));
}
}
});
return javaParams;
}
private List<File> invokeClassPathResolver(List<String> librariesNeeded, final Sdk sdk) {
List<File> classPaths = new ArrayList<File>();
VirtualFile homeDirectory = sdk.getHomeDirectory();
for (String library : librariesNeeded) {
//filter sdk libraries from classpath because it's too big
if (!library.contains(homeDirectory.getPath())) {
classPaths.add(new File(library));
}
}
return classPaths;
}
/**
* Executes Deploys and Runs App on remote target
* @param projectOutput
* @param commandLineTarget
*/
private void invokeDeployment(String projectOutput, CommandLineTarget commandLineTarget) throws RuntimeConfigurationException, IOException, ClassNotFoundException {
EmbeddedLinuxJVMConsoleView.getInstance(project).print(EmbeddedLinuxJVMBundle.getString("pi.deployment.start"), ConsoleViewContentType.SYSTEM_OUTPUT);
DeploymentTarget target = DeploymentTarget.builder().sshHandlerTarget(buildTargetHandler()).build();
target.upload(new File(projectOutput), commandLineTarget.toString());
}
/**
* Creates debugging settings for server
*
* @param project
* @param debugPort
* @param hostname
* @return
*/
private RunnerAndConfigurationSettings createRunConfiguration(Project project, String debugPort, String hostname) {
final RemoteConfigurationType remoteConfigurationType = RemoteConfigurationType.getInstance();
final ConfigurationFactory factory = remoteConfigurationType.getFactory();
final RunnerAndConfigurationSettings runSettings =
RunManager.getInstance(project).createRunConfiguration(getRunConfigurationName(debugPort), factory);
final RemoteConfiguration configuration = (RemoteConfiguration) runSettings.getConfiguration();
configuration.HOST = hostname;
configuration.PORT = debugPort;
configuration.USE_SOCKET_TRANSPORT = true;
configuration.SERVER_MODE = false;
return runSettings;
}
/**
* Closes old session only for debug mode
*
* @param project
* @param parameters
*/
private void closeOldSession(final Project project, EmbeddedLinuxJVMRunConfigurationRunnerParameters parameters) {
final String configurationName = AppCommandLineState.getRunConfigurationName(parameters.getPort());
final Collection<RunContentDescriptor> descriptors =
ExecutionHelper.findRunningConsoleByTitle(project, configurationName::equals);
if (descriptors.size() > 0) {
final RunContentDescriptor descriptor = descriptors.iterator().next();
final ProcessHandler processHandler = descriptor.getProcessHandler();
final Content content = descriptor.getAttachedContent();
if (processHandler != null && content != null) {
final Executor executor = DefaultDebugExecutor.getDebugExecutorInstance();
if (processHandler.isProcessTerminated()) {
ExecutionManager.getInstance(project).getContentManager()
.removeRunContent(executor, descriptor);
} else {
content.getManager().setSelectedContent(content);
ToolWindow window = ToolWindowManager.getInstance(project).getToolWindow(executor.getToolWindowId());
window.activate(null, false, true);
}
}
}
}
/**
* Closes an old descriptor and creates a new one in debug mode connecting to remote target
*
* @param project
* @param parameters
*/
private void closeOldSessionAndDebug(final Project project, EmbeddedLinuxJVMRunConfigurationRunnerParameters parameters) {
closeOldSession(project, parameters);
runSession(project, parameters);
}
/**
* Runs in remote debug mode using that executioner
*
* @param project
* @param parameters
*/
private void runSession(final Project project, EmbeddedLinuxJVMRunConfigurationRunnerParameters parameters) {
final RunnerAndConfigurationSettings settings = createRunConfiguration(project, parameters.getPort(), parameters.getHostname());
ProgramRunnerUtil.executeConfiguration(project, settings, DefaultDebugExecutor.getDebugExecutorInstance());
}
/**
* Builds SSH Handler
*
* @return
*/
private SSHHandlerTarget buildTargetHandler() {
return SSHHandlerTarget.builder().params(configuration.getRunnerParameters())
.consoleView(EmbeddedLinuxJVMConsoleView.getInstance(project))
.ssh(EmbeddedSSHClient.builder()
.port(configuration.getRunnerParameters().getSshPort())
.hostname(configuration.getRunnerParameters().getHostname())
.password(configuration.getRunnerParameters().getPassword())
.username(configuration.getRunnerParameters().getUsername())
.useKey(configuration.getRunnerParameters().isUsingKey())
.key(configuration.getRunnerParameters().getKeyPath())
.build())
.build();
}
}