Minilang and OFBiz

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 | November 2008 | Java Open Source

Minilang can help developers to reduce the time it takes to implement simple and repetitive tasks. Code does not need to be compiled and can therefore be implemented faster, breaking the typical Java code-compile-test cycle. Minilang gives the advantage of being able to change the code without a restart of the application. A simple browser refresh is enough to see the changes.

It is more "plain English" than Java code and is simpler to read and therefore easier to understand and maintain by people who may be unfamiliar with the system.

The main reason for Minilang's existence is to facilitate simple operations, notably CRUD operations and to validate and manipulate data. It should not be much used outside of this scope, but within this scope, it excels.

In this article by Jonathon Wong and Rupert Howell we will be looking at:

  • Minilang syntax and schema
  • Defining and creating a "Simple Service" using Minilang
  • Simple events
  • Validating and converting fields using the simple-map-processor
  • Security in Minilang
  • Invoking other services, methods, events, and BeanShell from Minilang
  • Using Minilang in screen widgets

What is Minilang?

The syntax of Minilang is simply well formed XML. Developers write XML that obeys a defined schema, this XML is then parsed by the framework and commands are executed accordingly. It is similar in concept to the Gang of Four Interpreter Pattern.

We can therefore consider Minilang's XML elements to be "commands".

Minilang is usually written in a simple method's XML file, which is specified at the top of the document like this:

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:noNamespaceSchemaLocation="http://ofbiz.apache.org/dtds/ simple-methods.xsd">

Although Minilang's primary use is to code services and events, concepts from Minilang are also used to prepare data for screen widgets.

Much of the simplicity of Minilang arises from the fact that variables are magically there for us to use. They do not have to be explicitly obtained, they are placed in the environment and we can take them as we wish. Should we wish to create a Map, we just use it, the framework will take care of its creation. For example:

<set field="tempMap.fieldOne" from-field="parameters.fieldOne"/>

will set the value of the fieldOne parameter to tempMap. If tempMap has already been used and is available, this will be added. If not, the Map will be created and the value added to the key fieldOne.

Tools to Code XML

Minilang is coded in XML and before it can be successfully parsed by the framework's XML parser, this XML must be well formed. Trying to code Minilang in a plain text editor like Notepad is not a wise move. Precious time can be wasted trying to discover a simple mistake such as a missing closing tag or a misspelled element. For this reason, before attempting to code Minilang services, make sure that you have installed some kind of XML editor and preferably one with an auto-complete feature. The latest versions of Eclipse come packaged with one and XML files are automatically associated to use this editor. Alternatively there are many editors available to download of varying functionality and price. For example XML Buddy (http://www.xmlbuddy.com), oXygen XML Editor (http://www.oxygenxml.com), or the heavyweight Altova XMLSpy (http://www.altova.com)

Defining a Simple Service

Minilang services are referred to as "simple" services. They are defined and invoked in the same way as a Java service. They can be invoked by the control servlet from the controller.xml file or from code in the same way as a Java service.

In the following example we will write a simple service that removes Planet Reviews from the database by deleting the records.

First open the file ${component:learning}widgetLearningForms.xml and find the PlanetReviews Form Widget. This widget displays a list of all reviews that are in the database.

Inside this Form Widget, immediately under the update field element add:

<field name="delete"><hyperlink target="RemovePlanetReview?reviewId=${reviewId}" 
description="Delete"/></field>

Our list will now also include another column showing us a hyperlink we can click, although clicking it now will cause an error. We have not added the request-map to handle this request in the controller.xml. It will be added a little later.

Defining the Simple Service

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

<service name="learningRemovePlanetReview" engine="simple" 
location="org/ofbiz/learning/learning/LearningServices.xml" invoke="removePlanetReview">
<description>Service to remove a planet review</description>
<attribute name="reviewId" type="String" mode="IN" optional="false"/>
</service>

Note that the engine type is simple.

It is a common practice to group service definitions into their own XML file according to behavior. For instance, we may see that all services to do with Order Returns are in a file called services_returns.xml. So long as we add the <service-resource> element to the parent component's ofbiz-component.xml file and let the system know that this service definition file needs to be loaded, we can structure our service definitions sensibly and avoid huge definition files.

It is not a common practice, however, to group service definitions by type. The type is abstracted from the rest of the system. When the service is invoked, the invoker doesn't care what type of service it is. It could be Java, it could be a simple service, it doesn't matter. All that matters is that the correct parameters are passed into the service and the correct parameters are passed out. For this reason, simple service definitions are found in the same XML files as Java service definitions.

