Apache OFBiz Service Engine: Part 1

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

€23.99    €12.00
by Jonathon Wong Rupert Howell | June 2009 | Java Open Source

In this two-part article by Jonathon Wong and Rupert Howell, we will be exploring the Service Engine. Services in OFBiz operate in a Service Oriented Architecture (SOA). These services not only have the ability to invoke other services internally, but can also be 'opened up' and invoked by remote applications using, amongst other methods, the widely adopted messaging protocol SOAP.

Besides serving as a platform for interoperability, OFBiz services also offer us additional capability to organize our code. The traditional organizational strategies in object-oriented Java were a great improvement over the procedural paradigm. Wrapping both methods and variables together into objects to form a powerful "behavioral model" for code organization (where object's methods and variables define their behavior). Similarly with OFBiz services we are able to bundle groups of behavior together to form a coherent "service". We can say that OFBiz services, in terms of code or software organization, operate at a higher level than Java object-oriented organizational strategies.

In this part, we will be looking at:

  • Defining and creating a Java service
  • Service parameters
  • Special unchecked (unmatched) IN/OUT parameters
  • Security-related programming

Defining a Service

We first need to define a service. Our first service will be named learningFirstService.

In the folder ${component:learning}, create a new folder called servicedef. In that folder, create a new file called services.xml and enter into it this:

<?xml version="1.0" encoding="UTF-8" ?>

<services xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.ofbiz.org/dtds/services.xsd">
<description>Learning Component Services</description>

<service name="learningFirstService" engine="java"
location="org.ofbiz.learning.learning.LearningServices" invoke="learningFirstService">
<description>Our First Service</description>
<attribute name="firstName" type="String" mode="IN" optional="true"/>
<attribute name="lastName" type="String" mode="IN" optional="true"/>
</service>
</services>

In the file ${component:learning}ofbiz-component.xml, add after the last <entity-resource> element this:

<service-resource type="model" loader="main" location="service	def/services.xml"/>

That tells our component learning to look for service definitions in the file ${component:learning}servicedefservices.xml.

It is important to note that all service definitions are loaded at startup; therefore any changes to any of the service definition files will require a restart!

Creating the Java Code for the Service

In the package org.ofbiz.learning.learning, create a new class called LearningServices with one static method learningFirstService:

package org.ofbiz.learning.learning;

import java.util.Map;

import org.ofbiz.service.DispatchContext;
import org.ofbiz.service.ServiceUtil;

public class LearningServices {

public static final String module = LearningServices.class.getName();

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

Map resultMap = ServiceUtil.returnSuccess("You have called on service
'learningFirstService' successfully!");
return resultMap;
}

}

Services must return a map. This map must contain at least one entry. This entry must have the key responseMessage (see org.ofbiz.service.ModelService.RESPONSE_MESSAGE), having a value of one of the following:

  • success or ModelService.RESPOND_SUCCESS
  • error or ModelService.RESPOND_ERROR
  • fail or ModelService.RESPOND_FAIL

By using ServiceUtil.returnSuccess() to construct the minimal return map, we do not need to bother adding the responseMessage key and value pair.

Another entry that is often used is that with the key successMessage (ModelService.SUCCESS_MESSAGE). By doing ServiceUtil.returnSuccess("Some message"), we will get a return map with entry successMessage of value "Some message". Again, ServiceUtil insulates us from having to learn the convention in key names.

Testing Our First Service

Stop OFBiz, recompile our learning component and restart OFBiz so that the modified ofbiz-component.xml and the new services.xml can be loaded.

In ${component:learning}widgetlearningLearningScreens.xml, insert a new Screen Widget:

<screen name="TestFirstService">
<section>
<widgets>
<section>
<condition><if-empty field-name="formTarget"/></condition>
<actions>
<set field="formTarget" value="TestFirstService"/>
<set field="title" value="Testing Our First Service"/>
</actions>
<widgets/>
</section>
<decorator-screen name="main-decorator" location="${parameters.mainDecoratorLocation}">
<decorator-section name="body">
<include-form name="TestingServices"
location="component://learning/widget/learning/LearningForms.xml"/>
<label text="Full Name: ${parameters.fullName}"/>
</decorator-section>
</decorator-screen>
</widgets>
</section>
</screen>

In the file ${component:learning}widgetlearningLearningForms.xml, insert a new Form Widget:

<form name="TestingServices" type="single" target="${formTarget}">
<field name="firstName"><text/></field>
<field name="lastName"><text/></field>
<field name="planetId"><text/></field>
<field name="submit"><submit/></field>
</form>

Notice how the formTarget field is being set in the screen and used in the form. For now don't worry about the Full Name label we are setting from the screen. Our service will eventually set that.

In the file ${webapp:learning}WEB-INFcontroller.xml, insert a new request map:

<request-map uri="TestFirstService">
<event type="service" invoke="learningFirstService"/>
<response name="success" type="view" value="TestFirstService"/>
</request-map>

The control servlet currently has no way of knowing how to handle an event of type service, so in controller.xml we must add a new handler element immediately under the other <handler> elements:

<handler name="service" type="request" class="org.ofbiz.webapp.event.ServiceEventHandler"/>
<handler name="service-multi" type="request" class="org.ofbiz.webapp.event.ServiceMultiEventHandler"/>

We will cover service-multi services later. Finally add a new view map:

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

Fire to webapp learning an http OFBiz request TestFirstService, and see that we have successfully invoked our first service:

Apache OFBiz Service Engine: Part 1

Service Parameters

Just like Java methods, OFBiz services can have input and output parameters and just like Java methods, the parameter types must be declared.

Input Parameters (IN)

Our first service is defined with two parameters:

<attribute name="firstName" type="String" mode="IN" optional="true"/>
<attribute name="lastName" type="String" mode="IN" optional="true"/>

Any parameters sent to the service by the end-user as form parameters, but not in the services list of declared input parameters, will be dropped. Other parameters are converted to a Map by the framework and passed into our static method as the second parameter.

Add a new method handleInputParamaters to our LearningServices class.

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

String firstName = (String)context.get("firstName");
String lastName = (String)context.get("lastName");
String planetId= (String)context.get("planetId");

String message = "firstName: " + firstName + "<br/>";
message = message + "lastName: " + lastName + "<br/>";
message = message + "planetId: " + planetId;

Map resultMap = ServiceUtil.returnSuccess(message);
return resultMap;
}

