Apache OFBiz Service Engine: Part 2

Exclusive offer: get 50% off this eBook here
Apache OFBiz Development: The Beginner's Tutorial

Apache OFBiz Development: The Beginner's Tutorial — Save 50%

Using Services, Entities, and Widgets to build custom ERP and CRM systems

$29.99    $15.00
by Jonathon Wong Rupert Howell | June 2009 | Java Open Source

In the previous part of the article by Jonathon Wong and Rupert Howell, we looked at Defining and creating a Java service, Service parameters, Special unchecked (unmatched) IN/OUT parameters, and Security-related programming. In this part, we will look at the following:

  • Calling services from code (using dispatcher)
  • IN/OUT parameter mismatch when calling services
  • Sending feedback; standard return codes success, error and fail
  • Implementing Service Interfaces
  • Synchronous and asynchronous services
  • Using the Service Engine tools
  • ECAs: Event Condition Actions

Calling Services from Java Code

So far, we have explored services invoked as events from the controller (example <event type="service" invoke="learningFirstService"/>). We now look at calling services explicitly from code.

To invoke services from code, we use the dispatcher object, which is an object of type org.ofbiz.service.ServiceDispatcher. Since this is obtainable from the DispatchContext we can invoke services from other services.

To demonstrate this we are going to create one simple service that calls another.

In our services.xml file in ${component:learning}servicedef add two new service definitions:

<service name="learningCallingServiceOne" engine="java"
location="org.ofbiz.learning.learning.LearningServices" invoke="callingServiceOne">
<description>First Service Called From The Controller</description>
<attribute name="firstName" type="String" mode="IN" optional="false"/>
<attribute name="lastName" type="String" mode="IN" optional="false"/>
<attribute name="planetId" type="String" mode="IN" optional="false"/>
<attribute name="fullName" type="String" mode="OUT" optional="true"/>
</service>
<service name="learningCallingServiceTwo" engine="java"
location="org.ofbiz.learning.learning.LearningServices" invoke="callingServiceTwo">
<description>Second Service Called From Service One</description>
<attribute name="planetId" type="String" mode="IN" optional="false"/>
</service>

In this simple example it is going to be the job of learningCallingServiceOne to prepare the parameter map and pass in the planetId parameter to learningCallingServiceTwo. The second service will determine if the input is EARTH, and return an error if not.

In the class org.ofbiz.learning.learning.LearningEvents, add the static method that is invoked by learningCallingServiceOne:

public static Map callingServiceOne(DispatchContext dctx, Map context){

LocalDispatcher dispatcher = dctx.getDispatcher();
Map resultMap = null;
String firstName = (String)context.get("firstName");
String lastName = (String)context.get("lastName");
String planetId = (String)context.get("planetId");
GenericValue userLogin = (GenericValue)context.get("userLogin");
Locale locale = (Locale)context.get("locale");

Map serviceTwoCtx = UtilMisc.toMap("planetId", planetId, "userLogin", userLogin, "locale", locale);
try{
resultMap = dispatcher.runSync("learningCallingServiceTwo", serviceTwoCtx);
}catch(GenericServiceException e){
Debug.logError(e, module);
}
resultMap.put("fullName", firstName + " " + lastName);

return resultMap;
}

and also the method invoked by learningServiceTwo:

public static Map callingServiceTwo(DispatchContext dctx, Map context){
String planetId = (String)context.get("planetId");
Map resultMap = null;
if(planetId.equals("EARTH")){
resultMap = ServiceUtil.returnSuccess("This planet is Earth");
}else{
resultMap = ServiceUtil.returnError("This planet is NOT Earth");
}
return resultMap;
}

To LearningScreens.xml add:

<screen name="TestCallingServices">
<section>
<actions><set field="formTarget" value="TestCallingServices"/></actions>
<widgets>
<include-screen name="TestFirstService"/>
</widgets>
</section>
</screen>

Finally add the request-map to the controller.xml file:

<request-map uri="TestCallingServices">
<security auth="false" https="false"/>
<event type="service" invoke="learningCallingServiceOne"/>
<response name="success" type="view" value="TestCallingServices"/>
<response name="error" type="view" value="TestCallingServices"/>
</request-map>

and also the view-map:

<view-map name="TestCallingServices" type="screen" 
page="component://learning/widget/learning/LearningScreens.xml#TestCallingServices"/>

Stop, rebuild, and restart, then fire an OFBiz http request TestCallingServices to webapp learning. Do not be alarmed if straight away you see error messages informing us that the required parameters are missing. By sending this request we have effectively called our service with none of our compulsory parameters present.

Enter your name and in the Planet Id, enter EARTH. You should see:

Apache OFBiz Service Engine: Part 2

Try entering MARS as the Planet Id.