Writing the Simple Method

Simple Method XML files belong in the component's script folder. In the root of ${component:learning} create the nested directory structure scriptorgofbizlearninglearning and in the final directory create a new file called LearningServices.xml.

Before we add anything to this file we must make sure that the script directory is on the classpath. Open the file ${component:learning}ofbiz-component.xml and if it is not already there add

<classpath type="dir" location="script"/>

immediately underneath the other classpath elements.

The location specified in the service definition can now be resolved.

In our newly created file LearningServices.xml add the following code:

<simple-methods xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:noNamespaceSchemaLocation="http://www.ofbiz.org/dtds/ simple-methods.xsd">
<simple-method method-name="removePlantetReview" short-description="Delete a Planet Review">
<entity-one entity-name="PlanetReview" value-name="lookedUpValue"/>
<remove-value value-name="lookedUpValue"/>
</simple-method>
</simple-methods>

Finally all that is left is to add the request-map to the controller.xml:

<request-map uri="RemovePlanetReview"> 
<security auth="true" https="true"/>
<event type="service" invoke="learningRemovePlanetReview"/>
<response name="success" type="view" value="ListPlanetReviews"/>
<response name="error" type="view" value="ListPlanetReviews"/>
</request-map>

Since we have added a new service definition OFBiz must be restarted. A compilation is not needed. Restart and fire an http request ListPlanetReviews to webapp learning:

Minilang and OFBiz

Selecting Delete will delete this PlanetReview record from the database.

Let's take a closer look at the line of code in the simple service that performs the lookup of the record that is to be deleted.

<entity-one entity-name="PlanetReview" value-name="lookedUpValue"/>

This command will perform a lookup on the PlanetReview entity. Since the command is <entity-one> the lookup criteria must be the primary key.

This code is equivalent in Java to:

GenericValue lookedUpValue = delegator.findByPrimaryKey 
("PlanetReview", UtilMisc.toMap("reviewId", reviewId));

Already we can see that Minilang is less complicated. And this is before we take into account that the Java code above is greatly simplified, ignoring the fact that the delegator had to be taken from the DispatchContext, the reviewId had to be explicitly taken from the context Map and the method call had to be wrapped in a try/catch block.

In Minilang, when there is a look up like this, the context is checked for a parameter with the same name as the primary key for this field, as specified in the entity definition for PlanetReview. If there is one, and we know there is since we have declared a compulsory parameter reviewId in the service definition, then the framework will automatically take it from the context. We do not need to do anything else.

Simple Events

We can call Minilang events, in the same way that we called Java events from the controller.xml. Just as Minilang services are referred to as simple services, the event handler for Minilang events is called "simple".

Tell the control servlet how to handle simple events by adding a new <handler> element to the learning component's controller.xml file, immediately under the other <handler> elements:

<handler name="simple" type="request" class="org.ofbiz.webapp.event.SimpleEventHandler"/>

A common reason for calling simple events would be to perform the preparation and validation on a set of parameters that are passed in from an XHTML form. Don't forget that when an event is called in this way, the HttpServletRequest object is passed in! In the case of the Java events, it is passed in as a parameter. For simple events, it is added to the context, but is nonetheless still available for us to take things from, or add things onto.

In the same location as our LearningServices.xml file (${component:learning} scriptorgofbizlearninglearning) create a new file called LearningEvents.xml.

To this file add one <simple-method> element inside a <simple-methods> tag:

<simple-methods xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:noNamespaceSchemaLocation="http://www.ofbiz.org/dtds/ simple-methods.xsd">
<simple-method method-name="simpleEventTest" short-description="Testing a simple Event">
<log level="info" message="Called the Event: simpleEventTest"/>
</simple-method>
</simple-methods>

Finally, we need to add a request-map to the controller from where this event will be invoked:

<request-map uri="SimpleEventTest"> 
<security auth=true»https=true/>
<event type=»simple»path=»org/ofbiz/learning/learning/
LearningEvents.xml»invoke=»simpleEventTest»/>
<response name=»success»type=»view»value=»SimplestScreen»/>
<response name=»error»type=»view»value=»SimplestScreen»/>
</request-map>

Notice our simple method doesn't actually do anything other than leave a message in the logs. It is with these messages that we can debug through Minilang.

