blob: f583141115c59643e9e6dc01102ecebc44c8424d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002, 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.service;
import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.eclipse.rap.rwt.application.EntryPoint;
import org.eclipse.rap.rwt.client.Client;
import org.eclipse.rap.rwt.client.WebClient;
import org.eclipse.rap.rwt.internal.application.ApplicationContextImpl;
import org.eclipse.rap.rwt.internal.lifecycle.EntryPointManager;
import org.eclipse.rap.rwt.internal.lifecycle.LifeCycle;
import org.eclipse.rap.rwt.internal.lifecycle.LifeCycleFactory;
import org.eclipse.rap.rwt.internal.lifecycle.RequestCounter;
import org.eclipse.rap.rwt.internal.protocol.ClientMessage;
import org.eclipse.rap.rwt.internal.protocol.ClientMessageConst;
import org.eclipse.rap.rwt.internal.protocol.ProtocolUtil;
import org.eclipse.rap.rwt.internal.util.HTTP;
import org.eclipse.rap.rwt.service.ServiceHandler;
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.testfixture.Fixture;
import org.eclipse.rap.rwt.testfixture.Message;
import org.eclipse.rap.rwt.testfixture.TestRequest;
import org.eclipse.rap.rwt.testfixture.TestResponse;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class LifeCycleServiceHandler_Test {
private static final String SESSION_STORE_ATTRIBUTE = "session-store-attribute";
private static final String HTTP_SESSION_ATTRIBUTE = "http-session-attribute";
private static final int THREAD_COUNT = 10;
private static final String ENTER = "enter|";
private static final String EXIT = "exit|";
private final StringBuilder log = new StringBuilder();
private LifeCycleServiceHandler handler;
@Before
public void setUp() {
Fixture.setUp();
EntryPointManager entryPointManager = getApplicationContext().getEntryPointManager();
entryPointManager.register( "/rap", TestEntryPoint.class, null );
entryPointManager.register( "/test", TestEntryPoint.class, null );
handler = new LifeCycleServiceHandler( mockLifeCycleFactory(), mockStartupPage() );
}
@After
public void tearDown() {
Fixture.tearDown();
}
@Test
public void testRequestSynchronization() throws InterruptedException {
List<Thread> threads = new ArrayList<Thread>();
// initialize session, see bug 344549
ContextProvider.getUISession();
ServiceContext context = ContextProvider.getContext();
for( int i = 0; i < THREAD_COUNT; i++ ) {
ServiceHandler syncHandler = new TestHandler( getLifeCycleFactory(), mockStartupPage() );
Thread thread = new Thread( new Worker( context, syncHandler ) );
thread.setDaemon( true );
thread.start();
threads.add( thread );
}
while( threads.size() > 0 ) {
Thread thread = threads.get( 0 );
thread.join();
threads.remove( 0 );
}
String expected = "";
for( int i = 0; i < THREAD_COUNT; i++ ) {
expected += ENTER + EXIT;
}
assertEquals( expected, log.toString() );
}
@Test
public void testUISessionClearedOnSessionRestart() throws IOException {
UISession uiSession = ContextProvider.getUISession();
uiSession.setAttribute( SESSION_STORE_ATTRIBUTE, new Object() );
LifeCycleServiceHandler.markSessionStarted();
simulateInitialUiRequest();
service( handler );
assertNull( uiSession.getAttribute( SESSION_STORE_ATTRIBUTE ) );
}
@Test
public void testShutdownUISession() throws IOException {
UISession uiSession = ContextProvider.getUISession();
LifeCycleServiceHandler.markSessionStarted();
simulateShutdownUiRequest();
service( handler );
assertFalse( uiSession.isBound() );
}
@Test
public void testShutdownUISession_RemoveUISessionFromHttpSession() throws IOException {
UISession uiSession = ContextProvider.getUISession();
HttpSession httpSession = uiSession.getHttpSession();
LifeCycleServiceHandler.markSessionStarted();
simulateShutdownUiRequest();
service( handler );
assertNull( UISessionImpl.getInstanceFromSession( httpSession, null ) );
}
@Test
public void testStartUISession_AfterPreviousShutdown() throws IOException {
UISession oldUiSession = ContextProvider.getUISession();
LifeCycleServiceHandler.markSessionStarted();
simulateShutdownUiRequest();
service( handler );
simulateInitialUiRequest();
service( handler );
UISession newUiSession = ContextProvider.getUISession();
assertNotSame( oldUiSession, newUiSession );
}
@Test
public void testUISessionListerenerCalledOnce_AfterPreviousShutdown() throws IOException {
UISession uiSession = ContextProvider.getUISession();
UISessionListener listener = mock( UISessionListener.class );
uiSession.addUISessionListener(listener );
LifeCycleServiceHandler.markSessionStarted();
simulateShutdownUiRequest();
service( handler );
simulateInitialUiRequest();
service( handler );
verify( listener, times( 1 ) ).beforeDestroy( any( UISessionEvent.class ) );
}
@Test
public void testHttpSessionNotClearedOnSessionRestart() throws IOException {
HttpSession httpSession = ContextProvider.getUISession().getHttpSession();
Object attribute = new Object();
httpSession.setAttribute( HTTP_SESSION_ATTRIBUTE, attribute );
LifeCycleServiceHandler.markSessionStarted();
simulateInitialUiRequest();
service( handler );
assertSame( attribute, httpSession.getAttribute( HTTP_SESSION_ATTRIBUTE ) );
}
@Test
public void testRequestCounterAfterSessionRestart() throws IOException {
RequestCounter.getInstance().nextRequestId();
RequestCounter.getInstance().nextRequestId();
int versionBeforeRestart = RequestCounter.getInstance().currentRequestId();
LifeCycleServiceHandler.markSessionStarted();
simulateInitialUiRequest();
service( new LifeCycleServiceHandler( getLifeCycleFactory(), mockStartupPage() ) );
int versionAfterRestart = RequestCounter.getInstance().currentRequestId();
assertEquals( versionBeforeRestart + 1, versionAfterRestart );
}
@Test
public void testApplicationContextAfterSessionRestart() throws IOException {
LifeCycleServiceHandler.markSessionStarted();
simulateInitialUiRequest();
ApplicationContextImpl applicationContext = getApplicationContext();
service( new LifeCycleServiceHandler( getLifeCycleFactory(), mockStartupPage() ) );
UISession uiSession = ContextProvider.getUISession();
assertSame( applicationContext, uiSession.getApplicationContext() );
}
/*
* When cleaning the session store, the display is disposed. This put a list with all disposed
* widgets into the service store. As application is restarted in the same request, we have to
* prevent these dispose calls to be rendered.
* See https://bugs.eclipse.org/bugs/show_bug.cgi?id=373084
*/
@Test
public void testClearServiceStoreAfterSessionRestart() throws IOException {
LifeCycleServiceHandler.markSessionStarted();
simulateInitialUiRequest();
service( new LifeCycleServiceHandler( getLifeCycleFactory(), mockStartupPage() ) );
simulateInitialUiRequest();
ContextProvider.getServiceStore().setAttribute( "foo", "bar" );
service( new LifeCycleServiceHandler( getLifeCycleFactory(), mockStartupPage() ) );
assertNull( ContextProvider.getServiceStore().getAttribute( "foo" ) );
}
@Test
public void testClearServiceStoreAfterSessionRestart_RestoreMessage() throws IOException {
LifeCycleServiceHandler.markSessionStarted();
simulateInitialUiRequest();
service( new LifeCycleServiceHandler( getLifeCycleFactory(), mockStartupPage() ) );
simulateInitialUiRequest();
ClientMessage message = ProtocolUtil.getClientMessage();
service( new LifeCycleServiceHandler( getLifeCycleFactory(), mockStartupPage() ) );
assertSame( message, ProtocolUtil.getClientMessage() );
}
@Test
public void testFinishesProtocolWriter() throws IOException {
simulateUiRequest();
service( handler );
TestResponse response = ( TestResponse )ContextProvider.getResponse();
assertTrue( response.getContent().contains( "\"head\":" ) );
}
@Test
public void testContentType() throws IOException {
simulateUiRequest();
service( handler );
TestResponse response = ( TestResponse )ContextProvider.getResponse();
assertEquals( "application/json; charset=UTF-8", response.getHeader( "Content-Type" ) );
}
@Test
public void testContentTypeForIllegalRequestCounter() throws IOException {
simulateUiRequestWithIllegalCounter();
service( new LifeCycleServiceHandler( getLifeCycleFactory(), mockStartupPage() ) );
TestResponse response = ( TestResponse )ContextProvider.getResponse();
assertEquals( "application/json; charset=UTF-8", response.getHeader( "Content-Type" ) );
}
@Test
public void testContentTypeForStartupJson() throws IOException {
Fixture.fakeNewRequest();
Fixture.fakeClient( mock( Client.class ) );
TestRequest request = ( TestRequest )ContextProvider.getRequest();
request.setMethod( HTTP.METHOD_GET );
service( new LifeCycleServiceHandler( getLifeCycleFactory(),
getApplicationContext().getStartupPage() ) );
TestResponse response = ( TestResponse )ContextProvider.getResponse();
assertEquals( "application/json; charset=UTF-8", response.getHeader( "Content-Type" ) );
}
@Test
public void testContentTypeForStartupPage() throws IOException {
Fixture.fakeNewRequest();
Fixture.fakeClient( mock( WebClient.class ) );
TestRequest request = ( TestRequest )ContextProvider.getRequest();
request.setMethod( HTTP.METHOD_GET );
StartupPage startupPage = getApplicationContext().getStartupPage();
service( new LifeCycleServiceHandler( getLifeCycleFactory(), startupPage ) );
TestResponse response = ( TestResponse )ContextProvider.getResponse();
assertEquals( "text/html; charset=UTF-8", response.getHeader( "Content-Type" ) );
}
@Test
public void testContentTypeForHeadRequest() throws IOException {
Fixture.fakeNewRequest();
Fixture.fakeClient( mock( WebClient.class ) );
TestRequest request = ( TestRequest )ContextProvider.getRequest();
request.setMethod( "HEAD" );
StartupPage startupPage = getApplicationContext().getStartupPage();
service( new LifeCycleServiceHandler( getLifeCycleFactory(), startupPage ) );
TestResponse response = ( TestResponse )ContextProvider.getResponse();
assertEquals( "text/html; charset=UTF-8", response.getHeader( "Content-Type" ) );
}
@Test
public void testHandleInvalidRequestCounter() throws IOException {
LifeCycleServiceHandler.markSessionStarted();
simulateUiRequestWithIllegalCounter();
service( new LifeCycleServiceHandler( getLifeCycleFactory(), mockStartupPage() ) );
Message message = Fixture.getProtocolMessage();
assertEquals( 0, message.getOperationCount() );
assertEquals( "invalid request counter", message.getError() );
TestResponse response = ( TestResponse )ContextProvider.getResponse();
assertEquals( HttpServletResponse.SC_PRECONDITION_FAILED, response.getStatus() );
}
@Test
public void testHandleSessionTimeout() throws IOException {
simulateUiRequest();
service( new LifeCycleServiceHandler( getLifeCycleFactory(), mockStartupPage() ) );
Message message = Fixture.getProtocolMessage();
assertEquals( 0, message.getOperationCount() );
assertEquals( "session timeout", message.getError() );
TestResponse response = ( TestResponse )ContextProvider.getResponse();
assertEquals( HttpServletResponse.SC_FORBIDDEN, response.getStatus() );
}
private void simulateInitialUiRequest() {
Fixture.fakeNewRequest();
Fixture.fakeHeadParameter( ClientMessageConst.RWT_INITIALIZE, "true" );
TestRequest request = ( TestRequest )ContextProvider.getRequest();
request.setServletPath( "/test" );
}
private void simulateShutdownUiRequest() {
Fixture.fakeNewRequest();
Fixture.fakeHeadParameter( ClientMessageConst.RWT_SHUTDOWN, "true" );
TestRequest request = ( TestRequest )ContextProvider.getRequest();
request.setServletPath( "/test" );
}
private void simulateUiRequest() {
Fixture.fakeNewRequest();
TestRequest request = ( TestRequest )ContextProvider.getRequest();
request.setServletPath( "/test" );
}
private void simulateUiRequestWithIllegalCounter() {
Fixture.fakeNewRequest();
Fixture.fakeHeadParameter( "requestCounter", "23" );
TestRequest request = ( TestRequest )ContextProvider.getRequest();
request.setServletPath( "/test" );
}
private static LifeCycleFactory mockLifeCycleFactory() {
LifeCycle lifecycle = mock( LifeCycle.class );
LifeCycleFactory lifeCycleFactory = mock( LifeCycleFactory.class );
when( lifeCycleFactory.getLifeCycle() ).thenReturn( lifecycle );
return lifeCycleFactory;
}
private LifeCycleFactory getLifeCycleFactory() {
return getApplicationContext().getLifeCycleFactory();
}
private static StartupPage mockStartupPage() {
return mock( StartupPage.class );
}
private static void service( LifeCycleServiceHandler serviceHandler ) throws IOException {
serviceHandler.service( ContextProvider.getRequest(), ContextProvider.getResponse() );
}
private class TestHandler extends LifeCycleServiceHandler {
public TestHandler( LifeCycleFactory lifeCycleFactory, StartupPage startupPage ) {
super( lifeCycleFactory, startupPage );
}
@Override
void synchronizedService( HttpServletRequest request, HttpServletResponse response ) {
log.append( ENTER );
try {
Thread.sleep( 2 );
} catch( InterruptedException e ) {
// ignore
}
log.append( EXIT );
}
}
private static class Worker implements Runnable {
private final ServiceContext context;
private final ServiceHandler serviceHandler;
private Worker( ServiceContext context, ServiceHandler serviceHandler ) {
this.context = context;
this.serviceHandler = serviceHandler;
}
public void run() {
ContextProvider.setContext( context );
try {
serviceHandler.service( context.getRequest(), context.getResponse() );
} catch( ServletException e ) {
throw new RuntimeException( e );
} catch( IOException e ) {
throw new RuntimeException( e );
} finally {
ContextProvider.releaseContextHolder();
}
}
}
public static final class TestEntryPoint implements EntryPoint {
public int createUI() {
Display display = new Display();
Shell shell = new Shell( display );
shell.setSize( 100, 100 );
shell.layout();
shell.open();
while( !shell.isDisposed() ) {
if( !display.readAndDispatch() ) {
display.sleep();
}
}
return 0;
}
}
}