Project

General

Profile

Validation

Progress 4GL provides a feature to associate a validation condition with a widget. When the user edits data with that widget, the condition must be satisfied before the edit is allowed to be saved.

Validation processing is a widget-level feature. There is no frame-level validation processing. Although there is database table validation processing (e.g. the VALIDATE language statement), this chapter has nothing to do with database table validation. Please see Part 5 of this book for details on database table validation.

The validation condition is specified using a validation expression and a message expression. The combination can be defined either in the schema dictionary or with the VALIDATE option of the format phrase. See Sources of Validation Logic for more details. This validation condition is defined as part of the source code and database schema for the application.

At runtime, editing operations on widgets that have validation conditions will not be allowed to save until the validation expression evaluates to true. The validation expression for a widget is evaluated when the user attempts to leave the widget after making some change to the data. If the data is unchanged, no validation occurs. The attempt to leave the widget can be through the use of TAB (or another action) to change focus to another widget, or it can be caused by any action that would generate a GO event (causing the entire editing operation to conclude). If the user aborts editing (e.g. presses the END-ERROR key), that cancels the edits and does not need to evaluate any validation expressions, since there are no changes.

If the validation expression evaluates to false, then the validation message expression Is evaluated (it can be a simple character literal or it can be a complex character expression). The generated message is displayed to the user and the input focus is placed back into the widget to allow the user to change the data such that it will pass the validation expression evaluation. There are only two ways exit the widget, either type a valid value or cancel input and undo the change.

Sources of Validation Logic

Only widgets defined from a variable or a database field can be validated. Widgets defined using a complex expression are not editable and thus they cannot be validated.

For a specific widget in a specific frame, there may be multiple references (to that widget) in different language statements. Each reference to that widget can have a format phrase. Each of these format phrases can have a VALIDATE clause.

Another source of validation logic is the schema dictionary for database tables. When a field is defined for a table, it can optionally have a validation expression and a validation message. The following example shows a field definition in a book table:

ADD FIELD "price" OF "book" AS decimal
  FORMAT "->>,>>9.99" 
  INITIAL "0" 
  LABEL "Price" 
  SQL-WIDTH 17
  COLUMN-LABEL "Price" 
  VALEXP "price >= cost" 
  VALMSG "Price must be greater than or equal to cost" 
  DECIMALS 2
  ORDER 100

The VALEXP and VALMSG options in the field definition above define implicit validation logic that MAY be honored for widgets created from that database field.

Variable definitions do NOT have any mechanism to define implicit validation logic. Variable definitions have no VALIDATE clause and there are no VALEXP and VALMSG options. Defining a variable LIKE a database field that has a VALEXP option does NOT provide implicit validation logic for the variable (the validation options are not “copied” into the variable definition).

Both DEFINE TEMP-TABLE and DEFINE WORK-TABLE MAY copy the validation options from the database schema, depending on the clauses specified. First, the temp-table or work-table must be defined LIKE a table in the database schema. Second, the VALIDATE option must be specified at the table level (this works for either DEFINE TEMP-TABLE or DEFINE WORK-TABLE ) or for a specific field (this only works for DEFINE TEMP-TABLE). For a temp-table or work-table that is defined without the LIKE clause, there is no mechanism to specify validation options. Likewise, if the VALIDATE option is not specified, any validation options that would be accessible via the LIKE clause will NOT be copied. For example:

define temp-table tt1 field x as int.                   /* no validation in this table */
define temp-table tt2 like book.                        /* no validation in this table */
define temp-table tt3 like book validate.               /* this one validates */
define temp-table tt4 field x like book.price.          /* no validation in this table */
define temp-table tt5 field x like book.price validate. /* this one validates */
define work-table wt1 field x as int.                   /* no validation in this table */
define work-table wt2 like book.                        /* no validation in this table */
define work-table wt3 like book validate.               /* this one validates */

Only tt3, tt5 and wt3 have validation logic copied from the schema in this example.

Validation Conversion

The 4GL syntax of a validation clause is this:

validate(condition, error-message)

condition is the logical expression to evaluate and allow the widget modification if the result of the evaluation is true.

error-message is the string line to display in the message line area when the condition evaluates to false.

The Java implementation of the validation is via callback. The Java application creates a non-static inner class which is a subclass of com.goldencode.p2j.util.Validator and which implements the Validatable interface. Inside the class is the logic which implements methods that evaluate to the condition and the error message respectively.