Validating and Converting Fields

We have now met the Simple Methods Mini-Language, which is responsible for general processing to perform simple and repetitive tasks as services or events. Validation and conversion of parameters are dealt with by another type of Minilang—the Simple Map Processor. The Simple Map Processor takes values from the context Map and moves them into another Map converting them and performing validation checks en-route.

Generally, Simple Map Processors will prepare the parameters passed into a simple event from an HTML form or query string. As such, the input parameters will usually be of type String. Other object types can be validated or converted using the Simple Map Processor including: BigDecimals, Doubles, Floats, Longs, Integers, Dates, Times, java.sql.Timestamps, and Booleans.

The Simple Map Processors are, like simple methods, coded in XML and they adhere to the same schema (simple-methods.xsd). Open this file up again and search for The Simple Map Processor Section.

The naming convention for XML files containing Simple Map Processors is to end the name of the file with MapProcs.xml (For example, LearningMapProcs.xml) and they reside in the same directory as the Simple Services and Events.

One of the best examples of validation and conversion already existing in the code is to be found in the PaymentMapProcs.xml file in ${component:accounting}scriptorgofbizaccountingpayment. Open this file and find the simple-map-processor named createCreditCard. Here we can see that immediately, the field expireDate is created from the two parameters expMonth and expYear with a "/" placed in between (example, 09/2012):

<make-in-string field="expireDate"> 
<in-field field="expMonth"/>
<constant>/</constant>
<in-field field="expYear"/>
</make-in-string>

Towards the end of the <simple-map-processor> this expireDate field is then copied into the returning Map and validated using isDateAfterToday. If the expiration date is not after today, then the card has expired and instead, a fail-message is returned.

<process field="expireDate"> 
<copy/>
<validate-method method="isDateAfterToday">
<fail-message message="The expiration date is before today"/>
</validate-method>
</process>

The <validate-method> element uses a method called isDateAfterToday. This method is in fact a Java static method found in the class org.ofbiz.base.util.UtilValidate.

We have already been using one of the OFBiz utility classes, UtilMisc, namely the toMap function, to create for us Maps from key-value pairs passed in as parameters. OFBiz provides a huge number of incredibly useful utility methods, ranging from validation, date preparation, and caching tools to String encryption and more.

The framework will automatically allow Minilang access to this class. By adding a bespoke validation method into this class and recompiling, you will be able to call it from the <validate-method> in Minilang, from anywhere in your application.

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:

Validating a Simple Event Example

Let's use a Simple Event to create a new Planet record and check that the input parameter is not empty. If it is, then return a failure message to the user.

Create a new file named LearningMapProcs.xml in ${component:learning}scriptorgofbizlearninglearning and add to it:

<simple-map-processors xmlns:xsi="http://www.w3.org/2001/ XMLSchema-instance" 
xsi:noNamespaceSchemaLocation="http://www.ofbiz.org/dtds/ simple-methods.xsd">
<simple-map-processor name="createPlanet">
<process field="planetId"><copy/><not-empty>
<fail-message message="Planet Id Cannot Be Empty" /> </not-empty></process>
<process field="planetName"><copy/><not-empty>
<fail-message message="Planet Name Cannot Be Empty" /> </not-empty></process>
</simple-map-processor>
</simple-map-processors>

In LearningEvents.xml add a new simple method:

<simple-method method-name="createPlanet" short-description="Creating a Planet"> 
<call-map-processor xml-resource="org/ofbiz/learning/ learning/LearningMapProcs.xml"
processor-name="createPlanet" in-map-name="parameters" out-map-name="context"/>
<check-errors/>
<make-value value-name="newEntity" entity-name="Planet"/>
<set-pk-fields map-name="context" value-name="newEntity"/>
<set-nonpk-fields map-name="context" value-name="newEntity"/>
<create-value value-name="newEntity"/>
</simple-method>

The <set-pk-fields> checks the context map returned from the createPlanet. function Simple Map Processor checks for any values with a key that have the same name as the primary key of the GenericValue object newEntity and sets the value to this newEntity. In the previous step the <make-value> element created a GenericValue object in memory from the Planet entity.

Likewise the <set-nonpk-fields> performs the same action for all other fields. In this case our entity only has two fields. It could be the case that the entity has 20 or more fields. This saves us from explicitly transferring each field from one map to another.

