/*******************************************************************************
* 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 implementation
******************************************************************************/
package org.eclipse.rap.rwt.internal.service;
import java.util.Map;
import java.util.WeakHashMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.rap.rwt.internal.application.ApplicationContextImpl;
import org.eclipse.rap.rwt.internal.protocol.ProtocolMessageWriter;
import org.eclipse.rap.rwt.internal.util.ParamCheck;
import org.eclipse.rap.rwt.service.UISession;
/**
* This class provides application-wide access to the context of the currently processed request.
* <p>
* It is possible to register a context to a thread other than the request thread (i.e. not the
* current thread). This is useful to enable background processes to access data stored in a UI
* session in the same way as 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 <code>setContext(ServiceContext,Thread)</code>
* no exception will be thrown. That's because a check is not possible due to implementation
* details. 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
private final static ThreadLocal<ServiceContext> CONTEXT_HOLDER = new ThreadLocal<>();
// Used to map contexts to background threads from another thread
// Note: this map could also be used to replace the CONTEXT_HOLDER, but we prefer the thread
// local for the common use case because of its smaller synchronization impact.
private final static Map<Thread, ServiceContext> CONTEXT_HOLDER_FOR_BG_THREADS
= new WeakHashMap<>();
/**
* Maps the given service context to the currently processed request.
* <p>
* Note: to dispose of contexts that are added with this method use {@link #disposeContext()}.
* </p>
*/
public static void setContext( 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 given service context to the specified thread. This is used to allow background
* processes to access data stored in the UI session.
* <p>
* Note: to dispose of contexts mapped with this method use {@link #disposeContext(Thread)}. To
* map the context to the current thread use <code>setContext(ServiceContext)</code> instead.
* </p>
*/
public static void setContext( ServiceContext context, 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 service context 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 processing.";
throw new IllegalStateException( msg );
}
return result;
}
/**
* Returns whether the current thread has a mapped service context.
*/
public static boolean hasContext() {
return getContextInternal() != null;
}
/**
* Releases the currently buffered context instance. This is automatically called by the framework
* 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 = 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();
}
/**
* Releases the association between a thread and its context. This is used for background
* processing that needs access to data stored in a session context.
* <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( Thread thread ) {
ParamCheck.notNull( thread, "thread" );
synchronized( CONTEXT_HOLDER_FOR_BG_THREADS ) {
ServiceContext removed = CONTEXT_HOLDER_FOR_BG_THREADS.remove( thread );
if( removed != null ) {
removed.dispose();
}
}
}
public static boolean releaseContextHolder() {
if( CONTEXT_HOLDER.get() != null ) {
CONTEXT_HOLDER.set( null );
return false;
}
synchronized( CONTEXT_HOLDER_FOR_BG_THREADS ) {
CONTEXT_HOLDER_FOR_BG_THREADS.remove( Thread.currentThread() );
}
return true;
}
private static ServiceContext getContextInternal() {
ServiceContext result = CONTEXT_HOLDER.get();
if( result == null ) {
synchronized( CONTEXT_HOLDER_FOR_BG_THREADS ) {
result = CONTEXT_HOLDER_FOR_BG_THREADS.get( Thread.currentThread() );
}
}
return result;
}
/**
* Returns the UI session that is associated with the currently processed request.
*/
public static UISession getUISession() {
return getContext().getUISession();
}
/**
* Returns the application context that is associated with the currently processed request.
*/
public static ApplicationContextImpl getApplicationContext() {
return getContext().getApplicationContext();
}
/**
* 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 service store that is mapped to the currently processed request. This is a
* convenience method that delegates to
* <code>ContextProvider.getContext().getServiceStore()</code>.
*/
public static ServiceStore getServiceStore() {
return getContext().getServiceStore();
}
/**
* Returns the protocol writer for the current request. This is a convenience method that
* delegates to <code>ContextProvider.getContext().getProtocolWriter()</code>.
*/
public static ProtocolMessageWriter getProtocolWriter() {
return getContext().getProtocolWriter();
}
}