| /******************************************************************************* |
| * Copyright (c) 2007, 2013 EclipseSource and others. |
| * 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: |
| * Innoopract Informationssysteme GmbH - initial API and implementation |
| * EclipseSource - ongoing development |
| ******************************************************************************/ |
| package org.eclipse.rap.tools.launch.internal; |
| |
| import static java.util.Arrays.asList; |
| import static org.eclipse.pde.internal.launching.launcher.LauncherUtils.clearWorkspace; |
| |
| import java.io.IOException; |
| import java.net.*; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.core.variables.IStringVariableManager; |
| import org.eclipse.core.variables.VariablesPlugin; |
| import org.eclipse.debug.core.*; |
| import org.eclipse.debug.core.model.RuntimeProcess; |
| import org.eclipse.pde.launching.EquinoxLaunchConfiguration; |
| import org.eclipse.rap.tools.launch.internal.RAPLaunchConfig.BrowserMode; |
| import org.eclipse.rap.tools.launch.internal.util.ErrorUtil; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.ui.*; |
| import org.eclipse.ui.browser.IWebBrowser; |
| import org.eclipse.ui.browser.IWorkbenchBrowserSupport; |
| |
| |
| public final class RAPLaunchDelegate extends EquinoxLaunchConfiguration { |
| |
| // VM argument contants |
| private static final String VMARG_PORT |
| = "-Dorg.osgi.service.http.port="; //$NON-NLS-1$ |
| private static final String VMARG_DEVELOPMENT_MODE |
| = "-Dorg.eclipse.rap.rwt.developmentMode="; //$NON-NLS-1$ |
| private static final String VMARG_SESSION_TIMEOUT |
| = "-Dorg.eclipse.equinox.http.jetty.context.sessioninactiveinterval="; //$NON-NLS-1$ |
| private static final String VMARG_CONTEXT_PATH |
| = "-Dorg.eclipse.equinox.http.jetty.context.path="; //$NON-NLS-1$ |
| |
| private static final int CONNECT_TIMEOUT = 20000; // 20 Seconds |
| |
| private ILaunch launch; |
| private RAPLaunchConfig config; |
| private int port; |
| private final boolean testMode; |
| |
| public RAPLaunchDelegate() { |
| this( false ); |
| } |
| |
| public RAPLaunchDelegate( boolean testMode ) { |
| this.testMode = testMode; |
| } |
| |
| @Override |
| public void launch( ILaunchConfiguration config, |
| String mode, |
| ILaunch launch, |
| IProgressMonitor monitor ) |
| throws CoreException |
| { |
| SubProgressMonitor subMonitor = doPreLaunch( config, launch, monitor ); |
| super.launch( config, mode, launch, subMonitor ); |
| } |
| |
| public SubProgressMonitor doPreLaunch( ILaunchConfiguration config, |
| ILaunch launch, |
| IProgressMonitor monitor ) |
| throws CoreException |
| { |
| // As this is the first method that is called after creating an instance |
| // of RAPLaunchDelegate, store the config and launch parameters to be |
| // accessible from member methods |
| // TODO [rh] find a better way |
| this.launch = launch; |
| this.config = new RAPLaunchConfig( config ); |
| SubProgressMonitor subMonitor; |
| subMonitor = new SubProgressMonitor( monitor, IProgressMonitor.UNKNOWN ); |
| terminateIfRunning( subMonitor ); |
| subMonitor = new SubProgressMonitor( monitor, IProgressMonitor.UNKNOWN ); |
| warnIfPortBusy( subMonitor ); |
| subMonitor = new SubProgressMonitor( monitor, IProgressMonitor.UNKNOWN ); |
| port = determinePort( subMonitor ); |
| if( this.config.getOpenBrowser() ) { |
| registerBrowserOpener(); |
| } |
| return subMonitor; |
| } |
| |
| @Override |
| public String[] getVMArguments( ILaunchConfiguration config ) throws CoreException { |
| List<String> list = new ArrayList<String>(); |
| // ORDER IS CRUCIAL HERE: |
| // Override VM arguments that are specified manually with the values |
| // necessary for the RAP launcher |
| list.addAll( asList( super.getVMArguments( config ) ) ); |
| list.addAll( getRAPVMArguments() ); |
| return toStringArray( list ); |
| } |
| |
| @Override |
| public String[] getProgramArguments( ILaunchConfiguration configuration ) throws CoreException { |
| List<String> programArguments = new ArrayList<String>(); |
| programArguments.addAll( asList( super.getProgramArguments( configuration ) ) ); |
| String dataLocationResolved = getResolvedDataLoacation(); |
| if( dataLocationResolved.length() > 0 ) { |
| programArguments.addAll( asList( "-data", dataLocationResolved ) ); |
| } |
| return toStringArray( programArguments ); |
| } |
| |
| private String getResolvedDataLoacation() throws CoreException { |
| String dataLocation = config.getDataLocation(); |
| return resolveVariables( dataLocation ); |
| } |
| |
| private String resolveVariables( String dataLocation ) throws CoreException { |
| VariablesPlugin variablePlugin = VariablesPlugin.getDefault(); |
| IStringVariableManager stringVariableManager = variablePlugin.getStringVariableManager(); |
| return stringVariableManager.performStringSubstitution( dataLocation ); |
| } |
| |
| private List<String> getRAPVMArguments() throws CoreException { |
| List<String> arguments = new ArrayList<String>(); |
| arguments.add( VMARG_PORT + port ); |
| arguments.add( VMARG_DEVELOPMENT_MODE + config.getDevelopmentMode() ); |
| if( config.getUseSessionTimeout() ) { |
| arguments.add( VMARG_SESSION_TIMEOUT + config.getSessionTimeout() ); |
| } else { |
| arguments.add( VMARG_SESSION_TIMEOUT + RAPLaunchConfig.MIN_SESSION_TIMEOUT ); |
| } |
| if( config.getUseManualContextPath() ) { |
| String contextPath = config.getContextPath(); |
| if( !contextPath.startsWith( URLBuilder.SLASH ) ) { |
| contextPath = URLBuilder.SLASH + contextPath; |
| } |
| if( contextPath.endsWith( URLBuilder.SLASH ) ) { |
| contextPath = contextPath.substring( 0, contextPath.length() - 1 ); |
| } |
| arguments.add( VMARG_CONTEXT_PATH + contextPath ); |
| } |
| return arguments; |
| } |
| |
| private static String[] toStringArray( List<String> list ) { |
| String[] result = new String[ list.size() ]; |
| list.toArray( result ); |
| return result; |
| } |
| |
| private void warnIfPortBusy( SubProgressMonitor monitor ) throws CoreException { |
| String taskName = LaunchMessages.RAPLaunchDelegate_CheckPortTaskName; |
| monitor.beginTask( taskName, IProgressMonitor.UNKNOWN ); |
| try { |
| if( config.getUseManualPort() && isPortBusy( config.getPort() ) ) { |
| DebugPlugin debugPlugin = DebugPlugin.getDefault(); |
| IStatusHandler prompter = debugPlugin.getStatusHandler( promptStatus ); |
| if( prompter != null ) { |
| IStatus status = PortBusyStatusHandler.STATUS; |
| Object resolution = prompter.handleStatus( status, config ); |
| if( Boolean.FALSE.equals( resolution ) ) { |
| String text = LaunchMessages.RAPLaunchDelegate_PortInUse; |
| Object[] args = new Object[] { |
| new Integer( config.getPort() ), |
| config.getName() |
| }; |
| String msg = MessageFormat.format( text, args ); |
| String pluginId = Activator.PLUGIN_ID; |
| Status infoStatus = new Status( IStatus.INFO, pluginId, msg ); |
| throw new CoreException( infoStatus ); |
| } |
| } |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| private int determinePort( IProgressMonitor monitor ) throws CoreException { |
| int result; |
| String taskName = LaunchMessages.RAPLaunchDelegate_DeterminePortTaskName; |
| monitor.beginTask( taskName, IProgressMonitor.UNKNOWN ); |
| try { |
| if( config.getUseManualPort() ) { |
| result = config.getPort(); |
| } else { |
| result = findFreePort(); |
| } |
| } finally { |
| monitor.done(); |
| } |
| return result; |
| } |
| |
| private static int findFreePort() throws CoreException { |
| try { |
| ServerSocket server = new ServerSocket( 0 ); |
| try { |
| return server.getLocalPort(); |
| } finally { |
| server.close(); |
| } |
| } catch( IOException e ) { |
| String msg = "Could not obtain a free port number."; //$NON-NLS-1$ |
| String pluginId = Activator.getPluginId(); |
| Status status = new Status( IStatus.ERROR, pluginId, msg, e ); |
| throw new CoreException( status ); |
| } |
| } |
| |
| private static boolean isPortBusy( int port ) { |
| ServerSocket server = null; |
| try { |
| server = new ServerSocket( port ); |
| } catch( IOException e1 ) { |
| // assume that port is occupied when getting here |
| } |
| if( server != null ) { |
| try { |
| server.close(); |
| } catch( IOException e ) { |
| // ignore |
| } |
| } |
| return server == null; |
| } |
| |
| private URL getUrl() throws CoreException { |
| try { |
| String url = URLBuilder.fromLaunchConfig( config, port, testMode ); |
| return new URL( url ); |
| } catch( MalformedURLException e ) { |
| String msg = "Invalid URL."; //$NON-NLS-1$ |
| String pluginId = Activator.getPluginId(); |
| Status status = new Status( IStatus.ERROR, pluginId, 0, msg, e ); |
| throw new CoreException( status ); |
| } |
| } |
| |
| private void terminateIfRunning( IProgressMonitor monitor ) throws CoreException { |
| String taskName = LaunchMessages.RAPLaunchDelegate_TerminatePreviousTaskName; |
| monitor.beginTask( taskName, IProgressMonitor.UNKNOWN ); |
| try { |
| ILaunch runningLaunch = findRunning(); |
| if( runningLaunch != null ) { |
| terminate( runningLaunch ); |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| private ILaunch findRunning() { |
| ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); |
| ILaunch[] runningLaunches = launchManager.getLaunches(); |
| ILaunch result = null; |
| for( int i = 0; result == null && i < runningLaunches.length; i++ ) { |
| ILaunch runningLaunch = runningLaunches[ i ]; |
| if( runningLaunch != launch |
| && config.getName().equals( getLaunchName( runningLaunch ) ) |
| && !runningLaunch.isTerminated() ) |
| { |
| result = runningLaunches[ i ]; |
| } |
| } |
| return result; |
| } |
| |
| private static String getLaunchName( ILaunch launch ) { |
| ILaunchConfiguration launchConfiguration = launch.getLaunchConfiguration(); |
| // the launch config might be null (e.g. if deleted) even though there |
| // still exists a launch for that config |
| return launchConfiguration == null ? null : launchConfiguration.getName(); |
| } |
| |
| private static void terminate( final ILaunch previousLaunch ) throws DebugException { |
| final Object signal = new Object(); |
| final boolean[] terminated = { false }; |
| DebugPlugin debugPlugin = DebugPlugin.getDefault(); |
| debugPlugin.addDebugEventListener( new IDebugEventSetListener() { |
| public void handleDebugEvents( DebugEvent[] events ) { |
| for( DebugEvent event : events ) { |
| if( isTerminateEventFor( event, previousLaunch ) ) { |
| DebugPlugin.getDefault().removeDebugEventListener( this ); |
| synchronized( signal ) { |
| terminated[ 0 ] = true; |
| signal.notifyAll(); |
| } |
| } |
| } |
| } |
| } ); |
| previousLaunch.terminate(); |
| try { |
| synchronized( signal ) { |
| if( !terminated[ 0 ] ) { |
| signal.wait(); |
| } |
| } |
| } catch( InterruptedException e ) { |
| // ignore |
| } |
| } |
| |
| private static boolean isCreateEventFor( DebugEvent event, ILaunch launch ) { |
| Object source = event.getSource(); |
| return event.getKind() == DebugEvent.CREATE |
| && source instanceof RuntimeProcess |
| && ( ( RuntimeProcess ) source ).getLaunch() == launch; |
| } |
| |
| private static boolean isTerminateEventFor( DebugEvent event, ILaunch launch ) { |
| boolean result = false; |
| if( event.getKind() == DebugEvent.TERMINATE |
| && event.getSource() instanceof RuntimeProcess ) |
| { |
| RuntimeProcess process = ( RuntimeProcess )event.getSource(); |
| result = process.getLaunch() == launch; |
| } |
| return result; |
| } |
| |
| private void waitForHttpService( IProgressMonitor monitor ) { |
| SubProgressMonitor subMonitor = new SubProgressMonitor( monitor, 1 ); |
| String taskName = LaunchMessages.RAPLaunchDelegate_WaitForHTTPTaskName; |
| subMonitor.beginTask( taskName, IProgressMonitor.UNKNOWN ); |
| try { |
| long start = System.currentTimeMillis(); |
| boolean canConnect = false; |
| boolean interrupted = false; |
| while( System.currentTimeMillis() - start <= CONNECT_TIMEOUT |
| && !canConnect |
| && !interrupted |
| && !monitor.isCanceled() |
| && !launch.isTerminated() ) |
| { |
| try { |
| Socket socket = new Socket( URLBuilder.getHost(), port ); |
| socket.close(); |
| canConnect = true; |
| } catch( Exception e ) { |
| // http service not yet started - wait a bit |
| try { |
| Thread.sleep( 200 ); |
| } catch( InterruptedException ie ) { |
| interrupted = true; |
| } |
| } |
| } |
| } finally { |
| subMonitor.done(); |
| } |
| } |
| |
| @Override |
| public void clear( ILaunchConfiguration configuration, IProgressMonitor monitor ) |
| throws CoreException |
| { |
| clearDataLocation( configuration, monitor ); |
| super.clear( configuration, monitor ); |
| } |
| |
| private void clearDataLocation( ILaunchConfiguration configuration, IProgressMonitor monitor ) |
| throws CoreException |
| { |
| String resolvedDataLocation = getResolvedDataLoacation(); |
| boolean isCleared = clearWorkspace( configuration, resolvedDataLocation, monitor ); |
| if( !isCleared ) { |
| throw new CoreException( Status.CANCEL_STATUS ); |
| } |
| } |
| |
| private void registerBrowserOpener() { |
| DebugPlugin debugPlugin = DebugPlugin.getDefault(); |
| debugPlugin.addDebugEventListener( new IDebugEventSetListener() { |
| public void handleDebugEvents( DebugEvent[] events ) { |
| for( DebugEvent event : events ) { |
| if( isCreateEventFor( event, launch ) ) { |
| DebugPlugin.getDefault().removeDebugEventListener( this ); |
| // Start a separate job to wait for the http service and launch the |
| // browser. Otherwise we would block the application on whose |
| // service we are waiting for |
| final String jobTaskName = LaunchMessages.RAPLaunchDelegate_StartClientTaskName; |
| Job job = new Job( jobTaskName ) { |
| @Override |
| protected IStatus run( IProgressMonitor monitor ) { |
| String taskName = jobTaskName; |
| monitor.beginTask( taskName, 2 ); |
| try { |
| waitForHttpService( monitor ); |
| monitor.worked( 1 ); |
| if( !launch.isTerminated() ) { |
| openBrowser( monitor ); |
| } |
| } finally { |
| monitor.done(); |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| job.schedule(); |
| } |
| } |
| } |
| } ); |
| } |
| |
| private void openBrowser( IProgressMonitor monitor ) { |
| SubProgressMonitor subMonitor = new SubProgressMonitor( monitor, 1 ); |
| String taskName = LaunchMessages.RAPLaunchDelegate_StartClientTaskName; |
| subMonitor.beginTask( taskName, IProgressMonitor.UNKNOWN ); |
| try { |
| URL url = null; |
| try { |
| url = getUrl(); |
| IWebBrowser browser = getBrowser(); |
| openUrl( browser, url ); |
| } catch( CoreException e ) { |
| String text = LaunchMessages.RAPLaunchDelegate_OpenBrowserFailed; |
| String msg = MessageFormat.format( text, new Object[]{ url } ); |
| ErrorUtil.show( msg, e ); |
| } |
| } finally { |
| subMonitor.done(); |
| } |
| } |
| |
| private IWebBrowser getBrowser() throws CoreException { |
| final IWebBrowser[] result = { null }; |
| final CoreException[] exception = { null }; |
| Display.getDefault().syncExec( new Runnable() { |
| public void run() { |
| try { |
| IWorkbench workbench = PlatformUI.getWorkbench(); |
| IWorkbenchBrowserSupport support = workbench.getBrowserSupport(); |
| int style = IWorkbenchBrowserSupport.LOCATION_BAR |
| | IWorkbenchBrowserSupport.NAVIGATION_BAR |
| | IWorkbenchBrowserSupport.STATUS; |
| if( BrowserMode.EXTERNAL.equals( config.getBrowserMode() ) ) { |
| style |= IWorkbenchBrowserSupport.AS_EXTERNAL; |
| } else { |
| style |= IWorkbenchBrowserSupport.AS_EDITOR; |
| } |
| // Starting the same launch first with the external, then with the |
| // internal browser without restarting the workbench will still open |
| // an external browser. |
| // The fix is to append the browserMode to the id |
| String id = config.getName() + config.getBrowserMode(); |
| String name = config.getName(); |
| String toolTip = config.getName(); |
| result[ 0 ] = support.createBrowser( style, id, name, toolTip ); |
| } catch( CoreException e ) { |
| exception[ 0 ] = e; |
| } |
| } |
| } ); |
| if( exception[ 0 ] != null ) { |
| throw exception[ 0 ]; |
| } |
| return result[ 0 ]; |
| } |
| |
| private static void openUrl( final IWebBrowser browser, final URL url ) throws PartInitException { |
| final PartInitException[] exception = { null }; |
| Display.getDefault().asyncExec( new Runnable() { |
| public void run() { |
| try { |
| browser.openURL( url ); |
| } catch( PartInitException e ) { |
| String text = LaunchMessages.RAPLaunchDelegate_OpenUrlFailed; |
| String msg = MessageFormat.format( text, new Object[] { url } ); |
| String pluginId = Activator.getPluginId(); |
| Status status = new Status( IStatus.ERROR, pluginId, msg, e ); |
| exception[ 0 ] = new PartInitException( status ); |
| } |
| } |
| } ); |
| if( exception[ 0 ] != null ) { |
| throw exception[ 0 ]; |
| } |
| } |
| |
| } |