/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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.apache.deltaspike.core.impl.future; import org.apache.deltaspike.core.api.future.Futureable; import org.apache.deltaspike.core.impl.util.AnnotatedMethods; import org.apache.deltaspike.core.spi.future.FutureableStrategy; import org.apache.deltaspike.core.util.ExceptionUtils; import javax.enterprise.context.Dependent; import javax.enterprise.inject.spi.AnnotatedMethod; import javax.enterprise.inject.spi.AnnotatedType; import javax.enterprise.inject.spi.BeanManager; import javax.inject.Inject; import javax.interceptor.InvocationContext; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.LinkedList; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; @Dependent public class DefaultFutureableStrategy implements FutureableStrategy { private static final Class<?> COMPLETION_STAGE; private static final Class<?> COMPLETABLE_FUTURE; private static final Method COMPLETABLE_STAGE_TO_FUTURE; // only for weld1 private static final boolean IS_WELD1; private static final ThreadLocal<LinkedList<CallKey>> STACK = new ThreadLocal<LinkedList<CallKey>>() { @Override protected LinkedList<CallKey> initialValue() { return new LinkedList<CallKey>(); } }; static { Class<?> completionStageClass = null; Class<?> completableFutureClass = null; Method completionStageClassToCompletableFuture = null; try { final ClassLoader classLoader = ClassLoader.getSystemClassLoader(); completionStageClass = classLoader.loadClass("java.util.concurrent.CompletionStage"); completionStageClassToCompletableFuture = completionStageClass.getMethod("toCompletableFuture"); completableFutureClass = classLoader.loadClass("java.util.concurrent.CompletableFuture"); } catch (final Exception e) { // not on java 8 } COMPLETION_STAGE = completionStageClass; COMPLETABLE_FUTURE = completableFutureClass; COMPLETABLE_STAGE_TO_FUTURE = completionStageClassToCompletableFuture; { // workaround for weld -> use a thread local to track the invocations boolean weld1 = false; try { final Class<?> impl = Thread.currentThread().getContextClassLoader() .loadClass("org.jboss.weld.manager.BeanManagerImpl"); final Package pck = impl.getPackage(); weld1 = "Weld Implementation".equals(pck.getImplementationTitle()) && pck.getSpecificationVersion() != null && pck.getSpecificationVersion().startsWith("1.1."); } catch (final Throwable cnfe) { // no-op } IS_WELD1 = weld1; } } @Inject private ThreadPoolManager manager; @Inject private BeanManager beanManager; private transient ConcurrentMap<Method, ExecutorService> configByMethod = new ConcurrentHashMap<Method, ExecutorService>(); @Override public Object execute(final InvocationContext ic) throws Exception { final CallKey invocationKey; if (IS_WELD1) { invocationKey = new CallKey(ic); { // weld1 workaround final LinkedList<CallKey> stack = STACK.get(); if (!stack.isEmpty() && stack.getLast().equals(invocationKey)) { try { return ic.proceed(); } finally { if (stack.isEmpty()) { STACK.remove(); } } } } } else { invocationKey = null; } // validate usage final Class<?> returnType = ic.getMethod().getReturnType(); if (!Future.class.isAssignableFrom(returnType) && !void.class.isAssignableFrom(returnType) && (COMPLETION_STAGE == null || !COMPLETION_STAGE.isAssignableFrom(returnType))) { throw new IllegalArgumentException("Return type should be a CompletableStage, Future or Void"); } if (configByMethod == null) { synchronized (this) { if (configByMethod == null) { configByMethod = new ConcurrentHashMap<Method, ExecutorService>(); } } } // running < j8 we cant have cancellation //final AtomicReference<Callable<?>> cancelHook = new AtomicReference<Callable<?>>(); final Callable<Object> invocation = new Callable<Object>() { @Override public Object call() throws Exception { final LinkedList<CallKey> callStack; if (IS_WELD1) { callStack = STACK.get(); callStack.add(invocationKey); } else { callStack = null; } try { final Object proceed = ic.proceed(); final Future<?> future = COMPLETION_STAGE == null || !COMPLETION_STAGE.isInstance(proceed) ? Future.class.cast(proceed) : Future.class.cast(COMPLETABLE_STAGE_TO_FUTURE.invoke(proceed)); return future.get(); } catch (final InvocationTargetException e) { throw ExceptionUtils.throwAsRuntimeException(e.getCause()); } catch (final Exception e) { throw ExceptionUtils.throwAsRuntimeException(e); } finally { if (IS_WELD1) { callStack.removeLast(); if (callStack.isEmpty()) { STACK.remove(); } } } } }; final ExecutorService pool = getOrCreatePool(ic); if (void.class.isAssignableFrom(returnType)) { pool.submit(invocation); return null; } if (COMPLETABLE_FUTURE == null) // not on java 8 can only be a future { return pool.submit(invocation); } // java 8, use CompletableFuture, it impl CompletionStage and Future so everyone is happy final Object completableFuture = COMPLETABLE_FUTURE.newInstance(); pool.submit(new J8PromiseCompanionTask(completableFuture, invocation)); // TODO: handle cancel return completableFuture; } protected ExecutorService getOrCreatePool(final InvocationContext ic) { final Method method = ic.getMethod(); ExecutorService executorService = configByMethod.get(method); if (executorService == null) { final AnnotatedType<?> annotatedType = beanManager.createAnnotatedType(method.getDeclaringClass()); final AnnotatedMethod<?> annotatedMethod = AnnotatedMethods.findMethod(annotatedType, method); final Futureable methodConfig = annotatedMethod.getAnnotation(Futureable.class); final ExecutorService instance = manager.find( (methodConfig == null ? annotatedType.getAnnotation(Futureable.class) : methodConfig).value()); configByMethod.putIfAbsent(method, instance); executorService = instance; } return executorService; } private static final class CallKey { private final InvocationContext ic; private final int hash; private CallKey(final InvocationContext ic) { this.ic = ic; final Object[] parameters = ic.getParameters(); this.hash = ic.getMethod().hashCode() + (parameters == null ? 0 : Arrays.hashCode(parameters)); } @Override public boolean equals(final Object o) { return this == o || !(o == null || getClass() != o.getClass()) && equals(ic, CallKey.class.cast(o).ic); } @Override public int hashCode() { return hash; } private boolean equals(final InvocationContext ic1, final InvocationContext ic2) { final Object[] parameters1 = ic1.getParameters(); final Object[] parameters2 = ic2.getParameters(); return ic2.getMethod().equals(ic1.getMethod()) && (parameters1 == parameters2 || (parameters1 != null && parameters2 != null && Arrays.equals(parameters1, ic2.getParameters()))); } } }