/******************************************************************************* * Copyright (c) 2002, 2015 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 implementation * Frank Appel - replaced singletons and static fields (Bug 337787) ******************************************************************************/ package org.eclipse.rap.rwt.testfixture.internal; import static org.eclipse.rap.rwt.internal.lifecycle.DisplayUtil.getAdapter; import static org.eclipse.rap.rwt.internal.lifecycle.WidgetUtil.getAdapter; import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext; import static org.eclipse.rap.rwt.internal.service.ContextProvider.getProtocolWriter; import static org.eclipse.rap.rwt.internal.service.ContextProvider.getUISession; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.servlet.ServletContext; 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.json.JsonValue; import org.eclipse.rap.rwt.application.Application; import org.eclipse.rap.rwt.application.Application.OperationMode; import org.eclipse.rap.rwt.application.ApplicationConfiguration; import org.eclipse.rap.rwt.client.Client; import org.eclipse.rap.rwt.client.WebClient; import org.eclipse.rap.rwt.internal.SingletonManager; import org.eclipse.rap.rwt.internal.application.ApplicationContextHelper; import org.eclipse.rap.rwt.internal.application.ApplicationContextImpl; import org.eclipse.rap.rwt.internal.client.ClientSelector; import org.eclipse.rap.rwt.internal.lifecycle.WidgetLCA; import org.eclipse.rap.rwt.internal.lifecycle.CurrentPhase; import org.eclipse.rap.rwt.internal.lifecycle.DisplayUtil; import org.eclipse.rap.rwt.internal.lifecycle.IUIThreadHolder; import org.eclipse.rap.rwt.internal.lifecycle.LifeCycleUtil; import org.eclipse.rap.rwt.internal.lifecycle.PhaseId; import org.eclipse.rap.rwt.internal.lifecycle.RWTLifeCycle; import org.eclipse.rap.rwt.internal.lifecycle.WidgetUtil; import org.eclipse.rap.rwt.internal.protocol.ClientMessage; import org.eclipse.rap.rwt.internal.protocol.ProtocolUtil; import org.eclipse.rap.rwt.internal.resources.ResourceDirectory; 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.internal.service.UISessionBuilder; import org.eclipse.rap.rwt.internal.service.UISessionImpl; import org.eclipse.rap.rwt.internal.theme.ThemeManager; import org.eclipse.rap.rwt.internal.util.HTTP; import org.eclipse.rap.rwt.remote.Connection; import org.eclipse.rap.rwt.service.ResourceManager; import org.eclipse.rap.rwt.testfixture.internal.engine.ThemeManagerHelper; import org.eclipse.swt.internal.widgets.WidgetRemoteAdapter; import org.eclipse.swt.internal.widgets.displaykit.DisplayLCA; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Widget; /** * Test fixture for RWT. * <p> * <strong>IMPORTANT:</strong> This class is <em>not</em> part the public RAP * API. It may change or disappear without further notice. Use this class at * your own risk. * </p> */ @SuppressWarnings( "deprecation" ) public final class Fixture { private static final String HEAD = "head"; private static final String OPERATIONS = "operations"; private static final String SET = "set"; private static final String CALL = "call"; private static final String NOTIFY = "notify"; public final static File TEMP_DIR = createTempDir(); public static final File WEB_CONTEXT_DIR = new File( TEMP_DIR, "testapp" ); public static final File WEB_CONTEXT_RWT_RESOURCES_DIR = new File( WEB_CONTEXT_DIR, ResourceDirectory.DIRNAME ); public static final String IMAGE1 = "resources/images/image1.gif"; public static final String IMAGE2 = "resources/images/image2.gif"; public static final String IMAGE3 = "resources/images/image3.gif"; public static final String IMAGE_100x50 = "resources/images/test-100x50.png"; public static final String IMAGE_50x100 = "resources/images/test-50x100.png"; private static final String SYS_PROP_USE_PERFORMANCE_OPTIMIZATIONS = "usePerformanceOptimizations"; static { setSkipResourceRegistration( isPerformanceOptimizationsEnabled() ); setSkipResourceDeletion( isPerformanceOptimizationsEnabled() ); } private static ServletContext servletContext; private static ApplicationContextImpl applicationContext; //////////////////////////////////////////// // Methods to control global servlet context public static ServletContext createServletContext() { servletContext = new TestServletContext(); return servletContext; } public static ServletContext getServletContext() { return servletContext; } public static void disposeOfServletContext() { servletContext = null; } public static void setInitParameter( String name, String value ) { ensureServletContext(); servletContext.setInitParameter( name, value ); } //////////////////////////////////////// // Methods to control ApplicationContext public static void createApplicationContext() { createApplicationContext( false ); } public static void createApplicationContext( final boolean useDefaultResourceManager ) { ensureServletContext(); createWebContextDirectory(); ApplicationConfiguration config = new FixtureApplicationConfiguration(); ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader( Fixture.class.getClassLoader() ); applicationContext = new ApplicationContextImpl( config, servletContext ) { @Override protected ThemeManager createThemeManager() { return ThemeManagerHelper.ensureThemeManager(); } @Override protected ResourceManager createResourceManager() { if( useDefaultResourceManager ) { return super.createResourceManager(); } return new TestResourceManager(); } }; applicationContext.attachToServletContext(); SingletonManager.install( applicationContext ); applicationContext.activate(); } finally { Thread.currentThread().setContextClassLoader( oldContextClassLoader ); } } public static void disposeOfApplicationContext() { applicationContext.deactivate(); applicationContext.removeFromServletContext(); disposeOfServletContext(); // TODO [ApplicationContext]: At the time being this improves RWTAllTestSuite performance by // 50% on my machine without causing any test to fail. However this has a bad smell // with it, so I introduced a flag that can be switch on for fast tests on local machines // and switched of for the integration build tests. Think about a less intrusive solution. if( !isPerformanceOptimizationsEnabled() ) { deleteWebContextDirectory(); } applicationContext = null; } //////////////////////////////////// // Methods to control ServiceContext public static void createServiceContext() { TestRequest request = new TestRequest(); ClientMessage message = createEmptyMessage(); request.setBody( message.toString() ); TestResponse response = new TestResponse(); HttpSession session = createTestSession(); request.setSession( session ); createNewServiceContext( request, response ); ProtocolUtil.setClientMessage( message ); } private static TestHttpSession createTestSession() { TestHttpSession result = new TestHttpSession(); if( servletContext != null ) { result.setServletContext( servletContext ); } return result; } public static void disposeOfServiceContext() { ThemeManagerHelper.resetThemeManagerIfNeeded(); HttpSession session = ContextProvider.getRequest().getSession(); ContextProvider.disposeContext(); session.invalidate(); } ///////////////////////////////////////////////////////////////////// // Methods to control web context directories and resource management public static void createWebContextDirectory() { WEB_CONTEXT_DIR.mkdirs(); } public static void deleteWebContextDirectory() { if( WEB_CONTEXT_DIR.exists() ) { FileUtil.delete( WEB_CONTEXT_DIR ); } } ////////////////////////////// // general setup and tear down public static void setUp() { setUp( false ); } public static void setUp( boolean useDefaultResourceManager ) { createApplicationContext( useDefaultResourceManager ); createServiceContext(); fakeClient( new WebClient() ); } public static void tearDown() { disposeOfServiceContext(); disposeOfApplicationContext(); disposeOfServletContext(); } //////////////////// // LifeCycle helpers public static void readDataAndProcessAction( Display display ) { DisplayLCA displayLCA = DisplayUtil.getLCA( display ); fakePhase( PhaseId.READ_DATA ); displayLCA.readData( display ); Fixture.preserveWidgets(); fakePhase( PhaseId.PROCESS_ACTION ); while( Display.getCurrent().readAndDispatch() ) { } } public static void readDataAndProcessAction( Widget widget ) { WidgetLCA widgetLCA = WidgetUtil.getLCA( widget ); fakePhase( PhaseId.READ_DATA ); widgetLCA.readData( widget ); fakePhase( PhaseId.PROCESS_ACTION ); while( Display.getCurrent().readAndDispatch() ) { } } public static void markInitialized( Widget widget ) { ( ( WidgetRemoteAdapter )getAdapter( widget ) ).setInitialized( true ); } public static void markInitialized( Display display ) { ( ( WidgetRemoteAdapter )getAdapter( display ) ).setInitialized( true ); } public static void preserveWidgets() { Display display = LifeCycleUtil.getSessionDisplay(); DisplayLCA displayLCA = DisplayUtil.getLCA( display ); PhaseId bufferedPhaseId = CurrentPhase.get(); fakePhase( PhaseId.RENDER ); displayLCA.clearPreserved( display ); fakePhase( PhaseId.READ_DATA ); displayLCA.preserveValues( display ); fakePhase( bufferedPhaseId ); } public static void clearPreserved() { Display display = LifeCycleUtil.getSessionDisplay(); DisplayLCA displayLCA = DisplayUtil.getLCA( display ); PhaseId bufferedPhaseId = CurrentPhase.get(); fakePhase( PhaseId.RENDER ); displayLCA.clearPreserved( display ); fakePhase( bufferedPhaseId ); } public static TestMessage getProtocolMessage() { TestResponse response = ( TestResponse )ContextProvider.getResponse(); finishResponse( response ); return new TestMessage( JsonObject.readFrom( response.getContent() ) ); } private static void finishResponse( TestResponse response ) { if( response.getContent().length() == 0 ) { try { getProtocolWriter().createMessage().toJson().writeTo( response.getWriter() ); } catch( IOException exception ) { throw new IllegalStateException( "Failed to get response writer", exception ); } } } public static void fakeClient( Client client ) { getUISession().setAttribute( ClientSelector.SELECTED_CLIENT, client ); } public static void fakeConnection( Connection connection ) { ( ( UISessionImpl )getUISession() ).setConnection( connection ); } public static TestRequest fakeNewRequest() { TestRequest request = createNewRequest( HTTP.METHOD_POST ); request.setContentType( HTTP.CONTENT_TYPE_JSON ); request.setParameter( "cid", getConnectionId() ); ClientMessage emptyMessage = createEmptyMessage(); request.setBody( emptyMessage.toString() ); createNewServiceContext( request, new TestResponse() ); ProtocolUtil.setClientMessage( emptyMessage ); fakeResponseWriter(); return request; } public static TestRequest fakeNewGetRequest() { TestRequest request = createNewRequest( HTTP.METHOD_GET ); createNewServiceContext( request, new TestResponse() ); return request; } private static TestRequest createNewRequest( String method ) { TestRequest request = new TestRequest(); request.setMethod( method ); request.setSession( ContextProvider.getRequest().getSession() ); return request; } private static void createNewServiceContext( HttpServletRequest request, HttpServletResponse response ) { ContextProvider.disposeContext(); ServiceContext serviceContext = new ServiceContext( request, response, applicationContext ); serviceContext.setServiceStore( new ServiceStore() ); ContextProvider.setContext( serviceContext ); ensureUISession( serviceContext ); } private static ClientMessage createEmptyMessage() { return new ClientMessage( new JsonObject() .add( HEAD, new JsonObject() ) .add( OPERATIONS, new JsonArray() ) ); } private static String getConnectionId() { UISessionImpl uiSession = ( UISessionImpl )getUISession(); return uiSession == null ? null : uiSession.getConnectionId(); } public static void fakeHeadParameter( String key, long value ) { fakeHeadParameter( key, JsonValue.valueOf( value ) ); } public static void fakeHeadParameter( String key, boolean value ) { fakeHeadParameter( key, JsonValue.valueOf( value ) ); } public static void fakeHeadParameter( String key, String value ) { fakeHeadParameter( key, JsonValue.valueOf( value ) ); } public static void fakeHeadParameter( String key, JsonValue value ) { TestRequest request = ( TestRequest )ContextProvider.getRequest(); String json = request.getBody(); try { JsonObject message = JsonObject.readFrom( json ); JsonObject header = message.get( HEAD ).asObject(); header.add( key, value ); request.setBody( message.toString() ); ProtocolUtil.setClientMessage( new ClientMessage( message ) ); } catch( Exception exception ) { throw new RuntimeException( "Failed to add header parameter", exception ); } } public static void fakeSetProperty( String target, String propertyName, long propertyValue ) { fakeSetProperty( target, propertyName, JsonValue.valueOf( propertyValue ) ); } public static void fakeSetProperty( String target, String propertyName, boolean propertyValue ) { fakeSetProperty( target, propertyName, JsonValue.valueOf( propertyValue ) ); } public static void fakeSetProperty( String target, String propertyName, String propertyValue ) { fakeSetProperty( target, propertyName, JsonValue.valueOf( propertyValue ) ); } public static void fakeSetProperty( String target, String key, JsonValue value ) { fakeSetOperation( target, new JsonObject().add( key, value ) ); } public static void fakeSetOperation( String target, JsonObject properties ) { TestRequest request = ( TestRequest )ContextProvider.getRequest(); String json = request.getBody(); try { JsonObject message = JsonObject.readFrom( json ); JsonArray operations = message.get( OPERATIONS ).asArray(); JsonArray newOperation = new JsonArray(); newOperation.add( SET ); newOperation.add( target ); newOperation.add( properties != null ? properties : new JsonObject() ); operations.add( newOperation ); request.setBody( message.toString() ); ProtocolUtil.setClientMessage( new ClientMessage( message ) ); } catch( Exception exception ) { throw new RuntimeException( "Failed to add set operation", exception ); } } public static void fakeNotifyOperation( String target, String eventName, JsonObject properties ) { TestRequest request = ( TestRequest )ContextProvider.getRequest(); String json = request.getBody(); try { JsonObject message = JsonObject.readFrom( json ); JsonArray operations = message.get( OPERATIONS ).asArray(); JsonArray newOperation = new JsonArray(); newOperation.add( NOTIFY ); newOperation.add( target ); newOperation.add( eventName ); newOperation.add( properties != null ? properties : new JsonObject() ); operations.add( newOperation ); request.setBody( message.toString() ); ProtocolUtil.setClientMessage( new ClientMessage( message ) ); } catch( Exception exception ) { throw new RuntimeException( "Failed to add notify operation", exception ); } } public static void fakeCallOperation( String target, String methodName, JsonObject parameters ) { TestRequest request = ( TestRequest )ContextProvider.getRequest(); String json = request.getBody(); try { JsonObject message = JsonObject.readFrom( json ); JsonArray operations = message.get( OPERATIONS ).asArray(); JsonArray newOperation = new JsonArray(); newOperation.add( CALL ); newOperation.add( target ); newOperation.add( methodName ); newOperation.add( parameters != null ? parameters : new JsonObject() ); operations.add( newOperation ); request.setBody( message.toString() ); ProtocolUtil.setClientMessage( new ClientMessage( message ) ); } catch( Exception exception ) { throw new RuntimeException( "Failed to add call operation", exception ); } } public static void fakeResponseWriter() { TestResponse testResponse = ( TestResponse )ContextProvider.getResponse(); testResponse.clearContent(); ContextProvider.getContext().resetProtocolWriter(); } public static void fakePhase( PhaseId phase ) { CurrentPhase.set( phase ); } public static void executeLifeCycleFromServerThread() { IUIThreadHolder threadHolder = registerCurrentThreadAsUIThreadHolder(); Thread serverThread = fakeRequestThread( threadHolder ); simulateRequest( threadHolder, serverThread ); RWTLifeCycle lifeCycle = ( RWTLifeCycle )getApplicationContext().getLifeCycleFactory().getLifeCycle(); while( LifeCycleUtil.getSessionDisplay().readAndDispatch() ) { } lifeCycle.sleep(); } public static void replaceServiceStore( ServiceStore serviceStore ) { HttpServletRequest request = ContextProvider.getRequest(); HttpServletResponse response = ContextProvider.getResponse(); ContextProvider.disposeContext(); ServiceContext serviceContext = new ServiceContext( request, response, applicationContext ); if( serviceStore != null ) { serviceContext.setServiceStore( serviceStore ); } ContextProvider.setContext( serviceContext ); ensureUISession( serviceContext ); } private static void ensureUISession( ServiceContext serviceContext ) { HttpServletRequest request = serviceContext.getRequest(); HttpSession httpSession = request.getSession( true ); String cid = request.getParameter( "cid" ); UISessionImpl uiSession = UISessionImpl.getInstanceFromSession( httpSession, cid ); if( uiSession == null ) { uiSession = new UISessionBuilder( serviceContext ).buildUISession(); } serviceContext.setUISession( uiSession ); } //////////////// // general stuff public static void setSkipResourceRegistration( boolean skip ) { ApplicationContextHelper.setSkipResoureRegistration( skip ); } public static void resetSkipResourceRegistration() { ApplicationContextHelper.setSkipResoureRegistration( isPerformanceOptimizationsEnabled() ); } public static void setSkipResourceDeletion( boolean skip ) { ApplicationContextHelper.setSkipResoureDeletion( skip ); } public static void resetSkipResourceDeletion() { ApplicationContextHelper.setSkipResoureDeletion( isPerformanceOptimizationsEnabled() ); } public static void copyTestResource( String resourceName, File destination ) throws IOException { ClassLoader loader = Fixture.class.getClassLoader(); InputStream is = loader.getResourceAsStream( resourceName ); if( is == null ) { throw new IllegalArgumentException( "Resource could not be found: " + resourceName ); } BufferedInputStream bis = new BufferedInputStream( is ); try { OutputStream out = new FileOutputStream( destination ); BufferedOutputStream bout = new BufferedOutputStream( out ); try { int c = bis.read(); while( c != -1 ) { bout.write( c ); c = bis.read(); } } finally { bout.close(); } } finally { bis.close(); } } private static void ensureServletContext() { if( servletContext == null ) { createServletContext(); } } private static void simulateRequest( IUIThreadHolder threadHolder, Thread serverThread ) { RWTLifeCycle lifeCycle = ( RWTLifeCycle )getApplicationContext().getLifeCycleFactory().getLifeCycle(); synchronized( threadHolder.getLock() ) { serverThread.start(); try { lifeCycle.sleep(); } catch( ThreadDeath e ) { throw new RuntimeException( e ); } } } private static Thread fakeRequestThread( final IUIThreadHolder threadHolder ) { final RWTLifeCycle lifeCycle = ( RWTLifeCycle )getApplicationContext().getLifeCycleFactory().getLifeCycle(); final ServiceContext context = ContextProvider.getContext(); Thread result = new Thread( new Runnable() { @Override public void run() { synchronized( threadHolder.getLock() ) { ContextProvider.setContext( context ); try { try { lifeCycle.execute(); lifeCycle.setPhaseOrder( null ); } catch( IOException e ) { throw new RuntimeException( e ); } } finally { ContextProvider.releaseContextHolder(); threadHolder.notifyAll(); } } } }, "ServerThread" ); return result; } private static IUIThreadHolder registerCurrentThreadAsUIThreadHolder() { final IUIThreadHolder result = new IUIThreadHolder() { private final Thread thread = Thread.currentThread(); @Override public void setServiceContext( ServiceContext serviceContext ) { } @Override public void switchThread() { synchronized( getLock() ) { notifyAll(); try { wait(); } catch( InterruptedException e ) { throw new RuntimeException( e ); } } } @Override public void updateServiceContext() { } @Override public void terminateThread() { } @Override public Thread getThread() { return thread; } @Override public Object getLock() { return this; } }; LifeCycleUtil.setUIThread( getUISession(), result ); return result; } //////////////// // general stuff private static boolean isPerformanceOptimizationsEnabled() { return Boolean.getBoolean( SYS_PROP_USE_PERFORMANCE_OPTIMIZATIONS ); } private static File createTempDir() { File globalTmpDir = new File( System.getProperty( "java.io.tmpdir" ) ); String subDirName = "rap-test-" + Long.toHexString( System.currentTimeMillis() ); File tmpDir = new File( globalTmpDir, subDirName ); if( !tmpDir.mkdir() ) { String message = "Failed to create temp directory: " + tmpDir.getAbsolutePath(); throw new IllegalStateException( message ); } return tmpDir; } private Fixture() { // prevent instantiation } private static class FixtureApplicationConfiguration implements ApplicationConfiguration { @Override public void configure( Application application ) { application.setOperationMode( OperationMode.SWT_COMPATIBILITY ); } } }