9. The Events – Apache OFBiz Development

Chapter 9. The Events

We have understood the basics of all of the different parts of the OFBiz MVC framework, we now return to the Controller part and explore the meatier part of the flow—the programming of business logic.

In Chapter 6 we merely covered the networking or communications channels (flow) of an application. This chapter deals with the actual working of an application, such as whether a loan application should be accepted or rejected and on what basis, whether a purchase qualifies for a refund, and any other logic to do with the running of a business or organization.

In this chapter, we will be looking at:

  • Java events and their context

  • OFBiz-related techniques for programming business logic

  • Security-related programming

  • Handling parameters sent by an end-user

  • Sending feedback to an end-user

  • Getting our application to speak in various languages

  • Database-related programming (quick recap of Chapter 8)

The context for OFBiz "service" events will be discussed in detail in the next chapter.

Java Events

Although we have already seen a couple of examples of simple Java events, we have not yet seen how to perform any useful business logic. The OFBiz framework places a number of useful objects onto the request as attributes for us. Other useful objects can be taken from the session. These objects are available for us to use from within our Java method, once we have extracted them from the right places.

As you will recall, the request is passed into the Java event as a parameter. The session can be obtained from the request in the standard way.

HttpSession session = request.getSession();

The availableLocales object can be constructed like this:

List UtlavailableLocales = UtilMisc.availableLocales();

The locale object can be retrieved like this:

Locale locale = UtilHttp.getLocale(request);

The delegator object can be retrieved like this:

GenericDelegator delegator = (GenericDelegator)request.getAttribute("delegator");

The dispatcher object can be retrieved like this:

LocalDispatcher dispatcher = (LocalDispatcher)request.getAttribute("dispatcher");

The security object can be retrieved like this:

Security security = (Security)request.getAttribute("security");

The userLogin object can be retrieved from the session:

GenericValue userLogin = (GenericValue)session.getAttribute("userLogin");

Security and Access Control

So far, our scripts and events have not been bothered with access control; any user can access them. We will now explore the security and access control mechanisms in OFBiz.

We will be using a Person record in OFBiz with first name OFBiz and last name Researcher. This Person record was created in Chapter 1 when we created an anonymous shopper to buy four "Round Gizmos". If the Person record doesn't exist for you, you can create a new Person through the "Party Manager" application. Go to http://localhost:8080/partymgr select Create and Create New Person.

User Logins are like Access Cards

A userLogin in OFBiz (or any software with access control) is like an access card that a person carries to gain access into some or all parts of a secure building. One or more userLogins can be created for a person, with each userLogin allowing access to different areas.

We can see that when we take the userLogin from the request, the object we get is a GenericValue. This GenericValue relates to a record in the UserLogin entity. Open up the Entity Data Maintenance screen for UserLogin and find the record with a userLoginId of admin. The userLogin has a foreign key relationship partyId to the Party entity. The partyId is the user's (whether they be a Person or a PartyGroup) unique identification number, this is one of the most widely used and important IDs across all of the OFBiz components.

So, to obtain the Party record from the userLogin we would have:

GenericValue userLogin = (GenericValue)session.getAttribute("userLogin");
GenericValue party =null;
Try{
party = userLogin.getRelatedOne("Party");
}catch(GenericEntityException e){
Debug.logError(e, module);
}

From the Party we can then determine whether they are a Person or a PartyGroup (Organization).

Once logged in, the user carries around the information about who they are, everywhere they go.

In the Party Manager Application, bring up our Person record.

Scroll down to section labeled User Name(s).

Click Create New to create a userLogin. Create two userLogins with user login IDs allowed and denied, respectively. Set the password to ofbiz for both. Enter zibfo for Password Hint.

Security Groups are like Access Levels

A security group is a single group of access rights. There can be many ways of defining security groups, depending on how we want to structure our access control. One way is the familiar "clearance level" mentioned in many spy movies—different areas in a building are assigned different clearance levels say 1 to 10 (10 being most classified and requiring highest access privileges). Another way is via the use of "color codes", each color representing a single department—this would serve to confine employees to their own departments.

