/******************************************************************************* * 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 java.text.MessageFormat; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; import org.eclipse.rap.rwt.client.Client; import org.eclipse.rap.rwt.client.service.ClientInfo; import org.eclipse.rap.rwt.internal.application.ApplicationContextImpl; import org.eclipse.rap.rwt.internal.client.ClientMessages; import org.eclipse.rap.rwt.internal.client.ClientSelector; import org.eclipse.rap.rwt.internal.lifecycle.ContextUtil; import org.eclipse.rap.rwt.internal.lifecycle.ISessionShutdownAdapter; import org.eclipse.rap.rwt.internal.remote.ConnectionImpl; import org.eclipse.rap.rwt.internal.util.ParamCheck; import org.eclipse.rap.rwt.internal.util.SerializableLock; import org.eclipse.rap.rwt.remote.Connection; import org.eclipse.rap.rwt.service.ApplicationContextEvent; import org.eclipse.rap.rwt.service.ApplicationContextListener; import org.eclipse.rap.rwt.service.UISession; import org.eclipse.rap.rwt.service.UISessionEvent; import org.eclipse.rap.rwt.service.UISessionListener; public class UISessionImpl implements UISession, ApplicationContextListener, HttpSessionBindingListener { private static final String ATTR_UI_SESSION = UISessionImpl.class.getName() + "#uisession:"; private static final String ATTR_LOCALE = UISessionImpl.class.getName() + "#locale"; private final SerializableLock requestLock; private final SerializableLock lock; private final Map<String, Object> attributes; private final Set<UISessionListener> listeners; private final String id; private final String connectionId; private Connection connection; private boolean bound; private boolean inDestroy; private transient HttpSession httpSession; private transient ISessionShutdownAdapter shutdownAdapter; private transient ApplicationContextImpl applicationContext; public UISessionImpl( ApplicationContextImpl applicationContext, HttpSession httpSession ) { this( applicationContext, httpSession, null ); } public UISessionImpl( ApplicationContextImpl applicationContext, HttpSession httpSession, String connectionId ) { setApplicationContext( applicationContext ); this.httpSession = httpSession; this.connectionId = connectionId; requestLock = new SerializableLock(); lock = new SerializableLock(); attributes = new HashMap<>(); listeners = new HashSet<>(); id = Integer.toHexString( hashCode() ); bound = true; connection = new ConnectionImpl( this ); } public static UISessionImpl getInstanceFromSession( HttpSession httpSession, String connectionId ) { return ( UISessionImpl )httpSession.getAttribute( getUISessionAttributeName( connectionId ) ); } public void attachToHttpSession() { httpSession.setAttribute( getUISessionAttributeName( connectionId ), this ); } public void setApplicationContext( ApplicationContextImpl applicationContext ) { if( this.applicationContext != null ) { this.applicationContext.removeApplicationContextListener( this ); } this.applicationContext = applicationContext; if( this.applicationContext != null ) { this.applicationContext.addApplicationContextListener( this ); } } @Override public ApplicationContextImpl getApplicationContext() { return applicationContext; } public void setShutdownAdapter( ISessionShutdownAdapter adapter ) { shutdownAdapter = adapter; if( shutdownAdapter != null ) { shutdownAdapter.setUISession( this ); shutdownAdapter.setShutdownCallback( new Runnable() { @Override public void run() { destroy(); } } ); } } public ISessionShutdownAdapter getShutdownAdapter() { return shutdownAdapter; } @Override public void beforeDestroy( ApplicationContextEvent event ) { shutdown(); } public void shutdown() { // Removing the object from the httpSession will trigger the valueUnbound method, // which actually kills the session getHttpSession().removeAttribute( getUISessionAttributeName( connectionId ) ); } @Override public Object getAttribute( String name ) { ParamCheck.notNull( name, "name" ); Object result = null; synchronized( lock ) { result = attributes.get( name ); } return result; } @Override public boolean setAttribute( String name, Object value ) { ParamCheck.notNull( name, "name" ); boolean result = false; synchronized( lock ) { if( bound ) { result = true; attributes.put( name, value ); } } return result; } @Override public boolean removeAttribute( String name ) { ParamCheck.notNull( name, "name" ); boolean result = false; synchronized( lock ) { if( bound ) { result = true; attributes.remove( name ); } } return result; } @Override public Enumeration<String> getAttributeNames() { return createAttributeNameEnumeration(); } @Override public String getId() { return id; } @Override public HttpSession getHttpSession() { synchronized( lock ) { return httpSession; } } public void setHttpSession( HttpSession httpSession ) { ParamCheck.notNull( httpSession, "httpSession" ); synchronized( lock ) { this.httpSession = httpSession; } } @Override public boolean isBound() { synchronized( lock ) { return bound; } } @Override public Client getClient() { ClientSelector clientSelector = applicationContext.getClientSelector(); return clientSelector.getSelectedClient( this ); } @Override public Connection getConnection() { return connection; } @Override public Locale getLocale() { Locale locale = ( Locale )getAttribute( ATTR_LOCALE ); if( locale == null ) { ClientInfo clientInfo = getClient().getService( ClientInfo.class ); if( clientInfo != null ) { locale = clientInfo.getLocale(); } if( locale == null ) { locale = Locale.getDefault(); } } return locale; } @Override public void setLocale( Locale locale ) { Locale oldLocale = getLocale(); setAttribute( ATTR_LOCALE, locale ); Locale newLocale = getLocale(); if( !newLocale.equals( oldLocale ) ) { ClientMessages messages = getClient().getService( ClientMessages.class ); if( messages != null ) { messages.update( newLocale ); } } } @Override public void exec( Runnable runnable ) { ParamCheck.notNull( runnable, "runnable" ); ContextUtil.runNonUIThreadWithFakeContext( this, runnable ); } @Override public boolean addUISessionListener( UISessionListener listener ) { ParamCheck.notNull( listener, "listener" ); boolean result = false; synchronized( lock ) { if( bound && !inDestroy ) { result = true; listeners.add( listener ); } } return result; } @Override public boolean removeUISessionListener( UISessionListener listener ) { ParamCheck.notNull( listener, "listener" ); boolean result = false; synchronized( lock ) { if( bound && !inDestroy ) { result = true; listeners.remove( listener ); } } return result; } @Override public void valueBound( HttpSessionBindingEvent event ) { synchronized( lock ) { bound = true; inDestroy = false; } } @Override public void valueUnbound( HttpSessionBindingEvent event ) { if( shutdownAdapter != null ) { shutdownAdapter.interceptShutdown(); } else { boolean fakeContext = false; if( !ContextProvider.hasContext() ) { fakeContext = true; ServiceContext context = ContextUtil.createFakeContext( this ); ContextProvider.setContext( context ); } try { destroy(); } finally { if( fakeContext ) { ContextProvider.releaseContextHolder(); } } } } public String getConnectionId() { return connectionId; } /* * test hook */ public void setConnection( Connection connection ) { this.connection = connection; } Object getRequestLock() { return requestLock; } private static String getUISessionAttributeName( String connectionId ) { return ATTR_UI_SESSION + ( connectionId == null ? "" : connectionId ); } private void destroy() { synchronized( lock ) { inDestroy = true; } fireBeforeDestroy(); synchronized( lock ) { setApplicationContext( null ); attributes.clear(); listeners.clear(); bound = false; inDestroy = false; } } private void fireBeforeDestroy() { UISessionListener[] listenersCopy; synchronized( lock ) { int size = listeners.size(); listenersCopy = listeners.toArray( new UISessionListener[ size ] ); } UISessionEvent event = new UISessionEvent( this ); for( UISessionListener listener : listenersCopy ) { try { listener.beforeDestroy( event ); } catch( RuntimeException exception ) { handleBeforeDestroyException( listener, exception ); } } } private void handleBeforeDestroyException( UISessionListener listener, RuntimeException exception ) { String pattern = "Could not execute {0}.beforeDestroy(UISessionEvent)."; String msg = MessageFormat.format( pattern, listener.getClass().getName() ); httpSession.getServletContext().log( msg, exception ); } private Enumeration<String> createAttributeNameEnumeration() { Set<String> names; synchronized( lock ) { names = new HashSet<>( attributes.keySet() ); } final Iterator<String> iterator = names.iterator(); return new Enumeration<String>() { @Override public boolean hasMoreElements() { return iterator.hasNext(); } @Override public String nextElement() { return iterator.next(); } }; } }