Inside the Rule System

The rule system is responsible for analyzing EOModels and from them, generating the XML for dynamic user interfaces. It answers the following questions:

How It Works

From the answers to these questions, the rule system builds a detailed description of the user interface. When the controller factory sends a request to the server application, the rule system works with a set of com.webobjects.directtoweb.D2WComponent classes and WebObjects dynamic elements to generate the XML. The rule system receives the controller factory’s requests and evaluates rules to determine which D2WComponent subclasses should generate the XML for the current request. The D2WComponent subclasses (using WOXMLNode dynamic elements internally) perform the actual XML generation.

All the information about how to configure a Direct to Java Client application is stored in rule system rules. A rule has a key, a value, and a priority. A key is a condition that must be true for the rule to fire. The rule system evaluates requests as follows:

  1. The controller factory makes a request to the rule system by specifying a key.

  2. The rule system identifies the rules whose key is the same as the request key.

  3. It then evaluates the conditions of the matching rules to see which can fire.

  4. Of the rules that can fire, the rule system fires the one with the highest priority, returning the value for the rule’s key.

By specifying -D2WTraceRuleFiringEnabled YES as a launch argument on the server application, you can see all the rules fire in a Direct to Java Client application.

To evaluate requests, the rule system needs information about the state of the client application. In addition to specifying a key, the controller factory also provides key-value pairs of state information that the rule system can use to evaluate the conditions of rules. For example, the rule system might need to know what task the client application is attempting to perform (query, list, or form) and the entity on which the client application is operating.

The controller factory packages all rule system input—the request key and the key-value pairs of state information—into a dictionary known as a specification. The following are examples of specifications:

A specification always contains a question, which is the request key. The request keys in the above examples are window and modalDialog.

In the rule system, you have access to the user’s language and platform, so you can write rules to provide application behavior based on those attributes of the client. This allows for mostly automatic localization of rule-generated components (as described in Localizing Dynamic Components) and provides automatic platform-specific user interface layout.

The rule system stores the key-value pairs of state information in a com.webobjects.directtoweb.D2WContext object. The D2WContext’s whole purpose is to keep track of state as a response is generated. Initially the D2WContext contains state information provided by the controller factory. As the rule system processes requests, the system adds more state information to the D2WContext.

Rule System Priorities

Each rule system rule has a priority, which is a mechanism to manage conflicting rules. It is possible to have two rules with the same condition (left-hand side), the same key (right-hand side), but a different value (right-hand side). To handle this conflict, the rule with the higher priority is fired.

The default rules provided by the com.webobjects.eogeneration package have a priority of 0. The Direct to Java Client Assistant gives its rules a priority of 100. You should never change the priority of rules generated by Assistant (this would involve editing the user.d2wmodel file, which is never a good idea since Assistant writes it out each time it saves). Also, the rules you create by hand should not have a priority of 100, since this confuses Assistant. If you want the rules created by Assistant to be preferred over your own rules, use a lower priority like 50. Otherwise, give your rules a higher priority.

D2WComponents

There is a one-to-one correspondence between Direct to Java Client D2WComponent subclasses (server side) and EOController subclasses (client side). For example, an EOTextFieldController (inheriting from EOController) on the client has a corresponding D2WComponent class on the server also named EOTextFieldController (inheriting from D2WComponent). The client-side class displays and manages user interface widgets while the server-side class generates XML to describe the client-side user interface.

The server-side D2WComponents for Direct to Java Client applications can be found in /System/Library/Frameworks/JavaEOGeneration.framework/Resources. You can open the components in WebObjects Builder to learn more about them.

Rule System Requests

The user interface components of a Direct to Java Client application are generated by the rule system when needed. The controller factory makes rule system requests as each new window in the client application is activated.

When an application starts up, the controller factory makes requests for the following keys:

  • availableSpecifications, which tells the controller factory all the specifications (request keys such as window and modalDialog, and any custom request keys)

  • defaultSpecifications, which tells the controller factory which windows to open automatically once the application is finished initializing

  • actions, which tells the controller factory what actions to add to the main menu along with standard menu items such as Quit

Then, to generate the controller hierarchy for a window or modal dialog, the controller factory makes requests for the following keys:

  • window, which returns the controller hierarchy XML for a window the application will open; the request from the controller hierarchy must also provide state information such as a task and, optionally, an entity name so the rule system can determine what window is being generated

  • modalDialog , which returns the controller hierarchy XML for a modal dialog the application will open; again, the request from the controller hierarchy must also provide state information such as a task and, optionally, an entity name

Internal Rule System Requests

When the rule system evaluates a request from the controller factory, the actual returned value is the name of a D2WComponent, not the controller hierarchy XML. The D2WComponent identified by the fired rule is responsible for generating the controller hierarchy XML that the controller factory receives.

In the process of generating XML, the D2WComponent objects might require the rule system to evaluate additional requests, the most significant of which are these two:

  • controller, which identifies a controller (a D2WComponent) for a task identified in the request’s specification; the entity-level controller defines the part of a window or dialog user interface for performing the specified task on the specified entity

  • propertyKeys, which identifies the property keys for a task and an entity identified in the request’s specification; the property keys are needed to identify the additional controllers needed to display and manipulate an object’s attributes and relationships

Sometimes it is necessary to know what kind of controller the rule system asks for. All the default rules therefore put additional information on the D2WContext (and you should maintain this information if you customize rules). This information can be used as additional criteria in the rule qualifiers. These are the two categories of controllers:

  • controllerType , (possible values: actionWidgetController, dividingController, groupingController, entityController, modalDialogController, tableController, widgetController, windowController)

  • isRootController, (false if not, nil otherwise)

