package org.jboss.resteasy.core;

import org.jboss.resteasy.core.registry.RootClassNode;
import org.jboss.resteasy.core.registry.RootNode;
import org.jboss.resteasy.plugins.server.resourcefactory.JndiResourceFactory;
import org.jboss.resteasy.plugins.server.resourcefactory.POJOResourceFactory;
import org.jboss.resteasy.plugins.server.resourcefactory.SingletonResource;
import org.jboss.resteasy.resteasy_jaxrs.i18n.LogMessages;
import org.jboss.resteasy.resteasy_jaxrs.i18n.Messages;
import org.jboss.resteasy.specimpl.ResteasyUriBuilderImpl;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.InjectorFactory;
import org.jboss.resteasy.spi.Registry;
import org.jboss.resteasy.spi.ResourceFactory;
import org.jboss.resteasy.spi.ResourceInvoker;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.spi.ResteasyUriBuilder;
import org.jboss.resteasy.spi.metadata.ResourceBuilder;
import org.jboss.resteasy.spi.metadata.ResourceClass;
import org.jboss.resteasy.spi.metadata.ResourceLocator;
import org.jboss.resteasy.spi.metadata.ResourceMethod;
import org.jboss.resteasy.spi.statistics.StatisticsController;
import org.jboss.resteasy.tracing.RESTEasyTracingLogger;
import org.jboss.resteasy.util.AnnotationResolver;
import org.jboss.resteasy.util.GetRestful;
import org.jboss.resteasy.util.IsHttpMethod;

import javax.ws.rs.Path;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.List;
import java.util.Map;
import java.util.Set;