From the Party Manager Application, click on Security then New Security Group. Create a new security group with an ID of LEARNSCREENS and description Learning Screens.

Security Permissions are like Individual Secured Areas

A security permission is a single unit of access and is the smallest indivisible unit of security. It is like a specific entry code for a single secured room in a building.

Unfortunately, OFBiz doesn't provide a ready-made screen and form for the creation (nor modification) of security permissions. Go to the Entity Data Maintenance screen for entity SecurityPermission, and create a new SecurityPermission record with permissionId of LEARN_VIEW and description containing Viewing of Learning Screens.

Security Permissions are Contained within Security Groups

It is not possible to assign individual security permissions to user logins. Only security groups can be handled. Hence, security permissions need to be contained within security groups.

Return back to the Party Manager Application's Security Groups by clicking on Security and find the newly added LEARNSCREENS group.

Add the security permission LEARN_VIEW to the security group EARNSCREENS. Either add via the drop-down box, or manually, as shown above.

Confirm that the security permission was successfully added to the security group.

User Logins are Assigned Security Groups

Go back to our Person record, section User Name(s). Click on the Security Groups button of user login allowed to bring up the security groups assignment screen. Select the Group LEARNSCREENS and click Add.

Confirm that the userLogin was successfully assigned to the security group.

Dealing with Security in Java

Having set up the required security permissions and groups and user logins, we now look at how to deal with these objects in Java.

Insert into the file ${component:learning}\widget\learning\LearningScreens.xml a new Screen Widget:

<screen name="CheckAccess">
<section>
<widgets>
<decorator-screen name="main-decorator" location="${parameters.mainDecoratorLocation}">
<decorator-section name="title">
<label text="Checking Access via Event BeanShell"/>
</decorator-section>
<decorator-section name="body">
<label text="${eventMessageList[0]}"/>
</decorator-section>
</decorator-screen>
</widgets>
</section>
</screen>

Insert into the file ${webapp:learning}\WEB-INF\controller.xml a new request map:

<request-map uri="CheckAccess">
<security auth="true"/>
<event type="java" path="org.ofbiz.learning.learning.LearningEvents" invoke="checkAccess "/>
<response name="success" type="view" value="CheckAccess"/>
</request-map>

and a new view map:

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

In the class org.ofbiz.learning.learning.LearningEvents, create a new static method checkAccess :

public static String checkAccess(HttpServletRequest request, HttpServletResponse response){
Security security = (Security)request.getAttribute("security");
String key = "_EVENT_MESSAGE_";
if (security.hasPermission("LEARN_VIEW", request.getSession())) {
request.setAttribute(key, "You have access!");
}
else {
request.setAttribute(key, "You DO NOT have access! You are denied!");
}
return "success";
}

also ensure that the correct class has been imported:

import org.ofbiz.security.Security;

Stop, recompile, and restart. Fire to webapp learning an http OFBiz request CheckAccess. Login as denied (password ofbiz) and see the following screen stating denied access.

Now, logout and then fire the same request again. This time, login as allowed and see the following screen stating access granted.

Sending Feedback to the End-User

Part of the job of the Controller component of MVC, after performing all the work it is asked to do by the end-user, is deciding what to tell the end-user. The feedback can be to notify about the success of one or more operations, or to warn about errors.

In the previous section, we had set into the http request object an attribute of key _EVENT_MESSAGE_.

Conventions for Message Placeholders

OFBiz considers a few key names to be placeholders for feedback messages:

  • _EVENT_MESSAGE_

  • _EVENT_MESSAGE_LIST_

  • _ERROR_MESSAGE_

  • _ERROR_MESSAGE_LIST_

  • _ERROR_MESSAGE_MAP_

The Widget Engine (the org.ofbiz.widget.screen.ScreenWidgetViewHandler handler, specifically) consolidates all messages in the above placeholders (except _ERROR_MESSAGE_MAP_) into two lists: eventMessageList and errorMessageList. The lists are then placed into the widget environment's context object.

