This tutorial will provide a more detailed overview of how data models and business logic is defined in Open For Business. It will show how to use beanshell with OFBiz.

Throughout this tutorial, click on any image to enlarge it.

Data Modeling with Entities

The data model of an application is a model of the physical "things" in the application, their properties, and their relationships. Typically, data models are implementetd either with SQL or objects or a combination of both.

OFBiz provides us with an "entity engine" which allows data models to be implemented at a higher level of abstraction. The data model is defined in an XML file, and OFBiz provides us with a set of generic APIs for working with the actual data: finding, creating, updating, and removing them. The entity engine thus allows us to:

The basic unit of the data model is an "entity," which is a discrete "thing" modeled in the data model. Here is an example of how an entity for information about a person is defined:

The entity is analogous to a SQL table or Java object, with various fields for specific attributes of a person. The definition, is however, at a more abstract level. The OFBiz entity engine will start with this definition and create the database tables, check the tables against the definitions over time, and modify tables if needed.

The types of the fields are defined in generic types, rather than specific SQL or Java types. OFBiz will then translate these fields to SQL types when working with the database and to Java types when working in Java. The exact translation is determined in a fieldtype file in the framework/entity/fieldtype/ directory: You can see the fieldtype definitions for all the different databases on the left and the actual field mappings for MySQL and Microsoft SQL Server on the right. Notice how different some of the SQL types can be.

The other important part of defining data models in OFBiz is creating relationships between entities. The entity engine lets you create explicit relationships between entities based on their keys:

Relationships can be one-to-one or one-to-many and can (but does not have to) involve referential integrity checks. Using these relationships allows you to go from one entity to another in your application without having to remember exactly which key was involved at all times. Should the keys used in a relationship change, it also avoids the need to make modifications throughout your application.

(Note: OFBiz can generate the foreign-key names automatically for you, but it is good practice to specify them yourself to avoid accidental key collisions, etc.)

In the OFBiz webtools application, you can take a look at all the entities available:

A Word about Beanshell

beanshell is a dynamic scripting language with Java syntax, and it is used extensively in OFBiz for view layer presentation data gathering. It can also be used for debugging, testing, and prototyping. In fact, entire services can be prototyped in beanshell and then used "as is" with the service engine or transformed to Java code when done.

When you start OFBiz, you may have noticed these lines on your console:

Httpd started on port: 9989
Sessiond started on port: 9990
23092 (main) [ BeanShellContainer.java:109:INFO ] Started BeanShell telnet service on 9989, 9990
23093 (main) [ BeanShellContainer.java:110:INFO ] NOTICE: BeanShell service ports are not secure. Please protect the ports
They tell you that OFBiz has opened a beanshell container on port 9990. (WARNING: protect or close this port on production systems.) You can telnet into this port and have full access to your OFBiz server, allowing you to test code and monitor events. I have a bshcontainer.bsh which, once placed in your ofbiz/ directory, can be called up with:
source("bshcontainer.bsh");
It will give you a delegator object for accessing the entity engine, a dispatcher for running services, and an admin UserLogin entity for services which require authentication.

I prefer to telnet into beanshell from Xemacs on Linux, which allows me to edit my beanshell commands. Unfortunately, I don't know of a good Windows telnet client. (If you do, please let me know.)

Working with Entities

Now let's work with our entities. Telnet into beanshell and (after some preliminary experiments) use the delegator object to access the Person entity. delegator is a GenericDelegator object with a set of methods for finding, creating, updating, and removing data. The delegator accepts parameters in the form of key-value pairs and translates them into actual SQL statements such as SELECT and INSERT. It will also observe the correct SQL syntax for the particular database you are using. Here is an example:

Above we had just found all Persons with a last name of "chen", and the delegator returned a List of just one. Now let's take a look at a particular person record:

bsh % me = persons.get(0);
bsh % print(me.getClass().getName());
org.ofbiz.entity.GenericValue
Each entity is an object of the GenericValue class, which itself is a sub-class of GenericEntity. GenericEntity provides us with methods to get and set values to fields, while GenericValue gives us methods for traversing through the data model with defined relationships.

Here is an example of how to get values from fields:

