blob: 3334ed11a4c053dafae29407c9716d0a84ef5422 [file] [log] [blame]
/*******************************************************************************
* 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 ];
}
}
}