Blog Archives

Step 7: CORS

Browsers implement a mechanism called Cross-Origin Resource Sharing (CORS). If your application (client) runs on a domain A, it is only possible to send requests on a domain B with a small set of HTTP verbs.

The domains (origins) differ in domain-name, protocol, and port, so i.e. it will not be possible to send DELETE requests from https://my-domain:443 (where the GUI resides) to https://my-domain:8000 (where we will find the web-service). Some simple non-destructive requests like GETs are possible.

Because of our own Content-Type header, our framework will run out of the CORS safelist and our client will not play with our webservice also for simple GET requests.

So we have to implement the mechanism of preflight requests. The browser will send an OPTIONS request just before the real request to the webservice. The webservice must dis-/allow the requested action and send these information as response to the OPTIONS preflight request. Then the browser will send the real request.

CORS on ERRest

ERRest implements the CORS mechanism within the ERXRouteController.optionsAction(). It is a public method, so every sub-class can overwrite the default implementation.

Some global properties control the access:

ERXRest.accessControlAllowOrigin
This can be set to “*” (for all) or a list of specific origins. The content of the property will be set as value of the Access-Control-Allow-Origin header within the response.

ERXRest.accessControlAllowRequestMethods
The default value of this property is “OPTIONS,GET,HEAD,POST,PUT,DELETE,TRACE,CONNECT”. If you set the property to “” or null, the current requested method will be allowed! The content of the property will be set as value of the Access-Control-Allow-Methods header within the response.

ERXRest.accessControlAllowRequestHeaders
The property contains a comma-separated list of the allowed request headers. If you set the property to “” or null, the requested headers will be allowed! The content of the property will be set as value of the Access-Control-Allow-Headers header within the response.

ERXRest.accessControlMaxAge
The property contains a value in seconds for how long the response to the preflight request can be cached for without sending another preflight request. The default value is 1728000 (20 days). The content of the property (must be greater or equals than 0) will be set as value of the Access-Control-Max-Age header within the response

The values will be retrieved by four methods, which are protected and could be overwritten by the sub-classes of ERXRouteController.

Step 6: performActionNamed

WebObjects’ request-response-loop calls now a method on the instantiated resource class: performActionNamed(). This method gets the already found method name as parameter, WebObjects called this “actionName”. The default implementation would now look for a method called actionNameAction() and would invoke this method with no parameters. The result of the method would be an WOActionResults instance, the response object. This would be sent to the client.

The “actionName” (method name) is not useful for our framework. We could have multiple methods within the resource class with the same name, but different parameter lists or annotations. The simple name without more information cannot be used. But we have already pre-processed the annotated methods during the registration of the resource class. Therefore we should ignore the “actionName” parameter of performActionNamed() and should better use the Method object stored within the ResourceDescription.

The handler class has been stored this description class into the userInfo dictionary of the WORequest object. We can now access this object (the handler has provided the request object over the constructor of the resource class) and extract the method. So the workflow here is:

  1. check the access of the user (is user authenticated, has the user permissions to execute the operation?)
  2. get the parameters for the method, fill the parameters with values according their annotations
  3. translate the request content into an XML and initialize the associated parameters
  4. invoke the method
  5. translate the result of the method into an XML
  6. store the result into the WOResponse
  7. go back to request-response-loop

Check Access

The default implementation of the checkAccess() method within our BaseRestResource class is empty, so we allow every access. But this is not useful for all use cases. The simplest implementation could be the usage of a Basic Authentication mechanism. It is supported by HTTP through the header key “authorization”. It consists of the key word “Basic” followed by a space character and then the Base64 encoded “username:password”:

authorization: Basic YXJvdGhlOm1hcHBl

There are two possible HTTP statuses associated with the authorization. Forbidden (403) should be sent if the authorized user has no permissions on this resource. Unauthorized (401) should be sent, if there are no authorization data available or the request contains the wrong authorization protocol. Both statuses can be reported to the base class by the two exceptions: ForbiddenException and UnauthorizedException. The base class will send the associated error response to the client.

In an own implementation of the checkAccess() method, you can query the role of the user and check the permissions of that role within the application. The example implementation BasicAuthorizationResource checks the username and password for a default user. It stores also the user, so we can later build a filter for the queried business data from the database. Not every user should see all data, so it would be necessary to hide some information per user or role.