Apache OFBiz Service Engine: Part 2

Notice how in the Java code for the static method callingServiceOne the line

resultMap = dispatcher.runSync("learningCallingServiceTwo", serviceTwoCtx);

is wrapped in a try/catch block. Similar to how the methods on the GenericDelegator object that accessed the database threw a GenericEntityException, methods on our dispatcher object throw a GenericServiceException which must be handled.

There are three main ways of invoking a service:

  • runSync—which runs a service synchronously and returns the result as a map.
  • runSyncIgnore—which runs a service synchronously and ignores the result. Nothing is passed back.
  • runAsync—which runs a service asynchronously. Again, nothing is passed back.

The difference between synchronously and asynchronously run services is discussed in more detail in the section called Synchronous and Asynchronous Services.

Implementing Interfaces

Open up the services.xml file in ${component:learning}servicedef and take a look at the service definitions for both learningFirstService and learningCallingServiceOne.

Do you notice that the <attribute> elements (parameters) are the same? To cut down on the duplication of XML code, services with similar parameters can implement an interface.

As the first service element in this file enter the following:

<service name="learningInterface" engine="interface">
<description>Interface to describe base parameters for Learning Services</description>
<attribute name="firstName" type="String" mode="IN" optional="false"/>
<attribute name="lastName" type="String" mode="IN" optional="false" />
<attribute name="planetId" type="String" mode="IN" optional="false"/>
<attribute name="fullName" type="String" mode="OUT" optional="true"/>
</service>

Notice that the engine attribute is set to interface.

Replace all of the <attribute> elements in the learningFirstService and learningCallingServiceOne service definitions with:

<implements service="learningInterface"/>

So the service definition for learningServiceOne becomes:

<service name="learningCallingServiceOne" engine="java"
location="org.ofbiz.learning.learning.LearningServices" invoke="callingServiceOne">
<description>First Service Called From The Controller</description>
<implements service="learningInterface"/>
</service>

Restart OFBiz and then fire an OFBiz http request TestCallingServices to webapp learning. Nothing should have changed—the services should run exactly as before, however our code is now somewhat tidier.

Overriding Implemented Attributes

It may be the case that the interface specifies an attribute as optional="false", however, our service does not need this parameter. We can simply override the interface and add the <attribute> element with whatever settings we wish.

For example, if we wish to make the planetId optional in the above example, the <implements> element could remain, but a new <attribute> element would be added like this:

<service name="learningCallingServiceOne" engine="java"
location="org.ofbiz.learning.learning.LearningServices" invoke="callingServiceOne">
<description>First Service Called From The Controller</description>
<implements service="learningInterface"/>
<attribute name="planetId" type="String" mode="IN" optional="false"/>
</service>

Synchronous and Asynchronous Services

The service engine allows us to invoke services synchronously or asynchronously. A synchronous service will be invoked in the same thread, and the thread will "wait" for the invoked service to complete before continuing. The calling service can obtain information from the synchronously run service, meaning its OUT parameters are accessible.

Asynchronous services run in a separate thread and the current thread will continue without waiting. The invoked service will effectively start to run in parallel to the service or event from which it was called. The current thread can therefore gain no information from a service that is run asynchronously. An error that occurs in an asynchronous service will not cause a failure or error in the service or event from which it is called.

A good example of an asynchronously called service is the sendOrderConfirmation service that creates and sends an order confirmation email. Once a customer has placed an order, there is no need to wait while the mail service is called and the mail sent. The mail server may be down, or busy, which may result in an error that would otherwise stop our customer form placing the order. It is much more preferable to allow the customer to continue to the Order Confirmation page and have our business receive the valuable order. By calling this service asynchronously, there is no delay to the customer in the checkout process, and while we log and fix any errors with the mail server, we still take the order.

Behind the scenes, an asynchronous service is actually added to the Job Scheduler. It is the Job Scheduler's task to invoke services that are waiting in the queue.

Using the Job Scheduler

Asynchronous services are added to the Job Scheduler automatically. However, we can see which services are waiting to run and which have already been invoked through the Webtools console. We can even schedule services to run once only or recur as often as we like.

Open up the Webtools console at https://localhost:8443/webtools/control/main and take a look under the Service Engine Tools heading. Select Job List to view a full list of jobs. Jobs without a Start Date/Time have not started yet. Those with an End Date/Time have completed. The Run Time is the time they are scheduled to run. All of the outstanding jobs in this list were added to the JobSandbox Entity when the initial seed data load was performed, along with the RecurrenceRule (also an Entity) information specifying how often they should be run. They are all maintenance jobs that are performed "offline".

The Pool these jobs are run from by default is set to pool. In an architecture where there may be multiple OFBiz instances connecting to the same database, this can be important. One OFBiz instance can be dedicated to performing certain jobs, and even though job schedulers may be running on each instance, this setting can be changed so we know only one of our instances will run this job.

