/*******************************************************************************
* Copyright (c) 2002, 2015 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
* Frank Appel - replaced singletons and static fields (Bug 337787)
******************************************************************************/
package org.eclipse.rap.rwt.internal.lifecycle;
import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext;
import static org.eclipse.rap.rwt.testfixture.internal.ConcurrencyTestUtil.runInThread;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.application.EntryPoint;
import org.eclipse.rap.rwt.client.WebClient;
import org.eclipse.rap.rwt.internal.application.ApplicationContextImpl;
import org.eclipse.rap.rwt.internal.lifecycle.IPhase.IInterruptible;
import org.eclipse.rap.rwt.internal.service.ContextProvider;
import org.eclipse.rap.rwt.internal.service.ServiceContext;
import org.eclipse.rap.rwt.internal.service.ServiceStore;
import org.eclipse.rap.rwt.service.UISession;
import org.eclipse.rap.rwt.service.UISessionEvent;
import org.eclipse.rap.rwt.service.UISessionListener;
import org.eclipse.rap.rwt.service.UIThreadListener;
import org.eclipse.rap.rwt.testfixture.internal.Fixture;
import org.eclipse.rap.rwt.testfixture.internal.TestMessage;
import org.eclipse.rap.rwt.testfixture.internal.TestRequest;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
@SuppressWarnings( "deprecation" )
public class RWTLifeCycle_Test {
private static final String ERR_MSG = "TEST_ERROR";
private static final String MY_ENTRY_POINT = "/myEntryPoint";
private static final String BEFORE = "before ";
private static final String AFTER = "after ";
private static final String DISPLAY_CREATED = "display created";
private static final String EXCEPTION_IN_RENDER = "Exception in render";
private static StringBuffer log = new StringBuffer();
private EntryPointManager entryPointManager;
@Before
public void setUp() {
log.setLength( 0 );
Fixture.setUp();
Fixture.fakeNewRequest();
Fixture.fakeResponseWriter();
entryPointManager = getApplicationContext().getEntryPointManager();
}
@After
public void tearDown() {
Fixture.tearDown();
}
@Test
public void testNoEntryPoint() throws IOException {
RWTLifeCycle lifeCycle = getLifeCycle();
try {
lifeCycle.execute();
fail( "Executing lifecycle without entry point must throw exception" );
} catch( IllegalArgumentException e ) {
// expected
}
}
@Test
public void testDefaultEntryPoint() throws IOException {
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH,
TestEntryPointWithLog.class,
null );
RWTLifeCycle lifeCycle = getLifeCycle();
lifeCycle.execute();
assertEquals( DISPLAY_CREATED, log.toString() );
}
@Test
public void testParamOfExistingEntryPoint() throws IOException {
fakeServletPath( MY_ENTRY_POINT );
RWTLifeCycle lifeCycle = getLifeCycle();
entryPointManager.register( MY_ENTRY_POINT, TestEntryPointWithLog.class, null );
lifeCycle.execute();
assertEquals( DISPLAY_CREATED, log.toString() );
}
@Test
public void testParamOfNonExistingEntryPoint() throws IOException {
RWTLifeCycle lifeCycle = getLifeCycle();
fakeServletPath( "/not-registered" );
try {
lifeCycle.execute();
fail( "Executing lifecycle with unknown entry point must fail." );
} catch( IllegalArgumentException expected ) {
}
}
@Test
public void testPhases() throws IOException {
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH,
TestPhasesEntryPoint.class,
null );
RWTLifeCycle lifeCycle = getLifeCycle();
PhaseListener listener = new PhaseListener() {
private static final long serialVersionUID = 1L;
@Override
public void afterPhase( PhaseEvent event ) {
log.append( AFTER + event.getPhaseId() + "|" );
}
@Override
public void beforePhase( PhaseEvent event ) {
log.append( BEFORE + event.getPhaseId() + "|" );
}
@Override
public PhaseId getPhaseId() {
return PhaseId.ANY;
}
};
lifeCycle.addPhaseListener( listener );
lifeCycle.execute();
String expected = BEFORE
+ PhaseId.PREPARE_UI_ROOT
+ "|"
+ AFTER
+ PhaseId.PREPARE_UI_ROOT
+ "|"
+ BEFORE
+ PhaseId.RENDER
+ "|"
+ AFTER
+ PhaseId.RENDER
+ "|";
assertEquals( expected, log.toString() );
log.setLength( 0 );
lifeCycle.execute();
expected = BEFORE
+ PhaseId.PREPARE_UI_ROOT
+ "|"
+ AFTER
+ PhaseId.PREPARE_UI_ROOT
+ "|"
+ BEFORE
+ PhaseId.READ_DATA
+ "|"
+ AFTER
+ PhaseId.READ_DATA
+ "|"
+ BEFORE
+ PhaseId.PROCESS_ACTION
+ "|"
+ AFTER
+ PhaseId.PROCESS_ACTION
+ "|"
+ BEFORE
+ PhaseId.RENDER
+ "|"
+ AFTER
+ PhaseId.RENDER
+ "|";
assertEquals( expected, log.toString() );
lifeCycle.removePhaseListener( listener );
log.setLength( 0 );
lifeCycle.execute();
assertEquals( "", log.toString() );
log.setLength( 0 );
lifeCycle.addPhaseListener( new PhaseListener() {
private static final long serialVersionUID = 1L;
@Override
public void afterPhase( PhaseEvent event ) {
log.append( AFTER + event.getPhaseId() + "|" );
}
@Override
public void beforePhase( PhaseEvent event ) {
log.append( BEFORE + event.getPhaseId() + "|" );
}
@Override
public PhaseId getPhaseId() {
return PhaseId.PREPARE_UI_ROOT;
}
} );
lifeCycle.execute();
expected = BEFORE
+ PhaseId.PREPARE_UI_ROOT
+ "|"
+ AFTER
+ PhaseId.PREPARE_UI_ROOT
+ "|";
assertEquals( expected, log.toString() );
}
@Test
public void testErrorInLifeCycle() throws IOException {
Class<TestErrorInLifeCycleEntryPoint> type = TestErrorInLifeCycleEntryPoint.class;
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH, type, null );
RWTLifeCycle lifeCycle = getLifeCycle();
LifeCycleUtil.setSessionDisplay( null );
try {
lifeCycle.execute();
fail();
} catch( RuntimeException e ) {
String msg = type.getName();
assertEquals( msg, e.getMessage() );
assertTrue( RWTLifeCycle.getUIThreadHolder().getThread().isAlive() );
}
}
@Test
public void testExceptionInPhaseListener() throws IOException {
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH,
TestEntryPoint.class,
null );
RWTLifeCycle lifeCycle = getLifeCycle();
lifeCycle.addPhaseListener( new ExceptionListenerTest() );
lifeCycle.addPhaseListener( new ExceptionListenerTest() );
lifeCycle.execute();
String expected = BEFORE
+ PhaseId.PREPARE_UI_ROOT
+ "|"
+ BEFORE
+ PhaseId.PREPARE_UI_ROOT
+ "|"
+ AFTER
+ PhaseId.PREPARE_UI_ROOT
+ "|"
+ AFTER
+ PhaseId.PREPARE_UI_ROOT
+ "|";
assertEquals( expected, log.toString() );
}
@Test
public void testRender() throws IOException {
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH, TestEntryPoint.class, null );
RWTLifeCycle lifeCycle = getLifeCycle();
lifeCycle.execute();
TestMessage message = Fixture.getProtocolMessage();
assertTrue( message.getOperationCount() > 0 );
}
@Test
public void testPhaseListenerRegistration() throws IOException {
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH, TestEntryPoint.class, null );
final AtomicReference<PhaseListener> callbackHandler = new AtomicReference<>();
PhaseListener listener = new PhaseListener() {
private static final long serialVersionUID = 1L;
@Override
public void beforePhase( PhaseEvent event ) {
callbackHandler.set( this );
}
@Override
public void afterPhase( PhaseEvent event ) {
}
@Override
public PhaseId getPhaseId() {
return PhaseId.PREPARE_UI_ROOT;
}
};
getApplicationContext().getPhaseListenerManager().addPhaseListener( listener );
// Run lifecycle in session one
RWTLifeCycle lifeCycle1 = getLifeCycle();
lifeCycle1.execute();
assertSame( listener, callbackHandler.get() );
// Simulate new session and run lifecycle
newSession();
Fixture.fakeResponseWriter();
callbackHandler.set( null );
RWTLifeCycle lifeCycle2 = getLifeCycle();
lifeCycle2.execute();
assertSame( listener, callbackHandler.get() );
}
@Test
public void testContinueLifeCycle() {
RWTLifeCycle lifeCycle = getLifeCycle();
lifeCycle.addPhaseListener( new PhaseListener() {
private static final long serialVersionUID = 1L;
@Override
public void afterPhase( PhaseEvent event ) {
log.append( "after" + event.getPhaseId() );
}
@Override
public void beforePhase( PhaseEvent event ) {
log.append( "before" + event.getPhaseId() );
}
@Override
public PhaseId getPhaseId() {
return PhaseId.ANY;
}
} );
lifeCycle.setPhaseOrder( new IPhase[] {
new IInterruptible() {
@Override
public PhaseId execute(Display display) throws IOException {
fail( "Interruptible phase should never get executed." );
return null;
}
@Override
public PhaseId getPhaseId() {
return PhaseId.PREPARE_UI_ROOT;
}
},
new IPhase() {
@Override
public PhaseId execute(Display display) throws IOException {
log.append( "execute" + getPhaseId() );
return null;
}
@Override
public PhaseId getPhaseId() {
return PhaseId.RENDER;
}
}
} );
lifeCycle.continueLifeCycle();
assertEquals( "before" + PhaseId.PREPARE_UI_ROOT, log.toString() );
log.setLength( 0 );
lifeCycle.continueLifeCycle();
String expected = "after"
+ PhaseId.PREPARE_UI_ROOT
+ "before"
+ PhaseId.RENDER
+ "execute"
+ PhaseId.RENDER
+ "after"
+ PhaseId.RENDER;
assertEquals( expected, log.toString() );
log.setLength( 0 );
}
@Test
public void testCreateUIIfNecessary() {
RWTLifeCycle lifeCycle = getLifeCycle();
int returnValue = lifeCycle.createUI();
assertEquals( -1, returnValue );
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH, MainStartup.class, null );
lifeCycle.setPhaseOrder( new IPhase[] {
new IInterruptible() {
@Override
public PhaseId execute(Display display) throws IOException {
return null;
}
@Override
public PhaseId getPhaseId() {
return PhaseId.PREPARE_UI_ROOT;
}
}
} );
returnValue = lifeCycle.createUI();
assertEquals( -1, returnValue );
lifeCycle.continueLifeCycle();
returnValue = lifeCycle.createUI();
assertEquals( 0, returnValue );
getApplicationContext().getEntryPointManager().deregisterAll();
lifeCycle.continueLifeCycle();
returnValue = lifeCycle.createUI();
assertEquals( -1, returnValue );
}
@Test
public void testReadAndDispatch() {
Display display = new Display();
boolean returnValue = Display.getCurrent().readAndDispatch();
assertFalse( returnValue );
Fixture.fakePhase( PhaseId.READ_DATA );
ProcessActionRunner.add( new Runnable() {
@Override
public void run() {
log.append( "executed" );
}
} );
Fixture.fakePhase( PhaseId.PROCESS_ACTION );
returnValue = Display.getCurrent().readAndDispatch();
assertTrue( returnValue );
assertEquals( "executed", log.toString() );
log.setLength( 0 );
returnValue = Display.getCurrent().readAndDispatch();
assertFalse( returnValue );
assertEquals( "", log.toString() );
Fixture.fakePhase( PhaseId.READ_DATA );
log.setLength( 0 );
Shell widget = new Shell( display ) {
private static final long serialVersionUID = 1L;
@Override
public boolean getVisible() {
return true;
}
};
Listener listener = new Listener() {
@Override
public void handleEvent( Event event ) {
log.append( "eventExecuted" );
}
};
widget.addListener( SWT.Selection, listener );
// event is scheduled but not executed at this point as there is no life
// cycle running
widget.notifyListeners( SWT.Selection, new Event() );
log.setLength( 0 );
Fixture.fakePhase( PhaseId.PROCESS_ACTION );
returnValue = Display.getCurrent().readAndDispatch();
assertTrue( returnValue );
assertEquals( "eventExecuted", log.toString() );
}
@Test
public void testNestedReadAndDispatch() {
Fixture.fakePhase( PhaseId.READ_DATA );
final Display display = new Display();
Shell widget = new Shell( display ) {
private static final long serialVersionUID = 1L;
@Override
public boolean getVisible() {
return true;
}
};
Listener listener = new Listener() {
@Override
public void handleEvent( Event event ) {
display.readAndDispatch();
}
};
widget.addListener( SWT.Selection, listener );
Event event = new Event();
widget.notifyListeners( SWT.Selection, event );
Fixture.fakePhase( PhaseId.PROCESS_ACTION );
display.readAndDispatch();
// This test ensures that nested calls of readAndDsipatch don't cause
// an endless loop or a stack overflow - therefore no assert is needed
}
@Test
public void testReadAndDispatchWithAsyncExec() {
final java.util.List<Runnable> log = new ArrayList<Runnable>();
Runnable runnable = new Runnable() {
@Override
public void run() {
log.add( this );
}
};
Display display = new Display();
display.asyncExec( runnable );
Fixture.fakePhase( PhaseId.PROCESS_ACTION );
boolean result = display.readAndDispatch();
assertTrue( result );
assertSame( runnable, log.get( 0 ) );
assertFalse( display.readAndDispatch() );
}
@Test
public void testBeginUIThread() throws Throwable {
ServiceContext originContext = ContextProvider.getContext();
RWTLifeCycle lifeCycle = getLifeCycle();
final AtomicBoolean continueLoop = new AtomicBoolean( true );
final AtomicReference<ServiceContext> uiContext = new AtomicReference<>();
final AtomicReference<Throwable> error = new AtomicReference<>();
Runnable runnable = new Runnable() {
@Override
public void run() {
while( continueLoop.get() ) {
IUIThreadHolder uiThread = ( IUIThreadHolder )Thread.currentThread();
synchronized( uiThread.getLock() ) {
}
uiThread.updateServiceContext();
uiContext.set( ContextProvider.getContext() );
log.append( "executedInUIThread" );
try {
uiThread.switchThread();
} catch( Throwable e ) {
synchronized( error ) {
error.set( e );
}
}
}
}
};
lifeCycle.uiRunnable = runnable;
// simulates first request
lifeCycle.executeUIThread();
synchronized( error ) {
if( error.get() != null ) {
throw error.get();
}
}
assertSame( originContext, uiContext.get() );
assertEquals( "executedInUIThread", log.toString() );
assertTrue( getUIThread().isAlive() );
// simulates subsequent request
log.setLength( 0 );
uiContext.set( null );
ServiceContext secondContext = newContext( originContext.getUISession() );
ContextProvider.releaseContextHolder();
ContextProvider.setContext( secondContext );
lifeCycle.executeUIThread();
synchronized( error ) {
if( error.get() != null ) {
throw error.get();
}
}
assertSame( secondContext, uiContext.get() );
assertEquals( "executedInUIThread", log.toString() );
assertTrue( getUIThread().isAlive() );
// simulates request that ends event loop
UIThread endingUIThread = getUIThread();
continueLoop.set( false );
lifeCycle.executeUIThread();
synchronized( error ) {
if( error.get() != null ) {
throw error.get();
}
}
assertFalse( endingUIThread.isAlive() );
assertNull( getUIThread() );
// clean up
ContextProvider.releaseContextHolder();
ContextProvider.setContext( originContext );
}
@Test
public void testUpdateServiceContext() {
UIThread thread = new UIThread( null );
ServiceContext firstContext = ContextProvider.getContext();
thread.setServiceContext( firstContext );
thread.run();
ServiceContext secondContext = newContext( firstContext.getUISession() );
thread.setServiceContext( secondContext );
thread.updateServiceContext();
// As we don't start the UIThread, we can use the test-thread for assertion
// instead of retrieving the actual context from inside the runnable
assertSame( secondContext, ContextProvider.getContext() );
// clean up
ContextProvider.releaseContextHolder();
ContextProvider.setContext( firstContext );
}
@Test
public void testUIRunnable() throws InterruptedException {
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH, MainStartup.class, null );
RWTLifeCycle lifeCycle = getLifeCycle();
lifeCycle.setPhaseOrder( new IPhase[] {
new IInterruptible() {
@Override
public PhaseId execute(Display display) throws IOException {
return null;
}
@Override
public PhaseId getPhaseId() {
return PhaseId.PREPARE_UI_ROOT;
}
}
} );
lifeCycle.addPhaseListener( new LoggingPhaseListener() );
UIThread thread = new UIThread( lifeCycle.uiRunnable );
thread.setServiceContext( ContextProvider.getContext() );
thread.start();
// TODO [rh] Find more failsafe solution
Thread.sleep( 200 );
String expected = "before"
+ PhaseId.PREPARE_UI_ROOT
+ "createUI"
+ "after"
+ PhaseId.PREPARE_UI_ROOT;
assertEquals( expected, log.toString() );
}
@Test
public void testSleep() throws Throwable {
final RWTLifeCycle lifeCycle = getLifeCycle();
final AtomicReference<ServiceContext> uiContext = new AtomicReference<>();
lifeCycle.addPhaseListener( new LoggingPhaseListener() );
lifeCycle.setPhaseOrder( new IPhase[] {
new IInterruptible() {
@Override
public PhaseId execute(Display display) throws IOException {
return null;
}
@Override
public PhaseId getPhaseId() {
return PhaseId.PREPARE_UI_ROOT;
}
}
} );
final AtomicReference<Throwable> error = new AtomicReference<>();
final AtomicReference<UIThread> uiThread = new AtomicReference<>();
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
synchronized( uiThread.get().getLock() ) {
}
IUIThreadHolder uiThread = ( IUIThreadHolder )Thread.currentThread();
uiThread.updateServiceContext();
lifeCycle.continueLifeCycle();
log.setLength( 0 );
lifeCycle.sleep();
uiContext.set( ContextProvider.getContext() );
log.append( "readAndDispatch" );
lifeCycle.sleep();
log.append( "readAndDispatch" );
} catch( Throwable e ) {
error.set( e );
}
}
};
uiThread.set( new UIThread( runnable ) );
LifeCycleUtil.setUIThread( ContextProvider.getUISession(), uiThread.get() );
uiThread.get().setServiceContext( ContextProvider.getContext() );
synchronized( uiThread.get().getLock() ) {
uiThread.get().start();
uiThread.get().switchThread();
}
if( error.get() != null ) {
throw error.get();
}
String expected = "after" + PhaseId.PREPARE_UI_ROOT;
assertEquals( expected, log.toString() );
log.setLength( 0 );
ServiceContext expectedContext = newContext( ContextProvider.getUISession() );
ContextProvider.releaseContextHolder();
ContextProvider.setContext( expectedContext );
lifeCycle.setPhaseOrder( new IPhase[] {
new IPhase() {
@Override
public PhaseId execute(Display display) throws IOException {
log.append( "prepare" );
return null;
}
@Override
public PhaseId getPhaseId() {
return PhaseId.PREPARE_UI_ROOT;
}
},
new IInterruptible() {
@Override
public PhaseId execute(Display display) throws IOException {
return null;
}
@Override
public PhaseId getPhaseId() {
return PhaseId.PROCESS_ACTION;
}
}
} );
uiThread.get().setServiceContext( expectedContext );
uiThread.get().switchThread();
if( error.get() != null ) {
throw error.get();
}
expected = "before"
+ PhaseId.PREPARE_UI_ROOT
+ "prepare"
+ "after"
+ PhaseId.PREPARE_UI_ROOT
+ "before"
+ PhaseId.PROCESS_ACTION
+ "readAndDispatch"
+ "after"
+ PhaseId.PROCESS_ACTION;
assertEquals( expected, log.toString() );
assertSame( expectedContext, uiContext.get() );
log.setLength( 0 );
lifeCycle.setPhaseOrder( new IPhase[] {
new IInterruptible() {
@Override
public PhaseId execute(Display display) throws IOException {
return null;
}
@Override
public PhaseId getPhaseId() {
return PhaseId.PROCESS_ACTION;
}
}
} );
uiThread.get().switchThread();
if( error.get() != null ) {
throw error.get();
}
expected = "before"
+ PhaseId.PROCESS_ACTION
+ "readAndDispatch";
assertEquals( expected, log.toString() );
assertFalse( uiThread.get().isAlive() );
}
@Test
public void testGetSetPhaseOrder() {
RWTLifeCycle lifeCycle = getLifeCycle();
IPhase[] phaseOrder = new IPhase[ 0 ];
lifeCycle.setPhaseOrder( phaseOrder );
assertSame( phaseOrder, lifeCycle.getPhaseOrder() );
// create new context to ensure that phase order is stored in context
ServiceContext bufferedContext = ContextProvider.getContext();
ContextProvider.releaseContextHolder();
Fixture.createServiceContext();
assertNull( lifeCycle.getPhaseOrder() );
// clean up
ContextProvider.releaseContextHolder();
ContextProvider.setContext( bufferedContext );
}
@Test
public void testErrorHandlingInCreateUI() throws IOException {
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH, ErrorStartup.class, null );
try {
getLifeCycle().execute();
fail();
} catch( RuntimeException re ) {
assertEquals( ERR_MSG, re.getMessage() );
}
}
@Test
public void testSessionInvalidateWithRunningEventLoop() throws Throwable {
final UISession uiSession = ContextProvider.getUISession();
final AtomicReference<String> invalidateThreadName = new AtomicReference<>();
final AtomicBoolean hasContext = new AtomicBoolean();
final AtomicReference<ServiceStore> serviceStore = new AtomicReference<>();
uiSession.addUISessionListener( new UISessionListener() {
@Override
public void beforeDestroy( UISessionEvent event ) {
invalidateThreadName.set( Thread.currentThread().getName() );
hasContext.set( ContextProvider.hasContext() );
serviceStore.set( ContextProvider.getServiceStore() );
}
} );
// Register and 'run' entry point with readAndDispatch/sleep loop
Class<? extends EntryPoint> entryPointClass = SessionInvalidateWithEventLoopEntryPoint.class;
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH, entryPointClass, null );
RWTLifeCycle lifeCycle = getLifeCycle();
lifeCycle.execute();
// Store some values for later comparison
IUIThreadHolder uiThreadHolder = LifeCycleUtil.getUIThread( uiSession );
String uiThreadName = uiThreadHolder.getThread().getName();
// Invalidate session
invalidateSession( uiSession );
//
assertFalse( uiThreadHolder.getThread().isAlive() );
assertFalse( uiSession.isBound() );
assertEquals( invalidateThreadName.get(), uiThreadName );
assertTrue( hasContext.get() );
assertNotNull( serviceStore.get() );
assertEquals( "", log.toString() );
}
@Test
public void testExceptionInRender() {
fakeServletPath( TestRequest.DEFAULT_SERVLET_PATH );
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH,
ExceptionInRenderEntryPoint.class,
null );
RWTLifeCycle lifeCycle = getLifeCycle();
try {
lifeCycle.execute();
fail( "Exception in render must be re-thrown by life cycle" );
} catch( Throwable e ) {
assertEquals( EXCEPTION_IN_RENDER, e.getMessage() );
}
}
@Test
public void testSessionInvalidateWithoutRunningEventLoop() throws Throwable {
final UISession uiSession = ContextProvider.getUISession();
final AtomicReference<String> uiThreadName = new AtomicReference<>( "unknown" );
final AtomicReference<String> invalidateThreadName = new AtomicReference<>( "unkown" );
final AtomicBoolean hasContext = new AtomicBoolean();
final AtomicReference<ServiceStore> serviceStore = new AtomicReference<>();
uiSession.addUISessionListener( new UISessionListener() {
@Override
public void beforeDestroy( UISessionEvent event ) {
invalidateThreadName.set( Thread.currentThread().getName() );
hasContext.set( ContextProvider.hasContext() );
serviceStore.set( ContextProvider.getServiceStore() );
}
} );
// Register and 'run' entry point with readAndDispatch/sleep loop
Class<? extends EntryPoint> entryPoint = SessionInvalidateWithoutEventLoopEntryPoint.class;
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH, entryPoint, null );
RWTLifeCycle lifeCycle = getLifeCycle();
lifeCycle.addPhaseListener( new PhaseListener() {
private static final long serialVersionUID = 1L;
@Override
public void beforePhase( PhaseEvent event ) {
uiThreadName.set( Thread.currentThread().getName() );
}
@Override
public void afterPhase( PhaseEvent event ) {
}
@Override
public PhaseId getPhaseId() {
return PhaseId.PREPARE_UI_ROOT;
}
} );
lifeCycle.execute();
// Invalidate session
invalidateSession( uiSession );
//
assertFalse( uiSession.isBound() );
assertEquals( uiThreadName.get(), invalidateThreadName.get() );
assertTrue( hasContext.get() );
assertNotNull( serviceStore.get() );
}
@Test
public void testDisposeDisplayOnSessionTimeout() throws Throwable {
UISession uiSession = ContextProvider.getUISession();
ContextProvider.getContext().getApplicationContext();
Class<? extends EntryPoint> clazz = DisposeDisplayOnSessionTimeoutEntryPoint.class;
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH, clazz, null );
RWTLifeCycle lifeCycle = getLifeCycle();
lifeCycle.execute();
invalidateSession( uiSession );
assertEquals( "display disposed", log.toString() );
}
@Test
public void testOrderOfDisplayDisposeAndSessionUnbound() throws Throwable {
UISession uiSession = ContextProvider.getUISession();
Class<? extends EntryPoint> clazz = TestOrderOfDisplayDisposeAndSessionUnboundEntryPoint.class;
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH, clazz, null );
RWTLifeCycle lifeCycle = getLifeCycle();
lifeCycle.execute();
invalidateSession( uiSession );
assertEquals( "disposeEvent, beforeDestroy", log.toString() );
}
@Test
public void testSwitchThreadCannotBeInterrupted() throws Exception {
final AtomicReference<Throwable> errorInUIThread = new AtomicReference<>();
errorInUIThread.set( new Exception( "did not run" ) );
final AtomicReference<UIThread> uiThread = new AtomicReference<>();
uiThread.set( new UIThread( new Runnable() {
@Override
public void run() {
try {
errorInUIThread.set( null );
uiThread.get().switchThread();
} catch( Throwable t ) {
errorInUIThread.set( t );
}
}
} ) );
uiThread.get().start();
Thread.sleep( 100 );
uiThread.get().interrupt();
assertNull( "switchThread must not unblock when thread is interrupted", errorInUIThread.get() );
// unblock ui thread, see bug 351277
synchronized( uiThread.get().getLock() ) {
uiThread.get().getLock().notifyAll();
}
}
@Test
public void testGetUIThreadWhileLifeCycleInExecute() throws IOException {
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH, TestEntryPoint.class, null );
RWTLifeCycle lifeCycle = new RWTLifeCycle( getApplicationContext() );
final AtomicReference<Thread> currentThread = new AtomicReference<>();
final AtomicReference<Thread> uiThread = new AtomicReference<>();
lifeCycle.addPhaseListener( new PhaseListener() {
private static final long serialVersionUID = 1L;
@Override
public PhaseId getPhaseId() {
return PhaseId.PREPARE_UI_ROOT;
}
@Override
public void beforePhase( PhaseEvent event ) {
}
@Override
public void afterPhase( PhaseEvent event ) {
currentThread.set( Thread.currentThread() );
uiThread.set( LifeCycleUtil.getUIThread( ContextProvider.getUISession() ).getThread() );
}
} );
lifeCycle.execute();
assertSame( currentThread.get(), uiThread.get() );
}
@Test
public void testGetUIThreadAfterLifeCycleExecuted() throws IOException {
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH, TestEntryPoint.class, null );
RWTLifeCycle lifeCycle = new RWTLifeCycle( getApplicationContext() );
lifeCycle.execute();
Thread uiThread = LifeCycleUtil.getUIThread( ContextProvider.getUISession() ).getThread();
assertNotNull( uiThread );
}
@Test
public void testNotifyUIThreadListeners() throws IOException {
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH, TestPhasesEntryPoint.class, null );
ApplicationContextImpl applicationContext = getApplicationContext();
UIThreadListener listener = mock( UIThreadListener.class );
applicationContext.addUIThreadListener( listener );
RWTLifeCycle lifeCycle = new RWTLifeCycle( applicationContext );
ArgumentCaptor<UISessionEvent> captor = ArgumentCaptor.forClass( UISessionEvent.class );
InOrder inOrder = inOrder( listener );
lifeCycle.execute(); // first request does not have process action
lifeCycle.execute();
inOrder.verify( listener ).enterUIThread( captor.capture() );
inOrder.verify( listener ).leaveUIThread( captor.capture() );
inOrder.verifyNoMoreInteractions();
for( UISessionEvent event : captor.getAllValues() ) {
assertSame( ContextProvider.getUISession(), event.getUISession() );
}
}
@Test
public void testNotifyUIThreadListeners_twice() throws IOException {
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH, TestPhasesEntryPoint.class, null );
ApplicationContextImpl applicationContext = getApplicationContext();
UIThreadListener listener = mock( UIThreadListener.class );
applicationContext.addUIThreadListener( listener );
RWTLifeCycle lifeCycle = new RWTLifeCycle( applicationContext );
lifeCycle.execute();
lifeCycle.execute();
lifeCycle.execute();
verify( listener, times( 2 ) ).enterUIThread( any( UISessionEvent.class ) );
verify( listener, times( 2 ) ).leaveUIThread( any( UISessionEvent.class ) );
}
@Test
public void testNotifyUIThreadListeners_haveAccessToUISession() throws IOException {
entryPointManager.register( TestRequest.DEFAULT_SERVLET_PATH, TestPhasesEntryPoint.class, null );
ApplicationContextImpl applicationContext = getApplicationContext();
UIThreadListener listener = new UIThreadListener() {
@Override
public void enterUIThread( UISessionEvent event ) {
assertNotNull( RWT.getUISession() );
}
@Override
public void leaveUIThread( UISessionEvent event ) {
assertNotNull( RWT.getUISession() );
}
};
applicationContext.addUIThreadListener( listener );
RWTLifeCycle lifeCycle = new RWTLifeCycle( applicationContext );
lifeCycle.execute();
lifeCycle.execute();
}
private static RWTLifeCycle getLifeCycle() {
return ( RWTLifeCycle )getApplicationContext().getLifeCycleFactory().getLifeCycle();
}
private static void invalidateSession( final UISession uiSession ) throws Throwable {
Runnable runnable = new Runnable() {
@Override
public void run() {
uiSession.getHttpSession().invalidate();
}
};
runInThread( runnable );
}
private static ServiceContext newContext( UISession uiSession ) {
HttpServletRequest request = ContextProvider.getRequest();
HttpServletResponse response = ContextProvider.getResponse();
ApplicationContextImpl applicationContext = mock( ApplicationContextImpl.class );
doReturn( Boolean.TRUE ).when( applicationContext ).isActive();
ServiceContext result = new ServiceContext( request, response, applicationContext );
result.setServiceStore( new ServiceStore() );
result.setUISession( uiSession );
return result;
}
private static void newSession() {
ContextProvider.disposeContext();
Fixture.createServiceContext();
Fixture.fakeClient( new WebClient() );
}
private static UIThread getUIThread() {
UISession uiSession = ContextProvider.getUISession();
return ( UIThread )LifeCycleUtil.getUIThread( uiSession );
}
private static void fakeServletPath( String myEntryPoint ) {
TestRequest request = ( TestRequest )RWT.getRequest();
request.setServletPath( myEntryPoint );
}
private static class LoggingPhaseListener implements PhaseListener {
private static final long serialVersionUID = 1L;
@Override
public void beforePhase( PhaseEvent event ) {
log.append( "before" + event.getPhaseId() );
}
@Override
public void afterPhase( PhaseEvent event ) {
log.append( "after" + event.getPhaseId() );
}
@Override
public PhaseId getPhaseId() {
return PhaseId.ANY;
}
}
public static final class MainStartup implements EntryPoint {
@Override
public int createUI() {
log.append( "createUI" );
return 0;
}
}
public static final class ErrorStartup implements EntryPoint {
@Override
public int createUI() {
throw new RuntimeException( ERR_MSG );
}
}
private final class ExceptionListenerTest implements PhaseListener {
private static final long serialVersionUID = 1L;
@Override
public void afterPhase( PhaseEvent event ) {
log.append( AFTER + event.getPhaseId() + "|" );
throw new RuntimeException();
}
@Override
public void beforePhase( PhaseEvent event ) {
log.append( BEFORE + event.getPhaseId() + "|" );
throw new RuntimeException();
}
@Override
public PhaseId getPhaseId() {
return PhaseId.PREPARE_UI_ROOT;
}
}
public static class TestEntryPoint implements EntryPoint {
@Override
public int createUI() {
new Display();
return 0;
}
}
public static class TestPhasesEntryPoint implements EntryPoint {
@Override
public int createUI() {
Display display = new Display();
while( !display.isDisposed() ) {
if( !display.readAndDispatch() ) {
display.sleep();
}
}
return 0;
}
}
public static class TestErrorInLifeCycleEntryPoint implements EntryPoint {
@Override
public int createUI() {
String msg = TestErrorInLifeCycleEntryPoint.class.getName();
throw new RuntimeException( msg );
}
}
public static class TestEntryPointWithLog implements EntryPoint {
@Override
public int createUI() {
new Display();
log.append( DISPLAY_CREATED );
return 0;
}
}
public static class DisposeDisplayOnSessionTimeoutEntryPoint implements EntryPoint
{
@Override
public int createUI() {
Display display = new Display();
display.addListener( SWT.Dispose, new Listener() {
@Override
public void handleEvent( Event event ) {
log.append( "display disposed" );
}
} );
return 0;
}
}
public static class SessionInvalidateWithEventLoopEntryPoint
implements EntryPoint
{
@Override
public int createUI() {
Display display = new Display();
Shell shell = new Shell( display );
shell.open();
while( !shell.isDisposed() ) {
if( !display.readAndDispatch() ) {
display.sleep();
}
}
log.append( "regular end of createUI" );
return 0;
}
}
public static class SessionInvalidateWithoutEventLoopEntryPoint
implements EntryPoint
{
@Override
public int createUI() {
new Display();
return 0;
}
}
public static class ExceptionInRenderEntryPoint implements EntryPoint {
public static class BuggyShell extends Shell {
private static final long serialVersionUID = 1L;
public BuggyShell( Display display ) {
super( display );
}
@SuppressWarnings("unchecked")
@Override
public <T> T getAdapter( Class<T> adapter ) {
Object result;
if( adapter.equals( WidgetLCA.class ) ) {
result = new WidgetLCA() {
@Override
public void preserveValues( Widget widget ) {
}
@Override
public void readData( Widget widget ) {
}
@Override
public void renderInitialization( Widget widget )
throws IOException
{
throw new RuntimeException( EXCEPTION_IN_RENDER );
}
@Override
public void renderChanges( Widget widget ) throws IOException {
throw new RuntimeException( EXCEPTION_IN_RENDER );
}
@Override
public void renderDispose( Widget widget ) throws IOException {
}
};
} else {
result = super.getAdapter( adapter );
}
return ( T )result;
}
}
@Override
public int createUI() {
Display display = new Display();
Shell shell = new BuggyShell( display );
shell.open();
while( !shell.isDisposed() ) {
try {
if( !display.readAndDispatch() ) {
display.sleep();
}
} catch( RuntimeException e ) {
// continue loop
}
}
log.append( "regular end of createUI" );
return 0;
}
}
public static final class TestOrderOfDisplayDisposeAndSessionUnboundEntryPoint
implements EntryPoint
{
@Override
public int createUI() {
Display display = new Display();
display.addListener( SWT.Dispose, new Listener() {
@Override
public void handleEvent( Event event ) {
log.append( "disposeEvent, " );
}
} );
UISession uiSession = RWT.getUISession();
uiSession.addUISessionListener( new UISessionListener() {
@Override
public void beforeDestroy( UISessionEvent event ) {
log.append( "beforeDestroy" );
}
} );
return 0;
}
}
}