The placeholder _ERROR_MESSAGE_MAP_ is mostly used by the Service Engine (discussed later in the book), and is not handled by the Widget Engine.

Testing the Conventions

Thanks to the file ${component:commoon}\webcommon\includes\messages.ftl which has been added to our screens by the GlobalDecorator we do not have to set up any other code to display our messages. This file takes care of analyzing each of the place holders and displaying them accordingly. For this reason, in the previous example, the returned message was displayed twice, once within the messages.ftl file and again in the Screen Widget:

<label text="${eventMessageList[0]}"/>

Insert into ${component:learning}\widget\learning\LearningScreens.xml a new Screen Widget:

<screen name="FeedbackMessages">
<section>
<widgets>
<decorator-screen name="CommonLearningDecorator">
<decorator-section name="title">
<label text="Exploring all placeholders for feedback messages"/>
</decorator-section>
<decorator-section name="body">
</decorator-section>
</decorator-screen>
</widgets>
</section>
</screen>

Insert into the file ${webapp:learning}\WEB-INF\controller.xml a new request map:

<request-map uri="FeedbackMessages">
<event type="java" path="org.ofbiz.learning.learning.LearningEvents" invoke="feedbackMessages"/>
<response name="success" type="view" value="FeedbackMessages"/>
</request-map>

and a new view map:

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

In the class org.ofbiz.learning.learning.LearningEvents, create a new static method feedbackMessages:

public static String feedbackMessages(HttpServletRequest request, HttpServletResponse response){
request.setAttribute("_EVENT_MESSAGE_", "Event Message Main");
request.setAttribute
("_EVENT_MESSAGE_LIST_",
UtilMisc.toList("Event Message 1", "Event Message 2"));
request.setAttribute("_ERROR_MESSAGE_", "Error Message Main");
request.setAttribute
("_ERROR_MESSAGE_LIST_",
UtilMisc.toList("Error Message 1", "Error Message 2", "Error Message 3"));
request.setAttribute
("_ERROR_MESSAGE_MAP_",
UtilMisc.toList("errMsgA", "Error Message A", "errMsgB", "Error Message B"));
return "success";
}

Ensure that the class UtilMisc has been imported:

import org.ofbiz.base.util.UtilMisc;

Shutdown, recompile the Learning Component, and restart. Fire to webapp learning an http OFBiz request FeedbackMessages to see how the Widget Engine consolidates feedback messages from the various placeholders into eventMessageList and errorMessageList.

Notice that the placeholder _ERROR_MESSAGE_MAP_ was not handled at all.

Handling Parameters

As has already been seen in previous sections, sending feedback to end-users (or sending values back to View component or front-end) is done by setting attributes in the request object.

Handling parameters sent by end-users is done with the org.ofbiz.base.util.UtilHttp class.

In the class org.ofbiz.learning.learning.LearningEvents, create a new static method receiveParams and enter into it this:

public static String receiveParams(HttpServletRequest request, HttpServletResponse response){
Map parameters = UtilHttp.getParameterMap(request);
String name = "";
String firstName = (String)parameters.get("firstName");
String lastName = (String)parameters.get("lastName");
if (firstName != null) name += firstName;
if (lastName != null) {
if (name != null) name += " ";
name += lastName;
}
String message = "Welcome, " + name + "!";
request.setAttribute("_EVENT_MESSAGE_", message);
return "success";
}

Ensure that the UtilHttp class has been imported:

import org.ofbiz.base.util.UtilHttp;

Note the line above:

Map parameters = UtilHttp.getParameterMap(request);

That method neatly packs all parameters residing in a request object, sent by the end-user, into a map.

Insert into the file ${component:learning}\widget\learning\LearningScreens.xml a new Screen Widget:

<screen name="WelcomeVisitor">
<section>
<widgets>
<decorator-screen name="main-decorator" location="${parameters.mainDecoratorLocation}">
<decorator-section name="title">
<label text="Welcome Message"/>
</decorator-section>
<decorator-section name="body">
<include-form name="WelcomeVisitor" location="component://learning/widget/learning/ LearningForms.xml"/>
</decorator-section>
</decorator-screen>
</widgets>
</section>
</screen>

