Chapter 2. Using Baritus

This chapter will show you the basics you need to know to get started with Baritus.

2.1. Flow of execution

This sequence diagram displays the commons flow of execution with Baritus.

The minimal thing you have to do to use Baritus - besides using Maverick -, is to extend from nl.openedge.baritus.FormBeanCtrl and implement the two abstract methods makeFormBean and perform.

2.2. A worked example

Let's start from the begining and work our way up to a working example.

You should know how to work with Maverick. If you are not aquainted with Maverick yet, please read the Maverick tutorial first. After you know how to work with Maverick, you should create a web application project in your favorite IDE, or you can download a stub project using Maverick, Baritus and Velocity here. The stub project is has a Maven project descriptor in order to assist you with getting the dependencies you need fast. Read more about Apache Maven here.

We begin by creating a command in the Maverick configuration. A command in Maverick is the combination of a url-mapping (name), a controller class and one or more views.

<command name="example">
	<controller class="nl.openedge.examples.baritus.ExampleCtrl" />
	<view name="success" path="/example1.vm"/>
	<view name="error" path="/example1.vm"/>
</command>

Next, we create a controller class that extends from nl.openedge.baritus.FormBeanCtrl and a bean that we will use for population.

package nl.openedge.examples.baritus;

import nl.openedge.baritus.FormBeanContext;
import nl.openedge.baritus.FormBeanCtrl;
import org.infohazard.maverick.flow.ControllerContext;

public class ExampleCtrl extends FormBeanCtrl
{

	protected String perform(
		FormBeanContext formBeanContext, 
		ControllerContext cctx)
		throws Exception
	{

		ExampleBean bean = (ExampleBean)formBeanContext.getBean();
		
		//... logic comes here...
		
		return "success";
	}

	protected Object makeFormBean(
		FormBeanContext formBeanContext, 
		ControllerContext cctx)
	{
		// create a new instance of the bean we want to populate
		return new ExampleBean();
	}
}
package nl.openedge.examples.baritus;

public class ExampleBean
{
	private Integer myInteger;
	
	public Integer getMyInteger()
	{
		return myInteger;
	}
	
	public void setMyInteger(Integer integer)
	{
		myInteger = integer;
	}
}

And finally, we create a Velocity template. If you are new to Velocity, you can check out their website here. Note that you can use whatever view type you like. We from OpenEdge mainly use Velocity, but you could use JSP's, XML/XSLT, webmacro, freemarker etc. just the same.

Note that we use the default Maverick configuration, which saves the current instance of FormBeanContext as request attribute 'model'.

<HTML>
<head>
	<title>First Baritus VM</title>
</head>
<body>
	<form action="${request.contextPath}/example.m" method="POST">
	
		Please give an integer:
		<input type="text" name="myInteger" size="20"
			value="$!{model.bean.myInteger}"/>
			
		<br><br>
		<input type="submit" value="save">
		
	</form>
</body>
</HTML>

Actually, the above code only works when population and validation succeeded. If it did not, the property myInteger was probably not set, and thus this example displays the old (null) value in case of an error. If population or validation errors occure, the user input is saved in a special map property 'overrideFields' in the FormBeanContext.

Now, instead of using override fields directely, we can use one of the utility methods that are available in FormBeanContext for displaying properties. In this case we use method 'displayProperty(String)', with the name of the form bean property to display as its parameter.

Method 'displayProperty' will not only lookup if an override value should be displayed, but also formats the output according to the current locale. For other formats, you can use 'displayProperty(propertyName, format)', but more about formatting later on. Here's our new version.

<HTML>
<head>
	<title>First Baritus VM</title>
</head>
<body>
	<form action="${request.contextPath}/example.m" method="POST">
	
		Please give an integer:
		<input type="text" name="myInteger" size="20"
			value="$!{model.displayProperty('myInteger')}"/>
			
		<br><br>
		<input type="submit" value="save">
		
	</form>
</body>
</HTML>

To further fine tune our displaying we will create a Velocity macro.

#macro( forminput $fieldname $type $size )

