/******************************************************************************* * Copyright (c) 2014 Pivotal Software, 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 Software, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.boot.launch.livebean; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.List; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; 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.jdt.launching.IJavaLaunchConfigurationConstants; import org.eclipse.jface.action.IAction; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.ISelection; import org.eclipse.ui.IViewPart; import org.eclipse.ui.PlatformUI; import org.springframework.ide.eclipse.beans.ui.livegraph.model.LiveBeansModel; import org.springframework.ide.eclipse.beans.ui.livegraph.model.LiveBeansModelGenerator; import org.springframework.ide.eclipse.beans.ui.livegraph.views.LiveBeansGraphView; import org.springframework.ide.eclipse.boot.core.BootActivator; import org.springframework.ide.eclipse.boot.core.BootPropertyTester; import org.springframework.ide.eclipse.boot.launch.BootLaunchConfigurationDelegate; import org.springframework.ide.eclipse.boot.launch.livebean.JmxBeanSupport.Feature; import org.springsource.ide.eclipse.commons.frameworks.ui.internal.actions.AbstractActionDelegate; import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil; import org.springsource.ide.eclipse.commons.ui.launch.LaunchUtils; public class OpenLiveBeansGraphAction extends AbstractActionDelegate { private static final String HOST = "127.0.0.1"; @Override public void selectionChanged(IAction action, ISelection sel) { super.selectionChanged(action, sel); action.setEnabled(BootPropertyTester.isBootProject(getSelectedProject())); } /** * @return The first selected project or null if no project is selected. */ private IProject getSelectedProject() { List<IProject> projects = getSelectedProjects(); if (projects!=null && !projects.isEmpty()) { return projects.get(0); } return null; } @Override public void run(IAction action) { try { IProject project = getSelectedProject(); connectToProject(project); } catch (Exception e) { BootActivator.log(e); MessageDialog.openError(getShell(), "Error", ExceptionUtil.getMessage(e)+"\n\n" + "Check the error log for more details"); } } /** * Tries to open live beans view and connect it to a running process associated with the * project. */ private void connectToProject(IProject project) { try { String serviceUrl = getServiceUrl(project); if (serviceUrl==null) { throw ExceptionUtil.coreException("Didn't find a JMX-enabled process for project '"+project.getName()+"'\n"+ "To open the livebeans graph a process must be run with the following or similar VM arguments:\n\n" + JmxBeanSupport.jmxBeanVmArgs("${jmxPort}", EnumSet.of(Feature.LIVE_BEAN_GRAPH)) ); } LiveBeansModel model = LiveBeansModelGenerator.connectToModel(serviceUrl, /*username*/null, /*password*/null, /*appName*/"", project); IViewPart part = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage() .showView(LiveBeansGraphView.VIEW_ID); if (part instanceof LiveBeansGraphView) { ((LiveBeansGraphView) part).setInput(model); } } catch (Exception e) { BootActivator.log(e); MessageDialog.openError(getShell(), "Error", ExceptionUtil.getMessage(e)+"\n\n" + "Check the error log for more details"); } } /** * Determine JMX service url to be used to connect live bean graph on a running JMX-enabled process * associated with given project. * * @return url never null * @throws CoreException with an explanation error message if url can not be determined. */ private String getServiceUrl(IProject project) throws CoreException { //The service url is derived from the jmxport property of an active launch associated // with the property. Therefore, we look for a launch that is associated with project // and sets the corresponding system property as one of its VMArguments. boolean hasActiveProcess = false; //set to true if at least one active process is found // used to create a better error message on failure. for (ILaunchConfiguration c : getLaunchConfigs(project)) { for (ILaunch l : LaunchUtils.getLaunches(c)) { if (!l.isTerminated()) { int jmxPortProp = getActiveJMXPort(l); hasActiveProcess = true; if (jmxPortProp>0) { //Looks like JMX is enabled. return "service:jmx:rmi:///jndi/rmi://" + HOST + ":" + jmxPortProp + "/jmxrmi"; } } } } if (hasActiveProcess) { //There's a active process but looks like JMX arguments are missing throw ExceptionUtil.coreException("Didn't find a JMX-enabled process for project '"+project.getName()+"'\n"+ "To open the livebeans graph a process must be run with the following VM arguments:\n\n" + JmxBeanSupport.jmxBeanVmArgs("${jmxPort}", EnumSet.of(Feature.LIVE_BEAN_GRAPH)) ); } else { throw ExceptionUtil.coreException("No active process found for project '"+project.getName()+"'\n"+ "The Live Bean Graph is created at runtime. The project must be running to open it.\n"+ "Run your project with the 'Run As >> Spring Boot App' context menu then try opening the view again." ); } } private int getActiveJMXPort(ILaunch l) { try { int port = BootLaunchConfigurationDelegate.getJMXPortAsInt(l); if (port > 0) { return port; } //Maybe user configured it manually himself in VM args: ILaunchConfiguration conf = l.getLaunchConfiguration(); if (conf!=null) { if (BootLaunchConfigurationDelegate.getEnableLiveBeanSupport(conf)) { String portStr = getVMSystemProp(conf, JmxBeanSupport.JMX_PORT_PROP); return Integer.parseInt(portStr); } } } catch (Exception e) { BootActivator.log(e); } return 0; } /** * Extract VM argument list from launch config and look for -D<prop>=<value> arguments. * @return the value corresponding to given system prop or null if this value is not found. */ private String getVMSystemProp(ILaunchConfiguration c, String propName) { try { String vmArgs = c.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, (String)null); if (vmArgs!=null) { String[] pieces = DebugPlugin.parseArguments(vmArgs); String lookFor = "-D"+propName+"="; for (String piece : pieces) { if (piece.startsWith(lookFor)) { return piece.substring(lookFor.length()); } } } } catch (Exception e) { BootActivator.log(e); } return null; } /** * Collect the listing of {@link ILaunchConfiguration}s that apply to a given project. */ private static List<ILaunchConfiguration> getLaunchConfigs(IProject project) { String ctypeId = JmxBeanSupport.LAUNCH_CONFIG_TYPE_ID; ILaunchConfigurationType ctype = DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurationType(ctypeId); List<ILaunchConfiguration> candidateConfigs = Collections.emptyList(); try { ILaunchConfiguration[] configs = DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurations(ctype); candidateConfigs = new ArrayList<ILaunchConfiguration>(configs.length); for (int i = 0; i < configs.length; i++) { ILaunchConfiguration config = configs[i]; if (config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, "").equals(project.getName())) { //$NON-NLS-1$ candidateConfigs.add(config); } } } catch (CoreException e) { BootActivator.log(e); } return candidateConfigs; } }