The Service Engine settings can be configured in frameworkserviceconfigserviceengine.xml. By changing both the send-to-pool attribute and the name attribute on the <run-from-pool element>, we can ensure that only jobs created on an OFBiz instance are run by this OFBiz instance.

Click on the Schedule Job button and in the Service field enter learningCallingServiceOne, leave the Pool as pool and enter today's date/time by selecting the calendar icon and clicking on today's date. We will need to add 5 minutes onto this once it appears in the box. In the below example the Date appeared as 2008-06-18 14:11:24.265. This job is only going to be scheduled to run once, although we could specify any recurrence information we wish.

Select Submit and notice that scheduler is already aware of the parameters that can (or must, in this case) be entered. This information has been taken from the service definition in our services.xml file.

Apache OFBiz Service Engine: Part 2

Press Submit to schedule the job and find the entry in the list. This list is ordered by Run Time so it may not be the first. Recurring maintenance jobs are imported in the seed data and are scheduled to run overnight. These will more than likely be above the job we have just scheduled since their run-time is further in the future. The entered parameters are converted to a map and then serialized to the database. They are then fed to the service at run time.

Quickly Running a Service

Using the Webtools console it is also possible to run a service synchronously. This is quicker than going through the scheduler should you need to test a service or debug through a service. Select the Run Service button from the menu and enter the same service name, submit then enter the same parameters again. This time the service is run straight away and the OUT parameters and messages are passed back to the screen:

Apache OFBiz Service Engine: Part 2

Naming a Service and the Service Reference

Service names must be unique throughout the entire application. Because we do not need to specify a location when we invoke a service, if service names were duplicated we can not guarantee that the service we want to invoke is the one that is actually invoked. OFBiz comes complete with a full service reference, which is in fact a dictionary of services that we can use to check if a service exists with the name we are about to choose, or even if there is a service already written that we are about to duplicate.

From https://localhost:8443/webtools/control/main select the Service Reference and select "l" for learning. Here we can see all of our learning services, what engine they use and what method they invoke. By selecting the service learningCallingServiceOne, we can obtain complete information about this service as was defined in the service definition file services.xml. It even includes information about the parameters that are passed in and out automatically.

Careful selection of intuitive service names and use of the description tags in the service definition files are good practice since this allows other developers to reuse services that already exists, rather than duplicate work unnecessarily.

Event Condition Actions (ECA)

ECA refers to the structure of rules of a process. The Event is the trigger or the reason why the rule is being invoked. The condition is a check to see if we should continue and invoke the action, and the action is the final resulting change or modification. A real life example of an ECA could be "If you are leaving the house, check to see if it is raining. If so, fetch an umbrella". In this case the event is "leaving the house". The condition is "if it is raining" and the action is "fetch an umbrella".

There are two types of ECA rules in OFBiz: Service Event Condition Actions (SECAs) and Entity Event Condition Actions (EECAs).

Service Event Condition Actions (SECAs)

For SECAs the trigger (Event) is a service being invoked. A condition could be if a parameter equalled something (conditions are optional), and the action is to invoke another service.

SECAs are defined in the same directory as service definitions (servicedef). Inside files named secas.xml

Take a look at the existing SECAs in applicationsorderservicedefsecas.xml and we can see a simple ECA:

<eca service="changeOrderStatus" event="commit" run-on-error="false">
<condition field-name="statusId" operator="equals" value="ORDER_CANCELLED"/>
<action service="releaseOrderPayments" mode="sync"/>
</eca>

When the changeOrderStatus transaction is just about to be committed, a lookup is performed by the framework to see if there are any ECAs for this event. If there are, and the parameter statusId is ORDER_CANCELLED then the releaseOrderPayments service is run synchronously.

Most commonly, SECAs are triggered on commit or return; however, it is possible for the event to be in any of the following stages in the service's lifecycle:

  • auth—Before Authentication
  • in-validate—Before IN parameter validation
  • out-validate—Before OUT parameter validation
  • invoke—Before service invocation
  • commit—Just before the transaction is committed
  • return—Before the service returns
  • global-commit
  • global-rollback

The variables global-commit and i are a little bit different. If the service is part of a transaction, they will only run after a rollback or between the two phases (JTA implementation) of a commit.

There are also two specific attributes whose values are false by default:

  • run-on-failure
  • run-on-error

You can set them to true if you want the SECA to run in spite of a failure or error. A failure is the same thing as an error, except it doesn't represent a case where a rollback is required.

It should be noted that parameters passed into the trigger service are available, if need be, to the action service. The trigger services OUT parameters are also available to the action service.

