| /******************************************************************************* |
| * Copyright (c) 2002, 2012 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.lifecycle; |
| |
| import java.io.IOException; |
| |
| import javax.servlet.http.HttpServletRequest; |
| |
| import org.eclipse.rap.rwt.application.EntryPoint; |
| import org.eclipse.rap.rwt.internal.application.ApplicationContextImpl; |
| import org.eclipse.rap.rwt.internal.lifecycle.IPhase.IInterruptible; |
| import org.eclipse.rap.rwt.internal.lifecycle.UIThread.UIThreadTerminatedError; |
| import org.eclipse.rap.rwt.internal.serverpush.ServerPushManager; |
| 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.UISessionImpl; |
| import org.eclipse.rap.rwt.lifecycle.PhaseId; |
| import org.eclipse.rap.rwt.lifecycle.PhaseListener; |
| import org.eclipse.rap.rwt.service.UISession; |
| import org.eclipse.swt.widgets.Display; |
| |
| |
| public class RWTLifeCycle extends LifeCycle { |
| |
| private static final Integer ZERO = Integer.valueOf( 0 ); |
| |
| 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_WAITING_FOR_TERMINATION |
| = UIThreadController.class.getName() + "#UIThreadWaitingForTermination"; |
| private static final String UI_THREAD_THROWABLE |
| = UIThreadController.class.getName() + "#UIThreadThrowable"; |
| private static final String REQUEST_THREAD_RUNNABLE |
| = RWTLifeCycle.class.getName() + "#requestThreadRunnable"; |
| |
| private static final IPhase[] PHASE_ORDER_STARTUP = new IPhase[] { |
| new IInterruptible() { |
| public PhaseId execute( Display display ) 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( Display display ) throws IOException { |
| return null; |
| } |
| public PhaseId getPhaseId() { |
| return PhaseId.PREPARE_UI_ROOT; |
| } |
| }, |
| new ReadData(), |
| new IInterruptible() { |
| public PhaseId execute( Display display ) throws IOException { |
| new ProcessAction().execute( display ); |
| return null; |
| } |
| public PhaseId getPhaseId() { |
| return PhaseId.PROCESS_ACTION; |
| } |
| }, |
| new Render() |
| }; |
| |
| private final ApplicationContextImpl applicationContext; |
| private final PhaseListenerManager phaseListenerManager; |
| Runnable uiRunnable; |
| |
| public RWTLifeCycle( ApplicationContextImpl applicationContext ) { |
| super( applicationContext ); |
| this.applicationContext = applicationContext; |
| phaseListenerManager = new PhaseListenerManager( this ); |
| uiRunnable = new UIThreadController(); |
| } |
| |
| @Override |
| public void execute() throws IOException { |
| if( LifeCycleUtil.isStartup() ) { |
| 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 ); |
| } |
| |
| @Override |
| public void addPhaseListener( PhaseListener listener ) { |
| phaseListenerManager.addPhaseListener( listener ); |
| } |
| |
| @Override |
| public void removePhaseListener( PhaseListener listener ) { |
| phaseListenerManager.removePhaseListener( listener ); |
| } |
| |
| @Override |
| public void requestThreadExec( Runnable runnable ) { |
| setRequestThreadRunnable( runnable ); |
| getUIThreadHolder().switchThread(); |
| } |
| |
| private static void setRequestThreadRunnable( Runnable runnable ) { |
| ServiceStore serviceStore = ContextProvider.getServiceStore(); |
| serviceStore.setAttribute( REQUEST_THREAD_RUNNABLE, runnable ); |
| } |
| |
| private static Runnable getRequestThreadRunnable() { |
| ServiceStore serviceStore = ContextProvider.getServiceStore(); |
| return ( Runnable )serviceStore.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 |
| phaseListenerManager.notifyAfterPhase( phaseOrder[ phaseIndex ].getPhaseId() ); |
| start = currentPhase.intValue() + 1; |
| } |
| boolean interrupted = false; |
| for( int i = start; !interrupted && i < phaseOrder.length; i++ ) { |
| IPhase phase = phaseOrder[ i ]; |
| CurrentPhase.set( phase.getPhaseId() ); |
| phaseListenerManager.notifyBeforePhase( phase.getPhaseId() ); |
| if( phase instanceof IInterruptible ) { |
| // IInterruptible phases return control to the user code, thus they don't call |
| // Phase#execute() |
| ServiceStore serviceStore = ContextProvider.getServiceStore(); |
| serviceStore.setAttribute( CURRENT_PHASE, new Integer( i ) ); |
| interrupted = true; |
| } else { |
| try { |
| phase.execute( LifeCycleUtil.getSessionDisplay() ); |
| } catch( Throwable e ) { |
| // Wrap exception in a ThreadDeath-derived error to break out of the application |
| // call stack |
| throw new PhaseExecutionError( e ); |
| } |
| phaseListenerManager.notifyAfterPhase( phase.getPhaseId() ); |
| } |
| } |
| if( !interrupted ) { |
| ContextProvider.getServiceStore().setAttribute( CURRENT_PHASE, null ); |
| } |
| } |
| } |
| |
| int createUI() { |
| int result = -1; |
| if( ZERO.equals( getCurrentPhase() ) && LifeCycleUtil.isStartup() ) { |
| EntryPointManager entryPointManager = applicationContext.getEntryPointManager(); |
| HttpServletRequest request = ContextProvider.getRequest(); |
| EntryPointRegistration registration = entryPointManager.getEntryPointRegistration( request ); |
| EntryPoint entryPoint = registration.getFactory().create(); |
| result = entryPoint.createUI(); |
| } |
| return result; |
| } |
| |
| void executeUIThread() throws IOException { |
| ServiceContext context = ContextProvider.getContext(); |
| UISession uiSession = ContextProvider.getUISession(); |
| 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 ); |
| // See bug 354368 |
| if( !Boolean.TRUE.equals( uiSession.getAttribute( UI_THREAD_WAITING_FOR_TERMINATION ) ) ) { |
| uiThread.switchThread(); |
| } |
| } |
| // TODO [rh] consider moving this to UIThreadController#run |
| if( !uiThread.getThread().isAlive() ) { |
| LifeCycleUtil.setUIThread( uiSession, null ); |
| } |
| handleUIThreadException(); |
| } |
| |
| private static void handleUIThreadException() throws IOException { |
| ServiceStore serviceStore = ContextProvider.getServiceStore(); |
| Throwable throwable = ( Throwable )serviceStore.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 ); |
| } |
| } |
| } |
| |
| @Override |
| public void sleep() { |
| continueLifeCycle(); |
| IUIThreadHolder uiThread = getUIThreadHolder(); |
| ServerPushManager.getInstance().notifyUIThreadEnd(); |
| uiThread.switchThread(); |
| uiThread.updateServiceContext(); |
| ServerPushManager.getInstance().notifyUIThreadStart(); |
| continueLifeCycle(); |
| } |
| |
| private IUIThreadHolder createUIThread() { |
| UISession uiSession = ContextProvider.getUISession(); |
| IUIThreadHolder result = new UIThread( uiRunnable ); |
| result.getThread().setDaemon( true ); |
| result.getThread().setName( "UIThread [" + uiSession.getId() + "]" ); |
| LifeCycleUtil.setUIThread( uiSession, result ); |
| setShutdownAdapter( ( ISessionShutdownAdapter )result ); |
| return result; |
| } |
| |
| private static Integer getCurrentPhase() { |
| ServiceStore serviceStore = ContextProvider.getServiceStore(); |
| return ( Integer )serviceStore.getAttribute( CURRENT_PHASE ); |
| } |
| |
| private static void setShutdownAdapter( ISessionShutdownAdapter adapter ) { |
| UISession uiSession = ContextProvider.getUISession(); |
| UISessionImpl uiSessionImpl = ( UISessionImpl )uiSession; |
| uiSessionImpl.setShutdownAdapter( adapter ); |
| } |
| |
| public void setPhaseOrder( IPhase[] phaseOrder ) { |
| ServiceStore serviceStore = ContextProvider.getServiceStore(); |
| serviceStore.setAttribute( PHASE_ORDER, phaseOrder ); |
| } |
| |
| IPhase[] getPhaseOrder() { |
| ServiceStore serviceStore = ContextProvider.getServiceStore(); |
| return ( IPhase[] )serviceStore.getAttribute( PHASE_ORDER ); |
| } |
| |
| public static IUIThreadHolder getUIThreadHolder() { |
| return LifeCycleUtil.getUIThread( ContextProvider.getUISession() ); |
| } |
| |
| private static final class PhaseExecutionError extends ThreadDeath { |
| public PhaseExecutionError( Throwable cause ) { |
| initCause( cause ); |
| } |
| } |
| |
| private final class UIThreadController implements Runnable { |
| public void run() { |
| IUIThreadHolder uiThread = ( IUIThreadHolder )Thread.currentThread(); |
| try { |
| // [rh] sync exception handling and switchThread (see bug 316676) |
| synchronized( uiThread.getLock() ) { |
| try { |
| uiThread.updateServiceContext(); |
| ServerPushManager.getInstance().notifyUIThreadStart(); |
| continueLifeCycle(); |
| createUI(); |
| continueLifeCycle(); |
| ServerPushManager.getInstance().notifyUIThreadEnd(); |
| } catch( UIThreadTerminatedError thr ) { |
| throw thr; |
| } catch( Throwable thr ) { |
| ServiceStore serviceStore = ContextProvider.getServiceStore(); |
| serviceStore.setAttribute( UI_THREAD_THROWABLE, thr ); |
| } |
| // We have to prevent the ui thread from waking up at that point, otherwise |
| // processShutdown would never be executed and session store would not be cleared. |
| // See bug 354368 |
| UISession uiSession = ContextProvider.getUISession(); |
| uiSession.setAttribute( UI_THREAD_WAITING_FOR_TERMINATION, Boolean.TRUE ); |
| // 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(); |
| } |
| } |
| } |
| } |