RESTEasy Implementation of JAX-RS SPEC 2.0 Section 3.7
RESTEasy Implementation of JAX-RS SPEC 2.0 Section 3.7
In this article I’d like to share with you my study on JAX-RS SPEC 2.0 Section 3.7 and relative implementations in RESTEasy.
JAX-RS SPEC 2.01 shows us how to match a URI path to actual Java method in Section 3.7:
- Identify a set of candidate root resource classes matching the request
- Obtain a set of candidate resource methods for the request
- Identify the method that will handle the request
To implement the above logic, RESTEasy has provided several classes in package org.jboss.resteasy.registry
, and here are the classes:
From the above class diagram, we can see some relationships about these classes. Firstly, there are four Node
classes, which are RootClassNode
, ClassNode
, RootNode
and SegmentNode
. Secondly, there one Expression
interface and its two implementations, which are ClassExpression
and MethodExpression
.
From the class name, we can deduce that the Expression
classes must contain information for matching the URI path. The ClassExpression
class should be used to match classes, and the MethodExpression
should be used to match methods.
For the Node
classes, we can see RootClassNode
is only connected with ClassNode
, as its root
attribute. From the name of the class, we can deduce that RootClassNode
is the top data structure in matching process. We can verify this deduction later.
Next we should check ClassNode
. We can see ClassNode
is connected with ClassExpression
bidirectionally. ClassNode
contains targets
field, which has the type ArrayList<ClassExpression>
. In another direction, ClassExpression
has a parent
field, and the type of the field is ClassNode
. So this is a One-To-Many relationship: one ClassNode
instance contains many ClassExpression
instances.
We need to examine ClassExpression
now. ClassExpression
also contains a root
field, which has a type of RootNode
. RootNode
contains a root
field too, which type is SegmentNode
.
Now we can check SegmentNode
. This class has a bidirectional relationship with MethodExpression
, which is simliar to the relationship between ClassNode
and ClassExpression
.
From the above analyze, we can deduce that RootNode
and SegmentNode
are two abstract concepts that connects the class matching and method matching processes. The relationship between these classes is like this:
RootClassNode -> ClassNode <-> ClassExpression -> RootNode -> SegmentNode <-> MethodExpression
So the whole matching process should start from RootClassNode
. We can verify our deduction by analyzing the real codes in RESTEasy. I have done this work, and I can say that the entry point of the matching process is ResourceMethodRegistry.getResourceInvoker
method call. Here is the sequence diagram of the method call:
The above diagram reflects the following codes in ResourceMethodRegistry.getResourceInvoker
method call:
public ResourceInvoker getResourceInvoker(HttpRequest request)
{
try
{
if (widerMatching) return rootNode.match(request, 0);
else return root.match(request, 0);
}
catch (RuntimeException e)
{
throw e;
}
}
We can see that rootNode : RootNode
and root : RootClassNode
are used in two different conditions, and the widerMatching
variable controls the above logic.
The widerMatching
variable is defined by ResteasyDeployment
class. In ResteasyDeployment
class it contains a variable called widerRequestMatching
:
public class ResteasyDeployment
{
protected boolean widerRequestMatching;
}
And it is set by user controlled Configuration Switches
2:
From the above screenshot of RESTEasy document, we can see the meaning of ` resteasy.wider.request.matching` switch:
Turns off the JAX-RS spec defined class-level expression filtering and instead tries to match version every method’s full path.
From the above descrption, we can see the switch is to override some SPEC defined behaviors, and the default value is false
. In this article I’ll focus on analyzing the SPEC defined behavior, so I will ignore the logic:
if (widerMatching) return rootNode.match(request, 0);
And treat this as the matching logic entry point:
else return root.match(request, 0);
Now let’s check the Node
classes. For all the Node
classes, there are match()
method inside. Let’s check these methods one by one. First is the match
method of RootClassNode
:
From above diagram, we can see the call chain like this: RootClassNode.match() -> ClassNode.match() -> RootNode.match()
. Now let’s check the match
method of ClassNode
:
From above digram, we can see the ClassNode -> RootNode
matching process is complex, and it uses the ClassExpression
in matching process. We’ll check the detail later. Now we should check RootNode.match()
:
We can see RootNode -> SegmentNode
is easy, because RootNode.match()
will just call SegmentNode.match()
. Let’s check the sequence diagram of SegmentNode.match()
:
From the above diagram, we can see SegmentNode
matching process is simliar to the ClassNode
matching process, however the MethodExpression
class is used instead of the ClassExpression
.
In conclusion, the matching processes are mainly in ClassNode
and SegmentNode
. ClassNode
deals with class matching process, and SegmentNode
processes the method matching process.
Now let’s go back to SPEC document and learn the terminology defined in Section 1.5:
From the above definitions, we need to understand the definitions of Resource class
, Root resource class
, Sub-resource locator
and Sub-resource method
.
Now let’s check Section 3.7.2, Request Matching. If you read through this section, regardless of the detail algorithms, you should catch some basic requirements: Firstly we need to have some candidate resources classes for the matching processes, secondly we need a relative regular expression to each class. This regular expression is actually the pattern of the resource classes that can be checked against the URL requests.
Besides the candidate classes, we also need to have candidate methods inside the classes, and we need to store the relative regular expressions of these methods.
To sum up the above requirements, RESTEasy has provided a Registry
interface and its implementation, the ResourceMethodRegistry
class, to support the storage of candidate resource classes.
In addition, we have seen there is an abstract class Expression
and its two extended classes, ClassExpression
and MethodExpression
classes to store the relative regular expressions of the resource classes and methods.
The Expression
and Registry
implementations provides basic data unit for multiple Node
classes to implement their match()
methods. The finally goal is to get a ResourceInvoker
, which contains the matched “class.method” and other useful information.
Now let’s check the Registry
firstly, and then let’s see ResourceInvoker
. Here is the class diagram with Registry
and ResourceMethodRegistry
included:
From the above diagram, we can see Registry
is mainly designed to store different kinds of resource classes. The name of ResourceMethodRegistry
is a little bit confusing, be cause we can see in this class are actually stored resource classes.
We can see ResourceMethodRegistry
connects with RootNode
and RootClassNode
.
We know that RootClassNode
and ClassNode
are for class matching, and RootNode
and SegmentNode
are for method processing.
And we also know the class and method matching processes are majoyly in ClassNode.match()
and SegmentNode
.
We can also see MethodExpression
is connected with SegmentNode
, and ClassExpression
has a parent
of ClassNode
, and has a root
of RootNode
.
Now let’s check the ResourceInvoker
class:
We can see there are two types of ResourceInvoker
, one is ResourceLocatorInvoker
and the other is ResourceMethodInvoker
. The ResourceMethodInvoker
is for the method finally will be invoked by the request, and the ResourceLocatorInvoker
is used for invoking Sub-resource locator
.
There is another important class we haven’t investigated till now, the UriInfo
and ResteasyUriInfo
:
We can see the UriInfo
interface is in javax.ws.rs.core
package, which means this is an interface defined by the SPEC that should be implemented by RESTEasy. The ResteasyUriInfo
is an implementation of this UriInfo
interface. Here is the javadoc in UriInfo
that describes the purpose of the interface:
An injectable interface that provides access to application and request URI information.
Besides the meaning of encapsulating URI info, it also defines many methods that deals with URI info. Here is the javadoc for UriInfo.getMatchedURIs()
method:
And the getMatchedURIs()
method in the RESTEasy implementation, ResteasyUriInfo
, should implement the behavior as described in above text.
The UriInfo
interface defines the methods to retrieve the URI info, but it doesn’t define how to build this info. It depends on the implementation side to build it properly. We can check the ResteasyUriInfo
to see how does RESTEasy build the URI info. Firstly here is the initialize
method used by the constructor of ResteasyUriInfo
:
We can see UriBuilder
and ResteasyUriBuilder
are involved in the initializing process to build the initial variables. Here are part of the codes in the method:
ResteasyUriBuilder absoluteBuilder = (ResteasyUriBuilder) UriBuilder.fromUri(absoluteUri);
absolutePath = absoluteBuilder.build();
requestURI = absoluteBuilder.replaceQuery(queryString).build();
encodedPath = PathHelper.getEncodedPathInfo(absolutePath.getRawPath(), contextPath);
baseURI = absolutePath;
path = UriBuilder.fromPath(encodedPath).build().getPath();
The UriBuilder
is a SPEC interface, and ResteasyUriBuilder
is its implementation. Here is the class diagram:
Here is the javadoc of the UriBuilder
:
Here is the javadoc of the javax.ws.rs.Path
annotation:
Here is the javadoc of the Path.value()
attribute:
Before going back to ResteasyUriInfo
, we should check another SPEC interface called PathSegment
, and its RESTEasy implementation PathSegmentImpl
:
Here is the javadoc of PathSegment
:
In Section 5.3 Client Targets of the document, it uses the term “path segment” like this:
Here is the sequence diagram of PathSegmentImpl.parseSegmentsOptimization()
method:
This method is used to split full URL path into path segments. The javadoc of the method is here:
The above method is used in ResteasyUriInfo.processPath()
method:
As we have built a knowledge base on UriInfo
and its UriBuilder
, now we can come back to review the sequence diagrams of the ClassNode.match()
method and the SegmentNode.match()
method to see how they use the UriInfo
. Here is the sequence diagram of the ClassNode.match()
method we have seen previously:
Here is the sequence diagram of the SegmentNode.match()
method:
Here is the whole picture of RESTEasy implementation related with resource matching process:
From the above class diagram, we can clearly see the design of RESTeasy core part. Firstly, the ResourceMethodRegistry
is the center of these Node
classes. The purpose of this part is to implement the matching process described in JAX-RS SPEC 2.0 Section 3.7. The matching result we finally get is a MethodExpression
, which has a invoker
instance. The Invoker
interface will do the real job to run the Java method, so we can see ResourceMethodInvoker
is the center of this part. We should remember the call sequence in ResourceMethodRegistry.getResourceInvoker
we have already seen in above:
In conclusion, we can see the whole picture is divided into two halves: The bottom half has the ResourceMethodRegistry
at center with some Node
classes to do the URL path to MethodExpression
matching work. The upper half has the ResourceMethodInvoker
at center, to do the real method running job. The name of ResourceMethodRegistry
is a little bit confusing, actually it doesn’t merely contain and deal with the method information, but also deal with resource classes. Maybe a more proper name should be ResourceClassAndMethodRegistry
.
We didn’t check the upper half in much detail in this article, because this article is focused on the bottom half of the above diagram. In future, I’ll write the article that focus on the ResourceMethodInvoker
side to check how does RESTEasy invoke the matched resource methods.
References
-
“JSR-000339 The Javatm API For Restful Web Services”. 2017. Jcp.Org. ↩
-
“RESTFul Web Services for Java - Chapter 3. Installation/Configuration - 3.4. Configuration switches”. 2017. Docs.Jboss.Org. ↩