For each instance of the frame, a new instance of this inner class is created and registered as the validation processor for the associated widget. This registration is called on the specific widget using the method setValidatable(). The parameter for this method is a newly created instance of the inner class. Since this is a non-static inner class, the validation logic can reference any data/state from the business logic as needed (just as in the 4GL).

The inner class has 2 methods. The condition expression is implemented as a core logic of a method with the following signature:

public logical validateExpression(BaseDataType _newValue_)

The error message expression is emitted as the core logic of a second method which will return the error message. This method must have the following signature:

public character validateMessage(BaseDataType _newValue_)

At runtime, when validation must occur, the validateExpression() method is called on the widget's validation instance. The proposed value (that is the result of the user's edits) is passed as _newValue_. The expression evaluates using this proposed value and any business logic state as needed. If the logical return value is false, then the validateMessage() method is called with the same proposed value. Again, this value and any business logic state can be accessed to build up the error message. That returned value is displayed to the user and the user is not allowed to leave the widget. If the logical return value of the validateExpression() method is true, then the edit is accepted and the user is allowed to leave the widget.

The following code checks the integer field to be no more than 99 while updating.

The 4GL code:

def var ix as integer initial 7.
do on endkey undo, leave:
   update ix validate( ix < 100, "The value must not exceed 99" ).
end.
message "Record buffer value:" ix.
message "Screen buffer value:" input ix.

The converted result in Java:

...
frame0.widgetIx().setValidatable(new Validation0());
frame0.openScope();

OnPhrase[] onPhrase0 = new OnPhrase[]
{
   new OnPhrase(Condition.ENDKEY, Action.LEAVE, "blockLabel0")
};

doBlock(TransactionType.SUB, "blockLabel0", onPhrase0, new Block()
{
   public void body()
   {
      FrameElement[] elementList0 = new FrameElement[]
      {
         new Element(ix, frame0.widgetIx())
      };

      frame0.update(elementList0);
   }
});

message(new Object[]
{
   "Record buffer value:", ix
});
message(new Object[]
{
   "Screen buffer value:", frame0.getIx()
});
...
private class Validation0
extends Validator
{
   public logical validateExpression(BaseDataType _newValue_)
   {
      final integer _ix = (integer) _newValue_;
      return isLessThan(_ix, 100);
   }

   public character validateMessage(BaseDataType _newValue_)
   {
      return new character("The value must not exceed 99");
   }
}

Type the invalid value, say 256 and press ENTER key:

To illustrate undo, press F4 at this time:

The screen buffer and the original variable values differ since the edit is not stored back into the variable until the UPDATE statement completes (it has a hidden ASSIGN statement that executes when all editing is complete). If the edit passes validation testing, the proposed value is valid the variable is updated:

In this case the both screen buffer and the variable's value are in sync.

The above validateExpression() method has this code:

final integer _ix = (integer) _newValue_;

This ensures that the reference to the proposed value is never modified as a side-effect of the validation expression. Such a thing would be possible if the validation expression contained a call to a user-defined function which treated its parameter as INPUT-OUTPUT. This is not quite logically correct, please see the Limitations section for details.

Field Validation Example

The following assumes that there is a valid permanent database table named book which has a field named price which has the following in the schema:

  VALEXP "price >= cost" 
  VALMSG "Price must be greater than or equal to cost" 

Other than the fact that the source of the validation clause is implicitly obtained from the schema, there is no appreciable difference in implementation of validation of a widget that is associated with a field versus validation of a widget that is associated with a variable.

The example 4GL:

create book.
def temp-table mytemptable like book validate.
update mytemptable.price.

This converts to Java as:

Book book = RecordBuffer.define(Book.class, "p2j_test", "book");

TempRecord1 mytemptable = TemporaryBuffer.define(TempRecord1.class, "mytemptable", false);
...
frame0.widgetPrice().setValidatable(new Validation0());
...
FrameElement[] elementList0 = new FrameElement[]
{
   new Element(new FieldReference(mytemptable, "price"), frame0.widgetPrice())
};

frame0.update(elementList0);
...
private class Validation0
extends Validator
{
   public logical validateExpression(BaseDataType _newValue_)
   {
      return isGreaterThanOrEqual(book.getPrice(), book.getCost());
   }

   public character validateMessage(BaseDataType _newValue_)
   {
      return new character("Price must be greater than or equal to cost");
   }
}

Business Logic State Access Example

In the 4GL, it is perfectly valid for validation expressions to access state and code from the enclosing procedure. This is fully supported in the converted code.

For example:

def var i as int init 14.
def var j as int init 21.
def var x as int init 21.

def temp-table tt field num as int.

