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:
- check the access of the user (is user authenticated, has the user permissions to execute the operation?)
- get the parameters for the method, fill the parameters with values according their annotations
- translate the request content into an XML and initialize the associated parameters
- invoke the method
- translate the result of the method into an XML
- store the result into the WOResponse
- 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.