/*
* Copyright (c) 2013-2014 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.werval.api.context;
import java.util.concurrent.Callable;
import org.slf4j.MDC;
import static io.werval.api.http.Headers.Names.X_WERVAL_CLIENT_IP;
import static io.werval.api.http.Headers.Names.X_WERVAL_REQUEST_ID;
/**
* Current Thread Context Helper.
*/
public final class ThreadContextHelper
{
/**
* Run a {@literal Runnable} with a Context.
* <p>
* Use a {@link ThreadContextHelper} instance, see its methods documentation.
*
* @param context Context
* @param runnable Runnable
*/
public static void withContext( Context context, Runnable runnable )
{
ThreadContextHelper helper = new ThreadContextHelper();
try
{
helper.setOnCurrentThread( context );
runnable.run();
}
finally
{
helper.clearCurrentThread();
}
}
/**
* Run a {@literal Callable} with a Context.
* <p>
* Use a {@link ThreadContextHelper} instance, see its methods documentation.
*
* @param <T> Parameterized type of the Callable result
* @param context Context
* @param callable Callable
*
* @return Callable result
*
* @throws java.lang.Exception if the Callable was unable to compute a result
*/
public static <T> T withContext( Context context, Callable<T> callable )
throws Exception
{
ThreadContextHelper helper = new ThreadContextHelper();
try
{
helper.setOnCurrentThread( context );
return callable.call();
}
finally
{
helper.clearCurrentThread();
}
}
private ClassLoader previousLoader = null;
private boolean logRequestId = false;
private boolean logClientIp = false;
/**
* Set {@literal Context} on current {@literal Thread}.
* <p>
* In order:
* <ul>
* <li>Keep previous thread context {@link ClassLoader}.</li>
* <li>Set thread {@link ClassLoader}.</li>
* <li>Set thread Context {@literal ThreadLocal}.</li>
* <li>
* Put current Request ID in SLF4J MDC at the {@link io.werval.api.http.Headers.Names#X_WERVAL_REQUEST_ID} key.
* </li>
* <li>
* Put current Request Client IP in SLF4J MDC at the
* {@link io.werval.api.http.Headers.Names#X_WERVAL_CLIENT_IP} key if enabled in the configuration.
* </li>
* </ul>
*
* @param context Context
*/
public void setOnCurrentThread( Context context )
{
previousLoader = Thread.currentThread().getContextClassLoader();
if( context != null )
{
Thread.currentThread().setContextClassLoader( context.application().classLoader() );
logRequestId = context.application().config().bool( "werval.http.log.context.request_id" );
if( logRequestId )
{
MDC.put( X_WERVAL_REQUEST_ID, context.request().identity() );
}
logClientIp = context.application().config().bool( "werval.http.log.context.client_ip" );
if( logClientIp )
{
MDC.put( X_WERVAL_CLIENT_IP, context.request().remoteAddress() );
}
}
CurrentContext.CONTEXT_THREAD_LOCAL.set( context );
}
/**
* Remove {@literal Context} from current {@literal Thread}.
* <p>
* In order:
* <ul>
* <li>Remove current Request ID from SLF4J MDC.</li>
* <li>Set thread {@link ClassLoader} to previous one.</li>
* <li>Remove thread Context {@literal ThreadLocal}.</li>
* </ul>
*/
public void clearCurrentThread()
{
if( logRequestId )
{
MDC.remove( X_WERVAL_REQUEST_ID );
}
if( logClientIp )
{
MDC.remove( X_WERVAL_CLIENT_IP );
}
Thread.currentThread().setContextClassLoader( previousLoader );
previousLoader = null;
CurrentContext.CONTEXT_THREAD_LOCAL.remove();
}
}