/******************************************************************************* * Copyright (c) 2002, 2009 Innoopract Informationssysteme GmbH. * 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.rwt.internal.lifecycle; import java.io.IOException; import java.text.MessageFormat; import java.util.*; import javax.servlet.http.HttpServletRequest; import org.eclipse.rwt.internal.AdapterFactoryRegistry; import org.eclipse.rwt.internal.lifecycle.IPhase.IInterruptible; import org.eclipse.rwt.internal.lifecycle.UIThread.UIThreadTerminatedError; import org.eclipse.rwt.internal.service.*; import org.eclipse.rwt.internal.util.ParamCheck; import org.eclipse.rwt.lifecycle.*; import org.eclipse.rwt.service.ISessionStore; import org.eclipse.swt.internal.graphics.TextSizeDetermination; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; /** * TODO: [fappel] comment * <p></p> */ public class RWTLifeCycle extends LifeCycle { public static final String UI_THREAD = RWTLifeCycle.class.getName() + ".uiThread"; private static final Integer ZERO = new Integer( 0 ); private static final String ATTR_SESSION_DISPLAY = RWTLifeCycle.class.getName() + "#sessionDisplay"; private static final String ATTR_REDRAW_CONTROLS = RWTLifeCycle.class.getName() + "#redrawControls"; private static final String INITIALIZED = RWTLifeCycle.class.getName() + "Initialized"; private static final String CURRENT_PHASE = RWTLifeCycle.class.getName() + ".currentPhase"; private static final String PHASE_ORDER = RWTLifeCycle.class.getName() + ".phaseOrder"; private static final String UI_THREAD_THROWABLE = UIThreadController.class.getName() + "#UIThreadThrowable"; private static final String REQUEST_THREAD_RUNNABLE = RWTLifeCycle.class.getName() + "#requestThreadRunnable"; private final static IPhase[] PHASES = new IPhase[] { new PrepareUIRoot(), new ReadData(), new ProcessAction(), new Render() }; private static final IPhase[] PHASE_ORDER_STARTUP = new IPhase[] { new IInterruptible() { public PhaseId execute() throws IOException { return null; } public PhaseId getPhaseID() { return PhaseId.PREPARE_UI_ROOT; } }, new Render() }; private static final IPhase[] PHASE_ORDER_SUBSEQUENT = new IPhase[] { new IPhase() { public PhaseId execute() throws IOException { return null; } public PhaseId getPhaseID() { return PhaseId.PREPARE_UI_ROOT; } }, new ReadData(), new IInterruptible() { public PhaseId execute() throws IOException { new ProcessAction().execute(); return null; } public PhaseId getPhaseID() { return PhaseId.PROCESS_ACTION; } }, new Render() }; private static final class PhaseExecutionError extends ThreadDeath { private static final long serialVersionUID = 1L; public PhaseExecutionError( final Throwable cause ) { initCause( cause ); } } private final class UIThreadController implements Runnable { public void run() { IUIThreadHolder uiThread = ( IUIThreadHolder )Thread.currentThread(); try { try { synchronized( uiThread.getLock() ) { uiThread.updateServiceContext(); UICallBackManager.getInstance().notifyUIThreadStart(); continueLifeCycle(); createUI(); continueLifeCycle(); UICallBackManager.getInstance().notifyUIThreadEnd(); } } catch( UIThreadTerminatedError thr ) { throw thr; } catch( Throwable thr ) { IServiceStateInfo stateInfo = ContextProvider.getStateInfo(); stateInfo.setAttribute( UI_THREAD_THROWABLE, thr ); } // In any case: wait for the thread to be terminated by session timeout uiThread.switchThread(); } catch( UIThreadTerminatedError e ) { // If we get here, the session is being invalidated, see // UIThread#terminateThread() ( ( ISessionShutdownAdapter )uiThread ).processShutdown(); } } } Runnable uiRunnable; private final Set listeners; public RWTLifeCycle() { listeners = new HashSet(); listeners.addAll( Arrays.asList( PhaseListenerRegistry.get() ) ); uiRunnable = new UIThreadController(); } public void execute() throws IOException { initialize(); if( getEntryPoint() != null ) { setPhaseOrder( PHASE_ORDER_STARTUP ); } else { setPhaseOrder( PHASE_ORDER_SUBSEQUENT ); } Runnable runnable = null; do { setRequestThreadRunnable( null ); executeUIThread(); runnable = getRequestThreadRunnable(); if( runnable != null ) { runnable.run(); } } while( runnable != null ); } public void addPhaseListener( final PhaseListener listener ) { ParamCheck.notNull( listener, "listener" ); synchronized( listeners ) { listeners.add( listener ); } } public void removePhaseListener( final PhaseListener listener ) { ParamCheck.notNull( listener, "listener" ); synchronized( listeners ) { listeners.remove( listener ); } } public Scope getScope() { return Scope.APPLICATION; } public static void requestThreadExec( final Runnable runnable ) { setRequestThreadRunnable( runnable ); switchThread(); } private static void setRequestThreadRunnable( final Runnable runnable ) { IServiceStateInfo stateInfo = ContextProvider.getStateInfo(); stateInfo.setAttribute( REQUEST_THREAD_RUNNABLE, runnable ); } private static Runnable getRequestThreadRunnable() { IServiceStateInfo stateInfo = ContextProvider.getStateInfo(); return ( Runnable )stateInfo.getAttribute( REQUEST_THREAD_RUNNABLE ); } ////////////////////////// // readAndDispatch & sleep void continueLifeCycle() { int start = 0; IPhase[] phaseOrder = getPhaseOrder(); if( phaseOrder != null ) { Integer currentPhase = getCurrentPhase(); if( currentPhase != null ) { int phaseIndex = currentPhase.intValue(); // A non-null currentPhase indicates that an IInterruptible phase // was executed before. In this case we now need to execute the // AfterPhase events afterPhaseExecution( phaseOrder[ phaseIndex ].getPhaseID() ); start = currentPhase.intValue() + 1; } boolean interrupted = false; for( int i = start; !interrupted && i < phaseOrder.length; i++ ) { IPhase phase = phaseOrder[ i ]; beforePhaseExecution( phase.getPhaseID() ); if( phase instanceof IInterruptible ) { // IInterruptible phases return control to the user code, thus // they don't call Phase#execute() IServiceStateInfo stateInfo = ContextProvider.getStateInfo(); stateInfo.setAttribute( CURRENT_PHASE, new Integer( i ) ); interrupted = true; } else { try { phase.execute(); } catch( Throwable e ) { // Wrap exception in a ThreadDeath-derived error to break out of // the application call stack throw new PhaseExecutionError( e ); } afterPhaseExecution( phase.getPhaseID() ); } } if( !interrupted ) { ContextProvider.getStateInfo().setAttribute( CURRENT_PHASE, null ); } } } static int createUI() { int result = -1; if( ZERO.equals( getCurrentPhase() ) ) { String startup = getEntryPoint(); if( startup != null ) { TextSizeDetermination.readStartupProbes(); result = EntryPointManager.createUI( startup ); } } return result; } void executeUIThread() throws IOException { ServiceContext context = ContextProvider.getContext(); ISessionStore session = ContextProvider.getSession(); IUIThreadHolder uiThread = getUIThreadHolder(); if( uiThread == null ) { uiThread = createUIThread(); // The serviceContext MUST be set before thread.start() is called uiThread.setServiceContext( context ); synchronized( uiThread.getLock() ) { uiThread.getThread().start(); uiThread.switchThread(); } } else { uiThread.setServiceContext( context ); uiThread.switchThread(); } // TODO [rh] consider moving this to UIThreadController#run if( !uiThread.getThread().isAlive() ) { session.setAttribute( UI_THREAD, null ); } handleUIThreadException(); } private static void handleUIThreadException() throws IOException { IServiceStateInfo stateInfo = ContextProvider.getStateInfo(); Throwable throwable = ( Throwable )stateInfo.getAttribute( UI_THREAD_THROWABLE ); if( throwable != null ) { if( throwable instanceof PhaseExecutionError ) { throwable = throwable.getCause(); } if( throwable instanceof IOException ) { throw ( IOException )throwable; } else if( throwable instanceof RuntimeException ) { throw ( RuntimeException )throwable; } else if( throwable instanceof Error ) { throw ( Error )throwable; } else { throw new RuntimeException( throwable ); } } } public void sleep() { continueLifeCycle(); IUIThreadHolder uiThread = getUIThreadHolder(); UICallBackManager.getInstance().notifyUIThreadEnd(); uiThread.switchThread(); uiThread.updateServiceContext(); UICallBackManager.getInstance().notifyUIThreadStart(); continueLifeCycle(); } private IUIThreadHolder createUIThread() { ISessionStore session = ContextProvider.getSession(); IUIThreadHolder result = new UIThread( uiRunnable ); result.getThread().setDaemon( true ); result.getThread().setName( "UIThread [" + session.getId() + "]" ); session.setAttribute( UI_THREAD, result ); setShutdownAdapter( ( ISessionShutdownAdapter )result ); return result; } private static void switchThread() { ISessionStore session = ContextProvider.getSession(); IUIThreadHolder uiThreadHolder = ( IUIThreadHolder )session.getAttribute( UI_THREAD ); uiThreadHolder.switchThread(); } private static Integer getCurrentPhase() { IServiceStateInfo stateInfo = ContextProvider.getStateInfo(); return ( Integer )stateInfo.getAttribute( CURRENT_PHASE ); } private static String getEntryPoint() { String result = null; HttpServletRequest request = ContextProvider.getRequest(); String startup = request.getParameter( RequestParams.STARTUP ); if( startup != null ) { result = startup; } else if( getSessionDisplay() == null ) { result = EntryPointManager.DEFAULT; } return result; } private static void setShutdownAdapter( final ISessionShutdownAdapter adapter ) { ISessionStore sessionStore = ContextProvider.getSession(); SessionStoreImpl sessionStoreImpl = ( SessionStoreImpl )sessionStore; sessionStoreImpl.setShutdownAdapter( adapter ); } private void beforePhaseExecution( final PhaseId current ) { PhaseListener[] phaseListeners = getPhaseListeners(); PhaseEvent evt = new PhaseEvent( this, current ); for( int i = 0; i < phaseListeners.length; i++ ) { PhaseId listenerId = phaseListeners[ i ].getPhaseId(); if( mustNotify( current, listenerId ) ) { try { phaseListeners[ i ].beforePhase( evt ); } catch( final Throwable thr ) { String text = "Could not execute PhaseListener before phase ''{0}''."; String msg = MessageFormat.format( text, new Object[] { current } ); ServletLog.log( msg, thr ); } } } } void afterPhaseExecution( final PhaseId current ) { PhaseListener[] phaseListeners = getPhaseListeners(); PhaseEvent evt = new PhaseEvent( this, current ); for( int i = 0; i < phaseListeners.length; i++ ) { PhaseId listenerId = phaseListeners[ i ].getPhaseId(); if( mustNotify( current, listenerId ) ) { try { phaseListeners[ i ].afterPhase( evt ); } catch( final Throwable thr ) { String text = "Could not execute PhaseListener after phase ''{0}''."; String msg = MessageFormat.format( text, new Object[] { current } ); ServletLog.log( msg, thr ); } } } if( current == PhaseId.PROCESS_ACTION ) { doRedrawFake(); } } private static void initialize() { ISessionStore session = ContextProvider.getSession(); if( session.getAttribute( INITIALIZED ) == null ) { AdapterFactoryRegistry.register(); session.setAttribute( INITIALIZED, Boolean.TRUE ); } } private static boolean mustNotify( final PhaseId currentId, final PhaseId listenerId ) { return listenerId == PhaseId.ANY || listenerId == PHASES[ currentId.getOrdinal() - 1 ].getPhaseID(); } private PhaseListener[] getPhaseListeners() { synchronized( listeners ) { PhaseListener[] result = new PhaseListener[ listeners.size() ]; listeners.toArray( result ); return result; } } public static void fakeRedraw( final Control control, final boolean redraw ) { IServiceStateInfo stateInfo = ContextProvider.getStateInfo(); Set set = ( Set )stateInfo.getAttribute( ATTR_REDRAW_CONTROLS ); if( set == null ) { set = new HashSet(); stateInfo.setAttribute( ATTR_REDRAW_CONTROLS, set ); } if( redraw ) { set.add( control ); } else { set.remove( control ); } } public static boolean needsFakeRedraw( final Control control ) { IServiceStateInfo stateInfo = ContextProvider.getStateInfo(); Set set = ( Set )stateInfo.getAttribute( ATTR_REDRAW_CONTROLS ); return set != null && set.contains( control ); } private static void doRedrawFake() { IServiceStateInfo stateInfo = ContextProvider.getStateInfo(); Set set = ( Set )stateInfo.getAttribute( ATTR_REDRAW_CONTROLS ); if( set != null ) { Object[] controls = set.toArray(); for( int i = 0; i < controls.length; i++ ) { Control control = ( Control )controls[ i ]; WidgetUtil.getLCA( control ).doRedrawFake( control ); } } } public void setPhaseOrder( final IPhase[] phaseOrder ) { IServiceStateInfo stateInfo = ContextProvider.getContext().getStateInfo(); stateInfo.setAttribute( PHASE_ORDER, phaseOrder ); } IPhase[] getPhaseOrder() { IServiceStateInfo stateInfo = ContextProvider.getContext().getStateInfo(); return ( IPhase[] )stateInfo.getAttribute( PHASE_ORDER ); } public static IUIThreadHolder getUIThreadHolder() { ISessionStore session = ContextProvider.getSession(); return ( IUIThreadHolder )session.getAttribute( UI_THREAD ); } public static void setSessionDisplay( final Display display ) { ContextProvider.getSession().setAttribute( ATTR_SESSION_DISPLAY, display ); } public static Display getSessionDisplay() { Display result = null; if( ContextProvider.hasContext() ) { ISessionStore sessionStore = ContextProvider.getSession(); result = ( Display )sessionStore.getAttribute( ATTR_SESSION_DISPLAY ); } return result; } }