Shared Application Module Instance Tricks, Part II: Displaying Data

This is the second part of a two-part series about undocumented tricks with shared application module instances. Last week, I talked about calling methods (at the application module, view object, or view row level) from shared application module instances. This week, I’m going to talk about displaying data (in a non-LOV context) out of them. If you want a general overview of what shared application module instances are and why I think using them is a good idea (particularly at the application scope), look here.

Normally, to display data out of an application module instance, you drag its view object instances, or particular attributes from those instances, onto a JSF page, document, or fragment. You can also navigate through that data (either page-by-page, for table-type components, or row-by-row for forms) by dragging various navigation operations from the under the view object instance onto the page or onto your task flow.

You can’t do this to display data from a shared application module instance. A data control for the application module will appear in the data control palette, with all its view objects below it, but dragging will not create components based on the shared instance–it will create an ordinary, top-level instance of the application module (which is not shared across users) and create components based on that.

And anyway, you really wouldn’t want to base components, especially navigation components, directly on the view object instances belonging to a shared application module instance, at least not if the application module instance is shared at application scope. These view object instances are shared by different people using your application. You don’t either want multiple people simultaneously trying to access the primary row set iterator or trying to create their own row set iterators (through iterator bindings) that share a name.

Instead, you need a way to make sure that each user of the shared application module gets their own iterator to use for navigation. As a bonus, it would be great if you could actually filter the rows differently per user. Fortunately, ADF provides a way to do that through view accessors, and with a bit of work, you can use them for data display and navigation in the UI.

Example Review

For those of you who didn’t read last week’s post, here’s the example we’re working with. It’s just based on HR, for simplicity. We have two application module definitions: TopLevelModule (which is going to be used to create ordinary top-level application module instances, one per user session, modulo pooling) and SharedModule, which is going to be used exclusively as to create an application-scoped shared application module instance. I’ve added that instance, called SharedModuleInstance, to the project properties. SharedModule has a single of the (boring) view object DepartmentsView, called AllDepartments, which we’re going to display data out of.

Exposing a View Accessor to the Client

A view accessor, as the ADF developer’s guide explains, is a way to get a rowset from one view object instance (in this case, the AllJobs instance), optionally filtered by additional view criteria, from a row in another view object instance (in this case, a view object instance in TopLevelModule). You specify a view accessor in a view object definition; then each row in any instance of that definition can get its very own rowset of the shared data.

You can write the view criteria to include parameter values supplied by a particular row. For example, if you had a view object definition EmployeesView, and you put an instance of this in TopLevelModule, you can access a RowSet containing the departments that any given employee manages by putting a view accessor, with the appropriate view criteria, on EmployeesView. Effectively, you’re getting detail data out of a shared view object cache for a particular master row in a regular, non-shared cache. (To keep from constantly re-executing a shared view object’s query, which would partially defeat the point of a shared instance, you should use in-memory execution mode, rather than database execution mode, for the view criteria.)

If you’re not interested in displaying detail data, and you are interested in displaying data in a non-LOV context (which is what we’re doing here), you probably don’t want more than one RowSet per top-level application module instance. So you can create a dummy view object, with just one row, to hold your view accessors for all non-detail data you want to display. In our example, we’ll call this view object SharedVORowsetAccessor, and make it a static VO (with rows populated at design time, rather than using a query) with just one row and one attribute. The attribute name or value doesn’t matter; it’s just a placeholder. We’ll add a view object accessor, called AllDepartments, that retrieves a rowset from the AllDepartments instance in the shared application module instance. (That Chapter 10 of the Developer’s Guide explains how to do this.) Don’t forget to put an instance of SharedVORowsetAccessor in TopLevelModule’s data model.

Next, we’re going to generate a custom view row class for SharedVORowsetAccessor. When you do this, the view row class automatically contains a method returning a row set for each view accessor you have. So in this case, the newly generated SharedVORowsetAccessorRowImpl will contain the following method:

public RowSet getAllDepartments() {
    return (RowSet)getAttributeInternal(ALLDEPARTMENTS);
}

Now that you have that, you can export it on a client row interface, so your client applications can call it to retrieve the row set. And the row set, of course, has its own primary iterator, so there will be no conflicts between different application users. In some sense, we’re almost done! But retrieving the rowset and actually displaying data from it are not quite the same thing.