bsh % print(me.getString("firstName") + " " + me.get("lastName"));
print(me.getString("firstName") + " " + me.get("lastName"));
si chen
and for setting values to fields:
bsh % me.set("memberId", "11111");
me.set("memberId", "11111");
and storing the entity:
bsh % me.store();
Now let's retrieve it again and see if our change was effecive:
bsh % me2 = delegator.findByPrimaryKeyCache("Person", UtilMisc.toMap("partyId", "10010"));
bsh % print(me2.get("firstName") + " " + me2.get("lastName") + " " + me2.get("memberId"));
si chen 11111
Notice the different find method here--I was able to use the primary key field to get to the entity directly. The entity engine also caches values for you, so I used a cached find method this time.

You can also traverse from one entity to another. For example, there is a one-to-one relationship from Person to Party, so you can get the Party for a Person like so: bsh % print(me2.getRelatedOne("Party"));

[GenericEntity:Party][createdByUserLogin,null()][createdDate,2005-06-07 06:42:57.919(java.sql.Timestamp)]
[createdStamp,2005-06-07 06:42:58.113(java.sql.Timestamp)][createdTxStamp,2005-06-07 06:42:58.113(java.sql.Timestamp)]
[externalId,null()][lastModifiedByUserLogin,null()][lastModifiedDate,2005-06-07 06:42:57.919(java.sql.Timestamp)]
[lastUpdatedStamp,2005-06-07 06:42:58.113(java.sql.Timestamp)][lastUpdatedTxStamp,2005-06-07 06:42:58.113(java.sql.Timestamp)]
[partyId,10010(java.lang.String)][partyTypeId,PERSON(java.lang.String)]
Any Party can have many UserLogins, so you can go a step further to get the UserLogins for a Person through Party: bsh % print(me2.getRelatedOne("Party").getRelated("UserLogin")); print(me2.getRelatedOne("Party").getRelated("UserLogin"));
[[GenericEntity:UserLogin][createdStamp,2005-06-07 06:42:58.343(java.sql.Timestamp)]
[createdTxStamp,2005-06-07 06:42:58.113(java.sql.Timestamp)][currentPassword,(java.lang.String)]
[disabledDateTime,null()][enabled,null()][hasLoggedOut,null()]
[isSystem,null()][lastCurrencyUom,null()][lastLocale,null()][lastUpdatedStamp,2005-06-07 06:42:58.343(java.sql.Timestamp)]
[lastUpdatedTxStamp,2005-06-07 06:42:58.113(java.sql.Timestamp)][partyId,10010(java.lang.String)][passwordHint,null()]
[successiveFailedLogins,null()][userLoginId,sichen0607@graciousstyle.com(java.lang.String)]]
Finally, an unusually complicated one for getting the shipping address of a person:
bsh % print(me2.getRelatedOne("Party").getRelatedByAnd("PartyContactMechPurpose", UtilMisc.toMap("contactMechPurposeTypeId", "SHIPPING_LOCATION")).get(0).getRelatedOne("ContactMech").getRelatedOne("PostalAddress"));
[GenericEntity:PostalAddress][address1,11986 Foxboro Dr(java.lang.String)][address2,null()][attnName,null()]
[city,Los Angeles(java.lang.String)][contactMechId,10000(java.lang.String)]
[countryGeoId,USA(java.lang.String)]
[createdStamp,2005-06-07 06:42:58.911(java.sql.Timestamp)][createdTxStamp,2005-06-07 06:42:58.808(java.sql.Timestamp)]
[directions,null()][lastUpdatedStamp,2005-06-07 06:42:58.911(java.sql.Timestamp)]
[lastUpdatedTxStamp,2005-06-07 06:42:58.808(java.sql.Timestamp)]
[postalCode,90049(java.lang.String)][postalCodeExt,null()][postalCodeGeoId,null()]
[stateProvinceGeoId,CA(java.lang.String)][toName,si  chen(java.lang.String)]
Notice how you can also filter results from getting related entities.

Business Logic and Services

The business logic tier in OFBiz is similar in philosophy to the way the data modeling is done. We create generic definitions of business logic, implement it in a variety of ways, and access it with a generic set of APIs. This is all done via a service engine. OFBiz is thus built on a Service Oriented Architecture (SOA).

The first step is to define your services in an XML file:

Here, services are declared, and they can be implemented in either Java (engine="java") or the OFBiz minilang (engine="simple".) Like Java, you can also define generic "interface" services which are implemented by other services. Each service has input and output parameter fields, and the fields have Java types. But the service engine goes a step further: you can also link a service directly to an entity, and OFBiz will figure out the parameters and requisite types for you. This not only saves a lot of typing but makes it unnecessary to modify a large number of services when an entity changes.

