/******************************************************************************* * Copyright 2014 Google Inc. All Rights Reserved. * * 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 * * 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.google.gwt.eclipse.wtp; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.UUID; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IDebugEventSetListener; 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.IStreamListener; import org.eclipse.debug.core.model.IProcess; import org.eclipse.debug.core.model.IStreamMonitor; import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.eclipse.wst.common.project.facet.core.IFacetedProject; import org.eclipse.wst.server.core.IModule; import org.eclipse.wst.server.core.IServer; import org.eclipse.wst.server.core.IServerListener; import org.eclipse.wst.server.core.ServerEvent; import org.eclipse.wst.server.core.ServerUtil; import org.eclipse.wst.server.core.internal.IModulePublishHelper; import org.eclipse.wst.server.core.model.IURLProvider; import org.osgi.framework.BundleContext; import com.google.gwt.eclipse.core.launch.GWTLaunchConstants; import com.google.gwt.eclipse.core.launch.GwtSuperDevModeLaunchConfiguration; import com.google.gwt.eclipse.core.launch.util.GwtSuperDevModeCodeServerLaunchUtil; import com.google.gwt.eclipse.core.properties.GWTProjectProperties; import com.google.gwt.eclipse.oophm.model.LaunchConfiguration; import com.google.gwt.eclipse.oophm.model.WebAppDebugModel; import com.google.gwt.eclipse.wtp.utils.GwtFacetUtils; /** * GWT WTP plug-in life-cycle. * * * This will start/stop the code server with web server runtime, but won't figure out the launcher directory. <br/> * IPath deployPath = server.getModuleDeployDirectory(modules[0]); */ public final class GwtWtpPlugin extends AbstractUIPlugin { public static final String PLUGIN_ID = "com.gwtplugins.gwt.eclipse.wtp"; private static GwtWtpPlugin INSTANCE; private static HashSet<String[]> commandsToExecuteAtExit = new HashSet<String[]>(); private static List<String> launchUrls = new ArrayList<String>(); public static IStatus createErrorStatus(String mess, Exception e) { return new Status(IStatus.ERROR, PLUGIN_ID, -1, mess, e); } public static GwtWtpPlugin getInstance() { return INSTANCE; } public static void logMessage(String msg) { msg = msg == null ? "GWT" : "GWT: " + msg; Status status = new Status(IStatus.INFO, PLUGIN_ID, 1, msg, null); getInstance().getLog().log(status); } public static void logError(String msg) { logError(msg, null); } public static void logError(String msg, Throwable e) { msg = msg == null ? "GWT Error" : "GWT: " + msg; Status status = new Status(IStatus.ERROR, PLUGIN_ID, 1, msg, e); getInstance().getLog().log(status); } public static void logMessage(Throwable e) { logError(null, e); } private IDebugEventSetListener serverProcessListener; /** * Observe the CodeServer console output */ private IStreamListener consoleStreamListenerCodeServer; private IStreamMonitor streamMonitorCodeServer; private IServerListener serverListener; private DebugEvent debugEvent; private boolean startedCodeServer; public GwtWtpPlugin() { INSTANCE = this; } /** * When a Server runtime server is started and terminated, and the project has a GWT Facet, start and stop the GWT * Super Dev Mode Code Server with runtime server. */ @Override public void start(BundleContext context) throws Exception { super.start(context); // Observe launch events that are from the WTP server serverProcessListener = new IDebugEventSetListener() { @Override public void handleDebugEvents(DebugEvent[] events) { if (events != null) { onDebugEvents(events); } } }; DebugPlugin.getDefault().addDebugEventListener(serverProcessListener); } protected void onDebugEvents(DebugEvent[] events) { for (int i = 0; i < events.length; i++) { try { onDebugEvent(events[i]); } catch (CoreException e) { logError("Error processing debug events " + e.getMessage()); e.printStackTrace(); } } } /** * Start or stop processes automatically. Start or stop the CodeServer. */ private void onDebugEvent(DebugEvent event) throws CoreException { if (!(event.getSource() instanceof IProcess)) { return; } IProcess runtimeProcess = (IProcess) event.getSource(); ILaunch launch = runtimeProcess.getLaunch(); ILaunchConfiguration launchConfig = launch.getLaunchConfiguration(); if (launchConfig == null) { return; } IServer server = getServerFromLaunchConfig(launch); ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); ILaunchConfigurationType sdmCodeServerType = launchManager .getLaunchConfigurationType(GwtSuperDevModeLaunchConfiguration.TYPE_ID); if (launchConfig.getType().equals(sdmCodeServerType)) { if (event.getKind() == DebugEvent.CREATE) { onAfterCodeServerStarted(event); } else if (event.getKind() == DebugEvent.TERMINATE) { onAfterCodeServerTerminated(event); } } // pass along the event to server started. if (server != null && server.getServerState() == IServer.STATE_STARTING) { debugEvent = event; startedCodeServer = false; // safety reset } // WTP Server Start/Stop if (server != null && serverListener == null) { // listen for server started. It throws two events... serverListener = new IServerListener() { @Override public void serverChanged(ServerEvent event) { if (event.getState() == IServer.STATE_STARTED && !startedCodeServer) { startedCodeServer = true; onServerStarted(debugEvent); } } }; server.addServerListener(serverListener); } if (server != null && (server.getServerState() == IServer.STATE_STOPPING || server.getServerState() == IServer.STATE_STOPPED)) { // Server Stop: Delineate event for Server Launching and then possibly terminate SDM onServerStopped(event); serverListener = null; startedCodeServer = false; onAfterWebServerTerminated(event); } } private void onAfterCodeServerTerminated(DebugEvent event) { if (streamMonitorCodeServer == null) { return; } streamMonitorCodeServer.removeListener(consoleStreamListenerCodeServer); } private void onAfterCodeServerStarted(DebugEvent event) { if (!(event.getSource() instanceof IProcess)) { return; } IProcess runtimeProcess = (IProcess) event.getSource(); final ILaunch launch = runtimeProcess.getLaunch(); IProcess[] processes = launch.getProcesses(); final IProcess process = processes[0]; // Look for the links in the sdm console output consoleStreamListenerCodeServer = new IStreamListener() { @Override public void streamAppended(String text, IStreamMonitor monitor) { displayCodeServerUrlInDevMode(launch, text); } }; // Listen to Console output streamMonitorCodeServer = process.getStreamsProxy().getOutputStreamMonitor(); streamMonitorCodeServer.addListener(consoleStreamListenerCodeServer); } private void onAfterWebServerTerminated(DebugEvent event) { // nothing at the moment } private void onAfterWebServerStarted(DebugEvent event) { if (!(event.getSource() instanceof IProcess)) { return; } // nothing at the moment } private void displayCodeServerUrlInDevMode(final ILaunch launch, String text) { if (!text.contains("http")) { return; } // Extract URL http://localhost:9876/ String url = text.replaceAll(".*(http.*).*?", "$1").trim(); if (url.matches(".*/")) { url = url.substring(0, url.length() - 1); launchUrls.add(url); } // Dev Mode View, add url LaunchConfiguration lc = WebAppDebugModel.getInstance().addOrReturnExistingLaunchConfiguration(launch, "", null); lc.setLaunchUrls(launchUrls); } private IServer getServerFromLaunchConfig(ILaunch launch) { ILaunchConfiguration launchConfig = launch.getLaunchConfiguration(); if (launchConfig == null) { return null; } IServer server = null; try { server = ServerUtil.getServer(launchConfig); } catch (CoreException e) { logError("getServerFromLaunchConfig: Getting the WTP server error.", e); return null; } return server; } private void addServerUrlsToDevModeView(ILaunch launch) { IServer server = getServerFromLaunchConfig(launch); if (server == null) { logMessage("posiblyLaunchGwtSuperDevModeCodeServer: No WTP server found."); return; } IModule[] modules = server.getModules(); if (modules == null || modules.length == 0) { return; } IModule rootMod = modules[0]; if (rootMod == null) { return; } // First clear the previous urls, before adding new ones launchUrls.clear(); String url = getServerUrl(server, rootMod); if (url != null) { launchUrls.add(url); } } private String getServerUrl(IServer server, IModule rootMod) { if (server == null || rootMod == null) { return null; } IURLProvider urlProvider = (IURLProvider) server.loadAdapter(IURLProvider.class, null); if (urlProvider == null) { return null; } URL url = urlProvider.getModuleRootURL(rootMod); if (url == null) { return null; } return url.toString(); } protected void onServerStopped(DebugEvent event) { logMessage("posiblyTerminateGwtSuperDevModeCodeServer: Stopping GWT Super Dev Mode Code Server."); if (!(event.getSource() instanceof IProcess)) { return; } IProcess runtimeProcess = (IProcess) event.getSource(); ILaunch serverLaunch = runtimeProcess.getLaunch(); ILaunchConfiguration launchConfig = serverLaunch.getLaunchConfiguration(); IServer server = null; try { server = ServerUtil.getServer(launchConfig); } catch (CoreException e) { logError("posiblyLaunchGwtSuperDevModeCodeServer: Could get the WTP server.", e); return; } if (server == null) { logMessage("posiblyLaunchGwtSuperDevModeCodeServer: No WTP server found."); return; } IFacetedProject gwtFacetedProject = GwtFacetUtils.getGwtFacetedProject(server); // If one of the server modules has a gwt facet if (gwtFacetedProject == null) { logMessage("posiblyLaunchGwtSuperDevModeCodeServer: Does not have a GWT Facet."); return; } // Sync Option - If the sync is off, ignore stopping the server if (!GWTProjectProperties.getFacetSyncCodeServer(gwtFacetedProject.getProject())) { logMessage("posiblyLaunchGwtSuperDevModeCodeServer: GWT Facet project properties, the code server sync is off."); return; } ILaunchConfiguration serverLaunchConfig = serverLaunch.getLaunchConfiguration(); if (serverLaunchConfig == null) { return; } ILaunchConfigurationType serverType = null; try { serverType = serverLaunchConfig.getType(); } catch (CoreException e) { logError("possiblyRemoveLaunchConfiguration: Could not retrieve the launch config type.", e); return; } String serverLaunchId; try { serverLaunchId = serverLaunchConfig.getAttribute(GWTLaunchConstants.SUPERDEVMODE_LAUNCH_ID, "NoId"); } catch (CoreException e1) { serverLaunchId = "NoId"; return; } try { ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); ILaunchConfigurationType sdmcodeServerType = launchManager .getLaunchConfigurationType(GwtSuperDevModeLaunchConfiguration.TYPE_ID); ILaunch[] launches = launchManager.getLaunches(); if (launches == null || launches.length == 0) { logMessage( "possiblyRemoveLaunchConfiguration: Launches is empty or null. Can't find the GWT sdm launch config"); return; } for (ILaunch launch : launches) { launchConfig = launch.getLaunchConfiguration(); String launcherId = launchConfig.getAttribute(GWTLaunchConstants.SUPERDEVMODE_LAUNCH_ID, (String) null); // If its the sdm code server if (sdmcodeServerType.equals(serverType)) { // TODO ? remove listener on console } else if (!sdmcodeServerType.equals(serverType) && sdmcodeServerType.equals(launch.getLaunchConfiguration().getType()) && serverLaunchId.equals(launcherId)) { // Skip if it's the Super Dev Mode Code Server terminating, so it doesn't // Terminate if the server terminated and they both have the same launcher id. launch.terminate(); } } } catch (CoreException e) { logError("possiblyRemoveLaunchConfiguration: Couldn't stop the Super Dev Mode Code Server.", e); } } private String setLauncherIdToWtpRunTimeLaunchConfig(ILaunchConfiguration launchConfig) { String launcherId = getLaunchId(); logMessage("setLauncherIdToWtpRunTimeLaunchConfig: Adding server launcherId id=" + getLaunchId()); try { ILaunchConfigurationWorkingCopy launchConfigWorkingCopy = launchConfig.getWorkingCopy(); launchConfigWorkingCopy.setAttribute(GWTLaunchConstants.SUPERDEVMODE_LAUNCH_ID, launcherId); launchConfigWorkingCopy.doSave(); } catch (CoreException e) { logError("posiblyLaunchGwtSuperDevModeCodeServer: Couldn't add server Launcher Id attribute.", e); } return launcherId; } private String getLaunchId() { return UUID.randomUUID().toString(); } /** * Possibly start the GWT Super Dev Mode CodeServer. <br/> * <br/> * This starts as separate process, which allows for custom args modification. <br/> * It adds a launcher id to both processes for reference. <br/> * 1. Get it from classic launch config <br/> * 2. Get it from server VM properties <br/> */ protected void onServerStarted(DebugEvent event) { onAfterWebServerStarted(event); IProcess runtimeProcess = (IProcess) event.getSource(); ILaunch launch = runtimeProcess.getLaunch(); ILaunchConfiguration launchConfig = launch.getLaunchConfiguration(); String launchMode = launch.getLaunchMode(); IServer server = null; try { server = ServerUtil.getServer(launchConfig); } catch (CoreException e) { logError("possiblyLaunchGwtSuperDevModeCodeServer: Could get the WTP server.", e); return; } if (server == null) { logMessage("possiblyLaunchGwtSuperDevModeCodeServer: No WTP server runtime found."); return; } IFacetedProject gwtFacetedProject = GwtFacetUtils.getGwtFacetedProject(server); // If one of the server modules has a gwt facet if (gwtFacetedProject == null) { logMessage("possiblyLaunchGwtSuperDevModeCodeServer: Does not have a GWT Facet."); return; } // Sync Option - the sync is off, ignore stopping the server if (!GWTProjectProperties.getFacetSyncCodeServer(gwtFacetedProject.getProject())) { logMessage("possiblyLaunchGwtSuperDevModeCodeServer: GWT Facet project properties, the code server sync is off."); return; } /** * Get the war output path for the `-launcherDir` in SDM launcher */ String launcherDir = getLauncherDirectory(server, launchConfig, gwtFacetedProject); // LauncherId used to reference and terminate the the process String launcherId = setLauncherIdToWtpRunTimeLaunchConfig(launchConfig); logMessage("possiblyLaunchGwtSuperDevModeCodeServer: Launching GWT Super Dev Mode CodeServer. launcherId=" + launcherId + " launcherDir=" + launcherDir); // Just in case if (launchMode == null) { // run the code server, no need to debug it launchMode = "run"; } if (launcherId == null) { // ids to link two processes together logMessage("possiblyLaunchGwtSuperDevModeCodeServer: No launcherId."); } // Add server urls to DevMode view for easy clicking on addServerUrlsToDevModeView(launch); // Creates ore launches an existing Super Dev Mode Code Server process GwtSuperDevModeCodeServerLaunchUtil.launch(gwtFacetedProject.getProject(), launchMode, launcherDir, launcherId); } /** * Return the launcher directory path. * * The -launcherDir war/output/path is the war deployment directory * * @param server * @param launchConfig * @param gwtFacetedProject * @return the launcher directory or war output path */ private String getLauncherDirectory(IServer server, ILaunchConfiguration launchConfig, IFacetedProject gwtFacetedProject) { String launcherDir = null; // The root module will be the server module // The root module may have children such as, client, shared // The root module may not have any children for (IModule[] module : getAllModules(server)) { if (module[module.length - 1].getProject() == gwtFacetedProject.getProject()) { // Child modules are overlaid or included in the root module IPath path = null; if (server instanceof IModulePublishHelper) { path = ((IModulePublishHelper) server).getPublishDirectory(new IModule[] { module[0] }); } else { IModulePublishHelper helper = server.getAdapter(IModulePublishHelper.class); if (helper != null) { path = helper.getPublishDirectory(new IModule[] { module[0] }); } } // example: // /Users/branflake2267/Documents/runtime-EclipseApplication/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/broyer-sandbox-server launcherDir = path == null ? null : path.toOSString(); if (launcherDir != null) { return launcherDir; } } } // Get the the war output path from classic launch configuration working directory // Also used GaeServerBehaviour.setupLaunchConfig(...) try { launcherDir = launchConfig.getAttribute(IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY, (String) null); } catch (CoreException e) { logMessage( "posiblyLaunchGwtSuperDevModeCodeServer: Couldn't get working directory from launchConfig IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY."); } return launcherDir; } /** * Return all the modules on the server, including child modules. */ private List<IModule[]> getAllModules(IServer server) { List<IModule[]> modules = new ArrayList<>(); // todo: add grandchildren for (IModule root : server.getModules()) { modules.add(new IModule[] {root}); for (IModule child : server.getChildModules(new IModule[] {root}, null)) { modules.add(new IModule[] {root, child}); } } return modules; } @Override public void stop(BundleContext v) throws Exception { DebugPlugin.getDefault().removeDebugEventListener(serverProcessListener); for (String[] command : commandsToExecuteAtExit) { try { logError(">>> " + command[0], null); BufferedReader input = new BufferedReader( new InputStreamReader(Runtime.getRuntime().exec(command).getInputStream())); String line = null; while ((line = input.readLine()) != null) { logError(">>> " + line, null); } input.close(); } catch (Throwable ex) { logMessage("Error executing process:\n" + ex); } } super.stop(v); } }