/**
* JBoss, Home of Professional Open Source
* Copyright 2011, Red Hat, Inc. and/or its affiliates, and individual
* contributors by the @authors tag. See the copyright.txt in the
* distribution for a full listing of individual contributors.
*
* 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 org.jboss.seam.cron.spi.asynchronous;
import java.util.concurrent.Future;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
import org.jboss.seam.cron.api.asynchronous.Asynchronous;
import org.jboss.seam.cron.api.queue.Queue;
import org.jboss.seam.cron.impl.scheduling.exception.InternalException;
import org.jboss.seam.cron.spi.SeamCronExtension;
import org.jboss.solder.logging.Logger;
/**
* <p>
* Interceptor for asynchronous methods. Service providers for asynchronous
* method invocation should enable this interceptor in the beans.xml which ships
* with their implementation jar.
* </p><p>
* Method may be directly marked as #{@link Asynchronous} or may exist on a type
* marked as #{@link Asynchronous}.
* </p>
*
* @author Peter Royle
* @Asnychronous or may exist on a type marked as @Asynchronous.
*/
@Asynchronous
@Interceptor
public class AsynchronousInterceptor {
// We need to track where the method is being invoked from so that we can
// handle it properly.
protected static final String INVOKED_IN_THREAD = "INVOKED_IN_THREAD";
public ThreadLocal<Boolean> invokedFromInterceptorInThread = new ThreadLocal<Boolean>();
Logger log = Logger.getLogger(AsynchronousInterceptor.class);
@Inject
BeanManager beanMan;
@Inject
Instance<Invoker> iceCopies;
@Inject SeamCronExtension cronExtension;
public AsynchronousInterceptor() {
}
@AroundInvoke
private Object executeAsynchronously(final InvocationContext ctx) throws Exception {
if (invokedFromInterceptorInThread.get() == null) {
if (ctx.getContextData().get(INVOKED_IN_THREAD) == null) {
// Step 1. When the method is invoked originally, the interceptor
// which wraps it will land here, because the InvocationContext
// hasn't been fiddled with in the background thread.
Object result = null;
if (log.isTraceEnabled()) {
log.trace("Intercepting method invocation of " + ctx.getMethod().getName() + " to make it @Asynchronous");
}
Queue queue = ctx.getMethod().getAnnotation(Queue.class);
String queueId = queue == null ? null : queue.value();
final Invoker ice = iceCopies.get();
ice.setInvocationContext(ctx);
final CronAsynchronousProvider asyncStrategy = cronExtension.getAsynchronousProvider();
if (Future.class.isAssignableFrom(ctx.getMethod().getReturnType())) {
// swap the "dummy" Future for a truly asynchronous future to return to the caller immediately
ice.setPopResultsFromFuture(true);
result = asyncStrategy.executeAndReturnFuture(queueId, ice);
} else {
asyncStrategy.executeWithoutReturn(queueId, ice);
result = null;
}
// this will either be a Future, or null
return result;
} else {
// Step 2. The new thread (created in Step 1 above) will assign
// INVOKED_IN_THREAD to TRUE in the InvocationContext and then
// invoke ctx.proceed(). The interceptor which wraps that invocation
// will land here.
if (Boolean.TRUE.equals(ctx.getContextData().get(INVOKED_IN_THREAD))) {
if (log.isTraceEnabled()) {
log.trace("Executing original method in new thread for " + ctx.getMethod().getName());
}
invokedFromInterceptorInThread.set(Boolean.TRUE);
return ctx.proceed();
} else {
throw new InternalException("The framework got into an illegal state while atempting to keep track of Interceptors around asynchronous method invocations. This is certainly a bug. Please file it in the SEAMCRON Jira with full stack trace");
}
}
} else {
// The interceptor around the backgrounded method invocation
// will set the invokedFromInterceptorInThread ThreadLocal to true
// (see Step 2 above) and then invoke ctx.proceed(); The interceptor
// around that invocation will land here. This simply forwards
// execution to the originating method.
if (log.isTraceEnabled()) {
log.trace("Bypassing interceptor in new thread for " + ctx.getMethod().getName());
}
return ctx.proceed();
}
}
}