All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.jersey.server.ServerRuntime Maven / Gradle / Ivy

Go to download

Ehcache is an open source, standards-based cache used to boost performance, offload the database and simplify scalability. Ehcache is robust, proven and full-featured and this has made it the most widely-used Java-based cache.

There is a newer version: 2.10.9.2
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2012-2014 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 org.glassfish.hk2.api.ServiceLocator;

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 jersey.repackaged.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);
        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();
                }
            }
        });
    }

    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); } finally { request.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(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); } }); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy