blob: e24f4a686eb357ba69fcd165f4a4d20462a8da6d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2013 Innoopract Informationssysteme GmbH 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.rwt.internal.lifecycle;
import org.eclipse.rap.rwt.internal.application.ApplicationContextImpl;
import org.eclipse.rap.rwt.internal.service.ContextProvider;
import org.eclipse.rap.rwt.internal.service.ServiceContext;
import org.eclipse.rap.rwt.internal.service.ServletLog;
import org.eclipse.rap.rwt.internal.service.UISessionImpl;
import org.eclipse.rap.rwt.lifecycle.PhaseId;
import org.eclipse.rap.rwt.service.UISession;
import org.eclipse.swt.widgets.Display;
final class UIThread extends Thread implements IUIThreadHolder, ISessionShutdownAdapter {
static final class UIThreadTerminatedError extends ThreadDeath {
private static final long serialVersionUID = 1L;
}
private ServiceContext serviceContext;
private UISession uiSession;
private Runnable shutdownCallback;
private volatile boolean uiThreadTerminating;
public UIThread( Runnable runnable ) {
super( runnable );
}
//////////////////////////
// interface IThreadHolder
public void setServiceContext( ServiceContext serviceContext ) {
this.serviceContext = serviceContext;
}
public void updateServiceContext() {
if( ContextProvider.hasContext() ) {
ContextProvider.releaseContextHolder();
}
ContextProvider.setContext( serviceContext );
}
public void switchThread() {
Object lock = getLock();
synchronized( lock ) {
checkAndReportTerminatedUIThread();
lock.notifyAll();
boolean done = false;
while( !done ) {
try {
lock.wait();
done = true;
} catch( InterruptedException e ) {
handleInterruptInSwitchThread( e );
}
}
}
}
private void checkAndReportTerminatedUIThread() {
// [rh] While working on bug 284202, there was the suspicion that a
// request thread might wait infinitely on an already terminated UIThread.
// To investigate this problem, we print to sys-err if this happens.
if( !getThread().isAlive() ) {
String msg
= "Thread '"
+ Thread.currentThread()
+ "' is waiting for already terminated UIThread";
ServletLog.log( "", new RuntimeException( msg ) );
}
}
private void handleInterruptInSwitchThread( InterruptedException e )
throws UIThreadTerminatedError
{
Thread.interrupted();
if( uiThreadTerminating ) {
// Equip the UI thread that is continuing its execution with a
// service context and the proper phase (see terminateThread).
updateServiceContext();
CurrentPhase.set( PhaseId.PROCESS_ACTION );
uiThreadTerminating = false;
throw new UIThreadTerminatedError();
}
if( Thread.currentThread() != getThread() ) {
String msg = "Received InterruptedException on request thread";
ServletLog.log( msg, e );
}
}
@Override
public void run() {
try {
super.run();
} finally {
// TODO [rh] call lock.notifyAll()?
}
}
public void terminateThread() {
// Prepare a service context to be used by the UI thread that may continue
// to run as a result of the interrupt call
ServiceContext serviceContext = ContextUtil.createFakeContext( uiSession );
setServiceContext( serviceContext );
uiThreadTerminating = true;
// interrupt the UI thread that is expected to wait in switchThread or already be terminated
synchronized( getLock() ) {
getThread().interrupt();
}
try {
getThread().join();
} catch( InterruptedException e ) {
String msg = "Received InterruptedException while terminating UIThread";
ServletLog.log( msg, e );
}
uiThreadTerminating = false;
}
public Thread getThread() {
return this;
}
public Object getLock() {
// TODO [rh] use a distinct (final) lock object instead of 'this'
return this;
}
////////////////////////////////////
// interface ISessionShutdownAdapter
public void setUISession( UISession uiSession ) {
this.uiSession = uiSession;
}
public void setShutdownCallback( Runnable shutdownCallback ) {
this.shutdownCallback = shutdownCallback;
}
public void interceptShutdown() {
terminateThread();
}
public void processShutdown() {
updateServiceContext();
try {
// Simulate PROCESS_ACTION phase if the session times out
CurrentPhase.set( PhaseId.PROCESS_ACTION );
// TODO [rh] find a better decoupled way to dispose of the display
Display display = LifeCycleUtil.getSessionDisplay( uiSession );
// TODO [fappel]: Think about a better solution: isActivated() checks whether
// the applicationContext is still activated before starting
// cleanup. This is due to the missing possibility of OSGi HttpService
// to shutdown HttpContext instances. Therefore sessions will survive the
// deactivation of ApplicationContext instances. In case the HttpService
// gets halted the corresponding ApplicationContext instances have already
// been deactivated and this will cause a NPE.
if( isApplicationContextActive() && display != null ) {
display.dispose();
}
shutdownCallback.run();
} finally {
ContextProvider.disposeContext();
}
}
private boolean isApplicationContextActive() {
ApplicationContextImpl applicationContext = ( ( UISessionImpl )uiSession ).getApplicationContext();
return applicationContext != null && applicationContext.isActive();
}
}