The
/specs/{fullyQualifedClassName}
links indicated in several places in the representations produced by
ObjectResource
corresponds to the
SpecsResource
:
import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; public interface SpecsResource { ... }
The implementation of this interface in Restful
Objects viewer (SpecsResourceImpl
)
also defines a @Path("/specs")
for the class as a whole.
This therefore defines a URL in the form
/services supporting the GET
method.
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.
The purpose of the /specs/ family of resources is to describe the structure of the domain objects, ie expose a metamodel for the domain objects. Client-side applications might choose to iterate through all the specs resources first and cache them; this would then simplify the task of rendering domain objects.
Again, let's break the resources provided by
SpecsResource
into sections.
First up, we have a resource to list all classes (or specs):
import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; public interface SpecsResource { @GET @Path("/") @Produces( { "application/xhtml+xml", "text/html" }) public abstract String specs(); ... }
This defines /specs as a URL accepting GET. This returns the following:
The raw XHTML produced (abbreviated) is:
<div class="nof-section"> <p class="nof-section">Specifications</p> <ul class="nof-specifications"> ... <li> <a href="/specs/int" rel="spec" rev="specs" class="nof-specification">int</a> </li> ... <li> <a href="/specs/java.lang.String" rel="spec" rev="specs" class="nof-specification">java.lang.String</a> </li> ... <li> <a href="/specs/org.nakedobjects.applib.value.Date" rel="spec" rev="specs" class="nof-specification">org.nakedobjects.applib.value.Date</a> </li> .. <li> <a href="/specs/org.nakedobjects.examples.claims.dom.claim.Claim" rel="spec" rev="specs" class="nof-specification">org.nakedobjects.examples.claims.dom.claim.Claim</a> </li> <li> <a href="/specs/org.nakedobjects.examples.claims.dom.claim.ClaimItem" rel="spec" rev="specs" class="nof-specification">org.nakedobjects.examples.claims.dom.claim.ClaimItem</a> </li> ... <li> <a href="/specs/void" rel="spec" rev="specs" class="nof-specification">void</a> </li> </ul> </div>
Useful XPath expressions:
//a[@class='nof-specification']/@href will give links to resources for all specifications
Let's look at an individual specification next.
The resource for a specification is:
... public interface SpecsResource { ... @GET @Path("/{specFullName}") @Produces( { "application/xhtml+xml", "text/html" }) public abstract String spec( @PathParam("specFullName") final String specFullName); ... }
This defines /specs/{specFullName} as a URL accepting GET. This returns:
The raw XHTML for this breaks into five regions.
First (abbreviated) we have the facets, which define additional semantics to the holder (in this case, the spec):
<div class="nof-facets"> <p class="nof-facets">Facets</p> <table border="1"> <tr> <th>FacetType</th> <th>Implementation</th> ... </tr> ... <tr> <td> <a href="org.nakedobjects.examples.claims.dom.claim.Claim/facet/org.nakedobjects.metamodel.facets.object.ident.plural.PluralFacet" rel="facet" rev="spec" class="nof-facet">org.nakedobjects.metamodel.facets.object.ident.plural.PluralFacet</a> </td> <td> <p>org.nakedobjects.metamodel.facets.object.ident.plural.PluralFacetInferred</p> </td> ... </tr> ... <tr> <td> <a href="org.nakedobjects.examples.claims.dom.claim.Claim/facet/org.nakedobjects.metamodel.facets.naming.named.NamedFacet" rel="facet" rev="spec" class="nof-facet">org.nakedobjects.metamodel.facets.naming.named.NamedFacet</a> </td> <td> <p>org.nakedobjects.metamodel.facets.naming.named.NamedFacetInferred</p> </td> ... </tr> ... <tr> <td> <a href="org.nakedobjects.examples.claims.dom.claim.Claim/facet/org.nakedobjects.metamodel.facets.naming.describedas.DescribedAsFacet" rel="facet" rev="spec" class="nof-facet">org.nakedobjects.metamodel.facets.naming.describedas.DescribedAsFacet</a> </td> <td> <p>org.nakedobjects.metamodel.facets.naming.describedas.DescribedAsFacetNone</p> </td> ... </tr> ... </table> </div>
Many of the facets listed will not be that relevant to us, but
some - such as the singular name of a class
(NamedFacet
), the plural name of a class
(PluralFacet
) and the description of a class
(DescribedAsFacet
) will be useful for
presentation purposes. It is also possible to define additional facets
that might be relevant to your own client-side application. For
example, if you were writing a mash-up GUI and
wanted to render an Address domain object within a map, you might want
to define a MapCoordinatesFacet
.
Actually, this isn't quite true; there is currently no way to evaluate a facet for a particular domain object instance.
As ever, we can use XPath to pull out values:
//div[@class='nof-facets']//a[.='org.nakedobjects.metamodel.facets.object.ident.plural.PluralFacet']/@href
will pull out the link for the PluralFacet
,
if there is one.
Next we have the (definition of the) properties for a spec:
<div class="nof-properties"> <p class="nof-properties">Properties</p> <ul class="nof-properties"> <li> <a href="/specs/org.nakedobjects.examples.claims.dom.claim.Claim/property/description" rel="property" rev="spec" class="nof-property">description</a> </li> <li> <a href="/specs/org.nakedobjects.examples.claims.dom.claim.Claim/property/date" rel="property" rev="spec" class="nof-property">date</a> </li> ... </ul> </div>
Useful XPath queries here:
//div[@class='nof-properties']//a/text() returns the property names
//div[@class='nof-properties']//a/@href
returns links to the property definitions (see Section 3.4.3, “Class Members
(NakedObjectMember
)”)
Similarly, we have collections:
<div class="nof-collections"> <p class="nof-collections">Collections</p> <ul class="nof-collections"> <li> <a href="/specs/org.nakedobjects.examples.claims.dom.claim.Claim/collection/items" rel="collection" rev="spec" class="nof-collection">items</a> </li> ... </ul> </div>
Useful XPath queries here:
//div[@class='nof-collections']//a/text() returns the collection names
//div[@class='nof-collections']//a/@href
returns links to the collection definitions (see Section 3.4.3, “Class Members
(NakedObjectMember
)”)
Lastly, the actions. These fall into three groups: regular
(USER) actions, debug actions (annotated with
@Debug
) and exploration actions (that are
available only in exploration mode):
<div class="nof-actions"> <p class="nof-actions">USER actions</p> <ul class="nof-actions"> <li> <a href="/specs/org.nakedobjects.examples.claims.dom.claim.Claim/action/addItem(int,double,java.lang.String)" rel="action" rev="spec" class="nof-action">addItem(int,double,java.lang.String)</a> </li> <li> <a href="/specs/org.nakedobjects.examples.claims.dom.claim.Claim/action/submit(org.nakedobjects.examples.claims.dom.claim.Approver)" rel="action" rev="spec" class="nof-action">submit(org.nakedobjects.examples.claims.dom.claim.Approver)</a> </li> </ul> </div> <div class="nof-actions"> <p class="nof-actions">DEBUG actions</p> <ul class="nof-actions" /> </div> <div class="nof-actions"> <p class="nof-actions">EXPLORATION actions</p> <ul class="nof-actions" /> </div>
Useful XPath queries here:
//div[@class='nof-actions' and p/text()='USER actions']//a/@href returns links to the regular user actions
//div[@class='nof-actions' and p/text()='DEBUG actions']//a/@href returns links to the debug actions
//div[@class='nof-actions' and p/text()='EXPLORATION actions']//a/@href returns links to the exploration actions
The next set of resources provided by SpecsResource are for the individual class members (properties, collections or actions):
... public interface SpecsResource { ... @GET @Path("/{specFullName}/property/{propertyName}") @Produces( { "application/xhtml+xml", "text/html" }) public abstract String specProperty( @PathParam("specFullName") final String specFullName, @PathParam("propertyName") final String propertyName); @GET @Path("/{specFullName}/collection/{collectionName}") @Produces( { "application/xhtml+xml", "text/html" }) public abstract String specCollection( @PathParam("specFullName") final String specFullName, @PathParam("collectionName") final String collectionName); @GET @Path("/{specFullName}/action/{actionId}") @Produces( { "application/xhtml+xml", "text/html" }) public abstract String specAction( @PathParam("specFullName") final String specFullName, @PathParam("actionId") final String actionId); ... }
This defines the following URLs all accepting GET:
/specs/{specFullName}/property/{propertyName} for a resource representing a property definition
/specs/{specFullName}/collection/{propertyName} for a resource representing a collection definition
/specs/{specFullName}/action/{actionId} for a resource representing a action definition
Each of these resources generates a similar representation, listing the facets for that class member. For example, here is the resource for a property spec:
The raw XHTML (abbreviated) is:
<div> <p>Owners</p> <ul class="nof-specification"> <a href="/specs/org.nakedobjects.examples.claims.dom.claim.Claim" rel="spec" rev="property" class="nof-specification">owning spec</a> </ul> </div> <div class="nof-facets"> <p class="nof-facets">Facets</p> <table border="1"> <tr> <th>FacetType</th> <th>Implementation</th> ... </tr> <tr> <td> <a href="description/facet/org.nakedobjects.metamodel.facets.propparam.typicallength.TypicalLengthFacet" rel="facet" rev="spec" class="nof-facet">org.nakedobjects.metamodel.facets.propparam.typicallength.TypicalLengthFacet</a> </td> <td> <p>org.nakedobjects.metamodel.facets.propparam.typicallength.TypicalLengthFacetDerivedFromType</p> </td> ... </tr> ... <tr> <td> <a href="description/facet/org.nakedobjects.metamodel.facets.ordering.memberorder.MemberOrderFacet" rel="facet" rev="spec" class="nof-facet">org.nakedobjects.metamodel.facets.ordering.memberorder.MemberOrderFacet</a> </td> <td> <p>org.nakedobjects.metamodel.facets.ordering.memberorder.MemberOrderFacetAnnotation</p> </td> ... </tr> <tr> <td> <a href="description/facet/org.nakedobjects.metamodel.facets.propparam.validate.mandatory.MandatoryFacet" rel="facet" rev="spec" class="nof-facet">org.nakedobjects.metamodel.facets.propparam.validate.mandatory.MandatoryFacet</a> </td> <td> <p>org.nakedobjects.metamodel.facets.propparam.validate.mandatory.MandatoryFacetDefault</p> </td> ... </tr> ... </table> </div>
As for specifications themselves (see Section 3.4.2, “Class (NakedObjectSpecification
)”), many of the facets will not be that relevant.
However, NamedFacet
and
DescribedAsFacet
mentioned earlier would be,
for presentation purposes. In addition, for properties the
TypicalLengthFacet
can be used as a hint for a
field in the UI, and
MemberOrderFacet
annotation can be used to
indicate the order of fields in the UI. The
MandatoryFacet
can be used to indicate
mandatory properties.
As ever, XPath can be used to pull out information from the resource.
Although actions produce a similar output, they ought to be extended to provide information on action parameters; both how many parameters there are, and also facets associated with those parameters.
A facet resource allows the value of a facet to be inspected.
The resources provided by SpecsResource
are:
... public interface SpecsResource { ... @GET @Path("/{specFullName}/facet/{facetType}") @Produces( { "application/xhtml+xml", "text/html" }) public abstract String specFacet( @PathParam("specFullName") final String specFullName, @PathParam("facetType") final String facetTypeName); @GET @Path("/{specFullName}/property/{propertyName}/facet/{facetType}") @Produces( { "application/xhtml+xml", "text/html" }) public abstract String specPropertyFacet( @PathParam("specFullName") final String specFullName, @PathParam("propertyName") final String propertyName, @PathParam("facetType") final String facetTypeName); @GET @Path("/{specFullName}/collection/{collectionName}/facet/{facetType}") @Produces( { "application/xhtml+xml", "text/html" }) public abstract String specCollectionFacet( @PathParam("specFullName") final String specFullName, @PathParam("collectionName") final String collectionName, @PathParam("facetType") final String facetTypeName); @GET @Path("/{specFullName}/action/{actionId}/facet/{facetType}") @Produces( { "application/xhtml+xml", "text/html" }) public abstract String specActionFacet( @PathParam("specFullName") final String specFullName, @PathParam("actionId") final String actionId, @PathParam("facetType") final String facetTypeName); ... }
This defines the following URLs all accepting GET:
/specs/{specFullName}/facet/{facetType} for a facet on a spec
/specs/{specFullName}/property/{propertyName}/facet/{facetType} for a facet on a property
/specs/{specFullName}/collection/{propertyName}/facet/{facetType} for a facet on a collection
/specs/{specFullName}/action/{actionId}/facet/{facetType} for a facet on an action
In addition we need a resource to allow the facets of an actions parameter to be queried.
(Mentioned elsewhere), we also need to provide the ability to
evaluate facets per instance. Although many facets are per class,
some (such as TitleFacet
) will vary by
instance. This will (presumably) need some additional resource
methods (eg specActionFacetFor(...)
) that
take an oid as a parameter.
Each of these resources generates a similar representation, evaluating a facets for its facet holder. For example, here is the resource for a property spec:
The raw XHTML (abbreviated) is:
<div> <p>Owners</p> <ul class="nof-properties"> <li> <a href="/specs/org.nakedobjects.examples.claims.dom.claim.Claim" rel="owning spec" rev="spec" class="facet"> org.nakedobjects.examples.claims.dom.claim.Claim </a> </li> <li> <a href="/specs/org.nakedobjects.examples.claims.dom.claim.Claim/property/description" rel="owning property" rev="property" class="facet"> description </a> </li> </ul> </div> <div class="nof-facet-elements"> <p class="nof-facet-elements">Facet Elements</p> <dl class="nof-facet-elements"> <dt>class</dt> <dd>class org.nakedobjects.metamodel.facets.propparam.typicallength.TypicalLengthFacetDerivedFromType</dd> <dt>derived</dt> <dd>false</dd> <dt>facetHolder</dt> <dd>Reference Association [name="org.nakedobjects.examples.claims.dom.claim.Claim#description(), type=JavaSpecification@9c22ff[class=java.lang.String,type=Object,persistable=User Persistable,superclass=Object] ]</dd> <dt>identified</dt> <dd>Reference Association [name="org.nakedobjects.examples.claims.dom.claim.Claim#description(), type=JavaSpecification@9c22ff[class=java.lang.String,type=Object,persistable=User Persistable,superclass=Object] ]</dd> <dt>noop</dt> <dd>false</dd> <dt>underlyingFacet</dt> <dd>(null)</dd> </dl> </div>
This representation is created by applying a JavaBean
conventions on the facet (all getXxx()
and
isXxx()
methods exposed by the facet
itself).
We need a more general purpose mechanism to query facets. As
can be seen, the above doesn't actually expose the typical value of
the facet, because the method we want is called
value()
, not
getValue()
.
Some XPath queries that might be useful are:
//div[@class='nof-facet-elements']//dt[.='class']/following-sibling::dd[1]
will return the <dd>
element
corresponding to the 'class' <dt>
//div[@class='nof-facet-elements']//dt[.='derived']/following-sibling::dd[1]
will return the <dd>
element
corresponding to the 'derived'
<dt>d