Putting a Custom UI on Naked Objects: Part 3, The MetaModel API

In part one of this series we saw how to bootstrap Naked Objects, and in part two we saw how we can interact directly with domain objects as pojos (with Naked Objects taking care of persistence and transaction management). But we can also interact with domain objects through their NakedObject wrapper/adapter. Although more indirect, it is also more powerful. So let’s see how.

Recall that NakedObject does three things main things: it wraps the pojo (getObject()), it provides access to the metamodel through the corresponding NakedObjectSpecification (getSpecification()), and it provides access to the object’s Oid (getOid()).

If NakedObject is analogous to java.lang.Object, then NakedObjectSpecification is analogous to java.lang.Class, describing the class members (properties, collections and actions) of each NakedObject. It provides:

  • getAssociationList() – each instance will be a OneToOneAssociation (ie a property) or a OneToManyAssociation (ie a collection)
  • getObjectActionList(NakedObjectActionType.USER) – each instance will be a NakedObjectAction
  • from NakedObjectAction in turn you can also use getParameters() – which returns a set of NakedObjectActionParameters.

If you explore these main interfaces – NakedObjectSpecification, OneToOneAssociation, OneToManyAssociation, NakedObjectAction and NakedObjectActionParameter – then you’ll see that they all implement FacetHolder, which means they all implement the getFacets() and getFacet(Class) methods; an example of the Extension Object pattern. Facets are key to understanding the Naked Objects metamodel.

So, a facet is a piece of information held about a class or its class members, and allowing us to interact with a domain object of that type. These facets break into three categories.

First, there are a set of well-defined facets, understood by all Naked Objects generic OOUIs, that are primarily for presentation purposes. For example, to obtain the (possibly localized) name of a class or class member, we use:

NamedFacet specName = someNoSpec.getFacet(NamedFacet.class);

Second, there are facets that allow the interaction to be performed. These are also well defined and understood by Naked Objects viewers. The full list of interactions and their corresponding facets are:

Member type Interaction Facet Prog model
Property getter/accessor PropertyAccessorFacet getXxx()
choices PropertyChoicesFacet choicesXxx()
default PropertyDefaultFacet defaultXxx()
validate PropertyValidateFacet validateXxx()
setter/mutator PropertySetterFacet setXxx()
clear PropertyClearFacet clearXxx()
initialization PropertyInitializationFacet setXxx()
Collection getter/accessor PropertyAccessorFacet getYyy()
add to CollectionAddToFacet addToYyy()
remove from CollectionRemoveFromFacet removeFromYyy()
clear CollectionClearFacet clearYyy()
validate add to CollectionValidateAddToFacet validateAddToYyy()
validate remove from CollectionValidateRemoveFromFacet validateAddToYyy()
action invocation ActionInvocationFacet zzz(arg0, arg1, …)
choices (all params) ActionChoicesFacet choicesZzz()
choices (param N) ActionParameterChoicesFacet choicesNZzz()
default (all params) ActionDefaultFacet defaultZzz()
default (param N) ActionParameterDefaultFacet defaultNZzz()
validate ActionValidationFacet validateZzz(arg0, arg1, …)

Third, there are the facets that enforce business rules. To keep this post of manageable size we’ll come back to these later.

For now though, let’s see how to use this metamodel API. Recall that in the claims example app we have an EmployeeRepository that defines an allEmployees action. Here’s how we call that action:

NakedObjectSpecification employeeRepositorySpec =
    employeeRepositoryNO.getSpecification();
NakedObjectAction allEmployeesAction =
    employeeRepositorySpec.getObjectAction(NakedObjectActionType.USER, "allEmployees()");
ActionInvocationFacet actionInvocationFacet =
    allEmployeesAction.getFacet(ActionInvocationFacet.class);
NakedObject resultNO = actionInvocationFacet.invoke(employeeRepositoryNO, new NakedObject[]{});

