Blog Archives

Install WebObjects/Wonder development environment on Linux

I have tried to install a running development environment for WebObjects applications on Linux (OpenSuse 15.4 and Xubuntu 18.04). There are some little problems to solve.

  1. You need an JDK 1.8 on your computer (in parallel with the current JDK11)
  2. You need an installation of ANT
  3. You need an Eclipse IDE.
  4. You need the WebObjects frameworks.
  5. You need WOLips as plugin to Eclipse.
  6. You need the WOnder source for modern applications.

Let’s start with the installations.

zypper install java-1_8_0-openjdk java-1_8_0-openjdk-devel java-1_8_0-openjdk-javadoc java-1_8_0-openjdk-src java-1_8_0-openjdk-headless java-1_8_0-openjdk-demo
zypper install ant

apt install openjdk-8-jdk openjdk-8-demo openjdk-8-doc  openjdk-8-headless openjdk-8-source
apt install ant

Now you have to download the Eclipse IDE (for Java Developers or for Enterprise Java and Web Developers).

https://www.eclipse.org/downloads/packages/

You can install it on /opt (extract the tar.gz there).

I have installed the WebObjects stuff into its own folder called “WODevelopment” within your home folder. There is also the preferred workspace folder for Eclipse.

cd ~
mkdir -p WODevelopment/workspace

Start the IDE with this workspace folder to see any problems. Maybe you have to install some more things you need, like Subclipse.

Now you install the WOLips plugin into Eclipse. Go to Help->Install New Software->Add and create a new location.

WOLips410
https://jenkins.wocommunity.org/job/WOLips410/lastSuccessfulBuild/artifact/temp/dist/

Select all options (the WOLips Goodies are not installable on Linux) and install them. After an IDE restart, you can open a new perspective “WOLips”.

The plugin needs a lot of WebObjects frameworks, which you have to install now. Actually there is an install tool, called WOInstaller, but this doesn’t work for me, it always stops with an exception message. I have tried two versions and end up with a manual installation.

curl -O https://jenkins.wocommunity.org/job/WOInstaller/lastSuccessfulBuild/artifact/Utilities/WOInstall/WOInstaller.jar
curl -O https://wocommunity.org/documents/tools/WOInstaller.jar
java -jar WOInstaller.jar 5.4.3 ~/WODevelopment/Libraries/WOnder

If you get also an exception, try the following.

  1. Go to the GITHUB project of the installer and look for the WebObjectsInstaller.java
    https://github.com/wocommunity/wonder/blob/master/Utilities/WOInstall/Sources/er/woinstaller/WebObjectsInstaller.java
  2. Copy the download link for the latest WebObjects version 5.4.3, which you will find as static value on top of the file
  3. Download the .dmg file (Mac archive)
  4. use 7-zip (7z) to extract the file

You will find a folder “WebObjects Update/Packages”, which contains four further archives (.pkg). These archive files you can also decompress with 7-zip. It generates a “Payload~” archive, which you decompress again.

7z x WebObjectsDevelopment.pkg
7z x Payload~
rm Payload~
7z x WebObjectsDocumentation.pkg
7z x Payload~
rm Payload~
7z x WebObjectsExamples.pkg
7z x Payload~
rm Payload~
7z x WebObjectsRuntime.pkg
7z x Payload~
rm Payload~

Now you have three folders (Developer, Library, System), which you copy into “~/WODevelopment/Libraries/WOnder”.

mkdir -p ~/WODevelopment/Libraries/WOnder
mv Developer ~/WODevelopment/Libraries/WOnder/.
mv Library ~/WODevelopment/Libraries/WOnder/.
mv System ~/WODevelopment/Libraries/WOnder/.

Check the owner of the files and use “chown” if necessary.

Now its time for the global wolips properties file, which contains some settings for the Eclipse plugin and the ANT build pipeline. The file must be on “~/Library/Application Support/WOLips/wolips.properties”.

mkdir -p "~/Library/Application Support/WOLips"
touch wolips.properties

Edit the newly generated file and copy the following properties into it. Change the path prefixes, as necessary, i.e. /home/me/WODevelopment. Don’t use Shortcuts like “~”. Be careful, every path must exist within the filesystem, generate them, if necessary.

