package org.jboss.resteasy.core; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.ws.rs.NotFoundException; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.ResourceContext; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import javax.ws.rs.ext.Providers; import org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext; import org.jboss.resteasy.plugins.server.servlet.Cleanable; import org.jboss.resteasy.plugins.server.servlet.Cleanables; import org.jboss.resteasy.resteasy_jaxrs.i18n.LogMessages; import org.jboss.resteasy.resteasy_jaxrs.i18n.Messages; import org.jboss.resteasy.specimpl.BuiltResponse; import org.jboss.resteasy.specimpl.RequestImpl; import org.jboss.resteasy.spi.Failure; import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequestPreprocessor; import org.jboss.resteasy.spi.HttpResponse; import org.jboss.resteasy.spi.InternalDispatcher; import org.jboss.resteasy.spi.InternalServerErrorException; import org.jboss.resteasy.spi.Registry; import org.jboss.resteasy.spi.ResteasyAsynchronousContext; import org.jboss.resteasy.spi.ResteasyConfiguration; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.jboss.resteasy.spi.UnhandledException; /** * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */ @SuppressWarnings("unchecked") public class SynchronousDispatcher implements Dispatcher { protected ResteasyProviderFactory providerFactory; protected Registry registry; protected List<HttpRequestPreprocessor> requestPreprocessors = new ArrayList<HttpRequestPreprocessor>(); protected Map<Class, Object> defaultContextObjects = new HashMap<Class, Object>(); protected Set<String> unwrappedExceptions = new HashSet<String>(); protected boolean bufferExceptionEntityRead = false; protected boolean bufferExceptionEntity = true; public SynchronousDispatcher(ResteasyProviderFactory providerFactory) { this.providerFactory = providerFactory; this.registry = new ResourceMethodRegistry(providerFactory); defaultContextObjects.put(Providers.class, providerFactory); defaultContextObjects.put(Registry.class, registry); defaultContextObjects.put(Dispatcher.class, this); defaultContextObjects.put(InternalDispatcher.class, InternalDispatcher.getInstance()); } public SynchronousDispatcher(ResteasyProviderFactory providerFactory, ResourceMethodRegistry registry) { this(providerFactory); this.registry = registry; defaultContextObjects.put(Registry.class, registry); } public ResteasyProviderFactory getProviderFactory() { return providerFactory; } public Registry getRegistry() { return registry; } public Map<Class, Object> getDefaultContextObjects() { return defaultContextObjects; } public Set<String> getUnwrappedExceptions() { return unwrappedExceptions; } public Response preprocess(HttpRequest request) { Response aborted = null; try { for (HttpRequestPreprocessor preprocessor : this.requestPreprocessors) { preprocessor.preProcess(request); } ContainerRequestFilter[] requestFilters = providerFactory.getContainerRequestFilterRegistry().preMatch(); PreMatchContainerRequestContext requestContext = new PreMatchContainerRequestContext(request); for (ContainerRequestFilter filter : requestFilters) { filter.filter(requestContext); aborted = requestContext.getResponseAbortedWith(); if (aborted != null) break; } } catch (Exception e) { //logger.error("Failed in preprocess, mapping exception", e); aborted = new ExceptionHandler(providerFactory, unwrappedExceptions).handleException(request, e); } return aborted; } /** * Call pre-process ContainerRequestFilters * * @return true if request should continue */ protected boolean preprocess(HttpRequest request, HttpResponse response) { Response aborted = null; try { for (HttpRequestPreprocessor preprocessor : this.requestPreprocessors) { preprocessor.preProcess(request); } ContainerRequestFilter[] requestFilters = providerFactory.getContainerRequestFilterRegistry().preMatch(); PreMatchContainerRequestContext requestContext = new PreMatchContainerRequestContext(request); for (ContainerRequestFilter filter : requestFilters) { filter.filter(requestContext); aborted = requestContext.getResponseAbortedWith(); if (aborted != null) break; } } catch (Exception e) { //logger.error("Failed in preprocess, mapping exception", e); writeException(request, response, e); return false; } if (aborted != null) { writeResponse(request, response, aborted); return false; } return true; } public void writeException(HttpRequest request, HttpResponse response, Throwable e) { if (!bufferExceptionEntityRead) { bufferExceptionEntityRead = true; ResteasyConfiguration context = ResteasyProviderFactory.getContextData(ResteasyConfiguration.class); if (context != null) { String s = context.getParameter("resteasy.buffer.exception.entity"); if (s != null) { bufferExceptionEntity = Boolean.parseBoolean(s); } } } if (response.isCommitted()) throw new UnhandledException(Messages.MESSAGES.responseIsCommitted(), e); Response handledResponse = new ExceptionHandler(providerFactory, unwrappedExceptions).handleException(request, e); if (handledResponse == null) throw new UnhandledException(e); if (!bufferExceptionEntity) { response.getOutputHeaders().add("resteasy.buffer.exception.entity", "false"); } try { ServerResponseWriter.writeNomapResponse(((BuiltResponse) handledResponse), request, response, providerFactory); } catch (Exception e1) { throw new UnhandledException(e1); } } public void invoke(HttpRequest request, HttpResponse response) { try { pushContextObjects(request, response); if (!preprocess(request, response)) return; ResourceInvoker invoker = null; try { invoker = getInvoker(request); } catch (Exception exception) { //logger.error("getInvoker() failed mapping exception", exception); writeException(request, response, exception); return; } invoke(request, response, invoker); } finally { clearContextData(); } } /** * Propagate NotFoundException. This is used for Filters * * @param request * @param response */ public void invokePropagateNotFound(HttpRequest request, HttpResponse response) throws NotFoundException { try { pushContextObjects(request, response); if (!preprocess(request, response)) return; ResourceInvoker invoker = null; try { invoker = getInvoker(request); } catch (Exception failure) { if (failure instanceof NotFoundException) { throw ((NotFoundException) failure); } else { //logger.error("getInvoker() failed mapping exception", failure); writeException(request, response, failure); return; } } invoke(request, response, invoker); } finally { clearContextData(); } } public ResourceInvoker getInvoker(HttpRequest request) throws Failure { LogMessages.LOGGER.pathInfo(request.getUri().getPath()); if (!request.isInitial()) { throw new InternalServerErrorException(Messages.MESSAGES.isNotInitialRequest(request.getUri().getPath())); } ResourceInvoker invoker = registry.getResourceInvoker(request); if (invoker == null) { throw new NotFoundException(Messages.MESSAGES.unableToFindJaxRsResource(request.getUri().getPath())); } return invoker; } public void pushContextObjects(final HttpRequest request, final HttpResponse response) { Map contextDataMap = ResteasyProviderFactory.getContextDataMap(); contextDataMap.put(HttpRequest.class, request); contextDataMap.put(HttpResponse.class, response); contextDataMap.put(HttpHeaders.class, request.getHttpHeaders()); contextDataMap.put(UriInfo.class, request.getUri()); contextDataMap.put(Request.class, new RequestImpl(request, response)); contextDataMap.put(ResteasyAsynchronousContext.class, request.getAsyncContext()); ResourceContext resourceContext = new ResourceContext() { @Override public <T> T getResource(Class<T> resourceClass) { return providerFactory.injectedInstance(resourceClass, request, response); } @Override public <T> T initResource(T resource) { providerFactory.injectProperties(resource, request, response); return resource; } }; contextDataMap.put(ResourceContext.class, resourceContext); contextDataMap.putAll(defaultContextObjects); contextDataMap.put(Cleanables.class, new Cleanables()); } public Response internalInvocation(HttpRequest request, HttpResponse response, Object entity) { // be extra careful in the clean up process. Only pop if there was an // equivalent push. ResteasyProviderFactory.addContextDataLevel(); boolean pushedBody = false; try { MessageBodyParameterInjector.pushBody(entity); pushedBody = true; ResourceInvoker invoker = getInvoker(request); if (invoker != null) { pushContextObjects(request, response); return execute(request, response, invoker); } // this should never happen, since getInvoker should throw an exception // if invoker is null return null; } finally { ResteasyProviderFactory.removeContextDataLevel(); if (pushedBody) { MessageBodyParameterInjector.popBody(); } } } public void clearContextData() { Cleanables cleanables = ResteasyProviderFactory.getContextData(Cleanables.class); if (cleanables != null) { for (Iterator<Cleanable> it = cleanables.getCleanables().iterator(); it.hasNext(); ) { try { it.next().clean(); } catch(Exception e) { // Empty } } } ResteasyProviderFactory.clearContextData(); // just in case there were internalDispatches that need to be cleaned up MessageBodyParameterInjector.clearBodies(); } /** * Return a response wither from an invoke or exception handling * * @param request * @param response * @param invoker * @return */ public Response execute(HttpRequest request, HttpResponse response, ResourceInvoker invoker) { Response jaxrsResponse = null; try { jaxrsResponse = invoker.invoke(request, response); if (request.getAsyncContext().isSuspended()) { /** * Callback by the initial calling thread. This callback will probably do nothing in an asynchronous environment * but will be used to simulate AsynchronousResponse in vanilla Servlet containers that do not support * asychronous HTTP. * */ request.getAsyncContext().getAsyncResponse().initialRequestThreadFinished(); jaxrsResponse = null; // we're handing response asynchronously } } catch (Exception e) { //logger.error("invoke() failed mapping exception", e); jaxrsResponse = new ExceptionHandler(providerFactory, unwrappedExceptions).handleException(request, e); if (jaxrsResponse == null) throw new UnhandledException(e); } return jaxrsResponse; } /** * Invoke and write response * * @param request * @param response * @param invoker */ public void invoke(HttpRequest request, HttpResponse response, ResourceInvoker invoker) { Response jaxrsResponse = null; try { jaxrsResponse = invoker.invoke(request, response); if (request.getAsyncContext().isSuspended()) { /** * Callback by the initial calling thread. This callback will probably do nothing in an asynchronous environment * but will be used to simulate AsynchronousResponse in vanilla Servlet containers that do not support * asychronous HTTP. * */ request.getAsyncContext().getAsyncResponse().initialRequestThreadFinished(); jaxrsResponse = null; // we're handing response asynchronously } } catch (Exception e) { //logger.error("invoke() failed mapping exception", e); writeException(request, response, e); return; } if (jaxrsResponse != null) writeResponse(request, response, jaxrsResponse); } public void asynchronousDelivery(HttpRequest request, HttpResponse response, Response jaxrsResponse) throws IOException { if (jaxrsResponse == null) return; try { pushContextObjects(request, response); ServerResponseWriter.writeNomapResponse((BuiltResponse) jaxrsResponse, request, response, providerFactory); } finally { ResteasyProviderFactory.removeContextDataLevel(); } } public void asynchronousExceptionDelivery(HttpRequest request, HttpResponse response, Throwable exception) { try { pushContextObjects(request, response); writeException(request, response, exception); } catch (Throwable ex) { LogMessages.LOGGER.unhandledAsynchronousException(ex); // unhandled exceptions need to be processed as they can't be thrown back to the servlet container if (!response.isCommitted()) { try { response.reset(); response.sendError(500); } catch (IOException e) { } } } finally { ResteasyProviderFactory.removeContextDataLevel(); } } protected void writeResponse(HttpRequest request, HttpResponse response, Response jaxrsResponse) { try { ServerResponseWriter.writeNomapResponse((BuiltResponse) jaxrsResponse, request, response, providerFactory); } catch (Exception e) { //logger.error("writeResponse() failed mapping exception", e); writeException(request, response, e); } } public void addHttpPreprocessor(HttpRequestPreprocessor httpPreprocessor) { requestPreprocessors.add(httpPreprocessor); } }