package org.jboss.resteasy.core;

import org.jboss.resteasy.resteasy_jaxrs.i18n.Messages;
import org.jboss.resteasy.specimpl.BuiltResponse;
import org.jboss.resteasy.specimpl.ResteasyUriInfo;
import org.jboss.resteasy.spi.ApplicationException;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse;
import org.jboss.resteasy.spi.InjectorFactory;
import org.jboss.resteasy.spi.InternalServerErrorException;
import org.jboss.resteasy.spi.MethodInjector;
import org.jboss.resteasy.spi.ResourceFactory;
import org.jboss.resteasy.spi.ResourceInvoker;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.spi.metadata.ResourceLocator;
import org.jboss.resteasy.spi.statistics.MethodStatisticsLogger;
import org.jboss.resteasy.statistics.StatisticsControllerImpl;
import org.jboss.resteasy.util.GetRestful;

import javax.ws.rs.NotFoundException;
import javax.ws.rs.Produces;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ConcurrentHashMap;

Author:Bill Burke
Version:$Revision: 1 $
/** * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */
public class ResourceLocatorInvoker implements ResourceInvoker { protected InjectorFactory injector; protected MethodInjector methodInjector; protected ResourceFactory resource; protected ResteasyProviderFactory providerFactory; protected ResourceLocator method; protected ConcurrentHashMap<Class<?>, LocatorRegistry> cachedSubresources = new ConcurrentHashMap<Class<?>, LocatorRegistry>(); protected final boolean hasProduces; protected MethodStatisticsLogger methodStatisticsLogger; public ResourceLocatorInvoker(final ResourceFactory resource, final InjectorFactory injector, final ResteasyProviderFactory providerFactory, final ResourceLocator locator) { this.resource = resource; this.injector = injector; this.providerFactory = providerFactory; this.method = locator; this.methodInjector = injector.createMethodInjector(locator, providerFactory); hasProduces = method.getMethod().isAnnotationPresent(Produces.class) || method.getMethod().getClass().isAnnotationPresent(Produces.class); methodStatisticsLogger = StatisticsControllerImpl.EMPTY; } @Override public boolean hasProduces() { return hasProduces; } @SuppressWarnings("unchecked") protected Object resolveTarget(HttpRequest request, HttpResponse response) { Object locatorResource = this.resource.createResource(request, response, providerFactory); if (locatorResource instanceof CompletionStage) { CompletionStage<Object> locatorStage = (CompletionStage<Object>)locatorResource; return locatorStage .thenCompose(resource -> { Object located = resolveTargetFromLocator(request, response, resource); if (located instanceof CompletionStage) { return (CompletionStage<Object>)located; } else { return CompletableFuture.completedFuture(located); } }); } else { return resolveTargetFromLocator(request, response, locatorResource); } } protected Object resolveTargetFromLocator(HttpRequest request, HttpResponse response, Object locator) { ResteasyUriInfo uriInfo = (ResteasyUriInfo)request.getUri(); RuntimeException lastException = (RuntimeException)request.getAttribute(ResourceMethodRegistry.REGISTRY_MATCHING_EXCEPTION); Object obj = methodInjector.injectArguments(request, response); if (obj == null || !(obj instanceof CompletionStage)) { return constructLocator(locator, uriInfo, (Object[])obj); } @SuppressWarnings("unchecked") CompletionStage<Object[]> stagedArgs = (CompletionStage<Object[]>)obj; return stagedArgs.exceptionally(t -> { if(t.getCause() instanceof NotFoundException && lastException != null) throw lastException; SynchronousDispatcher.rethrow(t); // never reached return null; }).thenApply(args -> { return constructLocator(locator, uriInfo, args); }); } private Object constructLocator(Object locator, ResteasyUriInfo uriInfo, Object[] args) { try { uriInfo.pushCurrentResource(locator); Object subResource = method.getMethod().invoke(locator, args); if (subResource instanceof Class) { subResource = this.providerFactory.injectedInstance((Class<?>)subResource); } return subResource; } catch (IllegalAccessException e) { throw new InternalServerErrorException(e); } catch (InvocationTargetException e) { throw new ApplicationException(e.getCause()); } catch (SecurityException e) { throw new ApplicationException(e.getCause()); } } public Method getMethod() { return method.getMethod(); } @SuppressWarnings("unchecked") public BuiltResponse invoke(HttpRequest request, HttpResponse response) { Object resource = resolveTarget(request, response); if (resource instanceof CompletionStage) { return ((CompletionStage<Object>)resource).thenApply(target -> invokeOnTargetObject(request, response, target)).toCompletableFuture().getNow(null); } return invokeOnTargetObject(request, response, resource); } @SuppressWarnings("unchecked") public BuiltResponse invoke(HttpRequest request, HttpResponse response, Object locator) { Object resource = resolveTargetFromLocator(request, response, locator); if (resource instanceof CompletionStage) { return ((CompletionStage<Object>)resource).thenApply(target -> invokeOnTargetObject(request, response, target)).toCompletableFuture().getNow(null); } return invokeOnTargetObject(request, response, resource); } protected BuiltResponse invokeOnTargetObject(HttpRequest request, HttpResponse response, Object target) { if (target == null) { NotFoundException notFound = new NotFoundException(Messages.MESSAGES.nullSubresource(request.getUri().getAbsolutePath())); throw notFound; } Class<? extends Object> clazz = target.getClass(); LocatorRegistry registry = cachedSubresources.get(clazz); if (registry == null) { if (!GetRestful.isSubResourceClass(clazz)) { String msg = Messages.MESSAGES.subresourceHasNoJaxRsAnnotations(clazz.getName()); throw new InternalServerErrorException(msg); } registry = new LocatorRegistry(clazz, providerFactory); cachedSubresources.putIfAbsent(clazz, registry); } ResourceInvoker invoker = registry.getResourceInvoker(request); if (invoker instanceof ResourceLocatorInvoker) { ResourceLocatorInvoker locator = (ResourceLocatorInvoker) invoker; final long timeStamp = methodStatisticsLogger.timestamp(); try { return locator.invoke(request, response, target); } finally { methodStatisticsLogger.duration(timeStamp); } } else { ResourceMethodInvoker method = (ResourceMethodInvoker) invoker; return method.invoke(request, response, target); } } public void setMethodStatisticsLogger(MethodStatisticsLogger msLogger) { methodStatisticsLogger = msLogger; } public MethodStatisticsLogger getMethodStatisticsLogger() { return methodStatisticsLogger; } }