wo.system.frameworks=/home/me/WODevelopment/Libraries/WOnder/System/Library/Frameworks
wo.bootstrapjar=/home/me/WODevelopment/Libraries/WOnder/System/Library/WebObjects/JavaApplications/wotaskd.woa/WOBootstrap.jar
wo.extensions=/home/me/WODevelopment/Libraries/WOnder/Library/WebObjects/Extensions
wolips.properties=wolips.properties
wo.system.root=/home/me/WODevelopment/Libraries/WOnder/System
wo.user.frameworks=/home/me/Library/Frameworks
wo.external.root=/home/me/WODevelopment/Libraries/WOnder/External
wo.local.root=/home/me/WODevelopment/Libraries/WOnder
wo.apps.root=/home/me/WODevelopment/Libraries/WOnder/Library/WebObjects/Applications
wo.api.root=/home/me/WODevelopment/Libraries/WOnder/Developer/Documentation/DocSets/com.apple.ADC_Reference_Library.WebObjectsReference.docset/Contents/Resources/Documents/documentation/MacOSXServer/Reference/WO54_Reference
wo.local.frameworks=/home/me/WODevelopment/Libraries/WOnder/Library/Frameworks
wo.network.root=/home/me/WODevelopment/Libraries/WOnder/Network
wo.network.frameworks=/home/me/WODevelopment/Libraries/WOnder/Network/Library/Frameworks
wo.user.root=/home/me
wo.server.root=/home/me/WODevelopment/Libraries/WOnder/Server

The last step is the download and the build of the WOnder source code, the current community extensions to WebObjects.

Go to the GITHUB repository of “WOnder” and download the latest release as ZIP archive.

https://github.com/wocommunity/wonder/releases/latest

Extract the downloaded archive file into the “~/WODevelopment/WonderSource”. Use the tar.gz instead of zip, there is a problem with long filenames.

tar xvzf ~/Downloads/wonder-<version>.tar.gz
mv ~/Downloads/wonder-wonder-<version> ~/WODevelopment/WonderSource

Copy the “wolips.properties” file as “build.properties” into “~/WODevelopment/WonderSource”. It is the config for the following ANT build process.

cd ~/WODevelopment/WonderSource
cp ~/Library/Application Support/WOLips/wolips.properties build.properties

Start the build with JDK1.8 (!). There can be some warnings, but it should end with “BUILD SUCCESSFUL”.

JAVA_HOME=<path to JDK1.8> ant all

Now you can start WebObjects development of your own project within Eclipse. The projects within Eclipse should use Java 1.8 within its Build Path, add this JRE as installed VM.

If you start the first project, you will get an error, that the application cannot be opened within the default browser. Linux is not a supported development platform. So you have to add a special method within your Application.java file:

@Override
public boolean _isSupportedDevelopmentPlatform() {
   return super._isSupportedDevelopmentPlatform() || "Linux".equals(System.getProperty("os.name"));
}

After that, WOLips will call /usr/bin/open to execute the dynamic application URI within the default browser. But this will not work within Linux, but you can define a symbolic link (as root) to your preferred browser:

cd /usr/bin
ln -s /usr/bin/firefox open

Now the browser should automatically display your application within Firefox.

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.

WebObject’s DirectAction for Rest

The ERRest framework uses DirectActions within WebObjects to handle REST requests. It registers a handler class (subclass of WODirectActionRequestHandler) for a specific part of the URI (“/ra”). It is called request handler key. We could set another key like “/api”, to differ client URIs and server URIs.

public class BaseRestRequestHandler extends WODirectActionRequestHandler {

   public static void register(BaseRestRequestHandler requestHandler) {
      WOApplication.application().registerRequestHandler(requestHandler, "api");
   }
}

Within the constructor of the Application class we will register our MessageRequestHandler, which is a subclass of BaseRestRequestHandler:

public class Application extends ERXApplication {

   public Application() {
      BaseRestRequestHandler requestHandler = new MessageRequestHandler();
      BaseRestRequestHandler.register(requestHandler);
      setDefaultRequestHandler(requestHandler);
   }
}

Now, all our requests to /api will be redirected to our MessageRequestHandler. And our application redirects all requests to our own handler per default (with setDefaultRequestHandler()). How does it work?

Request-Response-Loop

WebObjects handles requests during its Request-Response-Loop. There is a class WORequestHandler, which processes the request in its handleRequest(WORequest) method. It is an abstract class, which is used by a private class WOActionRequestHandler. This class implements the abstract method handleRequest(WORequest), which calls _handleRequest(WORequest) and returns a WOResponse object. The _handleRequest(WORequest) method implements the complete handling of the request, which subclasses can modify by overwriting the used methods. These are:

WO Request-Response-Loop

If we look into ERRest, the class ERXRouteRequestHandler overwrites the four blue colored methods. The first and second method will be re-implemented, so the default algorithm has been replaced. The third and fourth method extends the algorithm of the base-class WODirectActionRequestHandler. The latter only implements getRequestHandlerPathForRequest(), so we can focus us on the code of WOActionRequestHandler.

Excursion: What are ActionNames?

As you can see in the image above, the second method extracts a so called action name and the third blue method uses it to create an instance of a class. A normal direct action URI of a WebObjects application looks like:

http://127.0.0.1:10001/cgi-bin/WebObjects/DirectActionTest.woa/wa/queryLocation?filter=Leipzig

queryLocation is the ActionName. It will be enhanced by the term “Action”, so you need a method queryLocationAction() within your handler class. Per default, your handler class is named as DirectAction, which is registered for the key “/wa”. If your URI contains another key, you have to register an own class:

http://127.0.0.1:10001/cgi-bin/WebObjects/DirectActionTest.woa/testme/queryLocation?filter=Leipzig

/testme” needs another request handler (subclass of WODirectActionRequestHandler), which must be registered as direct action handler. It defines the class name which will execute the given action name (TestMeAction and queryLocationAction).

public class TestMeRequestHandler extends WODirectActionRequestHandler {

   public TestMeRequestHandler() {
      super("TestMeAction", "noOp", true);
   }

   @Override
   protected String defaultActionClassName() {
      return "TestMeAction";
   }

   @Override
   protected String defaultDefaultActionName() {
      return "noOp";
   }
}

public class TestMeAction extends ERXDirectAction {

   public DirectAction(WORequest request) {
      super(request);
   }

   public WOActionResults noOpAction() {
      // default action, if we don't define an action name in URI
      return pageWithName(Main.class.getName());
   }

   public WOActionResults queryLocationAction() {
      // TODO: query datastore and put data into page
      return pageWithName(Main.class.getName());
   }
}