Collect Parameter Values

We implement a method getParametersForMethod(), which extracts the annotations of the given method. The values for @HeaderParam and @CookieParam can be get from the request object.

protected String getCookieValueForKey(String key, String def) {
   String val = request().cookieValueForKey(key);
   return val == null ? def : val;
}

protected String getHeaderValueForKey(String key, String def) {
   return request().headerForKey(key, def);
}

The values from the query string (the part of the URI behind the “?”) must be parsed, the name of the parameter is defined in @QueryParam:

protected String getQueryValueForKey(String key String def) {
   String query = request().queryString();
   String[] parts = query.split("&");
   for (String p : parts) {
      if (!p.startsWith(key + "=")) {
         continue;
      }
      try {
         return URLDecoder.decode(p.substring(key.length() + 1), "UTF-8");
      } catch (Throwable t) {
         return def;
      }
   }
   return def;
}

For @PathParam we have already pre-processed some things. We can use the regular expression of the ResourceDescription and we have already built a Map with the parameter names and their matching group numbers within the regular expression.

protected String getPathParameterValueForKey(String key, String def) {
   ResourceDescription dscr = getResourceDescription();
   Integer idx = dscr.getPathParameters().get(key);
   if (idx == null) {
      return def;
   }
   try {
      Pattern pattern = Pattern.compile(dscr.getRegex());
      Matcher matcher = pattern.matcher(request().requestHandlerPath());
      if (!matcher.find()) {
         return def;
      }
      return matcher.group(idx);
   } catch (Throwable t) {
      return def;
   }
}

The methods above use also a possible defined @DefaultValue, which we extract with the following code (p is the Parameter instance from the method):

DefaultValue dv = p.getAnnotation(DefaultValue.class);
String def = dv == null ? null : dv.value();

All methods return String values for the parameters. But we have to instantiate the classes given by each parameter type. Allowed are some primitive types (like int, long, double, boolean), classes with a constructor which needs a single String parameter, classes with static method valueOf(String) or List<String>. To cast the String object into the necessary parameter type, we use the method buildObjectFromString():

protected Object buildObjectFromString(String str, Class<?> type) {
   if (str == null) {
      return null;
   }
   if (type.isPrimitive()) {
      if (type == Boolean.TYPE) {
         return Boolean.valueOf(str);
      }
      if (type == Integer.TYPE) {
         return Integer.valueOf(str);
      }
      if (type == Long.TYPE) {
         return Long.valueOf(str);
      }
      if (type == Float.TYPE) {
         return Float.valueOf(str);
      }
      if (type == Double.TYPE) {
         return Double.valueOf(str);
      }
      if (type == Character.TYPE) {
         return Character.valueOf(str.charAt(0));
      }
      if (type == Byte.TYPE) {
         return Byte.valueOf(str);
      }
   }
   try {
      Constructor<?> cons = type.getConstructor(String.class);
      if (cons != null) {
         return cons.newInstance(str);
      }
   } catch (Throwable t) {
         // do nothing
   }
   try {
	Method m = type.getMethod("valueOf", String.class);
        if (m != null && Modifier.isStatic(m.getModifiers())) {
           return m.invoke(null, str);
        }
   } catch (Throwable t) {
      // do nothing
   }
   List<String> res = new ArrayList<String>();
   if (type.isAssignableFrom(res.getClass())) {
      res.add(str);
      return res;
   }
   log.debug("cannot assign String \"" + str + "\" to the given type \"" + type.getName() + "\"");
   return null;
}

The objects will be added to an array, which we can use on invoking the action method. We await a further optional parameter, which we use to transport the request body content. If there is no content, all further parameters without annotations will be null.

Translate The XML-Content

To prevent a lot of self-written code, we will use JAXB as a framework to translate a POJO into an XML representation and vice versa. So we use so called message classes to transport the data from the request into our Resource class.

The method getParametersForMethod() from AbstractRestResource try to assign data to the method parameters. Some parameters will be annotated, the data will come from the request object. The message parameters will be filled by the request content. If there is no content, the parameters becomes null. Every parameter without an annotation should have a type which the JAXBContext can unmarshal (translate from XML to class instance). If it is not possible, the parameter will be null.

