/*******************************************************************************
* 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
******************************************************************************/
package org.eclipse.rap.rwt.internal.service;
import static org.eclipse.rap.rwt.internal.service.ContextProvider.getUISession;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doThrow;
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.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.eclipse.rap.json.JsonArray;
import org.eclipse.rap.json.JsonObject;
import org.eclipse.rap.rwt.client.WebClient;
import org.eclipse.rap.rwt.internal.lifecycle.RequestCounter;
import org.eclipse.rap.rwt.internal.protocol.ClientMessageConst;
import org.eclipse.rap.rwt.internal.protocol.RequestMessage;
import org.eclipse.rap.rwt.internal.protocol.ResponseMessage;
import org.eclipse.rap.rwt.internal.remote.MessageChainElement;
import org.eclipse.rap.rwt.internal.remote.MessageChainReference;
import org.eclipse.rap.rwt.internal.remote.MessageFilter;
import org.eclipse.rap.rwt.internal.remote.MessageFilterChain;
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.internal.Fixture;
import org.eclipse.rap.rwt.testfixture.internal.TestMessage;
import org.eclipse.rap.rwt.testfixture.internal.TestRequest;
import org.eclipse.rap.rwt.testfixture.internal.TestResponse;
import org.eclipse.rap.rwt.testfixture.internal.TestResponseMessage;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
public class LifeCycleServiceHandler_Test {
private static final int THREAD_COUNT = 10;
private static final String ENTER = "enter|";
private static final String EXIT = "exit|";
private MessageFilter filter;
private MessageChainReference messageChainReference;
private LifeCycleServiceHandler serviceHandler;
private StringBuilder log;
@Before
public void setUp() {
Fixture.setUp();
filter = mockMessageFilter();
MessageChainElement handlerWrapper = new MessageChainElement( filter, null );
messageChainReference = new MessageChainReference( handlerWrapper );
serviceHandler = new LifeCycleServiceHandler( messageChainReference );
log = new StringBuilder();
}
@After
public void tearDown() {
Fixture.tearDown();
}
@Test
public void testRequestSynchronization() throws InterruptedException {
List<Thread> threads = new ArrayList<Thread>();
// initialize session, see bug 344549
getUISession();
ServiceContext context = ContextProvider.getContext();
for( int i = 0; i < THREAD_COUNT; i++ ) {
ServiceHandler syncHandler = new TestHandler( messageChainReference );
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 testShutdownUISession() throws IOException {
UISession uiSession = getUISession();
simulateShutdownUiRequest();
service( serviceHandler );
assertFalse( uiSession.isBound() );
}
@Test
public void testShutdownUISession_returnsValidJson() throws IOException {
simulateShutdownUiRequest();
service( serviceHandler );
TestResponse response = getResponse();
JsonObject message = JsonObject.readFrom( response.getContent() );
assertEquals( "application/json; charset=UTF-8", response.getHeader( "Content-Type" ) );
assertNotNull( message.get( "head" ) );
assertNotNull( message.get( "operations" ) );
}
@Test
public void testShutdownUISession_removesUISessionFromHttpSession() throws IOException {
UISession uiSession = getUISession();
HttpSession httpSession = uiSession.getHttpSession();
simulateShutdownUiRequest();
service( serviceHandler );
assertNull( UISessionImpl.getInstanceFromSession( httpSession, null ) );
}
@Test
public void testStartUISession_AfterPreviousShutdown() throws IOException {
UISession oldUiSession = getUISession();
simulateShutdownUiRequest();
service( serviceHandler );
simulateInitialUiRequest();
service( serviceHandler );
UISession newUiSession = getUISession();
assertNotSame( oldUiSession, newUiSession );
}
@Test
public void testUISessionListerenerCalledOnce_AfterPreviousShutdown() throws IOException {
UISession uiSession = getUISession();
UISessionListener listener = mock( UISessionListener.class );
uiSession.addUISessionListener(listener );
simulateShutdownUiRequest();
service( serviceHandler );
simulateInitialUiRequest();
service( serviceHandler );
verify( listener, times( 1 ) ).beforeDestroy( any( UISessionEvent.class ) );
}
@Test
public void testIncrementRequestCounter() throws IOException {
RequestCounter requestCounter = RequestCounter.getInstance();
requestCounter.nextRequestId();
simulateUiRequest();
service( serviceHandler );
assertEquals( 2, requestCounter.currentRequestId() );
}
@Test
public void testFinishesProtocolWriter() throws IOException {
simulateUiRequest();
service( serviceHandler );
assertTrue( getResponse().getContent().contains( "\"head\":" ) );
}
@Test
public void testContentType() throws IOException {
simulateUiRequest();
service( serviceHandler );
assertEquals( "application/json; charset=UTF-8", getResponse().getHeader( "Content-Type" ) );
}
@Test
public void testContentType_forSessionTimeout() throws IOException {
simulateUiRequest();
service( serviceHandler );
assertEquals( "application/json; charset=UTF-8", getResponse().getHeader( "Content-Type" ) );
}
@Test
public void testContentType_forIllegalRequestCounter() throws IOException {
simulateUiRequestWithIllegalCounter();
service( serviceHandler );
assertEquals( "application/json; charset=UTF-8", getResponse().getHeader( "Content-Type" ) );
}
@Test
public void testStartupRequest_shutsDownUISession_ifExceptionInStartupPage() throws IOException {
Fixture.fakeNewGetRequest();
Fixture.fakeClient( mock( WebClient.class ) );
StartupPage startupPage = mock( StartupPage.class );
doThrow( new RuntimeException() ).when( startupPage ).send( any( HttpServletResponse.class ) );
try {
service( new LifeCycleServiceHandler( messageChainReference ) );
} catch( RuntimeException exception ) {
}
assertNull( getUISession() );
}
@Test
public void testHandleInvalidRequestCounter() throws IOException {
simulateUiRequestWithIllegalCounter();
service( serviceHandler );
assertEquals( HttpServletResponse.SC_PRECONDITION_FAILED, getResponse().getStatus() );
JsonObject message = JsonObject.readFrom( getResponse().getContent() );
assertEquals( "invalid request counter", getError( message ) );
assertTrue( message.get( "operations" ).asArray().isEmpty() );
}
@Test
public void testHandlesSessionTimeout() throws IOException {
simulateUiRequest();
( ( UISessionImpl )getUISession() ).shutdown();
service( serviceHandler );
TestResponse response = getResponse();
assertEquals( HttpServletResponse.SC_FORBIDDEN, response.getStatus() );
JsonObject message = JsonObject.readFrom( getResponse().getContent() );
assertEquals( "session timeout", getError( message ) );
assertTrue( message.get( "operations" ).asArray().isEmpty() );
}
@Test
public void testSendBufferedResponse() throws IOException {
simulateUiRequest();
RequestCounter.getInstance().nextRequestId();
int requestCounter = RequestCounter.getInstance().nextRequestId();
Fixture.fakeHeadParameter( "requestCounter", requestCounter );
service( serviceHandler );
JsonObject firstResponse = JsonObject.readFrom( getResponse().getContent() );
simulateUiRequest();
Fixture.fakeHeadParameter( "requestCounter", requestCounter );
service( serviceHandler );
JsonObject secondResponse = JsonObject.readFrom( getResponse().getContent() );
assertEquals( firstResponse, secondResponse );
}
@Test
public void testWritesValidJson() throws IOException {
simulateUiRequest();
service( serviceHandler );
JsonObject.readFrom( getResponse().getContent() );
}
@Test
public void testIsRequestCounterValid_trueWithValidParameter() {
int nextRequestId = RequestCounter.getInstance().nextRequestId();
RequestMessage message = new TestMessage();
message.getHead().set( "requestCounter", nextRequestId );
boolean valid = LifeCycleServiceHandler.isRequestCounterValid( message );
assertTrue( valid );
}
@Test
public void testIsRequestCounterValid_falseWithInvalidParameter() {
RequestCounter.getInstance().nextRequestId();
RequestMessage requestMessage = new TestMessage();
requestMessage.getHead().set( "requestCounter", 23 );
boolean valid = LifeCycleServiceHandler.isRequestCounterValid( requestMessage );
assertFalse( valid );
}
@Test
public void testIsRequestCounterValid_failsWithIllegalParameterFormat() {
RequestCounter.getInstance().nextRequestId();
RequestMessage requestMessage = new TestMessage();
requestMessage.getHead().set( "requestCounter", "not-a-number" );
try {
LifeCycleServiceHandler.isRequestCounterValid( requestMessage );
fail();
} catch( Exception exception ) {
assertTrue( exception.getMessage().contains( "Not a number" ) );
}
}
@Test
public void testIsRequestCounterValid_falseWithMissingParameter() {
RequestCounter.getInstance().nextRequestId();
RequestMessage requestMessage = new TestMessage();
boolean valid = LifeCycleServiceHandler.isRequestCounterValid( requestMessage );
assertFalse( valid );
}
@Test
public void testProcessesMessage() throws IOException {
simulateUiRequest();
JsonObject message = createExampleMessage();
getRequest().setBody( message.toString() );
ArgumentCaptor<RequestMessage> messageCaptor = ArgumentCaptor.forClass( RequestMessage.class );
service( serviceHandler );
verify( filter ).handleMessage( messageCaptor.capture(), any( MessageFilterChain.class ) );
assertEquals( message, messageCaptor.getValue().toJson() );
}
@Test
public void testUIRequest_shutsDownUISession_ifRuntimeExceptionInHandler() throws IOException {
simulateUiRequest();
doThrow( new RuntimeException() )
.when( filter ).handleMessage( any( RequestMessage.class ), any( MessageFilterChain.class ) );
try {
service( new LifeCycleServiceHandler( messageChainReference ) );
} catch( RuntimeException exception ) {
}
assertNull( getUISession() );
}
@Test
public void testUIRequest_shutsDownUISession_ifIOException() throws IOException {
simulateUiRequest();
HttpServletResponse response = mock( HttpServletResponse.class );
doThrow( new IOException() ).when( response ).getWriter();
try {
serviceHandler.service( getRequest(), response );
} catch( IOException exception ) {
}
assertNull( getUISession() );
}
private void simulateUiRequest() {
Fixture.fakeNewRequest();
Fixture.fakeHeadParameter( "requestCounter", RequestCounter.getInstance().currentRequestId() );
}
private void simulateInitialUiRequest() {
Fixture.fakeNewRequest();
Fixture.fakeHeadParameter( "requestCounter", 0 );
}
private void simulateShutdownUiRequest() {
Fixture.fakeNewRequest();
Fixture.fakeHeadParameter( ClientMessageConst.SHUTDOWN, true );
}
private void simulateUiRequestWithIllegalCounter() {
Fixture.fakeNewRequest();
Fixture.fakeHeadParameter( "requestCounter", 23 );
}
private static MessageFilter mockMessageFilter() {
MessageFilter filter = mock( MessageFilter.class );
ResponseMessage responseMessage = new TestResponseMessage();
responseMessage.getHead().add( "test", true );
when( filter.handleMessage( any( RequestMessage.class ), any( MessageFilterChain.class ) ) )
.thenReturn( responseMessage );
return filter;
}
private static JsonObject createExampleMessage() {
return new JsonObject()
.add( "head", new JsonObject().add( "test", true ).add( "requestCounter", 0 ) )
.add( "operations", new JsonArray() );
}
private static void service( LifeCycleServiceHandler serviceHandler ) throws IOException {
serviceHandler.service( getRequest(), getResponse() );
}
private static TestRequest getRequest() {
return ( TestRequest )ContextProvider.getRequest();
}
private static TestResponse getResponse() {
return ( TestResponse )ContextProvider.getResponse();
}
private static String getError( JsonObject message ) {
return message.get( "head" ).asObject().get( "error" ).asString();
}
private class TestHandler extends LifeCycleServiceHandler {
public TestHandler( MessageChainReference messageChainReference ) {
super( messageChainReference );
}
@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( Exception exception ) {
throw new RuntimeException( exception );
} finally {
ContextProvider.releaseContextHolder();
}
}
}
}