The /object link indicated above corresponds
to the ObjectResource
interface. This defines the
set of interactions described in Section 1.2, “Introducing Restful Objects”:
import java.io.InputStream; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; public interface ObjectResource { ... }
The implementation of this interface in Restful
Objects viewer (ObjectResourceImpl
)
also defines a @Path("/object")
for the class as a whole.
All URL paths are therefore prefixed
/object/{oid}.
I believe that the @Path
annotation should reside
on the interface, not the implementation. This seems to be a
limitation with RestEasy 1.0.2, the underlying library used by
Restful Objects.
Let's break it down into sections, starting with the object.
The /objects/OID resource corresponds to
the ObjectResource
interface:
... public interface ObjectResource { @GET @Path("/{oid}") @Produces( { "application/xhtml+xml", "text/html" }) public String object( @PathParam("oid") final String oidStr); ... }
This defines /object/{oid} as a URL supporting GET. Here's what calling this method gives for a repository object:
As ever, we get the initial "logged in" and "resources" sections. The remaining sections are details about this object, providing its title, the properties, the list of collections and then the actions. For repositories, the only items of significance here are the actions.
Let's also look at a typical domain object:
The structure is the same, but here there are properties and collections, as well as actions. Let's look at the XHTML that represents this domain object, starting with the title:
The raw XHTML looks like:
<div> <table border="1"> <tr> <td>Object title</td> <td class="nof-title"> <p>New - 2009-11-28</p> </td> </tr> <tr> <td>OID</td> <td class="nof-oid"> <a href="/object/OID:C" rel="object" rev="object" class="nof-oid"> OID:C </a> </td> </tr> <tr> <td>Specification</td> <td class="nof-specification"> <a href="/specs/org.nakedobjects.examples.claims.dom.claim.Claim" rel="spec" rev="object" class="nof-specification"> org.nakedobjects.examples.claims.dom.claim.Claim </a> </td> </tr> </table> </div>
Some useful XPath expressions:
//td[@class='nof-title']/p/text() will return the title
//td[@class='nof-oid']/a/text() is the OID (as a string)
//td[@class='nof-specification']/a/@href is a link the (resource repreenting the) specification of this object, in the form /specs/{fullyQualifiedClassName}
Next up is the properties section:
The raw XHTML (abbreviated; just the first property is listed) is:
<div class="nof-properties"> <p class="nof-properties">Properties</p> <table border="1"> <tr> <th>Name</th> <th>Type</th> <th>Hidden</th> <th>Access</th> <th>Disabled</th> <th>Disabled Reason</th> <th>Parseable</th> <th>Modify</th> <th>Clear</th> <th>Invalid Reason</th> </tr> <tr> <td> <a href="/specs/org.nakedobjects.examples.claims.dom.claim.Claim/property/description" rel="propertySpec" rev="property" class="nof-property">Description</a> </td> <td> <a href="/specs/java.lang.String" rel="propertyTypeSpec" rev="property" class="nof-property">java.lang.String</a> </td> <td> <p class="nof-visible">N</p> </td> <td> <p class="nof-property">Meeting at clients</p> </td> <td> <p class="nof-usable">N</p> </td> <td> <p /> </td> <td> <p class="nof-visible">N</p> </td> <td> <div class="nof-property"> <form name="property-description"> <input type="value" name="proposedValue" /> <input type="button" value="Set" onclick="modifyProperty("/object/OID:C","description",proposedValue.value);" /> </form> </div> </td> <td> <div class="nof-property"> <form name="property-description"> <input type="button" value="Clear" onclick="clearProperty("/object/OID:C","description");" /> </form> </div> </td> <td> <p class="nof-valid" id="property-invalid-description" /> </td> </tr> ... </table> </div>
Some useful XPath expressions are:
//div[@class='nof-properties']//tr/td[1]/a/text() returns the property names
//div[@class='nof-properties']//tr/td[2]/a/@href returns links to (resources representing the) property types, in the form /specs/{fullyQualifiedClassName}
//div[@class='nof-properties']//tr/td[3]/p/text() returns whether properties are invisible (N=not invisible)
//div[@class='nof-properties']//tr/td[4]
returns the <td>
table cells containing
the values; the values will either be <p>
(values) or <a href=...>
(references)
and so on.
The "modify" and "clear" columns bear further description. These are used to perform PUTs and DELETEs on the resources that represent the property. Because XHTML4 does not support PUT and DELETE verbs in forms, they actually call Javascript fragments to do these calls. See Section 3.3.2, “Properties” for further details of the format of these requests.
The values that are entered into modify for value properties is the parseable text form (eg TRUE for a boolean); for reference properties it is the OID.
If a property is disabled, then the "disabled reason" column will indicate why. If a property is enabled but the proposed property value is invalid, then the "invalid reason" will indicate why. Note that it takes a round-trip to do this validation, because the domain objects are not in any sense serialized into the browser.
Let's now look at the collections section:
The raw XHTML is:
<div class="nof-collections"> <p class="nof-collections">Collections</p> <table border="1"> <tr> <th>Name</th> <th>Type</th> <th>Hidden</th> <th>Disabled</th> <th>Access</th> <th>AddTo</th> <th>RemoveFrom</th> <th>Invalid Reason</th> </tr> <tr> <td> <a href="/specs/org.nakedobjects.examples.claims.dom.claim.Claim/collection/items" rel="propertySpec" rev="collection" class="nof-collection"> Items </a> </td> <td> <a href="/specs/org.nakedobjects.examples.claims.dom.claim.ClaimItem" rel="propertyTypeSpec" rev="property" class="nof-property"> org.nakedobjects.examples.claims.dom.claim.ClaimItem </a> </td> <td> <p class="nof-visible">N</p> </td> <td> <p class="nof-usable">N</p> </td> <td> <a href="/object/OID:C/collection/items" rel="collection" rev="nakedObject" class="nof-collection">items</a> </td> <td> <div class="nof-collection"> <form name="collection-items"> <input type="value" name="proposedValue" /> <input type="button" value="Add" onclick="addToCollection("/object/OID:C", "items", proposedValue.value);" /> </form> </div> </td> <td> <div class="nof-collection"> <form name="collection-items"> <input type="value" name="proposedValue" /> <input type="button" value="Remove" onclick="removeFromCollection("/object/OID:C", "items", proposedValue.value);" /> </form> </div> </td> <td> <p class="nof-valid" id="collection-invalid-items" /> </td> </tr> </table> </div>
In this particular case there is only a single collection, but in general there could be many.
Some useful XPath expressions are:
//div[@class='nof-collections']//tr/td[1]/a/text() returns the collection names
//div[@class='nof-properties']//tr/td[2]/a/@href returns links to (resources representing the) type of the collection, in the form /specs/{fullyQualifiedClassName}
//div[@class='nof-properties']//tr/td[3]/p/text() returns whether collections are invisible (N=not invisible)
//div[@class='nof-collections']//tr/td[5]/a/@href returns a link to a resource representing the contents of the collection, in the form /object/{oid}/collection/{collectionId}
and so on. Note that the table doesn't show the contents of each collection, instead it gives us a link to access the contents
Similar to properties, the "addTo" and "removeFrom" columns are used to submit PUT or DELETEs against the collection resource. See Section 3.3.3, “Collections” for more details. The arguments provided are the OIDs of objects in the collection.
Finally, let's look at actions:
The raw XHTML (abbreviated; just the first action is listed) is:
<div class="nof-actions"> <p class="nof-actions">Actions</p> <table border="1"> <tr> <th>Name</th> <th>Type</th> <th>Type</th> <th># Params</th> <th>Hidden</th> <th>Disabled</th> <th>Disabled Reason</th> <th>Real Target</th> <th>Invoke</th> </tr> <tr> <td> <a href="/specs/org.nakedobjects.examples.claims.dom.claim.Claim/action/addItem(int,double,java.lang.String)" rel="actionSpec" rev="action" class="nof-action">Add Item</a> </td> <td> <p class="nof-action">USER</p> </td> <td> <a href="/specs/void" rel="actionReturnTypeSpec" rev="action" class="nof-action">void</a> </td> <td> <p class="nof-action">3</p> </td> <td> <p class="nof-visible">N</p> </td> <td> <p class="nof-usable">N</p> </td> <td> <p /> </td> <td> <a href="/object/OID:C" rel="actionRealTarget" rev="action" class="nof-action">OID:C</a> </td> <td> <div class="nof-action"> <form name="action-addItem(int,double,java.lang.String)" method="POST" action="/object/OID:C/action/addItem(int,double,java.lang.String)"> <p>Days since</p> <input type="value" name="arg0" /> <p>Amount</p> <input type="value" name="arg1" /> <p>Description</p> <input type="value" name="arg2" /> <input type="submit" value="Invoke" /> </form> </div> </td> </tr> ... </table> </div>
Some useful XPath expressions are:
//div[@class='nof-actions']//tr/td[1]/a/text() returns the names of each action
//div[@class='nof-actions']//tr/td[3]/a/@href returns links to (resources representing the) type of the returned result of each action, in the form /specs/{fullyQualifiedClassName}
//div[@class='nof-actions']//tr/td[5]/p/text() returns whether each action is invisible or not (N=not invisible)
//div[@class='nof-actions']//tr/td[8]/a/text() returns the OID of the real target; more on this in a moment
//div[@class='nof-actions']//tr/td[9]//form returns a form to invoke the action using POST
and so on. Note that the table doesn't show the contents of each collection, instead it gives us a link to access the contents
There is no javascript calls this time; invoking actions is just a POST. The arguments provided are the same as for properties: text for parseable values, or the OIDs of objects for references.
The "realTarget" column is provided to support contributed
actions. For example, here are the actions listed for a domain object
(Employee
) where the
claimsFor()
action is contributed (by the
ClaimsRepository
):
In the DnD viewer this action would appear to reside on the
domain object. In reality though the action resides on the repository,
taking a single argument - the domain object
(Employee
).
And that concludes our run-through of the GET verb for object resources. Phew!
PUT and DELETE verbs for object resources have not yet been implemented.
The next set of resources exposed by
ObjectResource
are those specific to
properties:
... public interface ObjectResource { ... @PUT @Path("/{oid}/property/{propertyId}") @Produces( { "application/xhtml+xml", "text/html" }) public String modifyProperty( @PathParam("oid") final String oidStr, @PathParam("propertyId") final String propertyId, @QueryParam("proposedValue") final String proposedValue); @DELETE @Path("/{oid}/property/{propertyId}") @Produces( { "application/xhtml+xml", "text/html" }) public String clearProperty( @PathParam("oid") final String oidStr, @PathParam("propertyId") final String propertyId); ... }
This defines /object/{oid}/property/{propertyId} as the URL supporting:
PUT, to change the value of a property; the proposedValue should be a query parameter to this URL;
DELETE, to clear the property
Calling these will first validate the change, and if accepted apply the change:
If the validation fails, then an exception will be thrown. This translates to a response with error code in the range [400, 500), and with a response header of "nof-reason".
If the validation succeeds, the returned XHTML is the object's title only.
The next set of resources exposed by
ObjectResource
are those for
collections:
... public interface ObjectResource { ... @GET @Path("/{oid}/collection/{collectionId}") @Produces( { "application/xhtml+xml", "text/html" }) public String accessCollection( @PathParam("oid") final String oidStr, @PathParam("collectionId") final String collectionId); @PUT @Path("/{oid}/collection/{collectionId}") @Produces( { "application/xhtml+xml", "text/html" }) public String addToCollection( @PathParam("oid") final String oidStr, @PathParam("collectionId") final String collectionId, @QueryParam("proposedValue") final String proposedValueOidStr); @DELETE @Path("/{oid}/collection/{collectionId}") @Produces( { "application/xhtml+xml", "text/html" }) public String removeFromCollection( @PathParam("oid") final String oidStr, @PathParam("collectionId") final String collectionId, @QueryParam("proposedValue") final String proposedValueOidStr); ... }
This defines /object/{oid}/property/{collectionId} as a URL supporting:
GET, to read the contents of this collection.
PUT, to add an item to the collection; the proposedValue should be a query parameter to this URL and contain an OID;
DELETE, to remove an item to the collection; the proposedValue should be a query parameter to this URL and contain an OID;
The handling of PUT and the DELETE is similar to that of properties. The proposed value is validated first, and if invalid then a exception is thrown resulting in a response in range [400, 500) with a response header of "nof-reason". Otherwise the object's title and OID are returned.
We could perhaps change this to return the new contents of the collection, ie as if a GET had been performed?
A GET meanwhile returns the following:
The raw XHTML for the contents part of this is:
<div class="nof-collection"> <p class="nof-collection">items</p> <ul class="nof-collection"> <li> <a href="/object/OID:D" rel="object" rev="results" class="nof-action-result"> Car parking </a> </li> <li> <a href="/object/OID:E" rel="object" rev="results" class="nof-action-result"> Reading - London (return) </a> </li> </ul> </div>
Useful XPath here is:
//div[@class='nof-collection']//li/a/@href to return the links to object resources in the collection
The final set of resources exposed by
ObjectResource
is for actions:
... public interface ObjectResource { ... @POST @Path("/{oid}/action/{actionId}") @Produces( { "application/xhtml+xml", "text/html" }) public String invokeAction( @PathParam("oid") final String oidStr, @PathParam("actionId") final String actionId, final InputStream body); ... }
This defines
/object/{oid}/property/{actionId} as a
URL supporting a POST. What's
noteworthy here is the last argument, an
java.io.InputStream
. This provides a handle to
the POST's input stream which contains the parameter/argument
pairs.
The parameters should be named arg0, arg1, arg2 and so on, with the parameter value representing the argument. What this value is will depend on whether the parameter's type is a reference (entity) type or a parseable value type:
for reference types, the value should be the (string representation of the) OID of the entity
for value types, the value should be in parseable string
format. In practical terms, use the same string format as would
work in the the DnD viewer. (To be precise, it's the format
understood by the ParseableFacet
for the
parameter's type)
For example, for an action findCustomers(registeredDate, CustomerType) then the parameters would be something like:
arg0=20090103
arg1=CTP|12
where 20090103
is 3-Jan-2009 in parseable
format, and CTP|12
is the
OID (as assigned by the object store) for
CustomerType
with ID=12.
When an action is invoked, the response always includes the OID and title of the object on which the action was invoked. In addition:
if the action returned an object, then the response will include a link to the object
We could perhaps change this to return the GET of the returned object?
if the action returned a collection of objects, then the response will return a list of links to the objects
if the action returned void; then nothing further is added to the response