Minilang and OFBiz

0
99
10 min read

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:

 
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  
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  
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.

LEAVE A REPLY

Please enter your comment!
Please enter your name here