Displaying the Row Set in a Tabular Format

Here’s what you get in the data control palette after you export the method:

View accessor method in the data control palette

View accessor method in the data control palette

So, you get a return value with normal navigation operations, but no attributes. As far as I’ve been able to determine, there is no way to communicate metadata about a method return from an ADF business component to the ADF model layer. This is kind of surprising, since there is a way to access metadata about collections returned by methods exposed by a POJO data control, which you’d think would have less functionality. But at any rate, if we’re going to display this data, we’re going to have to do without design-time access to its attributes.

So, what happens if you drag the return value of that operation onto a JSF view component? You get the little-discussed dynamic table. A dynamic table doesn’t have separately-coded columns for each attribute it displays; instead, it loops through all attributes available at runtime, creating a column for each:

<af:table ...>
  <af:forEach items="#{bindings.return.attributeDefs}" var="def">
    <af:column headerText="#{bindings.return.labels[def.name]}"
               sortable="true" sortProperty="#{def.name}">
      <af:outputText value="#{row[def.name]}"/>
    </af:column>
  </af:forEach>
</af:table>

The binding created for this is also pretty low on metadata detail:

 <tree IterBinding="getSharedModuleInstance_AllDepartmentsIterator"
       id="return">
   <nodeDefinition/>
 </tree>

Now, that’s all fine–if you want to display every attribute, in the order in which it occurs in the view object, all using the same control type (you could obviously change that outputText to something else, although I’d recommend making whatever it is read-only–you almost assuredly don’t want to let one user change data in an application-scoped shared application module instance). But obviously, it doesn’t give you the customization power that a regular table would. (Oh, I suppose you could do something fancy with attribute control hints and custom properties, but it’s not trivial–especially getting the control type right.)

To get a regular static table, you’re going have to add some metadata to the binding, and use that metadata to create a regular tree element. First, the metadata. Alas, you’ll have to do this by hand:

<tree IterBinding="getSharedModuleInstance_AllDepartmentsIterator"
       id="return">
  <nodeDefinition DefName="sharedam.model.DepartmentsView">
    <AttrNames>
      <Item Value="DepartmentId"/>
      <Item Value="DepartmentName"/>
      <Item Value="ManagerId"/>
      <Item Value="LocationId"/>
    </AttrNames>
  </nodeDefinition>
</tree> 

Then, you’re going to have to make your table columns by hand, too. An example:

<af:column headerText="#{bindings.return.labels[DepartmentName]}"
           sortable="true" sortProperty="DepartmentName">
  <af:outputText value="#{row.DepartmentName.inputValue}"/>
</af:column>

Code insight or the property inspector will help you with this, except for the whole “DepartmentName” thing–it won’t know that “DepartmentName” is a legitimate attribute, and indeed JDeveloper provides no way to check this short of running the page and seeing if it comes out right. C’est la vie, though. At least you can create a static table if you want to.

Displaying Attributes from a Particular Row

Navigating to a particular row in one of these collections isn’t hard–after all, the data control does show normal navigation operations that you can drag onto the page to create buttons and links. The hard part is displaying attributes in a non-tabular format. You’d ordinarily do this by dragging an attribute from the data control palette onto the page, and then chosing a control type…but, as we saw above, the data control palette doesn’t show any of these attributes. You’re going to have to do this the long, slow way–create an attribute binding in the page definition, and bind a control to that binding.

It’s actually even worse than that, at least in the current 11g preview, because the new (and generally great) graphical UI for editing the pageDef file really isn’t great at all for creating AttributeBindings to attributes that the data control layer doesn’t know about. In fact, I’ve found no way at all to do this except by editing the pageDef source, by hand, and then very carefully avoiding editing this binding using any of JDeveloper’s binding editor dialogs.

Here’s an example of such a binding. Again, this was written by hand, which you’ll have to do to:

<attributeValues id="DepartmentName" IterBinding="getAllDepartmentsIterator">
  <AttrNames>
    <Item Value="DepartmentName" />
  </AttrNames>
</attributeValues>

Once you have this attribute binding, you can create a control (such as an outputText) using the component palette (not the data control panel) and set its value to #{bindings.DepartmentName.inputValue}.

Tricky? Yes. I’d like to see a bit more flexibility with shared data actually built into the tool. But it is possible to work with such data, even now.