package org.glassfish.jersey.server;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.ServiceUnavailableException;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.container.AsyncResponse;
import jakarta.ws.rs.container.CompletionCallback;
import jakarta.ws.rs.container.ConnectionCallback;
import jakarta.ws.rs.container.TimeoutHandler;
import jakarta.ws.rs.core.Configuration;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.inject.Provider;
import org.glassfish.jersey.internal.guava.Preconditions;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.Injections;
import org.glassfish.jersey.internal.inject.Providers;
import org.glassfish.jersey.internal.util.Closure;
import org.glassfish.jersey.internal.util.Producer;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.glassfish.jersey.internal.util.collection.Ref;
import org.glassfish.jersey.internal.util.collection.Refs;
import org.glassfish.jersey.internal.util.collection.Value;
import org.glassfish.jersey.message.internal.HeaderValueException;
import org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException;
import org.glassfish.jersey.message.internal.OutboundJaxrsResponse;
import org.glassfish.jersey.message.internal.OutboundMessageContext;
import org.glassfish.jersey.message.internal.TracingLogger;
import org.glassfish.jersey.process.internal.RequestContext;
import org.glassfish.jersey.process.internal.RequestScope;
import org.glassfish.jersey.process.internal.Stage;
import org.glassfish.jersey.process.internal.Stages;
import org.glassfish.jersey.server.internal.LocalizationMessages;
import org.glassfish.jersey.server.internal.ProcessingProviders;
import org.glassfish.jersey.server.internal.ServerTraceEvent;
import org.glassfish.jersey.server.internal.monitoring.EmptyRequestEventBuilder;
import org.glassfish.jersey.server.internal.monitoring.RequestEventBuilder;
import org.glassfish.jersey.server.internal.monitoring.RequestEventImpl;
import org.glassfish.jersey.server.internal.process.Endpoint;
import org.glassfish.jersey.server.internal.process.MappableException;
import org.glassfish.jersey.server.internal.process.RequestProcessingContext;
import org.glassfish.jersey.server.internal.routing.UriRoutingContext;
import org.glassfish.jersey.server.monitoring.ApplicationEventListener;
import org.glassfish.jersey.server.monitoring.RequestEvent;
import org.glassfish.jersey.server.monitoring.RequestEventListener;
import org.glassfish.jersey.server.spi.ContainerResponseWriter;
import org.glassfish.jersey.server.spi.ExternalRequestContext;
import org.glassfish.jersey.server.spi.ExternalRequestScope;
import org.glassfish.jersey.server.spi.ResponseErrorMapper;
import org.glassfish.jersey.spi.ExceptionMappers;
import static org.glassfish.jersey.server.AsyncContext.State.COMPLETED;
import static org.glassfish.jersey.server.AsyncContext.State.RESUMED;
import static org.glassfish.jersey.server.AsyncContext.State.RUNNING;
import static org.glassfish.jersey.server.AsyncContext.State.SUSPENDED;
public class ServerRuntime {
private final Stage<RequestProcessingContext> requestProcessingRoot;
private final ProcessingProviders processingProviders;
private final InjectionManager injectionManager;
private final ScheduledExecutorService backgroundScheduler;
private final Provider<ExecutorService> managedAsyncExecutor;
private final RequestScope requestScope;
private final ExceptionMappers exceptionMappers;
private final ApplicationEventListener applicationEventListener;
private final Configuration configuration;
private final ExternalRequestScope externalRequestScope;
private final TracingConfig tracingConfig;
private final TracingLogger.Level tracingThreshold;
private final boolean processResponseErrors;
private final boolean ;
private final boolean ;
static ServerRuntime createServerRuntime(
InjectionManager injectionManager,
ServerBootstrapBag bootstrapBag,
Stage<RequestProcessingContext> processingRoot,
ApplicationEventListener eventListener,
ProcessingProviders processingProviders) {
ScheduledExecutorService scheduledExecutorServiceSupplier =
injectionManager.getInstance(ScheduledExecutorService.class, BackgroundSchedulerLiteral.INSTANCE);
Provider<ExecutorService> asyncExecutorServiceSupplier =
() -> injectionManager.getInstance(ExecutorService.class, ManagedAsyncExecutorLiteral.INSTANCE);
return new ServerRuntime(
processingRoot,
processingProviders,
injectionManager,
scheduledExecutorServiceSupplier,
asyncExecutorServiceSupplier,
bootstrapBag.getRequestScope(),
bootstrapBag.getExceptionMappers(),
eventListener,
injectionManager.getInstance(ExternalRequestScope.class),
bootstrapBag.getConfiguration());
}
private ServerRuntime(final Stage<RequestProcessingContext> requestProcessingRoot,
final ProcessingProviders processingProviders,
final InjectionManager injectionManager,
final ScheduledExecutorService backgroundScheduler,
final Provider<ExecutorService> managedAsyncExecutorProvider,
final RequestScope requestScope,
final ExceptionMappers exceptionMappers,
final ApplicationEventListener applicationEventListener,
final ExternalRequestScope externalScope,
final Configuration configuration) {
this.requestProcessingRoot = requestProcessingRoot;
this.processingProviders = processingProviders;
this.injectionManager = injectionManager;
this.backgroundScheduler = backgroundScheduler;
this.managedAsyncExecutor = managedAsyncExecutorProvider;
this.requestScope = requestScope;
this.exceptionMappers = exceptionMappers;
this.applicationEventListener = applicationEventListener;
this.externalRequestScope = externalScope;
this.configuration = configuration;
this.tracingConfig = TracingUtils.getTracingConfig(configuration);
this.tracingThreshold = TracingUtils.getTracingThreshold(configuration);
this.processResponseErrors = PropertiesHelper.isProperty(
configuration.getProperty(ServerProperties.PROCESSING_RESPONSE_ERRORS_ENABLED));
this.disableLocationHeaderRelativeUriResolution = ServerProperties.getValue(configuration.getProperties(),
ServerProperties.LOCATION_HEADER_RELATIVE_URI_RESOLUTION_DISABLED,
Boolean.FALSE, Boolean.class);
this.rfc7231LocationHeaderRelativeUriResolution = ServerProperties.getValue(configuration.getProperties(),
ServerProperties.LOCATION_HEADER_RELATIVE_URI_RESOLUTION_RFC7231,
Boolean.FALSE, Boolean.class);
}
public void process(final ContainerRequest request) {
TracingUtils.initTracingSupport(tracingConfig, tracingThreshold, request);
TracingUtils.logStart(request);
final UriRoutingContext routingContext = request.getUriRoutingContext();
RequestEventBuilder monitoringEventBuilder = EmptyRequestEventBuilder.INSTANCE;
RequestEventListener monitoringEventListener = null;
if (applicationEventListener != null) {
monitoringEventBuilder = new RequestEventImpl.Builder()
.setContainerRequest(request)
.setExtendedUriInfo(routingContext);
monitoringEventListener = applicationEventListener.onRequest(
monitoringEventBuilder.build(RequestEvent.Type.START));
}
request.setProcessingProviders(processingProviders);
final RequestProcessingContext context = new RequestProcessingContext(injectionManager,
request,
routingContext,
monitoringEventBuilder,
monitoringEventListener);
request.checkState();
final Responder responder = new Responder(context, ServerRuntime.this);
final RequestContext requestScopeInstance = requestScope.createContext();
final AsyncResponderHolder asyncResponderHolder =
new AsyncResponderHolder(responder, externalRequestScope,
requestScopeInstance, externalRequestScope.open(injectionManager));
context.initAsyncContext(asyncResponderHolder);
requestScope.runInScope(requestScopeInstance, new Runnable() {
@Override
public void run() {
try {
if (!disableLocationHeaderRelativeUriResolution) {
final URI uriToUse =
rfc7231LocationHeaderRelativeUriResolution ? request.getRequestUri() : request.getBaseUri();
OutboundJaxrsResponse.Builder.setBaseUri(uriToUse);
}
final Ref<Endpoint> endpointRef = Refs.emptyRef();
final RequestProcessingContext data = Stages.process(context, requestProcessingRoot, endpointRef);
final Endpoint endpoint = endpointRef.get();
if (endpoint == null) {
throw new NotFoundException();
}
final ContainerResponse response = endpoint.apply(data);
if (!asyncResponderHolder.isAsync()) {
responder.process(response);
} else {
externalRequestScope.suspend(asyncResponderHolder.externalContext, injectionManager);
}
} catch (final Throwable throwable) {
responder.process(throwable);
} finally {
asyncResponderHolder.release();
OutboundJaxrsResponse.Builder.clearBaseUri();
}
}
});
}
ScheduledExecutorService getBackgroundScheduler() {
return backgroundScheduler;
}
private static void ensureAbsolute(final URI location, final MultivaluedMap<String, Object> headers,
final ContainerRequest request, final boolean incompatible) {
if (location == null || location.isAbsolute()) {
return;
}
final URI uri = incompatible ? request.getRequestUri() : request.getBaseUri();
headers.putSingle(HttpHeaders.LOCATION, uri.resolve(location));
}
private static class AsyncResponderHolder implements Value<AsyncContext> {
private final Responder responder;
private final ExternalRequestScope externalScope;
private final RequestContext requestContext;
private final ExternalRequestContext<?> externalContext;
private volatile AsyncResponder asyncResponder;
private AsyncResponderHolder(final Responder responder,
final ExternalRequestScope externalRequestScope,
final RequestContext requestContext,
final ExternalRequestContext<?> externalContext) {
this.responder = responder;
this.externalScope = externalRequestScope;
this.requestContext = requestContext;
this.externalContext = externalContext;
}
@Override
public AsyncContext get() {
final AsyncResponder ar = new AsyncResponder(responder, requestContext, externalScope, externalContext);
asyncResponder = ar;
return ar;
}
public boolean isAsync() {
final AsyncResponder ar = asyncResponder;
return ar != null && !ar.isRunning();
}
public void release() {
if (asyncResponder == null) {
requestContext.release();
}
}
}
private static class Responder {
private static final Logger LOGGER = Logger.getLogger(Responder.class.getName());
private final RequestProcessingContext processingContext;
private final ServerRuntime runtime;
private final CompletionCallbackRunner completionCallbackRunner = new CompletionCallbackRunner();
private final ConnectionCallbackRunner connectionCallbackRunner = new ConnectionCallbackRunner();
private final TracingLogger tracingLogger;
public Responder(final RequestProcessingContext processingContext, final ServerRuntime runtime) {
this.processingContext = processingContext;
this.runtime = runtime;
this.tracingLogger = TracingLogger.getInstance(processingContext.request());
}
public void process(ContainerResponse response) {
processingContext.monitoringEventBuilder().setContainerResponse(response);
response = processResponse(response);
release(response);
}
private ContainerResponse processResponse(ContainerResponse response) {
final Stage<ContainerResponse> respondingRoot = processingContext.createRespondingRoot();
if (respondingRoot != null) {
response = Stages.process(response, respondingRoot);
}
writeResponse(response);
completionCallbackRunner.onComplete(null);
return response;
}
public void process(final Throwable throwable) {
final ContainerRequest request = processingContext.request();
processingContext.monitoringEventBuilder().setException(throwable, RequestEvent.ExceptionCause.ORIGINAL);
processingContext.triggerEvent(RequestEvent.Type.ON_EXCEPTION);
ContainerResponse response = null;
try {
final Response exceptionResponse = mapException(throwable);
try {
try {
response = convertResponse(exceptionResponse);
if (!runtime.disableLocationHeaderRelativeUriResolution) {
ensureAbsolute(response.getLocation(), response.getHeaders(), request,
runtime.rfc7231LocationHeaderRelativeUriResolution);
}
processingContext.monitoringEventBuilder().setContainerResponse(response)
.setResponseSuccessfullyMapped(true);
} finally {
processingContext.triggerEvent(RequestEvent.Type.EXCEPTION_MAPPING_FINISHED);
}
processResponse(response);
} catch (final Throwable respError) {
LOGGER.log(Level.SEVERE, LocalizationMessages.ERROR_PROCESSING_RESPONSE_FROM_ALREADY_MAPPED_EXCEPTION());
processingContext.monitoringEventBuilder()
.setException(respError, RequestEvent.ExceptionCause.MAPPED_RESPONSE);
processingContext.triggerEvent(RequestEvent.Type.ON_EXCEPTION);
throw respError;
}
} catch (final Throwable responseError) {
if (throwable != responseError
&& !(throwable instanceof MappableException && throwable.getCause() == responseError)) {
LOGGER.log(Level.FINE, LocalizationMessages.ERROR_EXCEPTION_MAPPING_ORIGINAL_EXCEPTION(), throwable);
}
if (!processResponseError(responseError)) {
LOGGER.log(Level.FINE, LocalizationMessages.ERROR_EXCEPTION_MAPPING_THROWN_TO_CONTAINER(), responseError);
try {
request.getResponseWriter().failure(responseError);
} finally {
completionCallbackRunner.onComplete(responseError);
}
}
} finally {
release(response);
}
}
private boolean processResponseError(final Throwable responseError) {
boolean processed = false;
if (runtime.processResponseErrors) {
final Iterable<ResponseErrorMapper> mappers = Providers.getAllProviders(runtime.injectionManager,
ResponseErrorMapper.class);
try {
Response processedError = null;
for (final ResponseErrorMapper mapper : mappers) {
processedError = mapper.toResponse(responseError);
if (processedError != null) {
break;
}
}
if (processedError != null) {
processResponse(new ContainerResponse(processingContext.request(), processedError));
processed = true;
}
} catch (final Throwable throwable) {
LOGGER.log(Level.FINE, LocalizationMessages.ERROR_EXCEPTION_MAPPING_PROCESSED_RESPONSE_ERROR(), throwable);
}
}
return processed;
}
private ContainerResponse convertResponse(final Response exceptionResponse) {
final ContainerResponse containerResponse = new ContainerResponse(processingContext.request(), exceptionResponse);
containerResponse.setMappedFromException(true);
return containerResponse;
}
@SuppressWarnings("unchecked")
private Response mapException(final Throwable originalThrowable) throws Throwable {
LOGGER.log(Level.FINER, LocalizationMessages.EXCEPTION_MAPPING_START(), originalThrowable);
final ThrowableWrap wrap = new ThrowableWrap(originalThrowable);
wrap.tryMappableException();
do {
final Throwable throwable = wrap.getCurrent();
if (wrap.isInMappable() || throwable instanceof WebApplicationException) {
if (runtime.processResponseErrors && throwable instanceof InternalServerErrorException
&& throwable.getCause() instanceof MessageBodyProviderNotFoundException) {
throw throwable;
}
Response waeResponse = null;
if (throwable instanceof WebApplicationException) {
final WebApplicationException webApplicationException = (WebApplicationException) throwable;
processingContext.routingContext().setMappedThrowable(throwable);
waeResponse = webApplicationException.getResponse();
if (waeResponse.hasEntity()) {
LOGGER.log(Level.FINE, LocalizationMessages
.EXCEPTION_MAPPING_WAE_ENTITY(waeResponse.getStatus()), throwable);
return waeResponse;
}
}
final long timestamp = tracingLogger.timestamp(ServerTraceEvent.EXCEPTION_MAPPING);
final ExceptionMapper mapper = runtime.exceptionMappers.findMapping(throwable);
if (mapper != null) {
processingContext.monitoringEventBuilder().setExceptionMapper(mapper);
processingContext.triggerEvent(RequestEvent.Type.EXCEPTION_MAPPER_FOUND);
try {
final Response mappedResponse = mapper.toResponse(throwable);
if (tracingLogger.isLogEnabled(ServerTraceEvent.EXCEPTION_MAPPING)) {
tracingLogger.logDuration(ServerTraceEvent.EXCEPTION_MAPPING,
timestamp, mapper, throwable, throwable.getLocalizedMessage(),
mappedResponse != null ? mappedResponse.getStatusInfo() : "-no-response-");
}
processingContext.routingContext().setMappedThrowable(throwable);
if (mappedResponse != null) {
if (LOGGER.isLoggable(Level.FINER)) {
final String message = String.format(
"Exception '%s' has been mapped by '%s' to response '%s' (%s:%s).",
throwable.getLocalizedMessage(),
mapper.getClass().getName(),
mappedResponse.getStatusInfo().getReasonPhrase(),
mappedResponse.getStatusInfo().getStatusCode(),
mappedResponse.getStatusInfo().getFamily());
LOGGER.log(Level.FINER, message);
}
return mappedResponse;
} else {
return Response.noContent().build();
}
} catch (final Throwable mapperThrowable) {
LOGGER.log(Level.SEVERE, LocalizationMessages.EXCEPTION_MAPPER_THROWS_EXCEPTION(mapper.getClass()),
mapperThrowable);
LOGGER.log(Level.SEVERE, LocalizationMessages.EXCEPTION_MAPPER_FAILED_FOR_EXCEPTION(), throwable);
return Response.serverError().build();
}
}
if (waeResponse != null) {
LOGGER.log(Level.FINE, LocalizationMessages
.EXCEPTION_MAPPING_WAE_NO_ENTITY(waeResponse.getStatus()), throwable);
return waeResponse;
}
}
if (throwable instanceof HeaderValueException) {
if (((HeaderValueException) throwable).getContext() == HeaderValueException.Context.INBOUND) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
}
if (!wrap.isInMappable() || !wrap.isWrapped()) {
throw wrap.getWrappedOrCurrent();
}
} while (wrap.unwrap() != null);
throw originalThrowable;
}
private ContainerResponse writeResponse(final ContainerResponse response) {
final ContainerRequest request = processingContext.request();
final ContainerResponseWriter writer = request.getResponseWriter();
if (!runtime.disableLocationHeaderRelativeUriResolution) {
ServerRuntime.ensureAbsolute(response.getLocation(), response.getHeaders(), response.getRequestContext(),
runtime.rfc7231LocationHeaderRelativeUriResolution);
}
if (!response.hasEntity()) {
tracingLogger.log(ServerTraceEvent.FINISHED, response.getStatusInfo());
tracingLogger.flush(response.getHeaders());
writer.writeResponseStatusAndHeaders(0, response);
setWrittenResponse(response);
return response;
}
final Object entity = response.getEntity();
boolean skipFinally = false;
final boolean isHead = request.getMethod().equals(HttpMethod.HEAD);
try {
response.setStreamProvider(new OutboundMessageContext.StreamProvider() {
@Override
public OutputStream getOutputStream(final int contentLength) throws IOException {
if (!runtime.disableLocationHeaderRelativeUriResolution) {
ServerRuntime.ensureAbsolute(response.getLocation(), response.getHeaders(),
response.getRequestContext(), runtime.rfc7231LocationHeaderRelativeUriResolution);
}
final OutputStream outputStream = writer.writeResponseStatusAndHeaders(contentLength, response);
return isHead ? null : outputStream;
}
});
if ((writer.enableResponseBuffering() || isHead) && !response.isChunked()) {
response.enableBuffering(runtime.configuration);
}
try {
response.setEntityStream(request.getWorkers().writeTo(
entity,
entity.getClass(),
response.getEntityType(),
response.getEntityAnnotations(),
response.getMediaType(),
response.getHeaders(),
request.getPropertiesDelegate(),
response.getEntityStream(),
request.getWriterInterceptors()));
} catch (final MappableException mpe) {
if (mpe.getCause() instanceof IOException) {
connectionCallbackRunner.onDisconnect(processingContext.asyncContext());
}
throw mpe;
}
tracingLogger.log(ServerTraceEvent.FINISHED, response.getStatusInfo());
tracingLogger.flush(response.getHeaders());
setWrittenResponse(response);
} catch (final Throwable ex) {
if (response.isCommitted()) {
LOGGER.log(Level.SEVERE, LocalizationMessages.ERROR_WRITING_RESPONSE_ENTITY(), ex);
} else {
skipFinally = true;
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
} else {
throw new MappableException(ex);
}
}
} finally {
if (!skipFinally) {
boolean close = !response.isChunked();
if (response.isChunked()) {
try {
response.commitStream();
} catch (final Exception e) {
LOGGER.log(Level.SEVERE, LocalizationMessages.ERROR_COMMITTING_OUTPUT_STREAM(), e);
close = true;
}
final ChunkedOutput chunked = (ChunkedOutput) entity;
try {
chunked.setContext(
runtime.requestScope,
runtime.requestScope.referenceCurrent(),
request,
response,
connectionCallbackRunner);
} catch (final IOException ex) {
LOGGER.log(Level.SEVERE, LocalizationMessages.ERROR_WRITING_RESPONSE_ENTITY_CHUNK(), ex);
close = true;
}
if (!chunked.isClosed()
&& !writer.suspend(AsyncResponder.NO_TIMEOUT, TimeUnit.SECONDS, null)) {
LOGGER.fine(LocalizationMessages.ERROR_SUSPENDING_CHUNKED_OUTPUT_RESPONSE());
}
}
if (close) {
try {
response.close();
} catch (final Exception e) {
LOGGER.log(Level.SEVERE, LocalizationMessages.ERROR_CLOSING_COMMIT_OUTPUT_STREAM(), e);
}
}
}
}
return response;
}
private void setWrittenResponse(final ContainerResponse response) {
processingContext.monitoringEventBuilder()
.setContainerResponse(response)
.setSuccess(response.getStatus() < Response.Status.BAD_REQUEST.getStatusCode())
.setResponseWritten(true);
}
private void release(final ContainerResponse responseContext) {
try {
processingContext.closeableService().close();
if (responseContext != null && !responseContext.isChunked()) {
responseContext.close();
}
} catch (final Throwable throwable) {
LOGGER.log(Level.WARNING, LocalizationMessages.RELEASING_REQUEST_PROCESSING_RESOURCES_FAILED(), throwable);
} finally {
runtime.externalRequestScope.close();
processingContext.triggerEvent(RequestEvent.Type.FINISHED);
}
}
}
private static class AsyncResponder implements AsyncContext, ContainerResponseWriter.TimeoutHandler, CompletionCallback {
private static final Logger LOGGER = Logger.getLogger(AsyncResponder.class.getName());
private static final TimeoutHandler DEFAULT_TIMEOUT_HANDLER = new TimeoutHandler() {
@Override
public void handleTimeout(final AsyncResponse asyncResponse) {
throw new ServiceUnavailableException();
}
};
private final Object stateLock = new Object();
private State state = RUNNING;
private boolean cancelled = false;
private final Responder responder;
private final RequestContext requestContext;
private final ExternalRequestContext<?> foreignScopeInstance;
private final ExternalRequestScope requestScopeListener;
private volatile TimeoutHandler timeoutHandler = DEFAULT_TIMEOUT_HANDLER;
private final List<AbstractCallbackRunner<?>> callbackRunners;
public AsyncResponder(final Responder responder,
final RequestContext requestContext,
final ExternalRequestScope requestScopeListener,
final ExternalRequestContext<?> foreignScopeInstance) {
this.responder = responder;
this.requestContext = requestContext;
this.foreignScopeInstance = foreignScopeInstance;
this.requestScopeListener = requestScopeListener;
this.callbackRunners = Collections.unmodifiableList(Arrays.asList(
responder.completionCallbackRunner, responder.connectionCallbackRunner));
responder.completionCallbackRunner.register(this);
}
@Override
public void onTimeout(final ContainerResponseWriter responseWriter) {
final TimeoutHandler handler = timeoutHandler;
try {
synchronized (stateLock) {
if (state == SUSPENDED) {
handler.handleTimeout(this);
}
}
} catch (final Throwable throwable) {
resume(throwable);
}
}
@Override
public void onComplete(final Throwable throwable) {
synchronized (stateLock) {
state = COMPLETED;
}
}
@Override
public void invokeManaged(final Producer<Response> producer) {
responder.runtime.managedAsyncExecutor.get().submit(new Runnable() {
@Override
public void run() {
responder.runtime.requestScope.runInScope(requestContext, new Runnable() {
@Override
public void run() {
try {
requestScopeListener.resume(foreignScopeInstance, responder.runtime.injectionManager);
final Response response = producer.call();
if (response != null) {
resume(response);
}
} catch (final Throwable t) {
resume(t);
}
}
});
}
});
}
@Override
public boolean suspend() {
synchronized (stateLock) {
if (state == RUNNING) {
if (responder.processingContext.request().getResponseWriter().suspend(
AsyncResponse.NO_TIMEOUT, TimeUnit.SECONDS, this)) {
state = SUSPENDED;
return true;
}
}
}
return false;
}
@Override
public boolean resume(final Object response) {
return resume(new Runnable() {
@Override
public void run() {
try {
requestScopeListener.resume(foreignScopeInstance, responder.runtime.injectionManager);
final Response jaxrsResponse =
(response instanceof Response) ? (Response) response : Response.ok(response).build();
if (!responder.runtime.disableLocationHeaderRelativeUriResolution) {
ServerRuntime.ensureAbsolute(jaxrsResponse.getLocation(), jaxrsResponse.getHeaders(),
responder.processingContext.request(),
responder.runtime.rfc7231LocationHeaderRelativeUriResolution);
}
responder.process(new ContainerResponse(responder.processingContext.request(), jaxrsResponse));
} catch (final Throwable t) {
responder.process(t);
}
}
});
}
@Override
public boolean resume(final Throwable error) {
return resume(new Runnable() {
@Override
public void run() {
try {
requestScopeListener.resume(foreignScopeInstance, responder.runtime.injectionManager);
responder.process(new MappableException(error));
} catch (final Throwable error) {
}
}
});
}
private boolean resume(final Runnable handler) {
synchronized (stateLock) {
if (state != SUSPENDED) {
return false;
}
state = RESUMED;
}
try {
responder.runtime.requestScope.runInScope(requestContext, handler);
} finally {
requestContext.release();
}
return true;
}
@Override
public boolean cancel() {
return cancel(new Value<Response>() {
@Override
public Response get() {
return Response.status(Response.Status.SERVICE_UNAVAILABLE).build();
}
});
}
@Override
public boolean cancel(final int retryAfter) {
return cancel(new Value<Response>() {
@Override
public Response get() {
return Response
.status(Response.Status.SERVICE_UNAVAILABLE)
.header(HttpHeaders.RETRY_AFTER, retryAfter)
.build();
}
});
}
@Override
public boolean cancel(final Date retryAfter) {
return cancel(new Value<Response>() {
@Override
public Response get() {
return Response
.status(Response.Status.SERVICE_UNAVAILABLE)
.header(HttpHeaders.RETRY_AFTER, retryAfter)
.build();
}
});
}
private boolean cancel(final Value<Response> responseValue) {
synchronized (stateLock) {
if (cancelled) {
return true;
}
if (state != SUSPENDED) {
return false;
}
state = RESUMED;
cancelled = true;
}
responder.runtime.requestScope.runInScope(requestContext, new Runnable() {
@Override
public void run() {
try {
requestScopeListener.resume(foreignScopeInstance, responder.runtime.injectionManager);
final Response response = responseValue.get();
responder.process(new ContainerResponse(responder.processingContext.request(), response));
} catch (final Throwable t) {
responder.process(t);
}
}
});
return true;
}
public boolean isRunning() {
synchronized (stateLock) {
return state == RUNNING;
}
}
@Override
public boolean isSuspended() {
synchronized (stateLock) {
return state == SUSPENDED;
}
}
@Override
public boolean isCancelled() {
synchronized (stateLock) {
return cancelled;
}
}
@Override
public boolean isDone() {
synchronized (stateLock) {
return state == COMPLETED;
}
}
@Override
public boolean setTimeout(final long time, final TimeUnit unit) {
try {
responder.processingContext.request().getResponseWriter().setSuspendTimeout(time, unit);
return true;
} catch (final IllegalStateException ex) {
LOGGER.log(Level.FINER, "Unable to set timeout on the AsyncResponse.", ex);
return false;
}
}
@Override
public void setTimeoutHandler(final TimeoutHandler handler) {
timeoutHandler = handler;
}
@Override
public Collection<Class<?>> register(final Class<?> callback) {
Preconditions.checkNotNull(callback, LocalizationMessages.PARAM_NULL("callback"));
return register(Injections.getOrCreate(responder.runtime.injectionManager, callback));
}
@Override
public Map<Class<?>, Collection<Class<?>>> register(final Class<?> callback, final Class<?>... callbacks) {
Preconditions.checkNotNull(callback, LocalizationMessages.PARAM_NULL("callback"));
Preconditions.checkNotNull(callbacks, LocalizationMessages.CALLBACK_ARRAY_NULL());
for (final Class<?> additionalCallback : callbacks) {
Preconditions.checkNotNull(additionalCallback, LocalizationMessages.CALLBACK_ARRAY_ELEMENT_NULL());
}
final Map<Class<?>, Collection<Class<?>>> results = new HashMap<>();
results.put(callback, register(callback));
for (final Class<?> c : callbacks) {
results.put(c, register(c));
}
return results;
}
@Override
public Collection<Class<?>> register(final Object callback) {
Preconditions.checkNotNull(callback, LocalizationMessages.PARAM_NULL("callback"));
final Collection<Class<?>> result = new LinkedList<>();
for (final AbstractCallbackRunner<?> runner : callbackRunners) {
if (runner.supports(callback.getClass())) {
if (runner.register(callback)) {
result.add(runner.getCallbackContract());
}
}
}
return result;
}
@Override
public Map<Class<?>, Collection<Class<?>>> register(final Object callback, final Object... callbacks) {
Preconditions.checkNotNull(callback, LocalizationMessages.PARAM_NULL("callback"));
Preconditions.checkNotNull(callbacks, LocalizationMessages.CALLBACK_ARRAY_NULL());
for (final Object additionalCallback : callbacks) {
Preconditions.checkNotNull(additionalCallback, LocalizationMessages.CALLBACK_ARRAY_ELEMENT_NULL());
}
final Map<Class<?>, Collection<Class<?>>> results = new HashMap<>();
results.put(callback.getClass(), register(callback));
for (final Object c : callbacks) {
results.put(c.getClass(), register(c));
}
return results;
}
}
abstract static class AbstractCallbackRunner<T> {
private final Queue<T> callbacks = new ConcurrentLinkedQueue<>();
private final Logger logger;
protected AbstractCallbackRunner(final Logger logger) {
this.logger = logger;
}
public final boolean supports(final Class<?> callbackClass) {
return getCallbackContract().isAssignableFrom(callbackClass);
}
public abstract Class<?> getCallbackContract();
@SuppressWarnings("unchecked")
public boolean register(final Object callback) {
return callbacks.offer((T) callback);
}
protected final void executeCallbacks(final Closure<T> invoker) {
for (final T callback : callbacks) {
try {
invoker.invoke(callback);
} catch (final Throwable t) {
logger.log(Level.WARNING, LocalizationMessages.ERROR_ASYNC_CALLBACK_FAILED(callback.getClass().getName()), t);
}
}
}
}
private static class CompletionCallbackRunner
extends AbstractCallbackRunner<CompletionCallback> implements CompletionCallback {
private static final Logger LOGGER = Logger.getLogger(CompletionCallbackRunner.class.getName());
private CompletionCallbackRunner() {
super(LOGGER);
}
@Override
public Class<?> getCallbackContract() {
return CompletionCallback.class;
}
@Override
public void onComplete(final Throwable throwable) {
executeCallbacks(new Closure<CompletionCallback>() {
@Override
public void invoke(final CompletionCallback callback) {
callback.onComplete(throwable);
}
});
}
}
private static class ConnectionCallbackRunner
extends AbstractCallbackRunner<ConnectionCallback> implements ConnectionCallback {
private static final Logger LOGGER = Logger.getLogger(ConnectionCallbackRunner.class.getName());
private ConnectionCallbackRunner() {
super(LOGGER);
}
@Override
public Class<?> getCallbackContract() {
return ConnectionCallback.class;
}
@Override
public void onDisconnect(final AsyncResponse disconnected) {
executeCallbacks(new Closure<ConnectionCallback>() {
@Override
public void invoke(final ConnectionCallback callback) {
callback.onDisconnect(disconnected);
}
});
}
}
private static class ThrowableWrap {
private final Throwable original;
private Throwable wrapped = null;
private Throwable current;
private boolean inMappable = false;
private ThrowableWrap(Throwable original) {
this.original = original;
this.current = original;
}
private Throwable getOriginal() {
return original;
}
private Throwable getWrappedOrCurrent() {
return wrapped != null ? wrapped : current;
}
private Throwable getCurrent() {
return current;
}
private boolean isWrapped() {
final boolean isConcurrentWrap =
CompletionException.class.isInstance(current) || ExecutionException.class.isInstance(current);
return isConcurrentWrap;
}
private Throwable unwrap() {
if (wrapped == null) {
wrapped = current;
}
current = current.getCause();
return current;
}
private boolean tryMappableException() {
if (MappableException.class.isInstance(original)) {
inMappable = true;
current = original.getCause();
return true;
}
return false;
}
private boolean isInMappable() {
return inMappable;
}
}
}