/******************************************************************************* * Copyright (c) 2002, 2008 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 ******************************************************************************/ package org.eclipse.rwt.internal.service; import java.util.Map; import java.util.WeakHashMap; import javax.servlet.http.*; import org.eclipse.rwt.SessionSingletonBase; import org.eclipse.rwt.internal.ConfigurationReader; import org.eclipse.rwt.internal.IEngineConfig; import org.eclipse.rwt.internal.util.ParamCheck; import org.eclipse.rwt.service.ISessionStore; /** * <p>This class enables application wide access to the context of the * currently processed request during the service handler execution.</p> * * <p>Note: It is possible to register a context to a thread that isn't the * request thread (in particular not the current running thread). This may be * useful to enable background processes the access to data that is stored in a * session the same way as it is done in the request thread.</p> * * <p>Note: In case that a context was already added using the * <code>setContext(ServiceContext)</code> method and it's tried to add another * context using the <code>setContext(ServiceContext,Thread)</code> no * <code>IllegalStateException</code> will be thrown. This is because due to * implementation details a check is not possible. In such a case the * context added with <code>setContext(ServiceContext)</code> will be * preferred.</p> */ public class ContextProvider { // The context mapping mechanism used in standard UI requests from // the client is the CONTEXT_HOLDER. private final static ThreadLocal CONTEXT_HOLDER = new ThreadLocal(); // For background threads that need access to data stored in the session // a context can be mapped from outside the thread execution. // Herefore the CONTEXT_HOLDER_FOR_BG_THREADS is used. In theory it would // be possible to use the map also to replace the CONTEXT_HOLDER, but // due to the smaller synchronization impact the thread local mechanism // stays in place for the most common usecase. private final static Map CONTEXT_HOLDER_FOR_BG_THREADS = new WeakHashMap(); /** * Maps the {@link ServiceContext} to the currently * processed request. * * <p>Note: to dispose of contexts that are added with this method * use <code>disposeContext()</code>. </p> */ public static void setContext( final ServiceContext context ) { ParamCheck.notNull( context, "context" ); if( getContextInternal() != null ) { String msg = "Current thread has already a context instance buffered."; throw new IllegalStateException( msg ); } CONTEXT_HOLDER.set( context ); } /** * Maps the {@link ServiceContext} to the specified * thread. This may be useful to allow background processes access * to data stored in the session. * * <p>Note: to dispose of contexts that are mapped with this method * use <code>disposeContext(Thread)</code>. In case you want to map * the context to the current thread use * <code>setContext(ServiceContext)</code> instead.</p> */ public static void setContext( final ServiceContext context, final Thread thread ) { ParamCheck.notNull( context, "context" ); ParamCheck.notNull( thread, "thread" ); synchronized( CONTEXT_HOLDER_FOR_BG_THREADS ){ if( CONTEXT_HOLDER_FOR_BG_THREADS.containsKey( thread ) ) { String msg = "The given thread has already a context instance mapped."; throw new IllegalStateException( msg ); } CONTEXT_HOLDER_FOR_BG_THREADS.put( thread, context ); } } /** * Returns the {@link ServiceContext} mapped to the currently * processed request. */ public static ServiceContext getContext() { ServiceContext result = getContextInternal(); if( result == null ) { String msg = "No context available outside of the request " + "service lifecycle."; throw new IllegalStateException( msg ); } return result; } public static String getWebAppBase() { IEngineConfig engineConfig = ConfigurationReader.getEngineConfig(); return engineConfig.getServerContextDir().toString(); } /** * Returns the <code>HttpServletRequest</code> that is currently * processed. This is a convenience method that delegates to * <code>ContextProvider.getContext().getRequest()</code>; */ public static HttpServletRequest getRequest() { return getContext().getRequest(); } /** * Returns the <code>HttpServletResponse</code> that is mapped * to the currently processed request. This is a convenience method * that delegates to <code>ContextProvider.getContext().getResponse()</code>; */ public static HttpServletResponse getResponse() { return getContext().getResponse(); } /** * Returns the <code>ISessionStore</code> of the <code>HttpSession</code> * to which the currently processed request belongs. */ public static ISessionStore getSession() { ISessionStore result = getContext().getSessionStore(); if( result == null ) { HttpSession httpSession = getRequest().getSession( true ); String id = SessionStoreImpl.ID_SESSION_STORE; result = ( ISessionStore )httpSession.getAttribute( id ); if( result == null ) { result = new SessionStoreImpl( httpSession ); result.setAttribute( SessionSingletonBase.LOCK, new Object() ); } getContext().setSessionStore( result ); } return result; } /** * Returns the {@link IServiceStateInfo} that is mapped * to the currently processed request. This is a convenience method * that delegates to <code>ContextProvider.getContext().getStateInfo()</code>; */ public static IServiceStateInfo getStateInfo() { return getContext().getStateInfo(); } /** * Releases the currently buffered context instance. Note that this is * automatically called by the library to end the context's lifecycle. * A premature call will cause failure of the currently processed * request lifecycle. * * <p>Note: only <code>ServiceContext</code> instances that where mapped * by calling <code>setContext()</code> from the running thread can be * disposed of using this method. Contexts that were registered by * <code>setContext(Thread,ServiceContext)</code> must be disposed of * by using <code>disposeContext(Thread)</code>.</p> */ public static void disposeContext() { ServiceContext context = ( ServiceContext )CONTEXT_HOLDER.get(); if( context != null ) { if( !context.isDisposed() ) { context.dispose(); } } // DO NOT MOVE THIS LINE INTO THE IF BLOCK // This would cause a memory leak as disposeContext() is used to dispose // of a context *and* disassociate the context from the thread releaseContextHolder(); } public static boolean releaseContextHolder() { boolean result = false; Object object = CONTEXT_HOLDER.get(); if( object != null ) { CONTEXT_HOLDER.set( null ); } else { synchronized( CONTEXT_HOLDER_FOR_BG_THREADS ) { ServiceContext toRemove = getMappedContext( Thread.currentThread() ); if( toRemove != null ) { CONTEXT_HOLDER_FOR_BG_THREADS.remove( Thread.currentThread() ); } } result = true; } return result; } /** * Releases the association between a thread and it's context. This may be * useful in applications that use background processing that needs * access to data stored in session contexts. * * <p>Note: only <code>ServiceContext</code> instances that were mapped * with the <code>setContext(Thread,ServiceContext)</code> method can be * disposed by this method. Contexts that were registered by * <code>setContext(ServiceContext)</code> by the running thread * must be disposed of by calling <code>disposeContext()</code> from the * same thread.</p> */ public static void disposeContext( final Thread thread ) { ParamCheck.notNull( thread, "thread" ); synchronized( CONTEXT_HOLDER_FOR_BG_THREADS ) { ServiceContext toRemove = getMappedContext( thread ); if( toRemove != null ) { CONTEXT_HOLDER_FOR_BG_THREADS.remove( thread ); toRemove.dispose(); } } } /** * Returns whether the current thread has a mapped service context. */ public static boolean hasContext() { return getContextInternal() != null; } ////////////////// // helping methods private static ServiceContext getContextInternal() { ServiceContext result = ( ServiceContext )CONTEXT_HOLDER.get(); if( result == null ) { synchronized( CONTEXT_HOLDER_FOR_BG_THREADS ) { Thread currentThread = Thread.currentThread(); result = getMappedContext( currentThread ); } } return result; } private static ServiceContext getMappedContext( final Thread thread ) { return ( ServiceContext )CONTEXT_HOLDER_FOR_BG_THREADS.get( thread ); } }