Registry of resources and methods/classes that can dispatch HTTP method requests.
Author:Bill Burke
Version:$Revision: 1 $
/** * Registry of resources and methods/classes that can dispatch HTTP method requests. * * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */
@SuppressWarnings("rawtypes") public class ResourceMethodRegistry implements Registry { public static final String REGISTRY_MATCHING_EXCEPTION = "registry.matching.exception"; protected ResteasyProviderFactory providerFactory; protected RootClassNode root = new RootClassNode(); protected boolean widerMatching; protected RootNode rootNode = new RootNode(); protected ResourceBuilder resourceBuilder; protected StatisticsController statisticsController; public ResourceMethodRegistry(final ResteasyProviderFactory providerFactory) { this.providerFactory = providerFactory; this.resourceBuilder = providerFactory.getResourceBuilder(); this.statisticsController = providerFactory.getStatisticsController(); } public boolean isWiderMatching() { return widerMatching; } public void setWiderMatching(boolean widerMatching) { this.widerMatching = widerMatching; } public void addPerRequestResource(Class clazz, String basePath) { addResourceFactory(new POJOResourceFactory(resourceBuilder, clazz), basePath); }
Register a vanilla JAX-RS resource class.
Params:
  • clazz – class
/** * Register a vanilla JAX-RS resource class. * * @param clazz class */
public void addPerRequestResource(Class clazz) { addResourceFactory(new POJOResourceFactory(resourceBuilder, clazz)); } @Override public void addPerRequestResource(Class<?> clazz, ResourceBuilder resourceBuilder) { addResourceFactory(new POJOResourceFactory(resourceBuilder, clazz), resourceBuilder); } @Override public void addPerRequestResource(ResourceClass clazz) { POJOResourceFactory resourceFactory = new POJOResourceFactory(resourceBuilder, clazz); register(resourceFactory, null, clazz); resourceFactory.registered(providerFactory); } @Override public void addPerRequestResource(ResourceClass clazz, String basePath) { POJOResourceFactory resourceFactory = new POJOResourceFactory(resourceBuilder, clazz); register(resourceFactory, basePath, clazz); resourceFactory.registered(providerFactory); } public void addSingletonResource(Object singleton) { ResourceClass resourceClass = resourceBuilder.getRootResourceFromAnnotations(singleton.getClass()); addResourceFactory(new SingletonResource(singleton, resourceClass)); } public void addSingletonResource(Object singleton, String basePath) { ResourceClass resourceClass = resourceBuilder.getRootResourceFromAnnotations(singleton.getClass()); addResourceFactory(new SingletonResource(singleton, resourceClass), basePath); } @Override public void addSingletonResource(Object singleton, ResourceClass resourceClass) { SingletonResource resourceFactory = new SingletonResource(singleton, resourceClass); register(resourceFactory, null, resourceClass); resourceFactory.registered(providerFactory); } @Override public void addSingletonResource(Object singleton, ResourceClass resourceClass, String basePath) { SingletonResource resourceFactory = new SingletonResource(singleton, resourceClass); register(resourceFactory, basePath, resourceClass); resourceFactory.registered(providerFactory); } public void addJndiResource(String jndiName) { addResourceFactory(new JndiResourceFactory(jndiName)); } public void addJndiResource(String jndiName, String basePath) { addResourceFactory(new JndiResourceFactory(jndiName), basePath); } @Override public void addJndiResource(String jndiName, ResourceClass resourceClass) { JndiResourceFactory resourceFactory = new JndiResourceFactory(jndiName); register(resourceFactory, null, resourceClass); resourceFactory.registered(providerFactory); } @Override public void addJndiResource(String jndiName, ResourceClass resourceClass, String basePath) { JndiResourceFactory resourceFactory = new JndiResourceFactory(jndiName); register(resourceFactory, basePath, resourceClass); resourceFactory.registered(providerFactory); }
Bind an endpoint ResourceFactory. ResourceFactory.getScannableClass() defines what class should be scanned for JAX-RS annotations. The class and any implemented interfaces are scanned for annotations.
Params:
  • ref – resource factory
/** * Bind an endpoint ResourceFactory. ResourceFactory.getScannableClass() defines what class should be scanned * for JAX-RS annotations. The class and any implemented interfaces are scanned for annotations. * * @param ref resource factory */
public void addResourceFactory(ResourceFactory ref) { addResourceFactory(ref, (String)null); } public void addResourceFactory(ResourceFactory ref, ResourceBuilder resourceBuilder) { addResourceFactory(ref, resourceBuilder, null); }
ResourceFactory.getScannableClass() defines what class should be scanned for JAX-RS annotations. The class and any implemented interfaces are scanned for annotations.
Params:
  • ref – resource factory
  • base – base URI path for any resources provided by the factory, in addition to rootPath
/** * ResourceFactory.getScannableClass() defines what class should be scanned * for JAX-RS annotations. The class and any implemented interfaces are scanned for annotations. * * @param ref resource factory * @param base base URI path for any resources provided by the factory, in addition to rootPath */
public void addResourceFactory(ResourceFactory ref, String base) { addResourceFactory(ref, resourceBuilder, base); } public void addResourceFactory(ResourceFactory ref, ResourceBuilder resourceBuilder, String base) { Class<?> clazz = ref.getScannableClass(); Class restful = AnnotationResolver.getClassWithAnnotation(clazz, resourceBuilder.getCorrespondingRootAnnotation()); if (restful == null) { String msg = Messages.MESSAGES.classIsNotRootResource(clazz.getName()); for (Class intf : clazz.getInterfaces()) { msg += " " + intf.getName(); } throw new RuntimeException(msg); } addResourceFactory(ref, resourceBuilder, base, restful); }
ResourceFactory.getScannableClass() is not used, only the clazz parameter and not any implemented interfaces of the clazz parameter.
Params:
  • ref – resource factory
  • base – base URI path for any resources provided by the factory, in addition to rootPath
  • clazz – specific class
/** * ResourceFactory.getScannableClass() is not used, only the clazz parameter and not any implemented interfaces * of the clazz parameter. * * @param ref resource factory * @param base base URI path for any resources provided by the factory, in addition to rootPath * @param clazz specific class */
public void addResourceFactory(ResourceFactory ref, String base, Class<?> clazz) { addResourceFactory(ref, resourceBuilder, base, clazz); } public void addResourceFactory(ResourceFactory ref, ResourceBuilder resourceBuilder, String base, Class<?> clazz) { Class<?>[] classes = {clazz}; addResourceFactory(ref, resourceBuilder, base, classes); if (ref != null) ref.registered(providerFactory); }
ResourceFactory.getScannableClass() is not used, only the clazz parameter and not any implemented interfaces of the clazz parameter.
Params:
  • ref – resource factory
  • base – base URI path for any resources provided by the factory, in addition to rootPath
  • classes – specific class
/** * ResourceFactory.getScannableClass() is not used, only the clazz parameter and not any implemented interfaces * of the clazz parameter. * * @param ref resource factory * @param base base URI path for any resources provided by the factory, in addition to rootPath * @param classes specific class */
public void addResourceFactory(ResourceFactory ref, String base, Class<?>[] classes) { addResourceFactory(ref, resourceBuilder, base, classes); } public void addResourceFactory(ResourceFactory ref, ResourceBuilder resourceBuilder, String base, Class<?>[] classes) { if (ref != null) ref.registered(providerFactory); for (Class<?> clazz : classes) { if (Proxy.isProxyClass(clazz)) { for (Class<?> intf : clazz.getInterfaces()) { ResourceClass resourceClass = resourceBuilder.getRootResourceFromAnnotations(intf); register(ref, base, resourceClass); } } else { ResourceClass resourceClass = resourceBuilder.getRootResourceFromAnnotations(clazz); register(ref, base, resourceClass); } } // https://issues.jboss.org/browse/JBPAPP-7871 for (Class<?> clazz : classes) { for (Method method : getDeclaredMethods(clazz)) { Method _method = resourceBuilder.getAnnotatedMethod(clazz, method); if (_method != null && !java.lang.reflect.Modifier.isPublic(_method.getModifiers())) { LogMessages.LOGGER.JAXRSAnnotationsFoundAtNonPublicMethod(method.getDeclaringClass().getName(), method.getName()); } } } } private Method[] getDeclaredMethods(final Class<?> clazz) { Method[] methodList = new Method[0]; try { if (System.getSecurityManager() == null) { methodList = clazz.getDeclaredMethods(); } else { methodList = AccessController.doPrivileged(new PrivilegedExceptionAction<Method[]>() { @Override public Method[] run() throws Exception { return clazz.getDeclaredMethods(); } }); } } catch (PrivilegedActionException pae) { } return methodList; } @Override public void addResourceFactory(ResourceFactory rf, String base, ResourceClass resourceClass) { if (rf != null) rf.registered(providerFactory); register(rf, base, resourceClass); } protected void register(ResourceFactory rf, String base, ResourceClass resourceClass) { for (ResourceMethod method : resourceClass.getResourceMethods()) { processMethod(rf, base, method); } for (ResourceLocator method : resourceClass.getResourceLocators()) { processMethod(rf, base, method); } }
Resteasy 2.x does not properly handle sub-resource and sub-resource locator endpoints with the same uri. Resteasy 3.x does handle this properly. In assisting customers identify this issue during an upgrade from Resteasy 2 to 3 provides a waring when the situation is found.
/** * Resteasy 2.x does not properly handle sub-resource and sub-resource locator * endpoints with the same uri. Resteasy 3.x does handle this properly. In * assisting customers identify this issue during an upgrade from Resteasy 2 to 3 * provides a waring when the situation is found. */
public void checkAmbiguousUri() { for (Map.Entry<String, List<ResourceInvoker>> entry : this.root.getBounded().entrySet()) { List<ResourceInvoker> values = entry.getValue(); if (values.size() > 1) { int locatorCnt = 0; int methodCnt = 0; for(ResourceInvoker rInvoker : values) { if (rInvoker instanceof ResourceLocatorInvoker) { locatorCnt++; } else if (rInvoker instanceof ResourceMethodInvoker) { methodCnt++; } } if (methodCnt > 0 && locatorCnt > 0) { StringBuilder sb = new StringBuilder(); int cnt = values.size(); for (int i=0; i < cnt; i++) { ResourceInvoker exp = values.get(i); sb.append(exp.getMethod().getDeclaringClass().getName()) .append(".") .append(exp.getMethod().getName()); if (i < cnt-1) { sb.append(", "); } } LogMessages.LOGGER.uriAmbiguity(entry.getKey(), sb.toString()); } } } } protected void processMethod(ResourceFactory rf, String base, ResourceLocator method) { ResteasyUriBuilder builder = new ResteasyUriBuilderImpl(); if (base != null) builder.path(base); builder.path(method.getFullpath()); String fullpath = builder.getPath(); if (fullpath == null) fullpath = ""; builder = new ResteasyUriBuilderImpl(); if (base != null) builder.path(base); builder.path(method.getResourceClass().getPath()); String classExpression = builder.getPath(); if (classExpression == null) classExpression = ""; InjectorFactory injectorFactory = providerFactory.getInjectorFactory(); if (method instanceof ResourceMethod) { ResourceMethodInvoker invoker = new ResourceMethodInvoker((ResourceMethod) method, injectorFactory, rf, providerFactory); if (widerMatching) rootNode.addInvoker(fullpath, invoker); else root.addInvoker(classExpression, fullpath, invoker); statisticsController.register(invoker); } else { ResourceLocatorInvoker locator = new ResourceLocatorInvoker(rf, injectorFactory, providerFactory, method); if (widerMatching) rootNode.addInvoker(fullpath, locator); else root.addInvoker(classExpression, fullpath, locator); statisticsController.register(locator); } }
Find all endpoints reachable by clazz and unregister them.
Params:
  • clazz – class
/** * Find all endpoints reachable by clazz and unregister them. * * @param clazz class */
public void removeRegistrations(Class clazz) { removeRegistrations(clazz, null); } public void removeRegistrations(Class clazz, String base) { Class restful = GetRestful.getRootResourceClass(clazz); removeRegistration(base, restful); } @Override public void removeRegistrations(ResourceClass resourceClass) { for (ResourceMethod method : resourceClass.getResourceMethods()) { if (widerMatching) rootNode.removeBinding(method.getFullpath(), method.getMethod()); else root.removeBinding(resourceClass.getPath(), method.getFullpath(), method.getMethod()); } for (ResourceLocator method : resourceClass.getResourceLocators()) { if (widerMatching) rootNode.removeBinding(method.getFullpath(), method.getMethod()); else root.removeBinding(resourceClass.getPath(), method.getFullpath(), method.getMethod()); } } private void removeRegistration(String base, Class<?> clazz) { for (Method method : clazz.getMethods()) { Path path = method.getAnnotation(Path.class); Set<String> httpMethods = IsHttpMethod.getHttpMethods(method); if (path == null && httpMethods == null) continue; ResteasyUriBuilder builder = new ResteasyUriBuilderImpl(); if (base != null) builder.path(base); if (clazz.isAnnotationPresent(Path.class)) builder.path(clazz); String classExpression = builder.getPath(); if (path != null) builder.path(method); String fullpath = builder.getPath(); if (fullpath == null) fullpath = ""; if (widerMatching) rootNode.removeBinding(fullpath, method); else root.removeBinding(classExpression, fullpath, method); } } public Map<String, List<ResourceInvoker>> getBounded() { if (widerMatching) return rootNode.getBounded(); else return root.getBounded(); }
Number of endpoints registered.
Returns:number of endpoints registered
/** * Number of endpoints registered. * * @return number of endpoints registered */
public int getSize() { if (widerMatching) return rootNode.getSize(); else return root.getSize(); }
Find a resource to invoke on.
Returns:resource invoker
/** * Find a resource to invoke on. * * @return resource invoker */
public ResourceInvoker getResourceInvoker(HttpRequest request) { RESTEasyTracingLogger tracingLogger = RESTEasyTracingLogger.getInstance(request); final long timestamp = tracingLogger.timestamp("MATCH_SUMMARY"); try { if (widerMatching) { return rootNode.match(request, 0); } else { return root.match(request, 0); } } catch (RuntimeException e) { throw e; } finally { tracingLogger.logDuration("MATCH_SUMMARY", timestamp); } } }