|
|
Want to know more about Packt's Article Network? Interested in contributing your article ideas? Please visit our FAQ for more information. See More 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:
See More |
Minilang and OFBiz
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" 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 XMLMinilang 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 ServiceMinilang 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}" 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 ServiceIn the file ${component:learning}servicedefservices.xml add a new service definition: <service name="learningRemovePlanetReview" engine="simple" 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 MethodSimple 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" Finally all that is left is to add the request-map to the controller.xml: <request-map uri="RemovePlanetReview"> 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:
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 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 EventsWe 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" Finally, we need to add a request-map to the controller from where this event will be invoked: <request-map uri="SimpleEventTest"> 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 FieldsWe 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"> 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"> 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
Validating a Simple Event ExampleLet'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" In LearningEvents.xml add a new simple method: <simple-method method-name="createPlanet" short-description="Creating a Planet"> 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"> To LearningForms.xml add: <form name="CreatePlanet" type="single" target="CreatePlanet"> and finally to the learning component's controller.xml file add the request-maps: <request-map uri="Planets"> and the single view-map pointing to our screen: <view-map name="Planets" type="screen" page="component://learning/widget/ 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:
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: <request-map uri="SomeRequest"> Checking Security in MinilangPermissions 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> 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 MinilangOther 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 MinilangIn 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"> 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 MethodsThe <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 MethodsThe <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" Calling BeanShellThe <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"/> 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 WidgetsMinilang 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. SummaryWith 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
About the AuthorsJonathon 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-specific 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 first 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, while developing Java applications for the UK's Office 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 |
BROWSE
All Titles WordPress Web Services SOA BPEL Web Graphics & Video Web Development RAW Portugues, Espanol, Italiano PHP/MySQL Oracle Open Source Networking & Telephony Moodle Microsoft & .NET Linux Servers Joomla! JBoss Java e-Commerce Drupal CRM Content Management Beginner Guides Architecture and Analysis AJAX Future Titles Recently Published Titles |
| ||||||||