It could be possible, that a parameter without annotation has a primitive type, so an assignment of null will be wrong. This condition will result in an exception, which is reported to the client as internal error of the service.

The result of the called resource method should be “void” (no response content) or a message type, which the JAXBContext can marshal into an XML string. So we can fill the POJO within the method and return it to the caller (Rest handler). If the return type cannot by used by JAXB, it results in an exception, which will be reported as internal error to the client.

At the moment, the implementation doesn’t check the annotated @Produces/@Consumes against the type of the parameters. Because the message classes define the static MEDIA_TYPE, it could be possible to compare the annotation value against the static MEDIA_TYPE string. We would always need such a MEDIA_TYPE string within the message class, which I would avoid. Also the “Content-Type” response header will be set to the @Produces value and not to the possible available static MEDIA_TYPE member field of the method’s return value.

Create the response

As response, the Rest handler class will generate always an WOResponse instance with an HTTP code of 200 (OK). The “Content-Type” header will be set to @Produces value and the content of the request is filled with the return value of the method (XML string built from a POJO).

In the case of an exception, the response has an error code of 500 (internal error) and the response content should contain a description of the error. We can log the stack trace, but the client should always get a human readable description (i.e. from a ResourceBundle), which the client can show on a GUI. We will discuss this within the Blog post about the exception handling.

Back to request-response-loop

The created response instance is the return value of the method performActionNamed() within the RestResource class (which has been called by the request handler class). We should always prevent any exceptions on this level, because the RRL will handle exceptions with its own error responses. These responses cannot be handled by our Rest-based client.

The returned WOResponse object will be sent to the client and the loop will end for this request. Because our service doesn’t use a session (stateless service), a lot of other code within WOActionRequestHandler._handleRequest() will be ignored.

Step 5: Context

The next step builds the WOContext with data from the WORequest. The WOAction class (which is the abstract base class of WODirectAction which again is the base class for our resource classes) delegates the task to the WOApplication. Because the REST service is stateless and doesn’t generate webpages, we don’t change this process. The Request-Response-Loop can call the default implementation.

Step 4: getActionInstance

This method will be called by the Request-Response-Loop to instantiate the resource class. The default implementation invokes the constructor of the given action class with the WORequest instance.

ERRest uses the method to initialize the newly created class. In our framework, there is nothing to do at the moment. We can use the default implementation. Maybe we can use other constructors here to set URIBuilder, which we need later for the Ids within the XMLMessages.

@SuppressWarnings("rawtypes")
@Override
public WOAction getActionInstance(Class anActionClass, Class[] parameters, Object[] arguments) {
   BaseRestResource actionResource = (BaseRestResource) super.getActionInstance(anActionClass, parameters, arguments);

		
   // TODO: init more things here


   return actionResource;
}

The parameters of the method have been built by the getRequestActionClassAndNameForPath() – see last post.

Step 3: getRequestActionClassAndNameForPath

This method is very simple, it gets the NSArray<String> with class name and method name of the resource class. And it translates the data into an array of three Objects. The third object will be the Class for the given class name.

@Override
public Object[] getRequestActionClassAndNameForPath(@SuppressWarnings("rawtypes") NSArray requestHandlerPath) {
   String requestActionClassName = (String) requestHandlerPath.objectAtIndex(0);
   String requestActionName = (String) requestHandlerPath.objectAtIndex(1);
   return new Object[] { requestActionClassName, requestActionName, _NSUtilities.classWithName(requestActionClassName) };
}

The method name will be called “actionName”, because the normal behaviour of WODirectActionHandler adds the term “Action” at the end of the method name and looks for such a method within the class. But we don’t longer support that.

The method implementation is exact the same as in ERRest.

Step 2: getRequestHandlerPathForRequest

In the last post we have registered Resource classes within the RequestHandler. The handler can now choose one of the classes to execute one of class methods for the current request. Now we have to implement the methods, which the Request-Response-Loop will call.

The method getRequestHandlerPathForRequest() will be called with a WORequest instance, so we have access to all the attributes of our incoming request.

It is a good place to check, whether or not we have a registered resource class, which defines an endpoint, which will match the requested URI. It returns a NSArray<String> to the Request-Response-Loop, the first entry is the class name for the action, the second String is the method name within the class. We cannot change this behaviour except we overwrite the whole RR-Loop.