create tt.
tt.num = 30.

function not-twenty-one returns logical (input k as int):
   return k <> j.
end.

update x validate(x > i and x < tt.num and not-twenty-one(x), “Not good!”)
       with frame f0.

The result in Java (only the inner class is shown):

private class Validation0
extends Validator
{
   public logical validateExpression(BaseDataType _newValue_)
   {
      final integer _x = (integer) _newValue_;
      return and(and(isGreaterThan(_x, i), new LogicalExpression()
      {
         public logical execute()
         {
            return isLessThan(_x, tt.getNum());
         }
      }), new LogicalExpression()
      {
         public logical execute()
         {
            return notTwentyOne(_x);
         }
      });
   }

   public character validateMessage(BaseDataType _newValue_)
   {
      return new character("Not good!");
   }
}

This example shows that the expressions can access any state or code in the containing business logic including variables, buffers/fields and user-defined functions.

All resources accessed in the containing business logic class have been “promoted” to be instance members. This allows non-static inner class instances to directly reference this state. Likewise, such references can call converted user-defined functions (methods of the business logic class) as part of the expression evaluation.

Extent Widget Validation Example

As noted above in the Validation Conversion section, when a widget is defined based on a variable/field which has an extent, the 4GL expression does not have to reference the proposed value using a subscript. For example:

def var x as int extent 3 init [ 0, 14, 30 ].
update x[3] validate(x > 0, “Must be a positive number.”).

This converts to the following Java:

frame0.widgetxArray2().setValidatable(new Validation0());
frame0.openScope();

FrameElement[] elementList0 = new FrameElement[]
{
   new Element(subscript(x, 3), frame0.widgetxArray2())
};

frame0.update(elementList0);

...

private class Validation0
extends Validator
{
   public logical validateExpression(BaseDataType _newValue_)
   {
      final integer _x = (integer) _newValue_;
      return isGreaterThan(_x, 0);
   }

   public character validateMessage(BaseDataType _newValue_)
   {
      return new character("Must be a positive number.");
   }
}

The result works the same way as all other validation expression processing. The reason is simple: the validateExpression() method always receives a proposed value that is a scalar instance (the proposed value is not a Java array), so this syntax works naturally in the converted code.

If the 4GL were to contain an array subscript, it would not work properly. See the Limitations section for details.

DEFINE BROWSE Validation

A validation clause can be incorporated into the browse definition statement. For example:

def query q for book.
def browse brws query q no-lock no-wait
    display book
    enable book.price
    validate( book.price > input book.cost, "Price must be more than cost" )
    with single 5 down centered no-row-markers.

Currently this type of the validation is not supported.

Precedence Rules for Resolving Conflicts

Since there are multiple sources of validation logic and there can be multiple references to a widget, it is possible for the same widget to have validation logic specified more than once. This section defines the rules by which the proper validation logic can be chosen when there is a conflict.

Each widget's validation logic is controlled/chosen independent of all other widgets.

Variables

For widgets created from a variable definition, the only possible source of validation logic is from a format phrase. Still, there can be multiple widget references and each one can have its own format phrase. The first explicit VALIDATE clause present in a format phrase for a widget will be honored in preference to all other explicit VALIDATE clauses (which are silently ignored). If no format phrase for the widget has a VALIDATE clause, then there is no validation for that widget.

This example has no validation:

def var x as int.
update x
       with frame f0.

This example has no conflict, it uses “rule 1” for validation:

def var x as int.
update x validate(x > 0, “rule 1”)
       with frame f0.
update x
       with frame f0.

This example has no conflict, it uses “rule 2” for validation:

def var x as int.
update x
       with frame f0.
update x validate(x < 0, “rule 2”)
       with frame f0.

This example has 2 conflicting sources of validation logic, it uses “rule 1” for validation:

def var x as int.
update x validate(x > 0, “rule 1”)
       with frame f0.
update x validate(x < 0, “rule 2”)
       with frame f0.

Database Fields

Database fields have more complicated precedence rules since validation logic can be specified in format phrases as well as implicitly in the database schema (this includes the mechanisms by which DEFINE TEMP-TABLE and DEFINE WORK-TABLE can copy validation logic, see the Sources of Validation Logic section above).

If the first reference to a frame is a statement whose frame phrase includes the NO-VALIDATE option, then any implicit validation (the honoring of a VALEXP specified in the schema definition for that field) is disabled for all field widgets in that frame. Explicit VALIDATE clauses are still honored. The NO-VALIDATE option is ignored unless it is specified on the statement that “instantiates” a new frame.

