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

Leave a Reply