Every resource class has been registered within the handler as instance of ResourceDescription. We have to transfer this object into other methods of the loop. ERRest uses the userInfo dictionary (NSDictionary) of the WORequest instance, to transfer REST specific data together with the request. It is a Map, which can store an Object in relation to a key name (String). But if you call request.userInfo(), you will get an immutable Map. You cannot add your own data. However the WORequest class allows it to replace the whole Map with request.setUserInfo(Map). This we will use:

NSMutableDictionary<String, Object> userInfo = new NSMutableDictionary<String, Object>(request.userInfo());

// TODO: find ResourceDescription

userInfo.put(RequestUserInfoKey.RESOURCE_DSCR.name(), dscr);
request.setUserInfo(userInfo);

The client must send a header “Accept” to define the requested media type of the response. The server will only generate application/xml and some vendor specific subtypes:

Accept: */*
Accept: application/xml
Accept: application/vnd.info.phosco.rest.Staff.v1+xml
Accept: application/vnd.info.phosco.rest.LocationList.v1+xml

So we can check against the default */* or application/xml, and if it will not match, we return here an error 406 (Not Acceptable).

public class BaseRestRequestHandler extends WODirectActionRequestHandler {

   protected boolean isAcceptableMediaType(String mediaType) {
      return "*/*".equals(mediaType) || Pattern.matches("^application\\/(?:.*\\+)?xml$", mediaType);
   }

   protected List<MediaType> getAcceptableMediaTypes(String mediaTypes) {
      List<MediaType> res = new ArrayList<MediaType>();

      String[] types = mediaTypes.split(",");
      for (String t : types) {

         String[] extensions = t.split(";");
         String name = extensions[0].trim();
         if (!isAcceptableMediaType(name)) {
            continue;
         }

         MediaType mType = new MediaType(name);
         for (int idx = 1; idx < extensions.length; idx++) {
            if (!extensions[idx].trim().startsWith("q=")) {
               continue;
            }
            String[] values = extensions[idx].split("=", 2);
            mType.setQuality(Float.valueOf(values[1]));
            break;
         }
         res.add(mType);
      }
      Collections.sort(res);
      return res;
   }

   @Override
   public NSArray getRequestHandlerPathForRequest(WORequest request) {
      NSMutableArray<Object> requestHandlerPath = new NSMutableArray<Object>();
      
      List<MediaType> mediaTypes = getAcceptableMediaTypes(mediaType);
      if (mediaTypes.isEmpty()) {
         // TODO: generate error 406 response with special resource
      }

      
      return requestHandlerPath;
   }
}

The MediaType class is a simple POJO, which holds a name and a quality number. We sort the media types by quality, so we try the type with the highest quality first. “*/*” will have a quality of 0.0 initially (try it at least).

public class MediaType implements Comparable<MediaType> {

   private String name;
   private Float quality;

   public MediaType(String name) {
      this.name = name;
      this.quality = ("*/*".equals(name)) ? 0.0f : 1.0f;
   }

   public Float getQuality() {
      return quality;
   }

   public void setQuality(Float quality) {
      this.quality = quality;
   }

   public String getName() {
      return name;
   }

   @Override
   public int compareTo(MediaType other) {
      return -1 * this.quality.compareTo(other.quality);
   }

   @Override
   public String toString() {
      return "[" + name + ";q=" + quality + "]";
   }
}

After this initial check, we have to look for matching resources. As described in the last post, we look for the HTTP verb (GET, POST…) and the matching @Path annotation. If we cannot find such a resource, we will return NotFound (404) to the client.

Then we check the @Produces against the “Accept” header value. If we don’t find a matching ResourceDescription, we will return NotAcceptable (406).

The last check compares @Consumes with the “Content-Type” header value. If we don’t find a matching ResourceDescription, we will return UnsupportedMediaType (415).

At the end, we add the first matching ResourceDescription to the userInfo Map of the WORequest object. From there we extract the classname and method name for the return-value of getRequestHandlerPathForRequest().

Step 1: Register Resource Classes

In the last post we have registered a new RequestHandler class for the request key “/api”. But the handler doesn’t know, which actions should be executed for an incoming request.

