/******************************************************************************* * Copyright (c) 2015, 2016 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.boot.dash.cloudfoundry; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationType; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.model.IDebugTarget; import org.eclipse.debug.core.model.IProcess; import org.springframework.ide.eclipse.boot.core.BootActivator; import org.springframework.ide.eclipse.boot.dash.BootDashActivator; import org.springframework.ide.eclipse.boot.dash.model.AbstractBootDashModel; import org.springframework.ide.eclipse.boot.dash.model.BootDashModel; import org.springframework.ide.eclipse.boot.dash.model.BootDashViewModel; import org.springframework.ide.eclipse.boot.launch.BootLaunchConfigurationDelegate; import org.springframework.ide.eclipse.boot.launch.devtools.BootDevtoolsClientLaunchConfigurationDelegate; import org.springframework.ide.eclipse.boot.util.ProcessListenerAdapter; import org.springframework.ide.eclipse.boot.util.ProcessTracker; import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil; /** * @author Kris De Volder */ public class DevtoolsUtil { private static final String TARGET_ID = "boot.dash.target.id"; private static final String APP_NAME = "boot.dash.cloudfoundry.app-name"; private static final QualifiedName REMOTE_CLIENT_SECRET_PROPERTY = new QualifiedName(BootDashActivator.PLUGIN_ID, "spring.devtools.remote.secret"); private static final String JAVA_OPTS_ENV_VAR = "JAVA_OPTS"; private static final String REMOTE_SECRET_JVM_ARG = "-Dspring.devtools.remote.secret="; // private static final String REMOTE_DEBUG_JVM_ARGS = "-Dspring.devtools.restart.enabled=false -Xdebug -Xrunjdwp:server=y,transport=dt_socket,suspend=n"; private static ILaunchManager getLaunchManager() { return DebugPlugin.getDefault().getLaunchManager(); } private static ILaunchConfigurationType getConfigurationType() { return getLaunchManager().getLaunchConfigurationType(BootDevtoolsClientLaunchConfigurationDelegate.TYPE_ID); } private static ILaunchConfigurationWorkingCopy createConfiguration(IProject project, String host) throws CoreException { ILaunchConfigurationType configType = getConfigurationType(); String projectName = project.getName(); ILaunchConfigurationWorkingCopy wc = configType.newInstance(null, getLaunchManager().generateLaunchConfigurationName("cf-devtools-client["+projectName+"]")); BootLaunchConfigurationDelegate.setProject(wc, project); BootDevtoolsClientLaunchConfigurationDelegate.setRemoteUrl(wc, remoteUrl(host)); wc.setMappedResources(new IResource[] {project}); return wc; } public static String remoteUrl(String host) { return "http://"+host; } public static ILaunch launchDevtools(IProject project, String host, String debugSecret, CloudAppDashElement cde, String mode, IProgressMonitor monitor) throws CoreException { if (host==null) { throw ExceptionUtil.coreException("Can not launch devtools client: Host not specified"); } ILaunchConfiguration conf = getOrCreateLaunchConfig(project, host, debugSecret, cde); return conf.launch(mode, monitor == null ? new NullProgressMonitor() : monitor); } private static ILaunchConfiguration getOrCreateLaunchConfig(IProject project, String host, String debugSecret, CloudAppDashElement cde) throws CoreException { ILaunchConfiguration existing = findConfig(project, host); ILaunchConfigurationWorkingCopy wc; if (existing!=null) { wc = existing.getWorkingCopy(); } else { wc = createConfiguration(project, host); } BootDevtoolsClientLaunchConfigurationDelegate.setRemoteSecret(wc, debugSecret); setElement(wc, cde); return wc.doSave(); } private static ILaunchConfiguration findConfig(IProject project, String host) { String remoteUrl = remoteUrl(host); try { for (ILaunchConfiguration c : getLaunchManager().getLaunchConfigurations(getConfigurationType())) { if (project.equals(BootLaunchConfigurationDelegate.getProject(c)) && remoteUrl.equals(BootDevtoolsClientLaunchConfigurationDelegate.getRemoteUrl(c))) { return c; } } } catch (CoreException e) { BootActivator.log(e); } return null; } private static List<ILaunch> findLaunches(IProject project, String host) { String remoteUrl = remoteUrl(host); List<ILaunch> launches = new ArrayList<>(); for (ILaunch l : getLaunchManager().getLaunches()) { try { ILaunchConfiguration c = l.getLaunchConfiguration(); if (c!=null) { if (project.equals(BootLaunchConfigurationDelegate.getProject(c)) && remoteUrl.equals(BootDevtoolsClientLaunchConfigurationDelegate.getRemoteUrl(c))) { launches.add(l); } } } catch (Exception e) { BootActivator.log(e); } } return launches; } public static boolean isDevClientAttached(CloudAppDashElement cde, String launchMode) { IProject project = cde.getProject(); if (project!=null) { // else not associated with a local project... can't really attach debugger then String host = cde.getLiveHost(); if (host!=null) { // else app not running, can't attach debugger then return isLaunchMode(findLaunches(project, host), launchMode); } } return false; } private static boolean isLaunchMode(List<ILaunch> launches, String launchMode) { for (ILaunch l : launches) { if (!l.isTerminated()) { if (ILaunchManager.DEBUG_MODE.equals(launchMode) && launchMode.equals(l.getLaunchMode())) { for (IDebugTarget p : l.getDebugTargets()) { if (!p.isDisconnected() && !p.isTerminated()) { return true; } } } else if (ILaunchManager.RUN_MODE.equals(launchMode) && launchMode.equals(l.getLaunchMode())) { for (IProcess p : l.getProcesses()) { if (!p.isTerminated()) { return true; } } } else if (launchMode == null) { // Launch mode not specified? Launch is not terminated hence just return true return true; } } } return false; } public static void launchDevtools(CloudAppDashElement cde, String debugSecret, String mode, IProgressMonitor monitor) throws CoreException { launchDevtools(cde.getProject(), cde.getLiveHost(), debugSecret, cde, mode, monitor); } public static void setElement(ILaunchConfigurationWorkingCopy l, CloudAppDashElement cde) { //Tag the launch so we can easily determine what CDE it belongs to later. l.setAttribute(TARGET_ID, cde.getTarget().getId()); l.setAttribute(APP_NAME, cde.getName()); } public static boolean isLaunchFor(ILaunch l, CloudAppDashElement cde) { String targetId = getAttribute(l, TARGET_ID); String appName = getAttribute(l, APP_NAME); if (targetId!=null && appName!=null) { return targetId.equals(cde.getTarget().getId()) && appName.equals(cde.getName()); } return false; } /** * Retreive corresponding CDE for a given launch. */ public static CloudAppDashElement getElement(ILaunchConfiguration l, BootDashViewModel model) { String targetId = getAttribute(l, TARGET_ID); String appName = getAttribute(l, APP_NAME); if (targetId!=null && appName!=null) { BootDashModel section = model.getSectionByTargetId(targetId); if (section instanceof CloudFoundryBootDashModel) { CloudFoundryBootDashModel cfModel = (CloudFoundryBootDashModel) section; return cfModel.getApplication(appName); } } return null; } public static CloudAppDashElement getElement(ILaunch l, BootDashViewModel viewModel) { ILaunchConfiguration conf = l.getLaunchConfiguration(); if (conf!=null) { return getElement(conf, viewModel); } return null; } private static String getAttribute(ILaunch l, String name) { try { ILaunchConfiguration c = l.getLaunchConfiguration(); if (c!=null) { return c.getAttribute(name, (String)null); } } catch (Exception e) { BootActivator.log(e); } return null; } private static String getAttribute(ILaunchConfiguration l, String name) { try { return l.getAttribute(name, (String)null); } catch (CoreException e) { BootActivator.log(e); return null; } } public static ProcessTracker createProcessTracker(final BootDashViewModel viewModel) { return new ProcessTracker(new ProcessListenerAdapter() { @Override public void debugTargetCreated(ProcessTracker tracker, IDebugTarget target) { handleStateChange(target.getLaunch()); } @Override public void debugTargetTerminated(ProcessTracker tracker, IDebugTarget target) { handleStateChange(target.getLaunch()); } @Override public void processTerminated(ProcessTracker tracker, IProcess process) { handleStateChange(process.getLaunch()); } @Override public void processCreated(ProcessTracker tracker, IProcess process) { handleStateChange(process.getLaunch()); } private void handleStateChange(ILaunch l) { CloudAppDashElement e = DevtoolsUtil.getElement(l, viewModel); if (e!=null) { BootDashModel model = e.getBootDashModel(); model.notifyElementChanged(e); } } }); } public static DevtoolsDebugTargetDisconnector createDebugTargetDisconnector(AbstractBootDashModel model) { return new DevtoolsDebugTargetDisconnector(model); } public static void disconnectDevtoolsClientsFor(CloudAppDashElement e) { ILaunchManager lm = getLaunchManager(); for (ILaunch l : lm.getLaunches()) { if (!l.isTerminated() && isLaunchFor(l, e)) { if (l.canTerminate()) { try { l.terminate(); } catch (DebugException de) { BootActivator.log(de); } } } } } public static String getSecret(IProject project) throws CoreException { String secret = project.getPersistentProperty(REMOTE_CLIENT_SECRET_PROPERTY); if (secret == null) { secret = RandomStringUtils.randomAlphabetic(20); project.setPersistentProperty(REMOTE_CLIENT_SECRET_PROPERTY, secret); } return secret; } public static boolean isEnvVarSetupForRemoteClient(Map<String, String> envVars, String secret) { String javaOpts = envVars.get(JAVA_OPTS_ENV_VAR); if (javaOpts!=null && javaOpts.matches("(.*\\s+|^)" + REMOTE_SECRET_JVM_ARG + secret + "(\\s+.*|$)")) { // if (runOrDebug == RunState.DEBUGGING) { // return javaOpts.matches("(.*\\s+|^)" + REMOTE_DEBUG_JVM_ARGS + "(\\s+.*|$)"); // } else { // return !javaOpts.matches("(.*\\s+|^)" + REMOTE_DEBUG_JVM_ARGS + "(\\s+.*|$)"); // } return true; } return false; } public static void setupEnvVarsForRemoteClient(Map<String, String> envVars, String secret) { String javaOpts = clearJavaOpts(envVars.get(JAVA_OPTS_ENV_VAR)); StringBuilder sb = javaOpts == null ? new StringBuilder() : new StringBuilder(javaOpts); if (sb.length() > 0) { sb.append(' '); } sb.append(REMOTE_SECRET_JVM_ARG); sb.append(secret); // if (runOrDebug == RunState.DEBUGGING) { // sb.append(' '); // sb.append(REMOTE_DEBUG_JVM_ARGS); // } envVars.put(JAVA_OPTS_ENV_VAR, sb.toString()); } private static String clearJavaOpts(String opts) { if (opts!=null) { // opts = opts.replaceAll(REMOTE_DEBUG_JVM_ARGS + "\\s*", ""); opts = opts.replaceAll(REMOTE_SECRET_JVM_ARG +"\\w+\\s*", ""); } return opts; } }