Lesson 4 - Publishing a Part's Needs

Now things get downright interesting. We are about to see how a part's "needs" to collaborate with other parts are published and automatically satisfied.

Going back to PartA which printed "Hello World", what if we had a number of such parts doing things, and we wanted to know they were done successfully, such as in an automatic test. Let's build that system. We will reuse the uhr.part.collab.NotificationRegistry part from the Part Inventory and call it our TestRecorder. It registers notifications, such as test results, and allows you to list the results. Suppose we have the single notification "StateCommandableWorked", which is a simple test that the UHR core successfully passed a command to a StateCommandable.

The TestRecorder part DK would be:

<partdk>
     <notification_ids>
         StateCommandableWorked;
         </notification_ids>        
    </partdk>

We put that DK in file TestRecorder.dk. We modify our previous Contianer DK to be:

The Container DK with 2 parts:

<partdk>
    <parts>
        <part>
            <name>TestRecorder</name>
            <type>uhr.part.collab.NotificationRegistry</type>
            <abilities>inheritable</abilities>
            </part>
        <part>
            <name>HelloThere</name>
            <type>uhr.tutorial.system04.PartA</type>
            </part>
        </parts>
    <start_commands>
        <invocation>
            <part>HelloThere</part>
            <command>RunTest</command>
            </invocation>
        <invocation>
            <part>TestRecorder</part>
            <command>PrintResults</command>
            </invocation>
        </start_commands>        
    </partdk>

Now we need to modify PartA to pass the TestRecorder part the "StateCommandableWorked" notification. But since this is UHR, we don't let PartA talk directly to PartB. Instead we use Needs and Services. In this lesson we just discuss how to handle Needs. Here's how we do it:

PartA with 1 need published:

package uhr.tutorial.system04;

import uhr.core.attract.ServiceClient;
import uhr.core.attract.ServiceClientAide;
import uhr.core.attract.ServiceClientAideStd;
import uhr.core.attract.ServiceNeed;
import uhr.core.role.StateCommandable;
import uhr.core.role.StateCommandableAide;
import uhr.core.role.StateCommandableAideFactory;
import uhr.uniface.UF_void_String_String;

public class PartA implements ServiceClient, StateCommandable {

//---------- Internal Fields -------------------------------------
protected ServiceClientAide    clientAide = new ServiceClientAideStd(this);
protected StateCommandableAide stateAide = 
            StateCommandableAideFactory.createAide("RunTest", this);

// Host services
protected UF_void_String_String testRecorder;

protected final ServiceNeed TEST_RECORDER;

//---------- Initialization --------------------------------------
public PartA() {
    // Populate clientAide delegate with my needs
    TEST_RECORDER = clientAide.addNeed("uhr.uniface.UF_void_String_String",
        "ServiceID", "collaboration.NotifiyRegistryOfResult");
}
//---------- ServiceClient Implementation ------------------------
/**
* Returns the aide handling the ServiceClient's attractor work.
* My ServiceNeeds are: <pre>
*   Type:      uhr.uniface.UF_void_String_String
*   ServiceID: quality.test.RecordTestResult
* </pre>
* @return  the aide.
*/
public ServiceClientAide getServiceClientAide() {
    return clientAide;
}
public void setServiceNeeded(ServiceNeed need, Object service) {
    if (need == TEST_RECORDER) {
        testRecorder = (UF_void_String_String)service;
    }
}
//---------- StateCommandable Implementation ---------------------
public StateCommandableAide getStateCommandableAide() {
    return stateAide;
}
public void doStateCommand(String command) {
    if (command.equals("RunTest")) {
        print("Hello World 4");
        
        // The null is for success.
        testRecorder.act("StateCommandableWorked", null);
    }
}
//---------- Standard --------------------------------------------
private static void print(String text) {
    System.out.println(text);
}

} // End class

A part implements ServiceClient to publish its needs. You can see the TEST_RECORDER need is published in the line with clientAide.addNeed(...). The need is automaticlly provided in the setServiceNeeded(...) method. It's that simple. All the complexities are taken care of by the UHR core.

The output is:

Hello World 4
All 1 notifications successful.
+Root
    TestRecorder(I)
    HelloThere

The second output line was caused by the start_command "PrintResults" to the TestRecorder part. This is a fine example of quality control appearing early in a system's lifecycle. :-)

Notice how we reused the DK driven NotificationRegistry part with almost no effort at all. And note how we have two parts collaborating without knowing about each other. That is the power of UHR....

What would happen if you commented out the "RunTest" invocation from the Container DK? The output would be:

StateCommandableWorked: UNRECORDED
Notification Registry Failure =====> 1 out of 1 had problems.
+Root
    TestRecorder(I)
    HelloThere

The first line appears because PartA never notified the TestRecorder part that StateCommandableWorked. A similar thing would happen if you put the RunTest command after the PrintList command.

What did we learn here?

  1. Parts implement ServiceClient to publish their needs.
  2. Needs are automatically provided in the setServiceNeeded(...) method.
  3. A satisfied need is a reference to a service with one or more methods.
  4. Unifaces can provide very loose coupling if used for servcies.
  5. Parts are reused with very little effort.


Go to the next lesson.