We need some resource classes (ERRest called it Controller), which define endpoints (URIs). If the request contains an URI path like such an endpoint, the handler must instantiate the matching resource class and execute the matching method.

The method of the class must be declared for the used HTTP verb (like GET, POST, PUT…), so the handler would choose it to execute. We use also the “Accept” header of the request, to find a matching class method. The method should declare, that it can generate such media type. If not, we will use another method or return “Not Acceptable” as HTTP error code (406).

Some requests will send data within the request body (content). This content has a “Content-Type” header, which should also match the media type the method can handle. There must be a matching method parameter, which awaits this data structure from the request body. If not, we will use another method or return the HTTP status “Unsupported Media Type” (415)

HTTP verb

The HTTP verb of the request we can extract from the given WORequest object. This will be the first step:

protected String getHttpVerbFromMethod(Method method) {
   for (Annotation a : method.getAnnotations()) {
      if ((a instanceof GET) || (a instanceof POST) || (a instanceof PUT) || (a instanceof DELETE)) {
         return a.annotationType().getSimpleName();
      }
   }
   return null;
}

// clazz is the Class object of the resource
for (Method method : clazz.getMethods()) {
   String verb = getHttpVerbFromMethod(method);
   // do something
}

The verbs can be enhanced, if necessary (like OPTIONS or HEAD). For the moment we only use the defined four verbs.

Media Types

To declare these media types we will use the annotations from JSR311. There is a @Produces annotation, which defines the media types the method can generate and send back to the client (should match the requested “Accept” header). There is a special media type “*/*” which match all media types. If the method defines explicitly a @Produces annotation, it can implicitly also answer all requests for Accept=*/*.

The method can also declare a @Consumes annotation, which defines the media type the method can handle (must match the request’s “Content-Type”). The “Content-Type” can also be null (no header defined). If the @Consumes has been declared, but no explicit media type has been set, it assumes implicitly “*/*”. If there is a “Content-Type=*/*” within the request, all methods should match, if there is no “Content-Type”, we could also execute every method. The matching method parameter would contain in this case the value “null”. In all other cases “Content-Type” must exact match @Consumes, to execute the method.

@POST
@Produces("application/vnd.info.phosco.rest.Location.v1+xml")
@Consumes("application/vnd.info.phosco.rest.Location.v1+xml")
public LocationMessage newLocation(LocationMessage xml) {
   // store the new Location into database
}

In the example above we await an “application/vnd.info.phosco.rest.Location.v1+xml” (@Consumes) within the request body (as XML). These data contain an incomplete Location, which we use to create a complete new Location within our database backend. If we have create the entity, we will return this newly created entity within the HTTP response (also as XML). It will also have the content type “application/vnd.info.phosco.rest.Location.v1+xml” (@Produces). The class method newLocation() will only be called on POST requests (@POST).

To pre-process the @Produces/@Consumes information, we extract the data during the registration step of the resource class:

protected Set<String> getConsumeMediaTypeFromClassOrMethod(Class<? extends BaseRestResource> clazz, Method method) {
   Consumes consumes = method.getAnnotation(Consumes.class);
   if (consumes == null) {
      consumes = clazz.getAnnotation(Consumes.class);
   }
   Set<String> mediaTypes = new HashSet<String>();
   if (consumes != null) {
      mediaTypes.addAll(Arrays.asList(consumes.value())); // remove double entries
      return new ArrayList<String>(mediaTypes);
   }
   mediaTypes.add("*/*");
   return new ArrayList<String>(mediaTypes);
}

protected Set<String> getProduceMediaTypeFromClassOrMethod(Class<? extends BaseRestResource> clazz, Method method) {
   Produces produces = method.getAnnotation(Produces.class);
   if (produces == null) {
      produces = clazz.getAnnotation(Produces.class);
   }
   Set<String> mediaTypes = new HashSet<String>();
   if (produces != null) {
      mediaTypes.addAll(Arrays.asList(produces.value()));
   }
   mediaTypes.add("*/*");
   return new ArrayList<String>(mediaTypes);
}

