Blog Archives

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().

HTPP -> HTTP redirect on Glassfish 3.1.2.2

Execute this on your Glassfish machine:

###
### HttpToHttpsRedirectOnDifferentPort
###
asadmin create-protocol --securityenabled=false http-redirect
asadmin create-http-redirect --redirect-port 443 --secure-redirect true http-redirect
asadmin create-protocol --securityenabled=false http-redirect-base
asadmin create-protocol-finder --protocol http-redirect-base --target-protocol http-listener-2 --classname com.sun.grizzly.config.HttpProtocolFinder https-finder
asadmin create-protocol-finder --protocol http-redirect-base --target-protocol http-redirect --classname com.sun.grizzly.config.HttpProtocolFinder http-redirect
asadmin set configs.config.server-config.network-config.network-listeners.network-listener.http-listener-1.protocol=http-redirect-base