The result of the action is a collection, wrapped in resultNO. However, we can discover this dynamically by checking for the existence of a CollectionFacet, through which we can then list all elements in that collection:

NakedObjectSpecification resultSpec = resultNO.getSpecification();
CollectionFacet collectionFacet = resultSpec.getFacet(CollectionFacet.class);
if (collectionFacet != null) { // should be true
    Collection<NakedObject> adapterList =
        collectionFacet.collection(resultNO);
    for (NakedObject adapter : adapterList) {
        OneToOneAssociation nameAssoc =
            (OneToOneAssociation) adapter.getSpecification().getAssociation("name");
        PropertyAccessorFacet propertyAccessorFacet =
            nameAssoc.getFacet(PropertyAccessorFacet.class);
        Object name = propertyAccessorFacet.getProperty(adapter);
        System.out.println(name);
    }
}

In this code there are two hard-coded assumptions about the domain model: that EmployeeRepository has an action called allEmployees(), and the resultant domain objects have a property called “name”. In fact, we could get rid of this second assumption, either by iterating through every property, or (simpler) just by printing out each object’s title using the TitleFacet:

for (NakedObject adapter : adapterList) {
    TitleFacet titleFacet =
        adapter.getSpecification().getFacet(TitleFacet.class);
    String title = titleFacet.title(adapter);
    System.out.println(title);
}

Although (obviously) the metamodel API is a lot more verbose than simply using the domain objects, it is also (as I hope you are eralizing) a lot more powerful. The ability to iterate over results and render them in a standard, generic fashion is the root of the Naked Objects viewers. You could equally use the same technique sprinkled into your own custom UI, to take care of the 80~90% of cases where a simple UI is all that’s required.

That’s it for now. In the next, we’ll continue exploring the power of the metamodel API and see how that third category of facets can be used to enforcing various types of business rules.

Posted on December 3, 2009, in apache isis. Bookmark the permalink. 8 Comments.

  1. Great! You gave me idea how interact with raw reflection!

  2. *without

  3. This kind of posts is very helpful to grasp the architecture of Naked Objects and how it solves common problems of metadata extraction. Thanks.

  4. Some questions after having dig deeper into the noa package:
    - is OneToOneAssociation always used for fields? Also if the field is not a domain object but a String?
    - I ask this because there is a method OneToOneAssociation::setAssociation() whose second parameter is a NakedObject. But why wrapping a String in a NakedObject? Maybe is the PropertySetterFacet used instead?
    - when the Collection is returned it is wrapped in a NakedObject. Is its specification the same of a NakedObject that wraps a single element?
    Thanks for any help.

    • Hi Giorgio,
      to answer your questions:

      Q1. is OneToOneAssociation always used for fields?
      A1. yes.

      Q2. Also if the field is not a domain object but a String?
      A2. yes.

      Q3. I ask this because there is a method OneToOneAssociation::setAssociation() whose second parameter is a NakedObject. But why wrapping a String in a NakedObject?
      A3. Indeed, we even wrap Strings (or any values) in a NakedObject, mainly to give access to the metamodel (NakedObjectSpecification). That means we don’t need to distinguish between values and references.

      Behind the scenes, OneToOneAssociation#setAssociation() does use the PropertySetterFacet; the former is just a convenience. But you probably should call OneToOneAssociation#set() instead, which can also be used to clear a property (set it to null) using the PropertyClearFacet.

      Q4. When the Collection is returned it is wrapped in a NakedObject. Is its specification the same of a NakedObject that wraps a single element?
      A4. No, it’ll be a NakedObjectSpecification for ArrayList, (or whatever). But that specification will have a TypeOfFacet so you can see what the underlying type is.

      If you might want to contact me offline for more on the API, that’s fine with me.
      Cheers
      Dan

  1. Pingback: EasyMock, JUnit @Rules and Hamcrest | Domain Driven Design using Naked Objects

  2. Pingback: Putting a Custom UI on Naked Objects: Part 4, Business Rules | Domain Driven Design using Naked Objects

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 126 other followers