Insert into the file ${component:learning}\widget\learning\LearningForms a new Form Widget:

<form name="WelcomeVisitor" type="single" target="ReceiveParams">
<field name="firstName"><text/></field>
<field name="lastName"><text/></field>
<field name="submit"><submit/></field>
</form>

In the file ${webapp:learning}\WEB-INF\controller.xml, insert a new request map:

<request-map uri="ReceiveParams">
<event type="java" path="org.ofbiz.learning.learning.LearningEvents" invoke="receiveParams"/>
<response name="success" type="view" value="WelcomeVisitor"/>
</request-map>

and a new view map:

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

Shutdown OFBiz, recompile the Learning Component, and restart. Fire to webapp learning an http OFBiz request ReceiveParams, and submit some name.

Accessing Localized Messages

In our LearningUiLabels.properties file we created at the end of Chapter 5, add another two entries:

LearningWelcomeMsg=Welcome to France!
LearningSmallTalk=The weather is nice.

Properties files are organized into maps. In the above example, the properties file will be loaded into a map with two entries: LearningWelcomeMsg and LearningSmallTalk.

In file ${webapp:learning}\WEB-INF\controller.xml, insert a new request map:

<request-map uri="WelcomeVisitor">
<event type="java" path="org.ofbiz.learning.learning.LearningEvents" invoke="welcomeVisitor"/>
<response name="success" type="view" value="WelcomeVisitor"/>
</request-map>

We are going to use the same view-map that we used in the previous example. We do however need to change the target that the Form Widget uses. In LearningForms.xml replace the line:

<form name="WelcomeVisitor" type="single" target="ReceiveParams">

with

<form name="WelcomeVisitor" type="single" target="WelcomeVisitor">

In the class org.ofbiz.learning.learning.LearningEvents, create a new static method welcomeVisitor:

public static String welcomeVisitor(HttpServletRequest request, HttpServletResponse response){
String resource = "LearningUiLabels";
Locale locale = UtilHttp.getLocale(request);
String message = UtilProperties.getMessage(resource, "LearningWelcomeMsg", locale);
String smallTalk = UtilProperties.getMessage(resource, "LearningSmallTalk", locale);
Map parameters = UtilHttp.getParameterMap(request);
String name = "";
String firstName = (String)parameters.get("firstName");
String lastName = (String)parameters.get("lastName");
if (firstName != null) name += firstName;
if (lastName != null) {
if (name != null) name += " ";
name += lastName;
}
message += " " + name + "!";
message += "<br>" + smallTalk;
request.setAttribute("_EVENT_MESSAGE_", message);
return "success";
}

Ensure the correct classes have been imported:

import org.ofbiz.base.util.UtilProperties;
import java.util.Locale;

Shutdown OFBiz, recompile the Learning Component, and restart. Fire to webapp learning an http OFBiz request WelcomeVisitor. Submit the First Name and Last Name to see a customized welcome message.

Note that any changes to the .properties files will require an OFBiz restart. Although we can clear the cache in OFBiz, we can't clear the cache in the implementation of ResourceBundle.getBundle().

Parameterizing Messages

Note how we were forced to append the name of the visitor after the message. By parameterizing messages, we can insert the visitor's name into the message.

Change the entry LearningWelcomeMsg in the file ${component:learning}\config\LearnMessages.properties to this:

LearningWelcomeMsg=Welcome to France! {0}!

In the static method welcomeVisitor, delete the line:

String message = UtilProperties.getMessage(resource, "welcomeMsg", locale);

and replace:

String message += " " + name + "!";

with:

String message = UtilProperties.getMessage(resource, "LearningWelcomeMsg", new String[] {name}, locale);

Once again, shutdown OFBiz, recompile the Learning Component, and restart. Fire to webapp learning an http OFBiz request WelcomeVisitor, and submit some first and last name. This time, the name is inside of the welcome message, not outside it like before.