You can browse the services in OFBiz with the "Services Available" tool in Web Tools:

Services can be implemented in Java, and Java services follow a standard pattern, like this:


Every Java service is a public static method which takes two parameters, a DispatchContext and a Map called context, and returns a Map. The dispatch context will pass along tools such as the delegator and dispatcher (for running services, see below.) The context is a Map of all the parameters. The method also returns a Map with status of service (success or fail) and return parameters matching those defined in the XML file.

OFBiz also provides a scripting language called minilang or simple-method, which allows for more efficient coding of many tasks. It is specifically designed to work with the OFBiz entity and service engines and perform tasks such as look up, create, run services, or check permissions with simple directives. It is also interpreted and allows for iterative debugging. Here is an example:

Finally, OFBiz services can be implemented in a number of other Java scripting languages, such as Jython, beanshell, Javascript, and potentially any BSF compatible scripting language. The idea is to work with the best tool for the project, given the nature of the task and background of your team.

We call services through an object (usually) called dispatcher, which is of a sub-class of the LocalDispatcher interface:

esh % result = dispatcher.runSync("createPerson", UtilMisc.toMap("firstName", "A", "lastName", "Person"));
bsh % print(result);
[partyId=10050, responseMessage=success]
Here we have just called a service synchronously (in the same thread) to create a Person. We could have also asked OFBiz to run the service asynchronously, or on a separate thread in parallel with our current task.

Some services require validation, and a UserLogin value must be passed in:

bsh % result = dispatcher.runSync("updatePerson", UtilMisc.toMap("partyId", "10050", "nickname", "some bloke"));
// Error: // Uncaught Exception: Method Invocation dispatcher.runSync : at Line: 26 : in file:  : dispatcher .runSync ( "updatePerson" , UtilMisc .toMap ( "partyId" , "10050" , "nickname" , "some bloke" ) )

Target exception: org.ofbiz.service.ServiceAuthException: User authorization is required for this service: updatePerson

org.ofbiz.service.ServiceAuthException: User authorization is required for this service: updatePerson
        at org.ofbiz.service.ServiceDispatcher.runSync(ServiceDispatcher.java:309)
        at org.ofbiz.service.ServiceDispatcher.runSync(ServiceDispatcher.java:212)
        at org.ofbiz.service.GenericDispatcher.runSync(GenericDispatcher.java:110)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:324)
        at bsh.Reflect.invokeOnMethod(Reflect.java:117)
        at bsh.Reflect.invokeObjectMethod(Reflect.java:91)
        at bsh.Name.invokeMethod(Name.java:689)
        at bsh.BSHMethodInvocation.eval(BSHMethodInvocation.java:55)
        at bsh.BSHPrimaryExpression.eval(BSHPrimaryExpression.java:69)
        at bsh.BSHAssignment.eval(BSHAssignment.java:58)
        at bsh.Interpreter.run(Interpreter.java:436)
        at bsh.util.SessiondConnection.run(Sessiond.java:97)
bsh % result = dispatcher.runSync("updatePerson", UtilMisc.toMap("partyId", "10050", "nickname", "some bloke", "userLogin", admin));
bsh % print(result);
[successMessage=person.update.success, responseMessage=success]
bsh % bloke = delegator.findByPrimaryKey("Person", UtilMisc.toMap("partyId", "10050"));
bsh % print(bloke.get("nickname"));
some bloke
And we see that a new Person was created and updated.

If you do not have the userLogin object but have a username and password, the magic words are login.username and login.password, like this:

result = dispatcher.runSync("updatePerson", UtilMisc.toMap("partyId", "10050", "nickname", "some bloke", "login.username", "admin", "login.password", "ofbiz"));
This is appropriate if you need to call the service remotely such as from an external application via SOAP or XML-RPC.

A final note: OFBiz will wrap services automatically in transactions, so there is no need (usually) to write your own.

Mutually Aware Interactions

The most powerful feature of the OFBizFramework is the fact that all the tools work together, including the entity engine, service engine, web controller, and front end user screen and form generation tools. For example, you can define an entity, and the service engine will automatically figure out what fields it needs. The controller can automatically parse user form inputs into the service engine. Even user screens can be automatically generated.

The result is a drastic reduction in the amount of duplicate code that must be written for normal applications. This not only reduces initial development time and costs but also long-term maintenance costs and risks, as business requirements change.

For more information

Visit the Documents page on ofbiz.org for more information on the entity and service engines, minilang, and various APIs.