Finally the <create-value> element creates a record in the database and persists the populated newEntity object.

We must now go through the necessary steps of creating screen widgets, Form Widgets and adding request-maps and view-maps to the controller.xml file of our component.

To LearningScreens.xml add:

<screen name="Planets"> 
<section>
<actions><entity-condition entity-name="Planet" list-name="planets"/></actions>
<widgets>
<decorator-screen name="main-decorator" location="${parameters.mainDecoratorLocation}">
<decorator-section name="body">
<include-form name="CreatePlanet" location="component://learning/widget/
learning/ LearningForms.xml"/>
<include-form name="Planets" location="component://learning/widget/
learning/ LearningForms.xml"/>
</decorator-section>
</decorator-screen>
</widgets>
</section>
</screen>

To LearningForms.xml add:

<form name="CreatePlanet" type="single" target="CreatePlanet"> 
<field name="planetId"><text/></field>
<field name="planetName"><text/></field>
<field name="submit" title="Create Planet"><submit/></field>
</form>
<form name="Planets" type="list" target="Planet" list-name="planets">
<field name="planetId"><display/></field>
<field name="planetName"><display/></field>
</form>

and finally to the learning component's controller.xml file add the request-maps:

<request-map uri="Planets"> 
<security auth=false»https=false/>
<response name="success" type="view" value="Planets"/>
<response name="error" type="view" value="Planets"/>
</request-map>
<request-map uri="CreatePlanet">
<event type="simple" path="org/ofbiz/learning/learning/
LearningEvents.xml" invoke="createPlanet"/>
<response name="success" type="view" value="Planets"/>
<response name="error" type="view" value="Planets"/>
</request-map>

and the single view-map pointing to our screen:

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

Without restarting, fire an OFBiz http request Planets to component learning.

Try entering just a Planet Id or just a Planet Name and see that the validation fails in the Simple Map Processor and the fail-message is returned:

Minilang and OFBiz

Finally add a value into both fields and see that the event is executed and the new record is created.

By default the responses success or error are returned from simple methods. In the above example, the <check-errors> element checked the response of the Simple Map Processor and returned error if the validation had failed.

Just like the return String in a Java event, these responses determine the action of the request-map, usually, which view-map to use. To change the default response code simply add:

<set value="somethingelse" field="_response_code_"/>

which will correspond to the request-map response element:

Checking Security in Minilang

Permissions can be checked in Minilang by using <if-has-permissions>. Since the ecommerce component allows customers to edit and view their own orders, obviously checks need to be made to be sure that customers are not viewing or editing other people's orders. However, we also want customer services staff to be able to edit orders using the same code. Consider the following code:

<if> 
<condition>
<and>
<not><if-has-permission permission="ORDERMGR" action="_VIEW"/></not>
<if-compare-field field-name="parameters.partyId" operator="not-equals"
to-field-name="userLogin.partyId"/>
</and>
</condition>
<then>
<string-to-list string="To get order summary information you must have the
ORDERMGR_VIEW permission, or be logged in as the party to get the summary information
for." list-name="error_list"/>
</then>
</if>
<check-errors/>

This checks for the ORDERMGR_VIEW permission (our customer services staff). If The user login does not have this permission and the partyId of the user login is not equal to the one passed in as a parameter then an error is raised. This error will not cause the Minilang to return until the error is checked for in the <check-errors/> command.

Invoking from Minilang

Other services and events can be invoked from both simple services and simple events; again, there is a direct correlation with how other services are invoked from within Java, because ultimately, Minilang is using the Java code.

Calling Services from Minilang

In Java we see that a Map is passed into the service containing all of the service parameters, and a Map is returned:

Map resultMap = dispatcher.runSync("ourService", contextMap);

In Minilang, we pass a Map in and a Map is passed out, however, the contents of the Map are directly accessible:

<call-service service-name="ourService" in-map-name="contextMap"> 
<result-to-request result-name="exampleId"/>
</call-service>

This code calls the service ourService with the contextMap and takes the exampleId OUT parameter from ourService and adds it as an attribute on the request. The userLogin and locale objects are automatically added to the contextMap. They do not have to be explicitly set.

Calling Simple Methods

The <call-simple-method> command can be thought of as "including" the called simple method code. It is as if the called method code is copied and pasted into the parent simple methods. The environment and context are available to the called simple method:

<call-simple-method method-name="inlineMethod"/>

Calling Java Methods

