Remember our reusable PaymentPenaltyCalculator part? We built it to show how DK can be used to set the policy for calculating payment penalties. Let's publish the calculate method as a service and remove the RunTest that was in the first version of the part.
The PaymentPenaltyCalculator part with one service is:
package uhr.tutorial.system05;
import uhr.core.attract.ServiceHost;
import uhr.core.attract.ServiceHostAide;
import uhr.core.attract.ServiceHostAideStd;
import uhr.core.dk.ParamDriven;
import uhr.core.dk.ParamDrivenAide;
import uhr.core.dk.ParamDrivenAideStd;
import uhr.core.tron.Flexitron;
import uhr.uniface.UF_double_double_int;
public class PaymentPenaltyCalculator implements
ServiceHost, UF_double_double_int, ParamDriven {
//---------- Internal Fields -------------------------------------
protected Flexitron dk;
protected ParamDrivenAide paramAide = new ParamDrivenAideStd(this);
protected ServiceHostAide hostAide = new ServiceHostAideStd(this);
//---------- Initialization --------------------------------------
public PaymentPenaltyCalculator() {
// Populate hostAide with my services
hostAide.addItem("uhr.uniface.UF_double_double_int",
"ServiceID", "financial.calculate.PaymentPenalty", this);
}
//---------- ServiceHost Implementation --------------------------
/**
* Returns the aide handling the ServiceHost's attractor work.
* My ServiceItems are: <pre>
* Type: uhr.uniface.UF_double_double_int
* ServiceID: financial.calculate.PaymentPenalty
* </pre>
* @return the aide.
*/
public ServiceHostAide getServiceHostAide() {
return hostAide;
}
//---------- UF_double_double_int Implementation -----------------
/**
* Calculates the payment penalty given the amount and days late.
* @param paymentAmount the amount in dollars.
* @param daysLate the lateness in days, which may be zero.
* @return the penalty in dollars, which may be zero.
*/
public double get(double paymentAmount, int daysLate){
paramAide.provideDK();
// Get calculation policy from my dk
double interestRate = dk.getDouble("InterestRatePerDay");
double minimumPenalty = dk.getDouble("MinimumPenalty");
int gracePeriodDays = dk.getInt("GracePeriodDays");
// Calculate
double penalty = 0;
if (daysLate <= gracePeriodDays) {
// Do nothing, no penalty
} else {
// Calculate penalty
// Add minimumPenalty
penalty = minimumPenalty;
// Add interest. We use a simplified InterestRatePerDay.
penalty += paymentAmount * interestRate;
}
// Show work done
print("Calculated penalty of " + penalty + " on payment of "
+ paymentAmount + " that was " + daysLate + " days late.");
// Done
return penalty;
}
//---------- ParamDriven Implementation --------------------------
public ParamDrivenAide getParamDrivenAide() {
return paramAide;
}
/**
* Sets the DK describing the part's mission. My DK format is
* shown by this example:
<pre>
<partdk>
<InterestRatePerDay>.001</InterestRatePerDay>
<MinimumPenalty>5.00</MinimumPenalty>
<GracePeriodDays>7</GracePeriodDays>
</partdk>
</pre>
* @param dk the data structure containing the DK.
*/
public void setDK(Object dk) {
this.dk = (Flexitron)dk;
print("The dk = " + dk);
}
//---------- Standard --------------------------------------------
private static void print(String text) {
System.out.println(text);
}
} // End class
Parts implement ServiceHost to publish services. They add service items to their ServiceHostAide in their constructor, as shown. A service has a type, such as the Uniface UF_double_double_int. A service also has one or more attributes. This one has the single attribute name "ServiceID" and value "financial.calculate.PaymentPenalty". The service itself is the last argument in the addItem(...) arguments. In this case it's the object itself, sicne it implement the service type.UF_double_double_int directly. It could also use an inner class or a delegate.
That's all there is to the fine art of service publishing. The UHR core takes care of the rest. A part can publish as many services as it wants to.
We make as slight change in the Container DK to include the above part:
The Container DK with 3 parts:
<partdk>
<parts>
<part>
<name>TestRecorder</name>
<type>uhr.part.collab.NotificationRegistry</type>
<abilities>inheritable</abilities>
</part>
<part>
<name>Calculator</name>
<type>uhr.tutorial.system05.PaymentPenaltyCalculator</type>
</part>
<part>
<name>HelloThere</name>
<type>uhr.tutorial.system05.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>
And we add another test to the TestRecorder part for recording that the calculation worked:
The TestRecorder part DK with 2 tests:
<partdk>
<notification_ids>
StateCommandableWorked;
PaymentPenaltyCalculationWorked;
</notification_ids>
</partdk>
We must also modify PartA to need the above service, usie it and record the test result.
PartA with 2 needs published:
package uhr.tutorial.system05;
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_double_double_int;
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 UF_double_double_int penaltyCalculator;
protected final ServiceNeed TEST_RECORDER;
protected final ServiceNeed PENALTY_CALCULATOR;
//---------- Initialization --------------------------------------
public PartA() {
// Populate clientAide delegate with my needs
TEST_RECORDER = clientAide.addNeed("uhr.uniface.UF_void_String_String",
"ServiceID", "collaboration.NotifiyRegistryOfResult");
PENALTY_CALCULATOR = clientAide.addNeed("uhr.uniface.UF_double_double_int",
"ServiceID", "financial.calculate.PaymentPenalty");
}
//---------- ServiceClient Implementation ------------------------
public ServiceClientAide getServiceClientAide() {
return clientAide;
}
public void setServiceNeeded(ServiceNeed need, Object service) {
if (need == TEST_RECORDER) {
testRecorder = (UF_void_String_String)service;
} else if (need == PENALTY_CALCULATOR) {
penaltyCalculator = (UF_double_double_int)service;
}
}
//---------- StateCommandable Implementation ---------------------
public StateCommandableAide getStateCommandableAide() {
return stateAide;
}
public void doStateCommand(String command) {
if (command.equals("RunTest")) {
print("Hello World 5");
// The null is for success.
testRecorder.act("StateCommandableWorked", null);
// Next test the penaltyCalculator
double penalty = penaltyCalculator.get(100, 10);
if (penalty == 5.1) {
testRecorder.act("PaymentPenaltyCalculationWorked", null);
} else {
testRecorder.act("PaymentPenaltyCalculationWorked",
"Calculated penalty of " + penalty + " but should be 5.1");
}
}
}
//---------- Standard --------------------------------------------
private static void print(String text) {
System.out.println(text);
}
} // End class
As you can see, publishing another need is duck soup. A part can have any number of needs. The needs are automatically satiosfied, greatly relieving parts of the complexities that plague most systems.
Goodness gracious!!! That's a lot of code just to do simple things. But remember, you only write a reusble part once....
Run this system. The output is:
Hello World 5 The dk = [InterestRatePerDay=.001, MinimumPenalty=5.00, GracePeriodDays=7] Calculated penalty of 5.1 on payment of 100.0 that was 10 days late. All 2 notifications successful.+Root TestRecorder(I) Calculator HelloThere
Note in PartA's doStateCommand(...) how we report how the penaltyCalculator service succeeded or failed. What if we changed the Calculator part's DK to be wrong, such as changing the MinimumPenalty from 5.00 to 55.00.
<partdk>
<InterestRatePerDay>.001</InterestRatePerDay>
<MinimumPenalty>55.00</MinimumPenalty>
<GracePeriodDays>7</GracePeriodDays>
</partdk>
This changes the output to:
Hello World 5
The dk = [InterestRatePerDay=.001, MinimumPenalty=55.00, GracePeriodDays=7]
Calculated penalty of 55.1 on payment of 100.0 that was 10 days late.
PaymentPenaltyCalculationWorked: Calculated penalty of 55.1 but should be 5.1
Notification Registry Failure =====> 1 out of 2 had problems.
+Root
TestRecorder(I)
Calculator
HelloThere
What did we learn here?
Go to the next lesson.