<input type="${type}" name="${fieldname}" size="${size}" 
	#if( $model.errors.get($fieldname) ) 
		class="fielderror" 
	#else 
		class="field" 
	#end
	value="$!{model.displayProperty($fieldname)}">
		
#end

And a cascading stylesheet definition file (we'll call it style.css here).

.field {
	color: black;
	background-color: white;
}
.fielderror {
	color: white;
	background-color: red;
}
.error {
	color: red;
}

Now we change our Velcocity template, that now also includes the displaying of a list of error messages. Note that you probably create this only once, like in a footer (check out the Maverick transform options).

<HTML>
<head>
	<title>First Baritus VM</title>
	<link rel="stylesheet" type="text/css" href="${request.contextPath}/style.css">
</head>
<body>
	<form action="${request.contextPath}/example.m" method="POST">
	
		Please give an integer:
		#forminput( "myInteger" "text" 20 )
			
		<br><br>
		<input type="submit" value="save">
		
	</form>
	
	#foreach( $err in $model.errors )
		<span class="error">$!{err}</span><br>
	#end
	
</body>
</HTML>

That's all there is to it. You should experiment yourself now. Try adding properties of different types and try playing around with nested objects as well.

Be aware that nested objects that are null references will NOT get populated, so be sure to provide instances of all the objects that you want to have populated. The only execption to this rule is working with arrays.

2.3. Logging

In order to help you debug your web applications more easily, the following loggers are available. As a logging API Baritus uses Jakarta Commons Logging, so you may use any of the logging mechanisms that are supported by Commons Logging.

The two loggers currently in use are named 'nl.openedge.baritus.population' and 'nl.openedge.baritus.formatting' and can be found as constants in Interface nl.openedge.baritus.LogConstants.

To configure with Log4J, for instance, you can add the following lines to your Log4J configuration file:

log4j.logger.nl.openedge.baritus.population=DEBUG
log4j.logger.nl.openedge.baritus.formatting=DEBUG

Currently, only DEBUG gives you run time logging. And, as it is quite a lot, you probably do not want to turn it on in production systems.

2.4. ExecutionParams

ExecutionParams can used to tune the flow of execution. The execution params can be tuned just for the local request, or for all subsequent requests handled by the current controller.

If you want to tune just for the current request, you can get a working copy by calling 'getExecutionParams(cctx);', where cctx is the instance of the ControllerContext. If you pass null instead of cctx, you will allways get a new copy, otherwise you will get the save params for the current request. So, with chained controlls, the excecution params will be reused for the whole server side processing. Thus, if control A chains control B (e.g. A has view '/to-b.m'), and A changes the execution params, these changed params will be used when executing control B this request.

If you want to fix/ change the execution parameters for all subsequent requests, you have to call fixExecutionParams(params) in your control. For example:

public void init(Element controllerNode) throws ConfigException
{			
  ExecutionParams params = getExecutionParams(null);
  params.setIncludeRequestAttributes(true);
  fixExecutionParams(params);
}

For additional fine grained control, you can decide to override method getExectionParams instead of using the copy and the fix method.

2.4.1. Available parameters

In this section the available execution parameters will be listed. The execution parameters are properties of class ExectionParams, and can be accessed with their getters and setters.

  • noCache (default: true). If true, HTTP response headers that indicate that this page should not be cached will be set on each request.

  • setNullForEmptyString (default: true). Should empty strings be interpreted as null references (true) or should the empty String be interpreted as a proper empty string. The default (true) is probably what you want in most cases, as HTML forms with empty fields send empty strings.

  • includeControllerParameters (default: false). Indicates whether the configuration parameters of the controller shoudl be used for the population process.

  • includeSessionAttributes (default: false). Indicates whether the session attributes of the current session should be used for the population process.

  • includeRequestAttributes (default: false). Indicates whether the attributes of the current request should be used for the population process.

  • populateAndValidate is used as an indicator whether population and validation should be done at all. This property is especially useful when chaining controllers (that is, controller A has controller B as its view, hence both A and B are executed in the same request). It is a property of FormBeanContext instead of the ExecutionParams to provide easier access to non-controllers.

  • doFormValidationIfFieldValidationFailed (default: true). Indicates whether the form validators should be executed when one of the field validators failed.

  • doPerformIfPopulationFailed (default: false). Indicates whether the perform method of the control should be executed, even if population/ validation failed.

  • reuseFormBeanContext (default: true). Indicates whether the form bean context should be reused for multiple invocations within the same request.

  • saveReqParamsAsOverrideFieldsOnError (default: true). If population or validation fails and this property is true, all request parameters will be saved as override values. This will give you at least the full request the client sent, and guards you for the situation where properties that normally would be loaded in the command method are not set because of the population/ validation failure.

  • strictPopulationMode (default: true). When an unexpected error occurs, should that be interpreted as a failure, or should it just be ignored. E.g. If you have request parameter not-a-valid-property, Ognl will throw exception 'ognl.InappropriateExpressionException' In strict mode, this will have the effect that the population process is marked as failed. If not in strict mode, the exception will be ignored.

  • trimStringInputValues (default: true). Whether string input values should be trimmed before conversion. Note that the actual trimming depends on the used populator, so setting this parameter does not garantee trimming for all parameters.

2.5. FormBeanContext

In this section, we will look at the FormBeanContext in more detail.

The FormBeanContext is your interface between controllers and views and between controllers within the same request. Also, FormBeanContext has the utility methods that helps you to properly display values of the form bean. Furthermore, FormBeanContext keeps references to the form bean, the current (request scoped) locale and, in case errors occured during population/ validation, to error messages and the original input values. And lastely, FormBeanContext acts as a Map/ attribute decorator that you can use for request scoped atrributes you do not want to include in the population/ validation process.

2.5.1. References

  • The form bean (property 'bean') is the object that you provided for population with method 'makeFormBean'.

  • Property 'currentLocale' is the locale that will be used for this request. FormBeanCtrl gets the locale by calling it protected method 'getLocaleForRequest' and sets it as currentLocale in the FormBeanContext right after creating the formBeanContext.

  • Property 'controller' provides a link to the currently active controller.

2.5.2. Attributes

  • Map property 'errors' is used to store errors that occure during population and validation. You can use this map to store additional error messages as well.

  • Map property 'overrideFields' is used to store the original input values when population or validation generated errors.

  • Map property 'attributes' can be used to store additional attributes that should be available to the next controllers and views in the execution chain without it having any effect on population and validation, like end-user messages that are not errors. FormBeanContext acts as a wrapper for the attributes, so you should use the map methods like get(key), set(key, value) etc. This has the advantage of being able to compactely use these attributes in the view. E.g, say we have attribute with name 'myAttrib' stored as a FormBeanContxt attribute, in Velocity we could display this atribute like: '$model.myAttrib'.

2.5.3. Utility methods

The utility methods help you display properties. Allthough it is not nescesary to use these methods, as you could directly use the form bean, it is advisable to do so, as the utility methods help you look up override values and do formatting.

  • displayProperty(String propertyName). This method looks up the property with the provided propertyName from the form bean and returns the property value as a string using the type's default formatting. If an override value is found, this value will be returned. Baritus will try to format override values. If this fails (which is not unlikely as one of the reasons the override value was set in the first place is that conversion to the target type failed), the value will be converted to a string without using Converters.

  • displayProperty(String propertyName, String pattern). This method does the same as displayProperty(String propertyName), but tries to use the provided pattern for formatting.

  • format(Object value). The provided object is formatted using the Formatter that was registered for its object type. If no formatter is found, just convert to a String using ConvertUtils.

  • format(Object value, String pattern). The provided object is formatted using the Formatter that was registered for its object type and using the provided pattern with that formatter. If no formatter is found, just convert to a String using ConvertUtils.

  • format(String formatterName, Object value). The provided object is formatted using the Formatter that was registered for the provided formatterName. If no formatter is found, just convert to a String using ConvertUtils.

  • format(String formatterName, Object value, pattern). The provided object is formatted using the Formatter that was registered for the provided formatterName and using the provided pattern with that formatter. If no formatter is found, just convert to a String using ConvertUtils.

With all formatting the locale for the current request will be taken into account. If localized formatters were registered, these will be used instead of non-localized formatters.