We can now make our service definition invoke this method instead of the learningFirstService method by opening our services.xml file and replacing:

<service name="learningFirstService" engine="java" 
location="org.ofbiz.learning.learning.LearningServices" invoke="learningFirstService">

with:

<service name="learningFirstService" engine="java" 
location="org.ofbiz.learning.learning.LearningServices" invoke="handleParameters">

Once again shutdown, recompile, and restart OFBiz.

Enter for fields First Name, Last Name, and Planet Id values Some, Name, and Earth, respectively. Submit and notice that only the first two parameters went through to the service. Parameter planetId was dropped silently as it was not declared in the service definition.

Apache OFBiz Service Engine: Part 1

Modify the service learningFirstService in the file ${component:learning}servicedefservices.xml, and add below the second parameter a third one like this:

<attribute name="planetId" type="String" mode="IN" optional="true"/>

Restart OFBiz and submit the same values for the three form fields, and see all three parameters go through to the service.

Apache OFBiz Service Engine: Part 1

 

 

Apache OFBiz Development: The Beginner's Tutorial Using Services, Entities, and Widgets to build custom ERP and CRM systems
Published: October 2008
eBook Price: €23.99
Book Price: €38.99
See more
Select your format and quantity:

Output Parameters (OUT)

Just like Java methods have return values (although Java methods can have only one typed return value), services can be declared with output parameters. When invoked as events from the controller, parameters will be silently dropped if they are not declared in our service's definition. Add this to our service definition:

<attribute name="fullName" type="String" mode="OUT" optional="true"/>

And in the method handleParameters in org.ofbiz.learning.learning.LearningServices replace:

Map resultMap = ServiceUtil.returnSuccess(message);
return resultMap;

with

Map resultMap = ServiceUtil.returnSuccess(message);
resultMap.put("fullName", firstName + " " + lastName);
return resultMap;

We have now added the fullName parameter to the resultMap. To see this in action we need to create a new screen widget in LearningScreens.xml:

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

Add the request-map to the controller.xml file:

<request-map uri="TestFirstServiceOutput">
<event type="service" invoke="learningFirstService"/>
<response name="success" type="view" value="TestFirstServiceOutput"/>
</request-map>

and finally the view-map:

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

Stop OFBiz, rebuild our Learning Component and restart, fire an OFBiz http request TestFirstServiceOutput to webapp learning. Submit your first and last names and planet and notice that now the fullName parameter has been populated.

Apache OFBiz Service Engine: Part 1

Two Way Parameters (INOUT)

A service may change the value of an input parameter and we may need a calling service to be aware of this change. To save us declaring the same parameter twice, with a mode for IN and a mode for OUT, we may use the mode INOUT.