Before using SECAs in a component, the component must be informed of the location of the ECA service-resources:

<service-resource type="eca" loader="main" location="servicedef/secas.xml"/>

This line must be added under the existing <service-resource> elements in the component's ofbiz-component.xml file.

Entity Event Condition Actions (EECAs)

For EECAs, the event is an operation on an entity and the action is a service being invoked.

EECAs are defined in the same directory as entity definitions (entitydef): inside files named eecas.xml.

They are used when it may not necessarily be a service that has initiated an operation on the entity, or you may wish that no matter what service operates on this entity, a certain course of action to be taken.

Open the eecas.xml file in the applicationsproductentitydef directory and take a look at the first <eca> element:

<eca entity="Product" operation="create-store" event="return">
<condition field-name="autoCreateKeywords" operator="not-equals" value="N"/>
<action service="indexProductKeywords" mode="sync" value-attr="productInstance"/>
</eca>

This ECA ensures that once any creation or update operation on a Product record has been committed, so long as the autoCreateKeywords field of this record is not N, then the indexProductKeywords service will be automatically invoked synchronously.

The operation can be any of the following self-explanatory operations:

  • create
  • store
  • remove
  • find
  • create-store (create or store/update)
  • create-remove
  • store-remove
  • create-store-remove
  • any

The return event is by far the most commonly used event in an EECA. But there are also validate, run, cache-check, cache-put, and cache-clear events. There is also the run-on-error attribute.

Before using EECAs in a component, the component must be informed of the location of the eca entity-resource:

<entity-resource type="eca" loader="main" location="entitydef/eecas.xml"/>

must be added under the existing <entity-resource> elements in the component's ofbiz-component.xml file.

ECAs can often catch people out! Since there is no apparent flow from the trigger to the service in the code they can be difficult to debug. When debugging always keep an eye on the logs. When an ECA is triggered, an entry is placed into the logs to inform us of the trigger and the action.

Summary

This brings us to the end of our investigation into the OFBiz Service Engine. We have discovered how useful the Service Oriented Architecture in OFBiz can be and we have learnt how the use of some of the built in Service Engine tools, like the Service Reference, can help us when we are creating new services.

In this article we have looked at:

  • Calling services from code (using dispatcher).
  • IN/OUT parameter mismatch when calling services
  • Sending feedback; standard return codes success, error and fail.
  • Implementing Service Interfaces
  • Synchronous and asynchronous services
  • Using the Service Engine tools
  • ECAs: Event Condition Actions
Apache OFBiz Development: The Beginner's Tutorial Using Services, Entities, and Widgets to build custom ERP and CRM systems
Published: October 2008
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

About the Author :


Jonathon Wong

Jonathan Wong Jong Hann is an avid puzzle solver. He is constantly in search
of new problems to take apart. He has dabbled in Rubik's Cubes, maze navigation
algorithms, and various other logical and mechanical puzzles.

He had taken apart OFBiz within a month for a client who wanted to evaluate it.
Having mapped out the architectural structure of OFBiz, he also embarked on
documenting the functional and ERP-specifi c aspects of OFBiz. Within the following
six months, he had completed three small-scale projects with OFBiz. He is currently
using OFBiz in almost every new project, leveraging OFBiz's advantage for rapid
prototyping and development.

He fi rst delved into Java some 10 years ago. He has since specialized in clean
programming structures, design patterns, and parallel computing. Since then, he also
picked up the hobby of reverse engineering various open source software to equip
his employers and himself with new technologies. Jonathon has also worked for
clients who needed to take apart legacy systems to make corrections.

Rupert Howell

Rupert Howell, while developing Java applications for the UK's Offi ce for
National Statistics, stumbled upon Open For Business and has been working with
the framework ever since. Since early 2003, Rupert has been a consultant to some
of the UK's largest OFBiz implementations and has helped some major retailers
successfully migrate their entire ERP systems to OFBiz.

Rupert holds a Master's degree in Mechanical Engineering and is a Director
of Provolve Ltd, a company specializing in OFBiz-based solutions. For more
information see the Provolve website at www.provolve.com.

Books From Packt

Flex 3 with Java
Flex 3 with Java

Pentaho Reporting 3.5 for Java Developers
Pentaho Reporting 3.5 for Java Developers

Drools JBoss Rules 5.0 Developer's Guide
Drools JBoss Rules 5.0 Developer's Guide

Plone 3 Theming
Plone 3 Theming

Scratch 1.3: Beginner’s Guide
Scratch 1.3: Beginner’s Guide

Spring Web Flow 2 Web Development
Spring Web Flow 2 Web Development

Magento 1.3 Theme Design
Magento 1.3 Theme Design

Grails 1.1 Web Application Development
Grails 1.1 Web Application Development

No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
e
S
b
s
S
L
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software