public Application extends ERXApplication {
   public Application() {
      WODirectActionRequestHandler reqhandler = new TestMeRequestHandler();
      WOApplication.application().registerRequestHandler(reqHandler, "testme");
   }

As you can see, the DirectAction workflow needs always a class, which can handle the action and an action name, which is mapped to a method of this class.

Therefore the method getRequestActionClassAndNameForPath() of WOActionRequestHandler works with an array of two parameters: the class and the action name. Our own implementation must return such data.

XML-Rest interface with hyperlinks

I try to create a REST interface which sends/receives XML messages. For the fun factor I choose WebObjects 5.4.3 and Wonder 6, which comes with a framework called ERRest, that I can use for my project. I have a working client based on GXT, which play against a Glassfish server with JavaEE6. They communicate per Rest-XML, so I think it could be possible to replace the server implementation with a WebObjects server.

The interface

I have to use HTTP as the protocol layer. The messages are in XML. Every message has it’s own Content-Type header, the client sends this type to classify the XML messages to the server. It also generates Accept headers to declare allowed messages (i.e. for POST requests, which return a new entity, formatted in an XML of a specified type).

The server can check both headers on the resource/route endpoints (@Consumes und @Produces annotations of Jax-RS). Errors within this workflow will result in HTTP error codes (415 if Content-Type not equals to @Consumes, 406 if Accept not equals to @Produces).

Additional the server uses ETags and If-Match/If-None-Match header to reduce the transmitted data.

The XML data contain only the necessary data fields, to represent the response for the prior request. Every relationship and every Id are hyperlinks, which the client can call, to get more detailed data.

So you can have a company, which has different locations. If you ask for the company, you will use an URI like with a GET method (Accept=application/vnd.info.phosco.rest.Company.v1+xml):

http://127.0.0.1:10001/api/company-list/25

This will return the company with the Id=25 (Content-Type=application/vnd.info.phosco.rest.Company.v1+xml):

<company id="http://127.0.0.1:10001/api/company-list/25">
<name>TestCompany</name>
<type>LIMITED</type>
<link rel="staff-list" "http://127.0.0.1:10001/api/company-list/25/staff-list" />
<link rel="location-list" href="http://127.0.0.1:10001/api/company-list/25/location-list" />
</company>

The client has now the chance to get more data with subsequent calls. It can get a list of all company staff by calling the hyperlink for the link “staff-list”. Or, it can get a list of all locations of the company by calling the link behind the “location-list” relation. The type could be an enumeration, the name is a simple text. The id of the company (where you can get these data structure again) you can find a attribute of the root node.

You can now ask the server for the company-list. It will be an array of such company structures. Again you use a GET method on HTTP (Accept=application/vnd.info.phosco.rest.CompanyList.v1+xml

http://127.0.0.1:10001/api/company-list

This will return (Content-Type=application/vnd.info.phosco.rest.CompanyList.v1+xml):

<company-list id="http://127.0.0.1:10001/api/company-list" size="2">
 <company id="http://127.0.0.1:10001/api/company-list/2">
  <name>MyCompany</name>
  <type>LIMITED</type>
  <link rel="staff-list" "http://127.0.0.1:10001/api/company-list/2/staff-list" />
  <link rel="location-list" href="http://127.0.0.1:10001/api/company-list/2/location-list" />
 </company>
 <company id="http://127.0.0.1:10001/api/company-list/25">
  <name>TestCompany</name>
  <type>LIMITED</type>
  <link rel="staff-list" "http://127.0.0.1:10001/api/company-list/25/staff-list" />
  <link rel="location-list" href="http://127.0.0.1:10001/api/company-list/25/location-list" />
 </company>
</company-list>

Theoretically it should be possible to return only Ids of companies within the list. But you ask for a list to display some information about the entities. So it is better to return useful data about the list-entities. In the other case you have to create n subsequent calls (one for every list entity), to bring up a list-element on the client.

The next point are filtered lists and paging of lists. It could be possible to declare a filter (like a pattern for the company name) and the server should only return all matching company entities. So it is necessary to send the filter to the server. Also it could be possible, that the list are to huge for the client view, so it could be better to define a start/end for a sub-list. For example, the complete list contains 100 entities, but you can only display 10 entities on the screen. Then you have to remove them and display the next 10. On the second page you have a start=11 and an end of 20, third page displays companies from Id 21 till 30 and so on. This is called paging.

The client generates now query parameters “filter”, “offset” and “limit”. These are optional parameters. “filter” holds a String, “offset” ist the page start (zero-based) and “limit” is the maximum length of the page (list). The returned list can be smaller, but never longer. Let’s try:

http://127.0.0.1:10001/api/company-list?filter=MyCompany&offset=0&limit=10

This would get a list of maximal 10 company records, starting at position 0 and the name must contain “MyCompany”. The server decides, how the filter will work. Should the work somewhere within the name occur or at the beginning? Is the filter case-sensitive? Works it on the name or the type attribute? That ist part of the business logic.

There are default values for the optional parameters: If the filter does not exist, all companies will be returned. If the offset is missing, it will be replaced by “0”, if the “limit” not in the query, it will be Integer.MAX_VALUE (return all elements – no paging). Let’s try:

<company-list id="http://127.0.0.1:10001/api/company-list" filter="MyCompany" offset="0" limit="10" size="2">
 <company id="http://127.0.0.1:10001/api/company-list/2">
  <name>MyCompany</name>
  <type>LIMITED</type>
  <link rel="staff-list" "http://127.0.0.1:10001/api/company-list/2/staff-list" />
  <link rel="location-list" href="http://127.0.0.1:10001/api/company-list/2/location-list" />
 </company>
 <company id="http://127.0.0.1:10001/api/company-list/25">
  <name>That's MyCompany</name>
  <type>LIMITED</type>
  <link rel="staff-list" "http://127.0.0.1:10001/api/company-list/3/staff-list" />
  <link rel="location-list" href="http://127.0.0.1:10001/api/company-list/3/location-list" />
 </company>
</company-list>

The question is now, how I can program that with ERRest?

The server workflow

On the server we need classes, which can handle HTTP requests. They must differ between GET, POST, PUT, DELETE,… verbs and call the associated resources.

There must be classes which implement the logic, the resources, which are associated to a HTTP verb and an URI path. They must also define, which Content-Type they accept or return.

The HTTP requests contain XML documents, which must be parsed and translated into objects. With JavaEE6 the Jax-RS framework brings message readers, which generate the objects from XML. These messages are POJOs, which the resource methods use to modify the database backend.

For the response the resource classes will generate message objects with the data, the Jax-RS message writer will build XML documents of them, which are added to the HTTP response body. Every response will have a Content-Type.

The ETag must be calculated from the last-modified-time and the Id of the object. Then the server can compare the ETag sent by the client (If-None_Match header) with the pre-calculated ETag of the object. If the ETags differ, the server send the updated objects. If the ETags are equal, the server will send a HTTP code 304 (Not Modified). The ETag will be re-calculated on POST/PUT/PATCH verbs.

Any error within the stack will be mapped to HTTP error codes, so the client can handle these like a browser application.

The client workflow

A client will generate request to URIs, which come from the previous server responses. The first URIs the client will get from a service document, which contains all the possible root resources.

For POST/PUT/DELETE the client must generate XML fragments and a special Accept header. So we need XML writer and reader, which parse the fragments (from response) or create fragments (from POJOs). We need also a workflow for handling the ETags.

The client must recognize error responses and build some special error pages or states. The client holds the complete state of the application. The server is stateless. All entities must be cached on client side, so we have access to a lot of links, which we can use to go forward within the application.

Every request must contain a BaseAuth, which the server checks against a user management tool (like LDAP or database).

The ETag comes with the response from the server and must be stored on client side on every related object. So it is available, if the client will query data from the server and must be set on every request (If-None-Match header). It can be also used as concurrency control. If another client has changed data on the server, it will have a new ETag. So we can prevent updates on an already changed object on the server (optimistic lock on client).