// clazz is the Class object of the resource
for (Method method : clazz.getMethods()) {
   Set<String> consumes = getConsumeMediaTypeFromClassOrMethod(clazz, method);
   Set<String> produces = getProduceMediaTypeFromClassOrMethod(clazz, method);
   // do something
}

We will use the declared media types of the method, or if there isn’t one defined, will use the class media types. Both annotations allow multiple media types, so we must prevent double entries.

Resource Path

To differ the resource classes we define the path for the URI with the @Path annotation on every class. This annotation is also defined within JSR311. We can define these annotation on class level or method level.

@Path("/location-list/{location-id}
public class LocationResource extends BaseRestResource {

}	

The example defines a path “/location-list/{location-id}” for the whole class. So the request handler will use this resource class for an URI like:

http://127.0.0.1:10002/cgi-bin/WebObjects/api/location-list/24565

The URI will match the @Path after “/api”. Parts of the @Path which are enclosed into “{}” are dynamic parts. They will be set by the client according to the necessary data. We will use them as Id, in the example “24565” will be the Id of the request Location entity. The resource class must query the database for this specific Location and must return the data within the response body. You can directly use the Ids from the database, but it is also possible to hide these information, i.e. by using UUIDs.

Every method can have parameters, which the handler should fill with the correct request-specific values. JSR311 defines a lot of annotations, which define sources for the parameters:

@HeaderParam
It defines a header key name, so the handler should read the HTTP header value and store it into the annotated parameter variable.

@CookieParam
It defines a cookie key name, so the handler should read the cookie value and store it into the annotated parameter variable.

@QueryParam
The annotation declares a name of a query-string parameter (the part after the “?” within the requested URI). The associated value should be stored into the annotated variable. The query string is URLencoded, so we have to decode the value within the handler class.

@PathParam
Here we can define the name of a “dynamic part” of the declared URI (within @Path annotation). In the example above we could use “location-id” as parameter. The name of the @PathParam must match the name of a “{}” part within @Path (case-sensitive!). For the example the annotated variable must i.e. contain “24565”.

@DefaultValue
Every parameter can be annotated with a second annotation, which defines a default value. If the parameter cannot be filled by the request handler (i.e. the header key defined by @HeaderParam doesn’t exist within the current request object), the variable should then contain the declared default value.

The variables can have different types. JSR311 allows primitive types like int, long, boolean, double and so on. Allowed are also classes, which have a constructor with a single String parameter. Classes with a static method valueOf(String) are also possible and List<String> classes. According to the declared method parameter type, the handler will instantiate the correct class.

During the register workflow we cannot store values for the most of the parameters, because they are only known at the request time. But we can pre-process the path parameters. If we find @PathParam annotation at request time, we must look into the @Path annotation to find the right place of the parameter and must extract the matching characters from the request URI. This we could do with Regular Expressions. We would generate a pattern for the @Path declaration of every class/method and the “dynamic parts” would be replaced by RegEx groups. So we can say, that the “location-id” would be the group number 1 and if we use the pattern for the requested URI, we will get the matching substring for the group:

protected PathRegEx buildRegularExpressionFromPath(String path) {
   Map<String, Integer> params = new HashMap<>();
   int groupNumber = 0;
   String str = trim(path, '/');
   String res = "";

   for (String part : str.split("/")) {
      if (part.startsWith("{") && part.endsWith("}")) {
         params.put(trim(trim(part, '{'), '}'), ++groupNumber);
         part = "(\\d+)"; // allow only numbers as Id, enhance this?
      }
      res += ("\\/" + part);
   }
   res = "^" + res.substring(2) + "$";
   return new PathRegEx(res, params);
}

protected String getPathFromClassOrMethod(Class<? extends BaseRestResource> clazz, Method method) {
   Path path = method.getAnnotation(Path.class);
   if (path == null) {
      path = clazz.getAnnotation(Path.class);
   }
   return path == null ? null : trim(path.value(), '/');
}

// clazz is the Class object of the resource
for (Method method : clazz.getMethods()) {
   String path = getPathFromClassOrMethod(clazz, method);
   PathRegEx regex = buildRegularExpressionFromPath(path);
   // do something
}

First we extract the @Path annotation for every class method. If there is no one, we will use the annotation on class level. If the class has also no annotation, we cannot execute an action for this class. The path can contain trailing and leading slashes, so we have to remove them (the request path won’t have them too).

From the path we generate a RegEx pattern. Every “dynamic part” will be replaced with “(\d+)”, so we await only numbers for a path parameter. JSR311 defines more characters ([0-9a-zA-Z], underscore and minus). If we need that, we can enhance it.

“(\d+)” is a group of the regular expression. So if we use a Matcher, we can access the parameters by the group index. Group 0 is always the full match (complete pattern), group 1 is the first parameter, group 2 the second parameter and so on. Depending on the depth of the relations between the objects, there can be more than one group:

/person-list/{person-id}/function-list/{function-id}/location-list/{location-id}

will be:

^person-list/(\d+)/function-list/(\d+)/location-list/(\d+)$

Group 1 matches {person-id}, group 2 matches {function-id} and group 3 matches {location-id}. The example would return the function of a person on a specific location: a physician (person) can be the chief doctor (function) in a hospital (a location) but only an expert witness (function) on a laboratory (other location).

The method buildRegularExpressionFromPath() (see Java code above) stores every parameter name together with its group index within a Map. So we can later find the index to a given the name (the name comes from @PathParam annotation).

Return value

WODirectActions should always return WOActionResults, which is an interface to WOResponse. But we will return the matching message class, which the framework JAXB will transform into an XML representation (response content). This representation will added to the WOResponse together with an HTTP status of 200 (OK). This code we could move into a super class, to prevent a lot of boilerplate code within the resource class.

Preprocessing

How we can handle all these metadata of a class? I think we should extract them during the registering of the resource classes on the request handler. We must announce the resources to the handler within the Application class constructor (btw. we could also automatically scan the WebObjects classpath for resource classes), so we can store the resource classes into a list within the handler. But it is not enough. Take a look on the questions we have to answer during the Request/Response-Loop:

  1. Does the request URI match the @Path?
  2. Does the Accept header match the @Produces?
  3. Does the Content-Type header match the @Consumes?
  4. Does the HTTP verb match the annotation?
  5. Does the method use a return value, which JAXB can translate into the media type?
  6. Does the method have a parameter, which can be filled with the JAXB-translated content of the request?

To speed up the execution of a request on the server, we should pre-process as much as we can during the registration time on application initialization.

The examples above show you, that we should store the metadata for every method. An action method is the smallest entity, the request handler will call. We need the following data:

Class name
Method of the class
Path
Regular expression and group indexes
consuming media type
producing media type

Because every method can define more than one media types, we should store only one media type per registration and have to duplicate the registration for other declared media types.

To find the registered resource very fast we use Map<String, Set<ResourceDescription>>, where String contains a key to find a quick answer to the 6 questions above. So we store a set of all methods, which define the @GET annotation for a key “GET”. This allows us to answer the 4th question very quick:

Set<ResourceDescription> get = registerdVerb.get("GET");

We can get for every question a Set<ResourceDescription> and to find the matching ResourceDescription we build the intersection of the sets. To get a list of all GET methods which also match the requested URI we can do:

Set<ResourceDescription> exp = new HashSet<ResourceDescription>();
for (String regex : this.registeredRegEx.keySet()) {
   if (!Pattern.matches(regex, uri)) {
      continue;
   }
   exp.addAll(this.registeredRegEx.get(regex));
}
Set<ResourceDescription> get = registeredVerb.get("GET");
exp.retainAll(get);

The example matches the URI from the request against the registered RegEx pattern, which we have pre-processed from the @Path annotation. If the pattern will match, we store the ResourceDescription within a Set and then we build the intersection with the set of registered resources for the GET verb. The result is a set of ResourceDescriptions which will match both properties, the HTTP verb and the URI.

The next steps build further intersections with the media types of @Produces and @Consumes. At the end we get a small set of ResourceDescription. Actually it should contain only one. We choose the first ResourceDescription and have enough information within to instantiate the resource class and call the action method.

To prevent double registrations of resource classes we implement equals() and hashCode() on ResourceDescription. The implementation of the Set class will do the rest for us.

The advantage of this workflow is the speed. We can find the resource very quick. The disadvantage is the amount of memory we need. We have to handle multiple Map<String, Set<ResourceDescription>>. But the description classes are always the same (immutable), only the memory for the Set/Map classes is necessary.