The <call-class-method> tag allows us to call static Java methods. For example, imagine we wished to directly call the UtilValidate.isDateAfterToday method we looked at in the previous example:

<call-class-method class-name="org.ofbiz.base.util.UtilValidate" 
method-name="isDateAfterToday"
ret-field-name="booleanIsDateAfterToday">
<field field-name="expiryDate" type="String"/>
</call-class-method>

Calling BeanShell

The <call-bsh> tag allows us to put line script into the Minilang for more complicated processing. For example, the following code calculates the promised date of an order and requires manipulation of a Timestamp.

<set field="daysToShip" from-field="productFacility.daysToShip"/>
<if-empty field-name="daysToShip">
<set field="daysToShip" value="30" type="Long"/>
</if-empty>
<call-bsh><![CDATA[
java.sql.Timestamp orderDate = orderHeader.getTimestamp("orderDate");
java.util.Calendar cal = java.util.Calendar.getInstance();
cal.setTimeInMillis(orderDate.getTime());
cal.add(java.util.Calendar.DAY_OF_YEAR, daysToShip.intValue());
return org.ofbiz.base.util.UtilMisc.toMap("promisedDatetime",
new java.sql.Timestamp(cal.getTimeInMillis()));
]]></call-bsh>
<set from-field="promisedDatetime" field="reserveOisgirMap.promisedDatetime"/>

Notice how the daysToShip variable is accessible to the inline bsh, and how the promisedDateTime is returned in a Map from the inline bsh and is available in the Minilang to be set to the Map reserveOisgirMap.

Minilang in Screen Widgets

Minilang concepts are used in screen widgets to prepare data to display on the screen. Minilang commands can be placed within the Screen Widget's <actions>. In the Planets example earlier in this chapter we can see the action is:

<entity-condition entity-name="Planet" list-name="planets"/>

This command performs the lookup on the Planet entity with no conditions. All records are therefore returned to the List planets.

This planets variable is available to all sub-screen and Form Widgets of this Screen Widget. It is therefore available for the Form Widget Planets, which iterates through each entry in the list of GenericValues and displays the planetId and planetName fields.

Aside from entity lookups, services can also be invoked from the screen's actions, for example:

<service service-name="findParty" auto-field-map="parameters"/>

invokes the findParty service, automatically populating the input context map from whatever parameters have been passed in from the request. The OUT parameters from the service are available to use in the sub-screen and Form Widgets.

There are literally hundreds of existing usages of this concept within OFBiz. It dramatically reduces the need for unnecessary BeanShell action files to prepare data.

Summary

With Minilang, the developer must make a choice. Some developers choose not to increase the already steep learning curve associated with the framework by learning the syntax by heart. Since everything that is possible in Minilang is possible in Java and the fact that Java offers limitless possibilities, while Minilang is limited, many developers choose never to develop in Minilang. However, it is undisputedly easier to code quick and simple services in Minilang than in Java. Especially if you use an auto-completion XML editor. When designing their code, the developer should decide how complex their service is going to be and decide whether to use Java or Minilang.

Whatever the individual preference of the developer, many OFBiz contributors know Minilang intimately and are comfortable coding quite complex services in it. There are many examples existing in the code, and as OFBiz becomes easier and easier to customize, its usage is becoming more and more widespread. To understand the flow through the framework, at the very least a basic knowledge of Minilang must be gained.

We should now have built up enough knowledge to find our way through the code from request to output.

Now is a good time to put that to the test!

Open up any of the OFBiz components and click around, enter some details in some forms. Take the request and follow it all the way through the code, looking at any services that are called and understand what they do. This is after all the best way to learn OFBiz!

 

 

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

Liferay Portal Enterprise Intranets
Liferay Portal Enterprise Intranets

ZK Developer’s Guide
ZK Developer’s Guide

EJB 3 Developer Guide
EJB 3 Developer Guide

DWR Java AJAX Applications
DWR Java AJAX Applications

Java EE 5 Development with NetBeans 6
Java EE 5 Development with NetBeans 6

Learning Ext JS
Learning Ext JS

Service Oriented Java Business Integration
Service Oriented Java Business Integration

Openfire Administration
Openfire Administration

 


Your rating: None Average: 4.8 (4 votes)
post by
excellent and very use full for ofbiz devlopers
very gooooooooooooooood by
very gooooooooooooooood ........

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
N
W
N
a
u
5
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