/*
* #%L
* Wisdom-Framework
* %%
* Copyright (C) 2013 - 2014 Wisdom Framework
* %%
* 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.
* #L%
*/
package org.wisdom.executors;
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Instantiate;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Requires;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wisdom.api.annotations.scheduler.Async;
import org.wisdom.api.concurrent.ManagedExecutorService;
import org.wisdom.api.concurrent.ManagedFutureTask;
import org.wisdom.api.exceptions.HttpException;
import org.wisdom.api.http.AsyncResult;
import org.wisdom.api.http.Result;
import org.wisdom.api.interception.Interceptor;
import org.wisdom.api.interception.RequestContext;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
/**
* The interceptor managing {@link Async} actions.
*/
@Component
@Provides(specifications = Interceptor.class)
@Instantiate
public class AsyncInterceptor extends Interceptor<Async> {
private static final Logger LOGGER = LoggerFactory.getLogger(AsyncInterceptor.class);
@Requires(filter = "(name=" + ManagedExecutorService.SYSTEM + ")", proxy = false)
protected ManagedExecutorService executor;
private class ResultRetriever implements Callable<Result> {
private final RequestContext context;
private final Async configuration;
/**
* Creates a {@link org.wisdom.executors.AsyncInterceptor.ResultRetriever}.
*
* @param context the context
* @param configuration the annotation configuration
*/
public ResultRetriever(RequestContext context, Async configuration) {
this.context = context;
this.configuration = configuration;
}
@Override
public Result call() throws Exception {
final ManagedFutureTask<Result> task = executor.submit(new Callable<Result>() {
@Override
public Result call() throws Exception {
return context.proceed();
}
});
Result result;
try {
result = task.get(configuration.timeout(), configuration.unit());
} catch (TimeoutException e) {
LOGGER.debug("Call on {} was cancelled because it took more than {} {}",
context.route().getUrl(),
configuration.unit(),
configuration.timeout()
);
// Interrupt the computation if supported.
task.cancel(true);
throw new HttpException(Result.GATEWAY_TIMEOUT, "Request timeout");
} catch (InterruptedException e) {
LOGGER.debug("Call on {} was interrupted", context.route().getUrl());
throw new HttpException(Result.GATEWAY_TIMEOUT, "Request timeout");
} catch (ExecutionException e) {
throw new HttpException(Result.INTERNAL_SERVER_ERROR, "Computation error", e);
}
if (result == null) {
throw new HttpException(Result.INTERNAL_SERVER_ERROR, "Computation error");
}
return result;
}
}
/**
* Wrap the action method as an asynchronous method. The result is computed asynchronously and returned to the
* client once computed. Optionally a timeout can be set to return an error if the result takes too much time to
* be computed.
*
* @param configuration the interception configuration
* @param context the interception context
* @return an async result wrapping the action method invocation.
*/
@Override
public Result call(final Async configuration, final RequestContext context) throws Exception {
Callable<Result> callable;
if (configuration.timeout() > 0) {
callable = new ResultRetriever(context, configuration);
} else {
callable = new Callable<Result>() {
@Override
public Result call() throws Exception {
return context.proceed();
}
};
}
return new AsyncResult(callable);
}
/**
* Gets the annotation class configuring the current interceptor.
*
* @return the annotation
*/
@Override
public Class<Async> annotation() {
return Async.class;
}
}