/*******************************************************************************
* Copyright (c) 2011, 2015 Frank Appel 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:
* Frank Appel - initial API and implementation
* EclipseSource - ongoing development
******************************************************************************/
package org.eclipse.rap.rwt.internal.application;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.ServletContext;
import org.eclipse.rap.rwt.application.ApplicationConfiguration;
import org.eclipse.rap.rwt.application.ExceptionHandler;
import org.eclipse.rap.rwt.internal.client.ClientSelector;
import org.eclipse.rap.rwt.internal.lifecycle.EntryPointManager;
import org.eclipse.rap.rwt.internal.lifecycle.LifeCycleFactory;
import org.eclipse.rap.rwt.internal.lifecycle.PhaseListenerManager;
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.internal.resources.ClientResources;
import org.eclipse.rap.rwt.internal.resources.ResourceDirectory;
import org.eclipse.rap.rwt.internal.resources.ResourceManagerImpl;
import org.eclipse.rap.rwt.internal.resources.ResourceRegistry;
import org.eclipse.rap.rwt.internal.serverpush.ServerPushServiceHandler;
import org.eclipse.rap.rwt.internal.service.ApplicationStoreImpl;
import org.eclipse.rap.rwt.internal.service.LifeCycleServiceHandler;
import org.eclipse.rap.rwt.internal.service.RWTMessageHandler;
import org.eclipse.rap.rwt.internal.service.ServiceManagerImpl;
import org.eclipse.rap.rwt.internal.service.SettingStoreManager;
import org.eclipse.rap.rwt.internal.service.StartupPage;
import org.eclipse.rap.rwt.internal.textsize.ProbeStore;
import org.eclipse.rap.rwt.internal.textsize.TextSizeStorage;
import org.eclipse.rap.rwt.internal.theme.ThemeManager;
import org.eclipse.rap.rwt.internal.util.ParamCheck;
import org.eclipse.rap.rwt.internal.util.SerializableLock;
import org.eclipse.rap.rwt.service.ApplicationContext;
import org.eclipse.rap.rwt.service.ApplicationContextEvent;
import org.eclipse.rap.rwt.service.ApplicationContextListener;
import org.eclipse.rap.rwt.service.FileSettingStoreFactory;
import org.eclipse.rap.rwt.service.ResourceManager;
import org.eclipse.rap.rwt.service.UISession;
import org.eclipse.rap.rwt.service.UISessionEvent;
import org.eclipse.rap.rwt.service.UIThreadListener;
import org.eclipse.swt.internal.graphics.FontDataFactory;
import org.eclipse.swt.internal.graphics.ImageDataFactory;
import org.eclipse.swt.internal.graphics.ImageFactory;
import org.eclipse.swt.internal.graphics.InternalImageFactory;
import org.eclipse.swt.internal.graphics.ResourceFactory;
import org.eclipse.swt.internal.widgets.DisplaysHolder;
public class ApplicationContextImpl implements ApplicationContext {
private static enum State { INACTIVE, ACTIVATING, ACTIVE, ABOUT_TO_DEACTIVATE, DEACTIVATING }
private final static String ATTR_APPLICATION_CONTEXT
= ApplicationContextImpl.class.getName() + "#instance";
// TODO [fappel]: this flag is used to skip resource registration. Think about
// a less intrusive solution.
// [rst] made public to allow access from testfixture in OSGi (bug 391510)
public static boolean skipResoureRegistration;
// TODO [fappel]: this flag is used to skip resource deletion. Think about
// a less intrusive solution.
// [rst] made public to allow access from testfixture in OSGi (bug 391510)
public static boolean skipResoureDeletion;
private final ThemeManager themeManager;
private final ApplicationConfiguration applicationConfiguration;
private final ResourceDirectory resourceDirectory;
private final ResourceManager resourceManager;
private final PhaseListenerManager phaseListenerManager;
private final LifeCycleFactory lifeCycleFactory;
private final MessageChainReference messageChainReference;
private final EntryPointManager entryPointManager;
private final SettingStoreManager settingStoreManager;
private final ServiceManagerImpl serviceManager;
private final ResourceRegistry resourceRegistry;
private final ApplicationStoreImpl applicationStore;
private final ResourceFactory resourceFactory;
private final ImageFactory imageFactory;
private final InternalImageFactory internalImageFactory;
private final ImageDataFactory imageDataFactory;
private final FontDataFactory fontDataFactory;
private final StartupPage startupPage;
private final DisplaysHolder displaysHolder;
private final TextSizeStorage textSizeStorage;
private final ProbeStore probeStore;
private final ServletContext servletContext;
private final ClientSelector clientSelector;
private final Set<ApplicationContextListener> appContextListeners;
private final Set<UIThreadListener> uiThreadListeners;
private final SerializableLock listenersLock;
private final AtomicReference<State> state;
private ExceptionHandler exceptionHandler;
public ApplicationContextImpl( ApplicationConfiguration applicationConfiguration,
ServletContext servletContext )
{
this.applicationConfiguration = applicationConfiguration;
this.servletContext = servletContext;
applicationStore = new ApplicationStoreImpl();
resourceDirectory = new ResourceDirectory();
resourceManager = createResourceManager();
phaseListenerManager = new PhaseListenerManager();
entryPointManager = new EntryPointManager();
lifeCycleFactory = new LifeCycleFactory( this );
RWTMessageHandler rwtHandler = new RWTMessageHandler( lifeCycleFactory );
messageChainReference = new MessageChainReference( new MessageChainElement( rwtHandler, null ) );
themeManager = createThemeManager();
resourceFactory = new ResourceFactory();
imageFactory = new ImageFactory();
internalImageFactory = new InternalImageFactory();
imageDataFactory = new ImageDataFactory( resourceManager );
fontDataFactory = new FontDataFactory();
settingStoreManager = new SettingStoreManager();
resourceRegistry = new ResourceRegistry( getResourceManager() );
startupPage = new StartupPage( this );
serviceManager = createServiceManager();
displaysHolder = new DisplaysHolder();
textSizeStorage = new TextSizeStorage();
probeStore = new ProbeStore( textSizeStorage );
clientSelector = new ClientSelector();
appContextListeners = new HashSet<>();
listenersLock = new SerializableLock();
state = new AtomicReference<>( State.INACTIVE );
uiThreadListeners = new CopyOnWriteArraySet<>();
}
protected ThemeManager createThemeManager() {
return new ThemeManager();
}
protected ResourceManager createResourceManager() {
return new ResourceManagerImpl( resourceDirectory );
}
public static ApplicationContextImpl getFrom( ServletContext servletContext ) {
return ( ApplicationContextImpl )servletContext.getAttribute( ATTR_APPLICATION_CONTEXT );
}
public void attachToServletContext() {
servletContext.setAttribute( ATTR_APPLICATION_CONTEXT, this );
}
public void removeFromServletContext() {
servletContext.removeAttribute( ATTR_APPLICATION_CONTEXT );
}
@Override
public void setAttribute( String name, Object value ) {
applicationStore.setAttribute( name, value );
}
@Override
public Object getAttribute( String name ) {
return applicationStore.getAttribute( name );
}
@Override
public void removeAttribute( String name ) {
applicationStore.removeAttribute( name );
}
@Override
public void addUIThreadListener( UIThreadListener listener ) {
ParamCheck.notNull( listener, "listener" );
uiThreadListeners.add( listener );
}
@Override
public void removeUIThreadListener( UIThreadListener listener ) {
ParamCheck.notNull( listener, "listener" );
uiThreadListeners.remove( listener );
}
public void notifyEnterUIThread( UISession uiSession ) {
UISessionEvent event = new UISessionEvent( uiSession );
for( UIThreadListener listener : uiThreadListeners ) {
listener.enterUIThread( event );
}
}
public void notifyLeaveUIThread( UISession uiSession ) {
UISessionEvent event = new UISessionEvent( uiSession );
for( UIThreadListener listener : uiThreadListeners ) {
listener.leaveUIThread( event );
}
}
@Override
public boolean addApplicationContextListener( ApplicationContextListener listener ) {
ParamCheck.notNull( listener, "listener" );
boolean result = false;
synchronized( listenersLock ) {
if( state.get().equals( State.ACTIVE ) ) {
result = true;
appContextListeners.add( listener );
}
}
return result;
}
@Override
public boolean removeApplicationContextListener( ApplicationContextListener listener ) {
ParamCheck.notNull( listener, "listener" );
boolean result = false;
synchronized( listenersLock ) {
if( state.get().equals( State.ACTIVE ) ) {
result = true;
appContextListeners.remove( listener );
}
}
return result;
}
public boolean isActive() {
State currentState = state.get();
return currentState.equals( State.ACTIVE ) || currentState.equals( State.ABOUT_TO_DEACTIVATE );
}
public boolean allowsRequests() {
return state.get().equals( State.ACTIVE );
}
public void activate() {
if( state.compareAndSet( State.INACTIVE, State.ACTIVATING ) ) {
try {
doActivate();
state.set( State.ACTIVE );
} catch( RuntimeException rte ) {
state.set( State.INACTIVE );
throw rte;
}
}
}
public void deactivate() {
if( state.compareAndSet( State.ACTIVE, State.ABOUT_TO_DEACTIVATE ) ) {
try {
fireBeforeDestroy();
state.set( State.DEACTIVATING );
doDeactivate();
} finally {
state.set( State.INACTIVE );
}
}
}
public ServletContext getServletContext() {
return servletContext;
}
public ResourceDirectory getResourceDirectory() {
return resourceDirectory;
}
@Override
public ResourceManager getResourceManager() {
return resourceManager;
}
public EntryPointManager getEntryPointManager() {
return entryPointManager;
}
public SettingStoreManager getSettingStoreManager() {
return settingStoreManager;
}
public PhaseListenerManager getPhaseListenerManager() {
return phaseListenerManager;
}
public ResourceRegistry getResourceRegistry() {
return resourceRegistry;
}
@Override
public ServiceManagerImpl getServiceManager() {
return serviceManager;
}
public ThemeManager getThemeManager() {
return themeManager;
}
public LifeCycleFactory getLifeCycleFactory() {
return lifeCycleFactory;
}
public ResourceFactory getResourceFactory() {
return resourceFactory;
}
public ImageFactory getImageFactory() {
return imageFactory;
}
public InternalImageFactory getInternalImageFactory() {
return internalImageFactory;
}
public ImageDataFactory getImageDataFactory() {
return imageDataFactory;
}
public FontDataFactory getFontDataFactory() {
return fontDataFactory;
}
public StartupPage getStartupPage() {
return startupPage;
}
public DisplaysHolder getDisplaysHolder() {
return displaysHolder;
}
public TextSizeStorage getTextSizeStorage() {
return textSizeStorage;
}
public ProbeStore getProbeStore() {
return probeStore;
}
public ClientSelector getClientSelector() {
return clientSelector;
}
public ExceptionHandler getExceptionHandler() {
return exceptionHandler;
}
public void setExceptionHandler( ExceptionHandler exceptionHandler ) {
this.exceptionHandler = exceptionHandler;
}
public MessageFilterChain getHandlerChain() {
return messageChainReference.get();
}
public void addMessageFilter( MessageFilter filter ) {
ParamCheck.notNull( filter, "filter" );
messageChainReference.add( filter );
}
public void removeMessageFilter( MessageFilter filter ) {
ParamCheck.notNull( filter, "filter" );
messageChainReference.remove( filter );
}
void doActivate() {
themeManager.initialize();
applicationConfiguration.configure( new ApplicationImpl( this, applicationConfiguration ) );
resourceDirectory.configure( getContextDirectory() );
addInternalServiceHandlers();
setInternalSettingStoreFactory();
startupPage.activate();
lifeCycleFactory.activate();
// Note: order is crucial here
themeManager.activate();
if( !skipResoureRegistration ) {
ClientResources clientResources = new ClientResources( this );
clientResources.registerResources();
}
resourceRegistry.registerResources();
clientSelector.activate();
}
void doDeactivate() {
startupPage.deactivate();
lifeCycleFactory.deactivate();
serviceManager.clear();
themeManager.deactivate();
if( !skipResoureDeletion ) {
resourceDirectory.deleteDirectory();
}
entryPointManager.deregisterAll();
phaseListenerManager.clear();
resourceRegistry.clear();
settingStoreManager.deregisterFactory();
resourceDirectory.reset();
applicationStore.reset();
}
private ServiceManagerImpl createServiceManager() {
return new ServiceManagerImpl( new LifeCycleServiceHandler( messageChainReference ) );
}
private String getContextDirectory() {
String location
= ( String )servletContext.getAttribute( ApplicationConfiguration.RESOURCE_ROOT_LOCATION );
if( location == null ) {
location = servletContext.getRealPath( "/" );
}
return location;
}
private void addInternalServiceHandlers() {
serviceManager.registerServiceHandler( ServerPushServiceHandler.HANDLER_ID,
new ServerPushServiceHandler() );
}
private void setInternalSettingStoreFactory() {
if( !settingStoreManager.hasFactory() ) {
settingStoreManager.register( new FileSettingStoreFactory() );
}
}
private void fireBeforeDestroy() {
ApplicationContextEvent event = new ApplicationContextEvent( this );
for( ApplicationContextListener listener : copyListeners() ) {
try {
listener.beforeDestroy( event );
} catch( RuntimeException exception ) {
handleBeforeDestroyException( listener, exception );
}
}
}
private List<ApplicationContextListener> copyListeners() {
synchronized( listenersLock ) {
return new ArrayList<>( appContextListeners );
}
}
private void handleBeforeDestroyException( ApplicationContextListener listener,
RuntimeException exception )
{
String txt = "Could not execute {0}.beforeDestroy(ApplicationContextEvent).";
Object[] param = { listener.getClass().getName() };
String msg = MessageFormat.format( txt, param );
servletContext.log( msg, exception );
}
}