Catering for Multiple Languages

It is possible to have OFBiz load different .properties files based on the locale object. We can have multiple .properties, each one catering to a single language.

In the folder ${component:learning}\config, create a new file LearnMessages_fr.properties and enter into it this:

LearningApplication=Notre Application D'Etude
LearningWelcomeMsg= Bienvenue en France, {0}!

Restart OFBiz. Let's move on while OFBiz restarts.

The language and country codes are listed online at http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt and http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html, respectively. Variant codes are non-standard.

Change your browsers language settings to French by selecting Tools | Options then Languages and choosing French. Make sure that you move French to the top of the list. The process is the same for both Firefox and Internet Explorer.

Fire to webapp learning an http OFBiz request WelcomeVisitor and submit a name. We see the French version of the screen appear.

Notice that the smallTalk message shows in the the English version. OFBiz tries to find the most specific version (fr in this case). Failing that, it will fall back on the next general version (in LearnMessages.properties in this case).

In fact, much more on the screen has been translated than we entered into our LearningUiLabels_fr.properties file. This is because all of the messages in the CommonUiLabels have already been translated.

If we had wanted a locale of fr_CA (French language in Canada), OFBiz would still have fallen back on fr, since that will be the closest and most specific version available.

Note that requesting a more general locale, given only the availability of a more specific one, does not make OFBiz serve up the specific version. For example, asking for de_DE will not get de_DE_no. In the absence of a general locale and the presence of a specific one, we need to ask for the specific one.

Working with the Database

Although this was covered in detail in Chapter 8, there are a couple of differences between accessing the database in BeanShell and in Java.

Firstly, in Java we must obtain the GenericDelegator from the request before we can use it:

GenericDelegator delegator = (GenericDelegator)request.getAttribute("delegator");

Secondly, if we take a look at the class org.ofbiz.entity.GenericDelegator we can see all of the methods we were calling in the previous chapter. Virtually all of them throw a GenericEntityException which must be handled in our Java code. We must therefore wrap a try/catch block around any uses of these methods and handle the error accordingly.

For example:

postalAddresses = delegator.findAll("PostalAddress");

Would become in our Java code:

List postalAddresses = null;
try{
postalAddresses = delegator.findAll("PostalAddress");
}catch(GenericEntityException e){
Debug.logError(e, module);
return "error";
}

The try/catch block is not needed in BeanShell, because all of the code in the script is ultimately already wrapped in a try/catch block and any Exceptions thrown are handled by this.

Summary

In this chapter, we have learned a number of vital techniques relevant to programming the meat of the flow in an application. We have learnt more about security and how we create and assign permissions to users and how these permissions can be checked within the Java method. We saw how to invoke Java events from the controller including how to pass in and handle parameters inside the method and how to pass messages and data out of the method. While studying the messages out we took a look at how OFBiz caters for multiple languages.

In the previous chapters we used BeanShell scripts to quickly demonstrate the Entity Engine. In the real world we would port these scripts to compiled Java classes and so we looked at the differences between accessing the delegator’s methods in Java versus BeanShell.

In the following chapters we will be covering Service and Minilang environments. Although they fall under the category of Event components, events themselves are large enough to warrant their own chapter.

In this chapter we looked at:

  • Creating userLogin records to serve as access identities, much like physical access cards in the real world

  • Creating security groups to serve as clearance roles or levels

  • Creating security permissions to serve as individual (and indivisible) access rights to individual resources, much like individual secured rooms in the real world

  • Classifying security permissions under security groups

  • Assigning security groups to userLogins

  • Checking access rights in event BeanShell scripts

  • Sending feedback from the event to the end-user

  • Handling parameters sent to the event codes from the end-user by using UtilHttp.getParameterMap()

  • Setting up a centralized repository of user-interface words and messages.

  • Parameterizing the entries in that repository

  • Creating various translations of that repository

  • Using the "locale" object to select appropriate language or version of translation for the end-user

  • Using Java events to access the database

In the next chapter, we will look at another form of events—Services, and learn more.