org.glassfish.jersey.server.ServerRuntime Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jaxrs-ri Show documentation
Show all versions of jaxrs-ri Show documentation
A bundle project producing JAX-RS RI bundles. The primary artifact is an "all-in-one" OSGi-fied JAX-RS RI bundle
(jaxrs-ri.jar).
Attached to that are two compressed JAX-RS RI archives. The first archive (jaxrs-ri.zip) consists of binary RI bits and
contains the API jar (under "api" directory), RI libraries (under "lib" directory) as well as all external
RI dependencies (under "ext" directory). The secondary archive (jaxrs-ri-src.zip) contains buildable JAX-RS RI source
bundle and contains the API jar (under "api" directory), RI sources (under "src" directory) as well as all external
RI dependencies (under "ext" directory). The second archive also contains "build.xml" ANT script that builds the RI
sources. To build the JAX-RS RI simply unzip the archive, cd to the created jaxrs-ri directory and invoke "ant" from
the command line.
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2012-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
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.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.ServiceUnavailableException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.CompletionCallback;
import javax.ws.rs.container.ConnectionCallback;
import javax.ws.rs.container.TimeoutHandler;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.inject.Inject;
import javax.inject.Provider;
import org.glassfish.jersey.internal.inject.Injections;
import org.glassfish.jersey.internal.util.Closure;
import org.glassfish.jersey.internal.util.Producer;
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.OutboundJaxrsResponse;
import org.glassfish.jersey.message.internal.OutboundMessageContext;
import org.glassfish.jersey.message.internal.TracingLogger;
import org.glassfish.jersey.process.internal.RequestExecutorFactory;
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.BackgroundScheduler;
import org.glassfish.jersey.server.internal.LocalizationMessages;
import org.glassfish.jersey.server.internal.ServerTraceEvent;
import org.glassfish.jersey.server.internal.monitoring.RequestEventBuilder;
import org.glassfish.jersey.server.internal.monitoring.RequestEventImpl;
import org.glassfish.jersey.server.internal.process.AsyncContext;
import org.glassfish.jersey.server.internal.process.Endpoint;
import org.glassfish.jersey.server.internal.process.MappableException;
import org.glassfish.jersey.server.internal.process.RespondingContext;
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.spi.ExceptionMappers;
import static org.glassfish.jersey.server.internal.process.AsyncContext.State.COMPLETED;
import static org.glassfish.jersey.server.internal.process.AsyncContext.State.RESUMED;
import static org.glassfish.jersey.server.internal.process.AsyncContext.State.RUNNING;
import static org.glassfish.jersey.server.internal.process.AsyncContext.State.SUSPENDED;
import org.glassfish.hk2.api.ServiceLocator;
import com.google.common.base.Preconditions;
/**
* Server-side request processing runtime.
*
* @author Marek Potociar (marek.potociar at oracle.com)
*/
class ServerRuntime {
private final Stage requestProcessingRoot;
private final ServiceLocator locator;
private final ScheduledExecutorService backgroundScheduler;
private final RequestScope requestScope;
private final ExceptionMappers exceptionMappers;
private final Provider respondingContextProvider;
private final Provider closeableServiceProvider;
private final Provider>> asyncContextFactoryProvider;
private final Provider asyncContextProvider;
private final Provider uriRoutingContextProvider;
private final RequestExecutorFactory asyncExecutorFactory;
private final ApplicationEventListener applicationEventListener;
private final Configuration configuration;
private final TracingConfig tracingConfig;
private final TracingLogger.Level tracingThreshold;
/**
* Server-side request processing runtime builder.
*/
public static class Builder {
@Inject
private ServiceLocator locator;
@Inject
@BackgroundScheduler
private ScheduledExecutorService backgroundScheduler;
@Inject
private RequestScope requestScope;
@Inject
private ExceptionMappers exceptionMappers;
@Inject
private Provider respondingContextProvider;
@Inject
private Provider closeableServiceProvider;
@Inject
private Provider>> asyncContextRefProvider;
@Inject
private Provider asyncContextProvider;
@Inject
private Provider uriRoutingContextProvider;
@Inject
private RequestExecutorFactory asyncExecutorFactory;
@Inject
private Configuration configuration;
/**
* Create new server-side request processing runtime.
*
* @param processingRoot application request processing root stage.
* @param eventListener Application event listener registered for this runtime.
* @return new server-side request processing runtime.
*/
public ServerRuntime build(final Stage processingRoot, ApplicationEventListener eventListener) {
return new ServerRuntime(
processingRoot,
locator,
backgroundScheduler,
requestScope,
exceptionMappers,
respondingContextProvider,
closeableServiceProvider,
asyncContextRefProvider,
asyncContextProvider,
uriRoutingContextProvider,
asyncExecutorFactory,
eventListener,
configuration);
}
}
private ServerRuntime(Stage requestProcessingRoot,
ServiceLocator locator,
ScheduledExecutorService backgroundScheduler,
RequestScope requestScope,
ExceptionMappers exceptionMappers,
Provider respondingContextProvider,
Provider closeableServiceProvider,
Provider>> asyncContextFactoryProvider,
Provider asyncContextProvider,
Provider uriRoutingContextProvider,
RequestExecutorFactory asyncExecutorFactory,
ApplicationEventListener applicationEventListener,
Configuration configuration) {
this.requestProcessingRoot = requestProcessingRoot;
this.locator = locator;
this.backgroundScheduler = backgroundScheduler;
this.requestScope = requestScope;
this.exceptionMappers = exceptionMappers;
this.respondingContextProvider = respondingContextProvider;
this.closeableServiceProvider = closeableServiceProvider;
this.asyncContextFactoryProvider = asyncContextFactoryProvider;
this.asyncContextProvider = asyncContextProvider;
this.uriRoutingContextProvider = uriRoutingContextProvider;
this.asyncExecutorFactory = asyncExecutorFactory;
this.applicationEventListener = applicationEventListener;
this.configuration = configuration;
this.tracingConfig = TracingUtils.getTracingConfig(configuration);
this.tracingThreshold = TracingUtils.getTracingThreshold(configuration);
}
/**
* Process a container request.
*
* @param request request to be processed.
*/
public void process(final ContainerRequest request) {
initRequestEventListeners(request);
TracingUtils.initTracingSupport(tracingConfig, tracingThreshold, request);
try {
request.checkState();
requestScope.runInScope(new Runnable() {
@Override
public void run() {
TracingUtils.logStart(request);
final Responder responder = new Responder(request, ServerRuntime.this);
final AsyncResponderHolder asyncResponderHolder = new AsyncResponderHolder(
responder, requestScope.referenceCurrent());
try {
final Ref endpointRef = Refs.emptyRef();
// set base URI into response builder thread-local variable
// for later absolutization of relative location URIs
OutboundJaxrsResponse.Builder.setBaseUri(request.getBaseUri());
final ContainerRequest data = Stages.process(request, requestProcessingRoot, endpointRef);
final Endpoint endpoint = endpointRef.get();
if (endpoint == null) {
// not found
throw new NotFoundException();
}
asyncContextFactoryProvider.get().set(asyncResponderHolder);
final ContainerResponse response = endpoint.apply(data);
if (!asyncResponderHolder.isAsync()) {
responder.process(response);
}
} catch (Throwable throwable) {
responder.process(throwable);
} finally {
asyncResponderHolder.release();
// clear base URI from the thread
OutboundJaxrsResponse.Builder.clearBaseUri();
}
}
});
} finally {
request.triggerEvent(RequestEvent.Type.FINISHED);
}
}
private void initRequestEventListeners(ContainerRequest request) {
if (applicationEventListener != null) {
final RequestEventBuilder requestEventBuilder = new RequestEventImpl.Builder().setContainerRequest(request);
final RequestEventListener requestEventEventListener =
applicationEventListener.onRequest(requestEventBuilder.build(RequestEvent.Type.START));
if (requestEventEventListener != null) {
request.setRequestEventListener(requestEventEventListener, requestEventBuilder);
}
}
}
/**
* Get the Jersey server runtime background scheduler.
*
* @return server runtime background scheduler.
* @see BackgroundScheduler
*/
ScheduledExecutorService getBackgroundScheduler() {
return backgroundScheduler;
}
/**
* Ensure that the value a {@value HttpHeaders#LOCATION} header is an absolute URI, if present among headers.
*
* Relative URI value will be made absolute using a base request URI.
*
* @param location location URI; value of the HTTP {@value HttpHeaders#LOCATION} response header.
* @param headers mutable map of response headers.
* @param request container request.
*/
private static void ensureAbsolute(URI location, MultivaluedMap headers, ContainerRequest request) {
if (location == null || location.isAbsolute()) {
return;
}
// according to RFC2616 (HTTP/1.1), this field can contain one single URI
headers.putSingle(HttpHeaders.LOCATION, request.getBaseUri().resolve(location));
}
private static class AsyncResponderHolder implements Value {
private final Responder responder;
private final RequestScope.Instance scopeInstance;
private volatile AsyncResponder asyncResponder;
private AsyncResponderHolder(Responder responder,
RequestScope.Instance scopeInstance) {
this.responder = responder;
this.scopeInstance = scopeInstance;
}
@Override
public AsyncResponder get() {
final AsyncResponder ar = new AsyncResponder(responder, scopeInstance);
asyncResponder = ar;
return ar;
}
public boolean isAsync() {
final AsyncResponder ar = asyncResponder;
return ar != null && !ar.isRunning();
}
public void release() {
if (asyncResponder == null) {
scopeInstance.release();
}
}
}
private static class Responder {
private static final Logger LOGGER = Logger.getLogger(Responder.class.getName());
private final ContainerRequest request;
private final ServerRuntime runtime;
private final CompletionCallbackRunner completionCallbackRunner = new CompletionCallbackRunner();
private final ConnectionCallbackRunner connectionCallbackRunner = new ConnectionCallbackRunner();
private final TracingLogger tracingLogger;
public Responder(final ContainerRequest request, final ServerRuntime runtime) {
this.request = request;
this.runtime = runtime;
this.tracingLogger = TracingLogger.getInstance(request);
}
public void process(ContainerResponse response) {
request.getRequestEventBuilder().setContainerResponse(response);
response = processResponse(response);
release(response);
}
private ContainerResponse processResponse(ContainerResponse response) {
Stage respondingRoot = runtime.respondingContextProvider.get().createRespondingRoot();
if (respondingRoot != null) {
response = Stages.process(response, respondingRoot);
}
writeResponse(response);
// no-exception zone
// the methods below are guaranteed to not throw any exceptions
completionCallbackRunner.onComplete(null);
return response;
}
/**
* Process {@code throwable} by using exception mappers and generating the mapped
* response if possible.
*
* Note about logging:
*
* -
* we do not log exceptions that are mapped by ExceptionMappers.
*
-
* All other exceptions are logged: WebApplicationExceptions with entities,
* exceptions that were unsuccessfully mapped
*
*
*
* @param throwable Exception to be processed.
*/
public void process(Throwable throwable) {
request.getRequestEventBuilder().setException(throwable, RequestEvent.ExceptionCause.ORIGINAL);
request.triggerEvent(RequestEvent.Type.ON_EXCEPTION);
ContainerResponse response = null;
try {
final Response exceptionResponse = mapException(throwable);
try {
try {
response = convertResponse(exceptionResponse);
ensureAbsolute(response.getLocation(), response.getHeaders(), request);
request.getRequestEventBuilder().setContainerResponse(response).setResponseSuccessfullyMapped(true);
} finally {
request.triggerEvent(RequestEvent.Type.EXCEPTION_MAPPING_FINISHED);
}
processResponse(response);
} catch (Throwable respError) {
LOGGER.log(Level.SEVERE, LocalizationMessages.ERROR_PROCESSING_RESPONSE_FROM_ALREADY_MAPPED_EXCEPTION());
request.getRequestEventBuilder().setException(respError, RequestEvent.ExceptionCause.MAPPED_RESPONSE);
request.triggerEvent(RequestEvent.Type.ON_EXCEPTION);
throw respError;
}
} catch (Throwable responseError) {
if (throwable != responseError
&& !(throwable instanceof MappableException && throwable.getCause() == responseError)) {
LOGGER.log(Level.FINE, LocalizationMessages.ERROR_EXCEPTION_MAPPING_ORIGINAL_EXCEPTION(), throwable);
}
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 ContainerResponse convertResponse(Response exceptionResponse) {
final ContainerResponse containerResponse = new ContainerResponse(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);
Throwable throwable = originalThrowable;
boolean inMappable = false;
boolean mappingNotFound = false;
do {
if (throwable instanceof MappableException) {
inMappable = true;
} else if (inMappable || throwable instanceof WebApplicationException) {
Response waeResponse = null;
if (throwable instanceof WebApplicationException) {
final WebApplicationException webApplicationException = (WebApplicationException) 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);
ExceptionMapper mapper = runtime.exceptionMappers.findMapping(throwable);
if (mapper != null) {
request.getRequestEventBuilder().setExceptionMapper(mapper);
request.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-");
}
if (mappedResponse != null) {
// response successfully mapped
return mappedResponse;
} else {
return Response.noContent().build();
}
} catch (Throwable mapperThrowable) {
// spec: If the exception mapping provider throws an exception while creating a Response
// then return a server error (status code 500) response to the client.
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;
}
mappingNotFound = true;
}
// internal mapping
if (throwable instanceof HeaderValueException) {
if (((HeaderValueException) throwable).getContext() == HeaderValueException.Context.INBOUND) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
}
if (!inMappable || mappingNotFound) {
// user failures (thrown from Resource methods or provider methods)
// spec: Unchecked exceptions and errors that have not been mapped MUST be re-thrown and allowed to
// propagate to the underlying container.
// not logged on this level.
throw throwable;
}
throwable = throwable.getCause();
} while (throwable != null);
// jersey failures (not thrown from Resource methods or provider methods) -> rethrow
throw originalThrowable;
}
private ContainerResponse writeResponse(final ContainerResponse response) {
final ContainerResponseWriter writer = request.getResponseWriter();
ServerRuntime.ensureAbsolute(response.getLocation(), response.getHeaders(),
response.getRequestContext());
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(int contentLength) throws IOException {
ServerRuntime.ensureAbsolute(response.getLocation(), response.getHeaders(),
response.getRequestContext());
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(),
runtime.uriRoutingContextProvider.get().getBoundWriterInterceptors()));
} catch (MappableException mpe) {
if (mpe.getCause() instanceof IOException) {
connectionCallbackRunner.onDisconnect(runtime.asyncContextProvider.get());
}
throw mpe;
} finally {
tracingLogger.log(ServerTraceEvent.FINISHED, response.getStatusInfo());
tracingLogger.flush(response.getHeaders());
}
setWrittenResponse(response);
} catch (Throwable ex) {
if (response.isCommitted()) {
/**
* We're done with processing here. There's nothing we can do about the exception so
* let's just log it.
*/
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 (Exception e) {
LOGGER.log(Level.SEVERE, LocalizationMessages.ERROR_COMMITTING_OUTPUT_STREAM(), e);
close = true;
}
try {
((ChunkedOutput) entity).setContext(
runtime.requestScope,
runtime.requestScope.referenceCurrent(),
request,
response,
connectionCallbackRunner,
runtime.asyncContextProvider,
runtime.uriRoutingContextProvider.get());
} catch (IOException ex) {
LOGGER.log(Level.SEVERE, LocalizationMessages.ERROR_WRITING_RESPONSE_ENTITY_CHUNK(), ex);
close = true;
}
// suspend the writer indefinitely (passing null timeout handler is ok in such case).
// TODO what to do if we detect that the writer has already been suspended? override the timeout value?
if (!writer.suspend(0, TimeUnit.SECONDS, null)) {
LOGGER.fine(LocalizationMessages.ERROR_SUSPENDING_CHUNKED_OUTPUT_RESPONSE());
}
}
if (close) {
try {
// the response must be closed here instead of just flushed or committed. Some
// output streams writes out bytes only on close (for example GZipOutputStream).
response.close();
} catch (Exception e) {
LOGGER.log(Level.SEVERE, LocalizationMessages.ERROR_CLOSING_COMMIT_OUTPUT_STREAM(), e);
}
}
}
}
return response;
}
private void setWrittenResponse(ContainerResponse response) {
request.getRequestEventBuilder().setContainerResponse(response);
request.getRequestEventBuilder().setSuccess(response.getStatus() < Response.Status.BAD_REQUEST.getStatusCode());
request.getRequestEventBuilder().setResponseWritten(true);
}
private void release(ContainerResponse responseContext) {
try {
runtime.closeableServiceProvider.get().close();
// Commit the container response writer if not in chunked mode
// responseContext may be null in case the request processing was cancelled.
if (responseContext != null && !responseContext.isChunked()) {
// responseContext.commitStream();
responseContext.close();
}
} catch (Throwable throwable) {
LOGGER.log(Level.WARNING, LocalizationMessages.RELEASING_REQUEST_PROCESSING_RESOURCES_FAILED(), throwable);
}
}
}
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(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 RequestScope.Instance scopeInstance;
private volatile TimeoutHandler timeoutHandler = DEFAULT_TIMEOUT_HANDLER;
private final List> callbackRunners;
public AsyncResponder(final Responder responder,
final RequestScope.Instance scopeInstance) {
this.responder = responder;
this.scopeInstance = scopeInstance;
this.callbackRunners = Collections.unmodifiableList(Arrays.asList(
responder.completionCallbackRunner, responder.connectionCallbackRunner));
responder.completionCallbackRunner.register(this);
}
@Override
public void onTimeout(ContainerResponseWriter responseWriter) {
final TimeoutHandler handler = timeoutHandler;
try {
synchronized (stateLock) {
if (state == SUSPENDED) {
handler.handleTimeout(this);
}
}
} catch (Throwable throwable) {
resume(throwable);
}
}
@Override
public void onComplete(final Throwable throwable) {
synchronized (stateLock) {
state = COMPLETED;
}
}
@Override
public void invokeManaged(final Producer producer) {
responder.runtime.asyncExecutorFactory.getExecutor().submit(new Runnable() {
@Override
public void run() {
responder.runtime.requestScope.runInScope(scopeInstance, new Runnable() {
@Override
public void run() {
try {
final Response response = producer.call();
if (response != null) {
resume(response);
}
} catch (Throwable t) {
resume(t);
}
}
});
}
});
}
@Override
public boolean suspend() {
synchronized (stateLock) {
if (state == RUNNING) {
if (responder.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 {
final Response jaxrsResponse =
(response instanceof Response) ? (Response) response : Response.ok(response).build();
ServerRuntime.ensureAbsolute(
jaxrsResponse.getLocation(), jaxrsResponse.getHeaders(), responder.request);
responder.process(new ContainerResponse(responder.request, jaxrsResponse));
} catch (Throwable t) {
responder.process(t);
}
}
});
}
@Override
public boolean resume(final Throwable error) {
return resume(new Runnable() {
@Override
public void run() {
try {
responder.process(new MappableException(error));
} catch (final Throwable error) {
// Ignore the exception - already resumed but may be rethrown by ContainerResponseWriter#failure.
}
}
});
}
private boolean resume(Runnable handler) {
synchronized (stateLock) {
if (state != SUSPENDED) {
return false;
}
state = RESUMED;
}
responder.runtime.requestScope.runInScope(scopeInstance, handler);
return true;
}
@Override
public boolean cancel() {
return cancel(new Value() {
@Override
public Response get() {
return Response.status(Response.Status.SERVICE_UNAVAILABLE).build();
}
});
}
@Override
public boolean cancel(final int retryAfter) {
return cancel(new Value() {
@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() {
@Override
public Response get() {
return Response
.status(Response.Status.SERVICE_UNAVAILABLE)
.header(HttpHeaders.RETRY_AFTER, retryAfter)
.build();
}
});
}
private boolean cancel(final Value responseValue) {
synchronized (stateLock) {
if (cancelled) {
return true;
}
if (state != SUSPENDED) {
return false;
}
state = RESUMED;
cancelled = true;
}
responder.runtime.requestScope.runInScope(scopeInstance, new Runnable() {
@Override
public void run() {
try {
final Response response = responseValue.get();
responder.process(new ContainerResponse(responder.request, response));
} catch (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(long time, TimeUnit unit) {
try {
responder.request.getResponseWriter().setSuspendTimeout(time, unit);
return true;
} catch (IllegalStateException ex) {
LOGGER.log(Level.FINER, "Unable to set timeout on the AsyncResponse.", ex);
return false;
}
}
@Override
public void setTimeoutHandler(TimeoutHandler handler) {
timeoutHandler = handler;
}
@Override
public Collection> register(final Class> callback) {
Preconditions.checkNotNull(callback, LocalizationMessages.PARAM_NULL("callback"));
return register(Injections.getOrCreate(responder.runtime.locator, callback));
}
@Override
public Map, Collection>> register(Class> callback, 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, Collection>> results = new HashMap, Collection>>();
results.put(callback, register(callback));
for (Class> c : callbacks) {
results.put(c, register(c));
}
return results;
}
@Override
public Collection> register(Object callback) {
Preconditions.checkNotNull(callback, LocalizationMessages.PARAM_NULL("callback"));
Collection> result = new LinkedList>();
for (AbstractCallbackRunner> runner : callbackRunners) {
if (runner.supports(callback.getClass())) {
if (runner.register(callback)) {
result.add(runner.getCallbackContract());
}
}
}
return result;
}
@Override
public Map, Collection>> register(Object callback, 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, Collection>> results = new HashMap, Collection>>();
results.put(callback.getClass(), register(callback));
for (Object c : callbacks) {
results.put(c.getClass(), register(c));
}
return results;
}
}
private static abstract class AbstractCallbackRunner {
private final Queue callbacks = new ConcurrentLinkedQueue();
private final Logger logger;
protected AbstractCallbackRunner(Logger logger) {
this.logger = logger;
}
/**
* Return true if this callback runner supports the {@code callbackClass}.
*
* @param callbackClass Callback to be checked.
* @return True if this callback runner supports the {@code callbackClass}; false otherwise.
*/
public final boolean supports(Class> callbackClass) {
return getCallbackContract().isAssignableFrom(callbackClass);
}
/**
* Get the callback contract supported by this callback runner.
*
* @return callback contract supported by this callback runner.
*/
public abstract Class> getCallbackContract();
@SuppressWarnings("unchecked")
public boolean register(Object callback) {
return callbacks.offer((T) callback);
}
protected final void executeCallbacks(Closure invoker) {
for (T callback : callbacks) {
try {
invoker.invoke(callback);
} catch (Throwable t) {
logger.log(Level.WARNING, LocalizationMessages.ERROR_ASYNC_CALLBACK_FAILED(callback.getClass().getName()), t);
}
}
}
}
private static class CompletionCallbackRunner
extends AbstractCallbackRunner 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() {
@Override
public void invoke(CompletionCallback callback) {
callback.onComplete(throwable);
}
});
}
}
/**
* Executor of {@link ConnectionCallback connection callbacks}.
*/
static class ConnectionCallbackRunner
extends AbstractCallbackRunner 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() {
@Override
public void invoke(ConnectionCallback callback) {
callback.onDisconnect(disconnected);
}
});
}
}
}