In this example, the first frame reference specifies the NO-VALIDATE option, so all implicit validation logic on the book.price field is disabled.

update book.price
       with frame f0 no-validate.
update book.price
       with frame f0.

In this example, the second frame reference specifies the NO-VALIDATE option, but this is ignored. That means all validation logic is honored by other rules (see below).

update book.price
       with frame f0.
update book.price
       with frame f0 no-validate.

In this example, neither frame reference specifies the NO-VALIDATE option, so all validation logic is honored by other rules (see below).

update book.price
       with frame f0.
update book.price
       with frame f0.

Implicit validation occurs for widgets created from a database field if and only if the first reference to that widget has no explicit validation expression AND there is a validation expression defined for that field in the database schema AND the NO-VALIDATE option is not in effect for the containing frame.

If the NO-VALIDATE option IS in effect for the containing frame OR there is no validation expression defined for that field in the database schema, then only an explicit VALIDATE clause present in a format phrase will be honored. In this case, the behavior for database fields is exactly as for variables: the first explicit VALIDATE clause present in a format phrase for a widget will be honored in preference to all other explicit VALIDATE clauses (which are silently ignored).

If the NO-VALIDATE option is not in effect for the containing frame AND there is implicit validation logic in the schema for that field, BUT the first reference to that widget has an explicit VALIDATE clause present in its format phrase, then the explicit VALIDATE clause will take precedence over the implicit validation logic (from the schema dictionary).

For example, in this case the explicit VALIDATE clause of the format phrase will be used in preference to the implicit validation logic:

update book.price validate(price > 0, “price must be non-negative”)
       with frame f0.
update book.price
       with frame f0.

In this case the implicit validation logic is used in preference to the explicit VALIDATE clause of the format phrase since the first widget reference has no explicit VALIDATE clause:

update book.price
       with frame f0.
update book.price validate(price > 0, “price must be non-negative”)
       with frame f0.

Disabling Validation

When the NO-VALIDATE option is present in the frame phrase of a statement which is the first reference to a frame, then any validations defined for widgets in the Data Dictionary are ignored. However, any manually specified validation expressions are still honored.

To completely disable the validation for particular field use the VALIDATE option as shown in the following fragment:

UPDATE my-variable VALIDATE(TRUE, "")

This will be converted to Java as:

private class Validation0
extends Validator
{
   public logical validateExpression(BaseDataType _newValue_)
   {
      return new logical(true);
   }

   public character validateMessage(BaseDataType _newValue_)
   {
      return new character("");
   }
}

The result is that the Validator inner class is emitted but the expression can never be false. A future improvement to the conversion will identify these cases and change the conversion to eliminate all validation expressions for the given field or variable.

Limitations

1. Validation expressions which contain field references which cannot be resolved to valid buffers in the source code are ignored (they are silently dropped). For example, this can happen in a FORM statement where a widget is defined using a database field and there is a validation expression for that widget referencing that field. Testing of the runtime behavior in each such case is necessary to determine if the Progress 4GL runtime behaves the same way. It is possible that this matches the Progress behavior in some cases while it may be incorrect in other cases. Validation expressions are evaluated at runtime and it is possible that Progress may have some buffer resolution functionality that is implemented based on runtime state, which is not apparent from the source code itself.

2. Validation clauses in DEFINE BROWSE are not honored yet.

3. The NO-AUTO-VALIDATE option of the frame phrase is supported in the conversion but the runtime does not support it yet. See the Frames chapter for details on how this converts.

4. The VALIDATE() handle based widget method is not supported yet.

5. A special case occurs with variable or field array references (extents). In Progress, the specific extent element can be referenced without subscript when it is in a validation expression. This works in the converted code (in fact it is the only form that can work right now). If the code has an explicit subscript, then it will convert to code that will access the original instance of the variable/field instead of accessing the passed-in scalar instance to test against. That will mean that such validation expressions would never work properly.

6. The initialization code for validateExpression() method copies the proposed value to a final local variable, as in the following:

final integer _ix = (integer) _newValue_;

This is supposed to ensure that the reference to the proposed value is never modified as a side-effect of the validation expression. Such a thing would be possible if the validation expression contained a call to a user-defined function which treated its parameter as INPUT-OUTPUT. The problem here is that methods can still be called on the given reference which can mutate the value. To correct this, the local variable must be a duplicate (a deep copy) of the method parameter instead of being just a final reference.


© 2004-2017 Golden Code Development Corporation. ALL RIGHTS RESERVED.