<attribute name="fullName" type="String" mode="INOUT" optional="true"/>

Special Unchecked Parameters

There are a few special cases where IN/OUT parameters can exist even though the service definition does not declare them. They are:

  • responseMessage
  • errorMessage
  • errorMessageList
  • successMessage
  • successMessageList
  • userLogin
  • locale

The parameters responseMessage, errorMessage, errorMessageList, successMessage and successMessageList are necessary placeholders for feedback messages. They must be allowed through all validation checks.

The parameter userLogin is often required for authentication and permissions checks.

The parameter locale is needed just about everywhere in OFBiz. For locale-specificity in certain operations like retrieving template feedback messages, or like formatting numbers and currency figures.

Optional and Compulsory Parameters

The Service Engine checks the validity of the input and the output to ensure that what is coming into the service and is leaving adheres to the service definition. If the optional attribute is set to false and an expected parameter is missing, then the validation will fail and the service will return an error. This transaction will now be marked for rollback, meaning any changes to the database made during this transaction will never be committed. This could include any changes made to the database by calling services. For example:

<attribute name="fullName" type="String" mode="INOUT" optional="false"/>

Here the parameter fullName must be passed into the service and the service must also add this parameter to the resultMap and pass it out or validation will fail and an error will be thrown.

Try changing all of the optional flags on our newly created service to false. After a restart we should see:

Apache OFBiz Service Engine: Part 1

The DispatchContext

We have already seen how parameters are passed into our Java method as a Map. Just as the userLogin object of type GenericValue and the locale object of type Locale were added as attributes to the request for the Java events, both are now automatically added to this context map when the service is invoked in this way.

The first parameter, the DispatchContext, contains the remaining tools we need to access the database, or to invoke other services.

From our Java code we can get access to the following handy objects like this:

GenericValue userLogin = (GenericValue)context.get("userLogin");

Locale locale = (Locale)context.get("locale");

GenericDelegator delegator = dctx.getDelegator();

LocalDispatcher dispatcher = dctx.getDelegator();

Security security = dctx.getSecurity();

For a full list of objects that are available from the DispatchContext, take a look through the code in org.ofbiz.service.DispatchContext.

The service engine is in no way reliant on there being HTTPServletRequest or HTTPServletResponse objects available. Because of this we are able to invoke services outside of the web environment and they can be invoked remotely or scheduled to run "offline".

Service Security and Access Control

Security-related programming in services is exactly like that in events.

In the class org.ofbiz.learning.learning.LearningServices, create a new static method serviceWithAuth:

public static Map serviceWithAuth(DispatchContext dctx, Map context){	
Security security = dctx.getSecurity();
Map resultMap = null;
if (context.get("userLogin") == null ||
!security.hasPermission("LEARN_VIEW", (GenericValue)context.get("userLogin"))) {
resultMap = ServiceUtil.returnError("You have no access here. You're not welcome!");
}
else {
resultMap = ServiceUtil.returnSuccess("Welcome! You have access!");
}
return resultMap;
}

Ensure that the correct imports have been added to the class:

import java.util.Map;
import org.ofbiz.entity.GenericValue;
import org.ofbiz.security.Security;

In the file ${component:learning}servicedefservices.xml, add a new service definition:

<service name="learningServiceWithAuth" engine="java"
location="org.ofbiz.learning.learning.LearningServices" invoke="serviceWithAuth">
<description>Service with some security-related codes</description>
</service>

In the file ${webapp:learning}WEB-INFcontroller.xml, add a new request map:

<request-map uri="TestServiceWithAuth">
<security auth="true" https="true"/>
<event type="service" invoke="learningServiceWithAuth"/>
<response name="success" type="view" value="SimplestScreen"/>
<response name="error" type="view" value="login"/>
</request-map>

Rebuild and restart and then fire to webapp learning an http OFBiz request TestServiceWithAuth, login with username allowed password ofbiz, and see the welcome message displayed:

Apache OFBiz Service Engine: Part 1

Logging in with username denied password zibfo will show an error message. Thanks to the request-map's response element named error having a value of login, we are returned back to the login screen:

Apache OFBiz Service Engine: Part 1

Summary

In this article we have looked at:

  • Defining and creating services
  • Service parameters
  • Special unchecked (unmatched) IN/OUT parameters
  • Security-related programming

In the next part, we will look at Calling services from Java code, Implementing Service Interfaces, Synchronous and asynchronous services, Using the Service Engine tools, and 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: €23.99
Book Price: €38.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

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