$pageTitle="Entities and Services - Data Models and Business Logic in OFBiz"; ?> include("../header.php") ?> include("ofbiz_header.php") ?>include("byline.php") ?>
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.
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:
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 portsThey 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.)
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.GenericValueEach 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 chenand 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 11111Notice 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,email@example.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.
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:
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:And we see that a new Person was created and updated.
: 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
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.
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.
Visit the Documents page on ofbiz.org for more information on the entity and service engines, minilang, and various APIs.include("ofbiz_footer.php") ?> include("../footer.php") ?>