Generating the Student Form Window

As an example of how the Direct to Java Client D2WComponent classes work, consider the form window for the Student entity in the tutorials. Suppose a user clicks the New button in a Query window for the Student entity. The controller factory then makes a request to the rule system with the following specification:

question = window, entity.name = Student, task = form

This specification tells the rule system that for the form task for the Student entity it should evaluate the window key and return a controller hierarchy based on what the window key evaluates to in the rule system.

The default rule fired to satisfy this request is as follows:

Left-Hand Side:
*true*
Key:
window
Value:
"EOWindow"
Priority:
0

You could write a custom rule (and give it a higher priority) to, for example, associate an EOModalDialog with the window key rather than the default EOWindow. Since in this case the default rule is not overridden, the D2WComponent that generates the XML for the form task for the Student entity is EOWindow (a WebObjects component).

Open EOWindow.wo in WebObjects Builder. (You can find it in /System/Library/Frameworks/JavaEOGeneration.framework/Resources).

EOWindow.wo contains a .html file (containing XML) and a .wod file.

Here’s an excerpt from EOWindow.html:

    <WEBOBJECT name=windowController>
        <WEBOBJECT name=actionWidgetController>
            <WEBOBJECT name=taskController>
            </WEBOBJECT>
        </WEBOBJECT>
        <WEBOBJECT name=content>
        </WEBOBJECT>
    </WEBOBJECT>

And here’s an excerpt from EOModalDialog.wod:

windowController: EOSwitchComponent {
    componentNameKey = "windowController";
    d2wContext = localContext;
    controllerType = "windowController";
}

The EOSwitchComponent in the .wod file is a dynamic element that makes a new rule system request using the componentNameKey as the request key. So in the case of windowController, the switch component makes a new rule system request with the key windowController, the name of the componentNameKey binding.

Before making the request, however, the switch component updates the rule system’s state information. Generally it creates a new D2WContext based on the state information in the old D2WContext. That’s what the d2wcontext binding specifies. Bindings other than componentNameKey and d2wcontext identify additional state that the switch component adds to the new D2WContext. For windowController, the additional state is simply that the controllerType is windowController.

In this manner, the XML controller hierarchy is built recursively using switch components.

One of the leaf nodes in the Student form window is for an EOFormController whose .wod file looks like this:

content: WOComponentContent {
}
 
controller: WOXMLNode {
    elementName = "FORMCONTROLLER";
    alignmentWidth = d2wContext.alignmentWidth;
    alignsComponents = d2wContext.alignsComponents;
    archive = d2wContext.archive;
    className = d2wContext.className;
    displayGroupProviderMethodName =       d2wContext.displayGroupProviderMethodName;
    editability = d2wContext.editability;
    editingContextProviderMethodName =       d2wContext.editingContextProviderMethodName;
    entity = controllerEntityName;
    horizontallyResizable = d2wContext.horizontallyResizable;
    iconName = d2wContext.iconName;
    iconURL = d2wContext.iconURL;
    label = d2wContext.label;
    minimumHeight = d2wContext.minimumHeight;
    minimumWidth = d2wContext.minimumWidth;
    path = controllerRelationshipPath;
    prefersIconOnly = d2wContext.prefersIconOnly;
    transient = d2wContext.transient;
    usesHorizontalLayout = d2wContext.usesHorizontalLayout;
    verticallyResizable = d2wContext.verticallyResizable;
}
disabledActionNamesArray: EOSwitchComponent {
    componentName = "EOStringArray";
    array = d2wContext.disabledActionNames;
    name = "disabledActionNames";
}
mandatoryRelationshipPathsArray: EOSwitchComponent {
    componentName = "EOStringArray";
    array = d2wContext.mandatoryRelationshipPaths;
    name = "mandatoryRelationshipPaths";
}

A WOXMLNode is a component that generates XML for a node in the controller hierarchy. Its bindings tell the server-side D2WComponent how to configure its client-side counterpart. For example, the binding names in the EOFormController .wod file correspond to XML attributes understood by the client-side EOFormController. Correspondingly, the binding values are the values assigned to those XML attributes. Most of the bindings are set to a key path starting with “d2wContext”. These key paths refer to the state information stored in the D2WContext.

EOSwitchComponent

EOSwitchComponent is a special dynamic element that takes a D2WContext and passes it as a copy with additional arguments to a D2WComponent. Usually it is used to pass a D2WContext to a subcomponent, but since the context is copied first, the context of the parent component is not modified and can be passed to other subcomponents without risk.

There are three bindings on EOSwitchComponent:

The componentName and componentNameKey bindings are mutually exclusive (only one of them can be used). d2wContext is usually localContext (usually the EOSwitchComponent is used inside a D2WComponent and localContext returns the D2WContext of it then).

All other bindings on the EOSwitchComponent are considered additional parameters for the newly created D2WContext.

Example:

queryListController: EOSwitchComponent {
    componentNameKey = "controller";
    d2wContext = localContext;
    controllerType = noValue;
    forceHorizontallyNotResizable = noValue;
    forceVerticallyNotResizable = noValue;
    forceEntityReadOnly = "true";
    forceWidgetReadOnly = "true";
    isRootController = "false";
    propertyKey = noValue;
    task = "list";
}

This creates a D2WContext with the local context of the component using this entry in the .wod file, gets the name of the contained component from controller, and adds all the other values to the D2WContext passed to that component.