2012/05/04

the nature of ducks

There is a saying that goes like this ... if it looks like a duck, walks like a duck and quacks like a duck, chances are good that it is a duckThat does of course not keep the animal from being 10 feet tall, but even then it probably is a duck.

Here is a table I want to share with you : 
REST method ROC verb Database dml/ddl
create POST NEW INSERT
read GET SOURCE SELECT
update PUT SINK UPDATE
delete DELETE DELETE DELETE

You've probably seen that table before or noticed the similarity, the point I'm trying to make in this post is that ... if it looks like a crud, walks like a crud and quacks like a crud, chances are good that it is a crud.

In the REST world, it is considered bad form to use a GET when you should be using another method. Example
GET http://yourserver/kernelproperty/get/x
GET http://yourserver/kernelproperty/delete/x
is bad and
GET http://yourserver/kernelproperty/x
DELETE http://yourserver/kernelproperty/x
is good.

Yet, from the point of view of most browsers, GET is the only thing you will ever need. And developers follow that adagio in their web applications. Possibly wrong, but hey, it works ...

In the ROC world, all you need is SOURCE, the abstraction requires no more. However, from a code-convenience point of view things look different and the other verbs can be put to good use in creating a model that gives us a handle on the abstraction.

A good place to start is where REST and ROC meet. Now, HTTP is only one entry into ROC, but no method to verb translation is done. Everything comes in as a SOURCE request, you do have access to the httpRequest:/ space though and a very useful resource in there is httpRequest:/method.

An interesting and recent development in NetKernel is the RESTOverlay. I played a bit with that, mixed it with the above thoughts and came up with this :

    <rootspace
    name="System Admin"
    public="true"
    uri="urn:org:elbeesee:ext:system:admin">
    
    <fileset>
      <regex>res:/etc/system/SimpleDynamicImportHook.xml</regex>
    </fileset>


    <overlay>
      <prototype>RESTOverlay</prototype>
      <config>
        <basepath>/elbeesee/</basepath>
      </config>
      <space>
        <endpoint>
          <meta>
            <rest>
              <simple>{accessorname}/{propertyname}/{propertyvalue}</simple>
              <method>PUT,POST</method>
            </rest>
          </meta>
          <grammar>
            <active>
              <identifier>active:restMethodToVerb_pp</identifier>
              <argument name="accessorname"/>
              <varargs/>
            </active>
          </grammar>
          <class>org.elbeesee.ext.system.RESTMethodToVerbAccessor</class>
        </endpoint>
        <endpoint>
          <meta>
            <rest>
              <simple>{accessorname}/{propertyname}</simple>
              <method>GET,DELETE</method>
            </rest>
          </meta>
          <grammar>
            <active>
              <identifier>active:restMethodToVerb_gd</identifier>
              <argument name="accessorname"/>
              <varargs/>
            </active>
          </grammar>
          <class>org.elbeesee.ext.system.RESTMethodToVerbAccessor</class>
        </endpoint>
        <import>
          <private/>
          <uri>urn:org:elbeesee:ext:system:accessors</uri>
        </import>
      </space>
    </overlay>


    <import>
      <private/>
      <uri>urn:org:netkernel:tpt:http</uri>
    </import>
    
  </rootspace> 


And the onSource method (everything that comes in over HTTP is a SOURCE) of the org.elbeesee.ext.system.RESTMethodToVerbAccessor class looks like this :

   public void onSource(INKFRequestContext aContext) throws Exception {
    INKFRequestReadOnly lThisRequest = aContext.getThisRequest();


    // One mandatory argument
    String aAccessorName = aContext.getThisRequest().getArgumentValue("accessorname");
    String aHTTPMethod   = (String) aContext.source("httpRequest:/method");
    
    aContext.logRaw(INKFLocale.LEVEL_DEBUG,"SOURCE HTTPMethod = " + aHTTPMethod);
    
    INKFRequest subrequest = aContext.createRequest("active:" + aAccessorName);
    for (int i = 0; i < lThisRequest.getArgumentCount(); i++) {
      aContext.logRaw(INKFLocale.LEVEL_DEBUG,"SOURCE argument = " + lThisRequest.getArgumentName(i));
      if (! "accessorname".equals(lThisRequest.getArgumentName(i))) {
        subrequest.addArgument(lThisRequest.getArgumentName(i), lThisRequest.getArgumentValue(i));
      }
    }
    
    if ("GET".equals(aHTTPMethod)) {
      subrequest.setVerb(INKFRequestReadOnly.VERB_SOURCE);
    }
    else if ("POST".equals(aHTTPMethod)) {
      subrequest.setVerb(INKFRequestReadOnly.VERB_NEW);
    }
    else if ("PUT".equals(aHTTPMethod)) {
      subrequest.setVerb(INKFRequestReadOnly.VERB_SINK);
    }
    else if ("DELETE".equals(aHTTPMethod)) {
      subrequest.setVerb(INKFRequestReadOnly.VERB_DELETE);
    }
    else if ("HEAD".equals(aHTTPMethod)) {
      subrequest.setVerb(INKFRequestReadOnly.VERB_EXISTS);
    }
    else {
      subrequest.setVerb(INKFRequestReadOnly.VERB_SOURCE);
    }


    // One response
    aContext.createResponseFrom(aContext.issueRequestForResponse(subrequest));
  }

That was not too hard. The RESTMethodToVerbAccessor works as a dispatcher, doing the method-to-verb translation and launching the real request with the correct verb.

There's one thing I'm not too happy about. For the RESTOverlay the inner grammar has to be unique. Hence the active:restMethodToVerb_pp and active:restMethodToVerb_gd identifiers. That is not elegant. For me the inner grammar + method would have to be unique.

Let us start the discussion. I've created a topic on the NetKernelROC forums where we can have a free-for-all (discussion that is).