/** * Licensed to the Austrian Association for Software Tool Integration (AASTI) * under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright * ownership. The AASTI licenses this file to you 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 org.openengsb.core.util; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Collection; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; /** * creates context-aware Threadpools */ public final class ThreadLocalUtil { private static final ClassLoader CLASS_LOADER = ThreadLocalUtil.class.getClassLoader(); private static final Class<?>[] INTERFACES = new Class<?>[] { ExecutorService.class }; static final class ExecutorServiceHandler implements InvocationHandler { private final ExecutorService original; public ExecutorServiceHandler(ExecutorService original) { this.original = original; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if ("submit".equals(methodName)) { transformSingleInvocation(args); } else if ("invokeAll".equals(methodName) || "invokeAny".equals(methodName)) { transformBatchInvocation(args); } return method.invoke(original, args); } } @SuppressWarnings("unchecked") private static Object transform(Object arg) { if (arg instanceof Runnable && arg.getClass() != ContextAwareRunnable.class) { return new ContextAwareRunnable((Runnable) arg); } else if (arg instanceof Callable<?> && arg.getClass() != ContextAwareCallable.class) { return new ContextAwareCallable<Object>((Callable<Object>) arg); } return arg; } private static void transformSingleInvocation(Object[] args) { args[0] = transform(args[0]); } @SuppressWarnings("unchecked") private static void transformBatchInvocation(Object[] args) { Collection<?> tasks = (Collection<?>) args[0]; Collection<Object> result; try { result = tasks.getClass().newInstance(); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } for (Object o : tasks) { result.add(transform(o)); } args[0] = result; } /** * creates a context-aware proxy of the given ExecutorService. All calls to the resulting proxy are directly * forwarded to the original after wrapping all {@link Runnable} and {@link Callable} in their context-aware * counter-parts. * * All submitted tasks to the resulting ExecutorService are guaranteed to run in the same context as the method on * the ExecutorService was called. When doing this: * * <pre> * ExecutorService pool = ThreadlocalUtil.contextAwareExecutor(Executors.newCachedThreadPool()); * ContextHolder.get().setCurrentContextId("42"); * pool.submit(command); * </pre> * * You can assume that the command is executed in a thread where the contextid is set to "42". * */ public static ExecutorService contextAwareExecutor(ExecutorService original) { return (ExecutorService) Proxy.newProxyInstance(CLASS_LOADER, INTERFACES, new ExecutorServiceHandler(original)); } private ThreadLocalUtil() { } }